live: heartbeat sync 2026-10-19T06:09:41-07:00 (Refs #11)
This commit is contained in:
1 parent
e67bbd0de0
commit
1251cfbd40
6 files changed
+109
-63
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: gitea-api-pr-operations
|
name: gitea-api-pr-operations
|
||||||
description: "Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea API (192.168.0.143:3000, repo dank/command-center) with the token stored in ~/.config/secrets/gitea — incl. the required merge payload and where review comments actually live on 1.27.3."
|
description: "\"Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea API (192.168.0.143:3000, repo dank/command-center) with the token stored in ~/.config/secrets/gitea — incl. the required merge payload, where review comments actually live on 1.27.3, and how to recover the session id for the mandatory body footer.\""
|
||||||
---
|
---
|
||||||
|
|
||||||
# Gitea API PR operations (Dankclaw `dank/command-center`)
|
# Gitea API PR operations (Dankclaw `dank/command-center`)
|
||||||
@@ -25,11 +25,13 @@ description: "Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea A
|
|||||||
fold footers into the --arg string instead.)
|
fold footers into the --arg string instead.)
|
||||||
- **List open PRs**: `GET /pulls?state=open` → `jq -r '.[] | "#\(.number) \(.head.ref) \(.title)"'`.
|
- **List open PRs**: `GET /pulls?state=open` → `jq -r '.[] | "#\(.number) \(.head.ref) \(.title)"'`.
|
||||||
- **Read PR**: `GET /pulls/<n>` (no token; public repo) → `jq -c '{state,merged,mergeable,head:.head.ref}'`.
|
- **Read PR**: `GET /pulls/<n>` (no token; public repo) → `jq -c '{state,merged,mergeable,head:.head.ref}'`.
|
||||||
|
`mergeable:false` is often Gitea's *not-yet-checked* state, not a conflict — prove a real
|
||||||
|
conflict locally with `git merge-tree --write-tree HEAD origin/main` before believing it.
|
||||||
- **MERGE**: `POST /pulls/<n>/merge` with `-H 'Content-Type: application/json'`
|
- **MERGE**: `POST /pulls/<n>/merge` with `-H 'Content-Type: application/json'`
|
||||||
and body **`{"Do":"merge"}`** — this build REQUIRES the `Do` field:
|
and body **`{"Do":"merge"}`** — this build REQUIRES the `Do` field:
|
||||||
`{}` or `{"DoNotMerge":false,...}` → 422; `{"Do":"merge"}` → 200 +
|
`{}` or `{"DoNotMerge":false,...}` → 422; `{"Do":"merge"}` → 200 +
|
||||||
`{"state":"closed","merged":true,"merge_commit_sha":"..."}`. Verify with a
|
`{"state":"closed","merged":true,"merge_commit_sha":"..."}`. Verify with a plain
|
||||||
plain GET /pulls/<n> afterwards (merge endpoint's own stdout may not parse).
|
GET /pulls/<n> afterwards (merge endpoint's own stdout may not parse).
|
||||||
Merge is the `--no-ff` merge commit on main; then `git -C ~/.omp fetch && git
|
Merge is the `--no-ff` merge commit on main; then `git -C ~/.omp fetch && git
|
||||||
merge --ff-only origin/main` propagates the live deployment.
|
merge --ff-only origin/main` propagates the live deployment.
|
||||||
- **Issues**: same shape at `/issues` (`POST {title, body}`), `/issues/<n>/comments`
|
- **Issues**: same shape at `/issues` (`POST {title, body}`), `/issues/<n>/comments`
|
||||||
@@ -51,6 +53,13 @@ description: "Open, list, merge, and diagnose issues/PRs on the Dankclaw Gitea A
|
|||||||
Every issue/PR body ends with `---` line then `cwd: <wd> · session: <id>`. Keep
|
Every issue/PR body ends with `---` line then `cwd: <wd> · session: <id>`. Keep
|
||||||
it inside the --arg string (not a `---` CLI arg).
|
it inside the --arg string (not a `---` CLI arg).
|
||||||
|
|
||||||
|
`cwd` is THIS session's own working directory (note a build worktree separately if they
|
||||||
|
differ). When `/status` is unreachable (headless/subagent context), recover the session id
|
||||||
|
from the session store instead of inventing one:
|
||||||
|
`ls -t ~/.omp/agent/sessions/<cwd-slug>/ | head -2` → `<timestamp>_<uuid>` (the same uuid as
|
||||||
|
`<uuid>.jsonl`); the slug is the cwd with `/` replaced by `-`. Subagent files live under the
|
||||||
|
**parent** session's directory, so the newest entry there is the parent id.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
- Content-Type: POSTs with `-d @file.json` MUST carry
|
- Content-Type: POSTs with `-d @file.json` MUST carry
|
||||||
`-H 'Content-Type: application/json'` on the command line. `-H` entries put in
|
`-H 'Content-Type: application/json'` on the command line. `-H` entries put in
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: vault-one-fact-one-owner-dedupe
|
name: vault-one-fact-one-owner-dedupe
|
||||||
description: "Collapse per-payload/per-attempt vault pages into one owner page per endpoint without losing a fact: port-before-cut orphan census, index regeneration, the one-page seam, and the merge-resolution order."
|
description: "\"Collapse per-payload/per-attempt vault pages into one owner page per endpoint without losing a fact: port-before-cut orphan census, doubled-page repair, trash-aware tools, delegated-write verification, index regeneration, and the merge-resolution order.\""
|
||||||
---
|
---
|
||||||
|
|
||||||
# One fact, one owner: dedupe the vault without losing a fact
|
# One fact, one owner: dedupe the vault without losing a fact
|
||||||
@@ -18,6 +18,9 @@ mechanics are general.
|
|||||||
table or boot ledger**.
|
table or boot ledger**.
|
||||||
- `log.md` owns incidents and laws only. A swap is an Eras row, never a log entry.
|
- `log.md` owns incidents and laws only. A swap is an Eras row, never a log entry.
|
||||||
- `AGENTS.md` carries operator-critical numbers + cross-border laws; everything else links.
|
- `AGENTS.md` carries operator-critical numbers + cross-border laws; everything else links.
|
||||||
|
- A **retired** endpoint still gets one locator page: era windows quoted verbatim from each
|
||||||
|
era page's own banner, no numbers restated, and any date conflict recorded as an open
|
||||||
|
question instead of silently resolved. Never pick a winner between two primary sources.
|
||||||
|
|
||||||
## 2. Single-writer rule (the mistake that costs the most)
|
## 2. Single-writer rule (the mistake that costs the most)
|
||||||
|
|
||||||
@@ -29,56 +32,71 @@ to be reconciled by hand.
|
|||||||
|
|
||||||
## 3. Port-before-cut census (proves nothing was lost)
|
## 3. Port-before-cut census (proves nothing was lost)
|
||||||
|
|
||||||
For every deleted line, extract distinctive keys (integers ≥ 4 digits with commas stripped,
|
For every deleted line, extract distinctive keys and check them against the vault minus the
|
||||||
decimals with ≥ 2 fraction digits) and check them against the whole vault minus the file
|
file being rewritten. Any line with **no** surviving key is an orphan → port, or accept
|
||||||
being rewritten. Any line with **no** surviving key is an orphan → port, or accept
|
deliberately and say so. Use `scripts/port-census.py <new-dir> <git-HEAD:path|file>` (numbers
|
||||||
deliberately and say so.
|
with ≥ 4 int digits, decimals, flags, paths, backticked spans, `--`, ALL-CAPS, or ≥ 2 hyphens).
|
||||||
|
|
||||||
```python
|
Two calibration traps:
|
||||||
num = re.compile(r'\d[\d,]*(?:\.\d+)?')
|
- **Prose words are not facts.** A hyphen or trailing period inside a word (`long-standing`,
|
||||||
def keys(line):
|
`deployed:`) was counted as a tracked token while real identifiers hid behind it; that noise
|
||||||
out = set()
|
buried 300 real lines under 979 fake ones. Strip punctuation, require fact shape.
|
||||||
for n in num.findall(line):
|
- **The corpus decides the question.** Corpus = the new file only → "what did this rewrite
|
||||||
i = n.split('.')[0].replace(',', '')
|
drop?" Corpus = whole vault → "is the dropped fact orphaned repo-wide?" Ask one at a time.
|
||||||
if len(i) >= 4: out.add(i)
|
|
||||||
if '.' in n and len(n.split('.')[1]) >= 2: out.add(n.replace(',', '')[:6])
|
|
||||||
return out
|
|
||||||
# orphan = deleted line whose keys are absent from corpus(vault minus target file, minus .AGENTTRASH)
|
|
||||||
```
|
|
||||||
|
|
||||||
Expect ~30 orphans per 1,000 deleted lines, of which ~40 % are process trivia recoverable
|
Also grep `AGENTS.md` separately — a fact it carries is not an orphan.
|
||||||
from git (PR/commit hashes, per-boot log lines). Port the measurement/law ones to the owner
|
|
||||||
page's Laws/Eras, and list the accepted losses in the commit message and PR body. Also grep
|
|
||||||
`AGENTS.md` separately — a fact it carries is not an orphan.
|
|
||||||
|
|
||||||
## 4. Deletion mechanics
|
## 4. When a page is the same page twice
|
||||||
|
|
||||||
- Trash is **gitignored** (`.gitignore` has `.AGENTTRASH/`). Move with plain `mv` into the
|
Signature: a page whose `wc -l` is ~2× its `HEAD` size, with a **second frontmatter block in
|
||||||
repo-root dated dir, e.g. `.AGENTTRASH/<YYYYMMDD>-topic/LLM/`. `git mv` a tracked page into
|
the body** whose closing `---` is glued onto the end of a body/timestamp line. Cause chain: a
|
||||||
it stages a rename *into an ignored path* (index entry the next agent cannot see); undo with
|
frontmatter rewrite that re-reads the file it just wrote (so its own output becomes the body),
|
||||||
`git restore --staged <src> <dst>` and let the deletion be the change.
|
then a fence-repair pass keyed on `line == "---"` that cannot see a glued fence and inserts a
|
||||||
- Never `rm`, never `git clean -f`.
|
fence mid-page, so the next pass treats the tail as a new document.
|
||||||
|
|
||||||
## 5. Rewire + indexes mechanically
|
Un-double rule that worked: find the first frontmatter's close (accepting a glued fence), then
|
||||||
|
the first `type:` line after it that has more keys (`title|description|tags|timestamp`) behind
|
||||||
|
it — that is the second copy, and a stray `type:` in a code block has no key run. Keep the copy
|
||||||
|
whose body is a **superset** (compare after stripping the dropped copy's own frontmatter),
|
||||||
|
un-glue the fence, and prove `0` census losses before writing. Refuse to guess: if the halves
|
||||||
|
differ materially, stop.
|
||||||
|
|
||||||
|
## 5. `.AGENTTRASH` is not vault content
|
||||||
|
|
||||||
|
Every tool that walks the vault must exclude it — **both the scan set and the resolution
|
||||||
|
index**. The link checker resolving links *into* the trash turns "live page points at a
|
||||||
|
deleted page" into a false PASS, which is worse than a false alarm. The `docs_completeness`
|
||||||
|
seam's `find` must carry `-not -path '*/.AGENTTRASH/*'` in both walks, or a trashed page
|
||||||
|
demands an index row and a trashed page can satisfy the docs requirement for a live service.
|
||||||
|
|
||||||
|
Trash is gitignored; move with plain `mv` into the dated dir. `git mv` into an ignored path
|
||||||
|
stages a rename the next agent cannot see (`git restore --staged <src> <dst>`). Never `rm`,
|
||||||
|
never `git clean -f`.
|
||||||
|
|
||||||
|
## 6. Rewire + regenerate indexes mechanically
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
grep -rnoE "\[\[(page-a|page-b|...)\]\]" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
|
grep -rnoE "\[\[(page-a|page-b|...)\]\]" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
|
||||||
grep -rnoE "\((page-a|page-b|...)\.md\)" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
|
grep -rnoE "\((page-a|page-b|...)\.md\)" --include="*.md" serverAppConfigs-docs | grep -v AGENTTRASH
|
||||||
```
|
```
|
||||||
Both wikilinks and markdown path refs must be repointed (Research `See Also` lists and
|
|
||||||
`ubatch`-style bench pages are where path refs hide). Then **regenerate** index pages with a
|
|
||||||
script that walks the vault (`os.walk`, skip `.obsidian`/`.AGENTTRASH`, one row = `[[basename]]`
|
|
||||||
+ first-sentence description stripped of markdown links) rather than hand-editing rows — the
|
|
||||||
`docs index links every docs page` seam fails on any row left behind.
|
|
||||||
|
|
||||||
## 6. Enforce the law with a seam
|
Both wikilinks and markdown path refs must be repointed (Research `See Also` lists and bench
|
||||||
|
pages are where path refs hide). Then **regenerate** index rows with a script, never by hand.
|
||||||
|
The generator bug to check for: presence tested as a **bare stem substring** — a page whose
|
||||||
|
name prefixes another's (`frank-medium` inside `frank-medium-rocmfpx`) then silently never
|
||||||
|
gets a row. Test the wikilink form `[[<stem>]]`.
|
||||||
|
|
||||||
|
## 7. Enforce the law with a seam
|
||||||
|
|
||||||
`tests/docs_completeness.bats`: derive the live set instead of hardcoding it — an endpoint is
|
`tests/docs_completeness.bats`: derive the live set instead of hardcoding it — an endpoint is
|
||||||
live when `scripts/` holds **both** `<name>.env` and `<name>.service`; fail if any
|
live when `scripts/` holds **both** `<name>.env` and `<name>.service`; fail if any
|
||||||
`<name>-*.md` page exists in the vault, and guard against a vacuous derivation
|
`<name>-*.md` page exists, and guard against a vacuous derivation (`derived -ge 3`). A
|
||||||
(`derived -ge 3`). A re-created era page then cannot pass tests.
|
re-created era page then cannot pass tests. For the length hint, prefer **derived** exemptions
|
||||||
|
(live endpoint, or a `retired-endpoint` tag: a campaign ledger that exists nowhere else is
|
||||||
|
owned by the page that measured it) plus one documented per-page opt-out; never let a size
|
||||||
|
ceiling force a ledger to be split across two owners.
|
||||||
|
|
||||||
## 7. Edit-tool hazard (costs the most time)
|
## 8. Edit-tool hazard (costs the most time)
|
||||||
|
|
||||||
Body rows beginning with `-` or `+` are op markers. The documented `+-` escape **writes a
|
Body rows beginning with `-` or `+` are op markers. The documented `+-` escape **writes a
|
||||||
literal `+`** — do not use it. Safe rewrites inside an existing bullet: keep the `- ` opener
|
literal `+`** — do not use it. Safe rewrites inside an existing bullet: keep the `- ` opener
|
||||||
@@ -86,30 +104,49 @@ line untouched and PUT only the indented continuation lines, or convert the note
|
|||||||
bullet whose body lines start with two/four spaces. If a wrapped `[[Two Word Page]]` shows up
|
bullet whose body lines start with two/four spaces. If a wrapped `[[Two Word Page]]` shows up
|
||||||
as an unresolved link, the target was hard-wrapped across lines — unwrap it.
|
as an unresolved link, the target was hard-wrapped across lines — unwrap it.
|
||||||
|
|
||||||
## 8. Merge order (a long-lived branch under a moving main)
|
## 9. Delegated writes: verify bytes, never narrative
|
||||||
|
|
||||||
|
Measured in one campaign, all four failure modes live:
|
||||||
|
- A subagent killed by the runtime limit yielded a 495-line owner page, a verified era table
|
||||||
|
and a byte census — **the file did not exist** and its table was self-referential.
|
||||||
|
- Another wrote its output to disk and died without reporting, leaving a rewritten `log.md`;
|
||||||
|
the census showed it had dropped laws held nowhere else.
|
||||||
|
- Verdict tables claimed "0 sole records / no contradictions" while missing the contradiction
|
||||||
|
sitting in their own evidence row, and invented bullet IDs — one renamed a bullet to the
|
||||||
|
**opposite** claim.
|
||||||
|
- A scout called accurate docs "WRONG" because the docs said `wip: "down"`.
|
||||||
|
|
||||||
|
Rules: after ANY delegated write run `wc`/`ls` + the orphan census yourself; re-grep every
|
||||||
|
claimed holder before cutting a line; treat `cancelled`/timed-out yields as unverified no
|
||||||
|
matter how concrete they read; and re-read the source bullet before trusting a table's ID.
|
||||||
|
|
||||||
|
## 10. Merge order (a long-lived branch under a moving main)
|
||||||
|
|
||||||
1. Commit your work in atomic groups **before** merging (a dirty tree cannot merge cleanly).
|
1. Commit your work in atomic groups **before** merging (a dirty tree cannot merge cleanly).
|
||||||
2. Inspect the incoming commits first: `git log --oneline --stat HEAD..origin/main`. If they
|
2. Inspect the incoming commits: `git log --oneline --stat HEAD..origin/main`. If they modify
|
||||||
modify a page you deleted, **port their additions into the owner page**, then merge and
|
a page you deleted, **port their additions into the owner page**, then merge and resolve
|
||||||
resolve the modify/delete as `git rm`.
|
the modify/delete as `git rm`.
|
||||||
3. Resolve a content block with `write conflict://<id>` (`@ours` / `@theirs` / explicit text),
|
3. Resolve a content block with `write conflict://<id>` (`@ours` / `@theirs` / explicit text),
|
||||||
then **`git add` the file** — resolving content does not stage it, and `git commit` refuses
|
then **`git add` the file** — resolving content does not stage it.
|
||||||
with "you have unmerged files".
|
4. **Re-probe live state after the merge, before you write any number.** A campaign that
|
||||||
4. `git * --no-verify` is forbidden here; commit hooks are fine.
|
reported fleet shapes at the start was made stale by a mid-campaign reslice on main: probe
|
||||||
5. Sweep for dead paths left in `AGENTS.md`: `grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -u`
|
`/slots` and `tr '\0' '\n' < /proc/$(pidof <engine>)/cmdline` again at the end, and let the
|
||||||
then `find` each. Expect false positives from names hard-wrapped across two lines — confirm
|
live answer beat both your earlier probe and the docs.
|
||||||
each with `ls` before "fixing".
|
5. `git * --no-verify` is forbidden here. Sweep dead paths in `AGENTS.md`
|
||||||
|
(`grep -oE "[A-Za-z0-9._-]+\.md" AGENTS.md | sort -u`, then `find` each; hard-wrapped names
|
||||||
|
are false positives — confirm with `ls`).
|
||||||
|
|
||||||
## 9. Verify, open, land
|
## 11. Verify, open, land
|
||||||
|
|
||||||
- `./tests/run.sh` (full suite) + `bats tests/docs_completeness.bats` +
|
- `./tests/run.sh` (full suite) + `python3 scripts/check-vault-links.py serverAppConfigs-docs`
|
||||||
`python3 scripts/check-vault-links.py serverAppConfigs-docs` → must print `0 unresolved links`.
|
→ must print `0 unresolved links`, and the OKF linter `0 violations`.
|
||||||
- Env trims: prove value-neutrality mechanically — `git show HEAD:scripts/<svc>.env | grep -E
|
- Env trims: prove value-neutrality mechanically (`git show HEAD:scripts/<svc>.env | grep -E
|
||||||
'^[A-Z_]+=' | sort` vs the working copy, diff must be empty, then run that service's seam.
|
'^[A-Z_]+=' | sort` vs working copy, diff empty). **`scripts/<svc>.env` is a deploy-planner
|
||||||
**Warn that `scripts/<svc>.env` is a deploy-planner path**: a comment-only trim makes the
|
path**: a comment-only trim makes the next service-scoped deploy restart a healthy endpoint.
|
||||||
next service-scoped deploy restart a healthy endpoint for identical values.
|
|
||||||
- Open the PR through the API with a 600-perm curl config (managed skill
|
- Open the PR through the API with a 600-perm curl config (managed skill
|
||||||
`gitea-api-pr-operations`), end the body with the `cwd:` / `session:` footer, then
|
`gitea-api-pr-operations`), end the body with the `cwd:` / `session:` footer, then
|
||||||
`xdg-open <PR-URL>` the instant it exists.
|
`xdg-open <PR-URL>` the instant it exists. Never merge without Frank's current explicit OK.
|
||||||
- Docs-only change ⇒ **no deploy**. Fast-forward the deploy checkout only
|
- Docs-only change ⇒ **no deploy**. Fast-forward the deploy checkout only
|
||||||
(`git -C ~/Projects/serverAppConfig merge --ff-only origin/main`).
|
(`git -C ~/Projects/serverAppConfig merge --ff-only origin/main`).
|
||||||
|
- Say what was NOT done, in the PR body: soft lint tallies left (missing `## Citations`),
|
||||||
|
conflicts left standing, repo config that was never installed on a host.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792411412495,
|
"lastConsolidatedAt": 1792415312526,
|
||||||
"lastResult": {
|
"lastResult": {
|
||||||
"at": 1792401456699,
|
"at": 1792401456699,
|
||||||
"sessions": 1,
|
"sessions": 1,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792411509877,
|
"lastConsolidatedAt": 1792415169883,
|
||||||
"lastResult": {
|
"lastResult": {
|
||||||
"at": 1792402864677,
|
"at": 1792402864677,
|
||||||
"sessions": 1,
|
"sessions": 1,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"v": 1,
|
"v": 1,
|
||||||
"lastConsolidatedAt": 1792411648565,
|
"lastConsolidatedAt": 1792415248646,
|
||||||
"lastResult": {
|
"lastResult": {
|
||||||
"at": 1792401394555,
|
"at": 1792401394555,
|
||||||
"sessions": 1,
|
"sessions": 1,
|
||||||
|
|||||||
@@ -275,10 +275,10 @@ transition = [ "fade" ]
|
|||||||
interval_seconds = 1200
|
interval_seconds = 1200
|
||||||
|
|
||||||
[wallpaper.default]
|
[wallpaper.default]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_6kzekl.jpg"
|
||||||
|
|
||||||
[wallpaper.last]
|
[wallpaper.last]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_6kzekl.jpg"
|
||||||
|
|
||||||
[wallpaper.monitors.DP-1]
|
[wallpaper.monitors.DP-1]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_y8ewzk.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_y8ewzk.jpg"
|
||||||
@@ -290,7 +290,7 @@ transition = [ "fade" ]
|
|||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_v9e2w3.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_v9e2w3.jpg"
|
||||||
|
|
||||||
[wallpaper.monitors.eDP-1]
|
[wallpaper.monitors.eDP-1]
|
||||||
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_odl3r9.jpg"
|
path = "/home/blank/.local/share/noctalia/wallpapers/wallhaven_6kzekl.jpg"
|
||||||
|
|
||||||
[weather]
|
[weather]
|
||||||
unit = "imperial"
|
unit = "imperial"
|
||||||
|
|||||||
Reference in new issue
Block a user