Compare commits
16
Commits
cf9d9c18b4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f873fe4c2 | ||
|
|
79acaa5eb9 | ||
|
|
a57cb1b19d | ||
|
|
c24859c925 | ||
|
|
91596b7965 | ||
|
|
750ed9ecc2 | ||
|
|
7f1d3c130a | ||
|
|
b018aa52d2 | ||
|
|
8a1bcb03f5 | ||
|
|
d963bd0a4c | ||
|
|
9dd128b77a | ||
|
|
a9daefa427 | ||
|
|
bbf743b961 | ||
|
|
f0a5086dbc | ||
|
|
606e332305 | ||
|
|
d393926d9e |
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: Notifying the user
|
||||
description: Use when you want to reach the user on their phone — work finished while they were away, something needs a decision, or you are about to ask them to paste you a credential so you can tell them something.
|
||||
version: 1
|
||||
---
|
||||
|
||||
**You have a key for this. `$OFFICER_AGENT_KEY`.** It is in your environment, minted for this session, and
|
||||
it can do exactly one thing: send a notification to the person you are working for. Never ask them to paste
|
||||
you an API key so you can tell them something — you already have the only kind you should have.
|
||||
|
||||
```
|
||||
curl -X POST "$OFFICER_API_URL/api/notify/_officer/notify" \
|
||||
-H "Authorization: Bearer $OFFICER_AGENT_KEY" -H "Host: $OFFICER_API_HOST" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{"code":"officer/core:message","title":"<a few words>","body":"<one sentence>"}'
|
||||
```
|
||||
|
||||
No `userId` — the key says who you are, and it can only reach that person. `title` and `body` are yours to
|
||||
write. It arrives on their phone.
|
||||
|
||||
**This is not the same channel as your harness's own push tool.** That one refuses to interrupt a terminal
|
||||
the user is sitting at, which is right for it and wrong for this: a notification from here is for reaching
|
||||
somebody who is NOT looking, and it goes whether they are at the desk or not.
|
||||
|
||||
**So send few, and only what needs them.** A notification is an interruption. Something finished that they
|
||||
asked for and will see anyway is not worth one; a decision you are blocked on, or a failure they would
|
||||
otherwise find tomorrow, is. Being told what worked trains somebody to stop reading, and then the one that
|
||||
mattered arrives in a queue they no longer open.
|
||||
|
||||
**What you write goes through Apple and Google to reach a lock screen.** Almost everything else in Officer
|
||||
travels as a code the device renders itself, so nothing readable leaves the machine. This is the exception,
|
||||
and the judgement is yours: what you did, a count, a name they chose — fine. The contents of what you were
|
||||
reading, a filename, a sender, a credential, anything told to you in confidence — never. When in doubt, say
|
||||
that something happened and let them come and look.
|
||||
|
||||
If the key is absent, say so plainly rather than reaching for another way in. It means minting one failed,
|
||||
which is worth knowing about.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: Restarting your own host
|
||||
description: Use before restarting, stopping or reloading any PM2 process, and whenever a change you made lives in a sidecar rather than in the main server. Names the one process that kills you, and what to do instead.
|
||||
version: 1
|
||||
---
|
||||
|
||||
**You are a child of `officer-claude-code`.** Your process tree is:
|
||||
|
||||
```
|
||||
zsh ← every Bash command you run
|
||||
claude --output-format stream-json …
|
||||
bun run src/servers/sidecar/claude/user-instance.ts ← officer-claude-code, a PM2 process
|
||||
PM2 God Daemon
|
||||
```
|
||||
|
||||
So **`pm2 restart officer-claude-code` kills you, mid-command.** PM2 tree-kills the sidecar, which takes
|
||||
`claude` and your shell with it. The tool call never returns. What the user sees is the conversation
|
||||
stopping dead — and what the harness records is an *interrupted* or *rejected* tool call, so the transcript
|
||||
blames them for something they did not do. That misattribution is the worst part: it sends everyone
|
||||
looking in the wrong place.
|
||||
|
||||
The same applies to `pm2 stop`, `pm2 delete`, `pm2 reload` and `pm2 restart all` or
|
||||
`ecosystem.config.cjs`, which include you.
|
||||
|
||||
## What is safe
|
||||
|
||||
**`pm2 restart officer` is safe.** The sidecars are PM2 PEERS of the main server, not its children, so
|
||||
restarting `officer` costs you a socket reconnect and nothing else. Every other sidecar —
|
||||
`officer-pty`, `officer-email`, `officer-notify` and the rest — is equally safe for the same reason.
|
||||
|
||||
Only your own host is special.
|
||||
|
||||
## The trap this actually sets
|
||||
|
||||
A change under `src/servers/sidecar/claude/` — the session credential, the manager, the transcript
|
||||
reader — **does not take effect when you restart `officer`.** It lives in the process you are running
|
||||
inside. So the honest failure mode is not a crash, it is this:
|
||||
|
||||
> You edit the file, restart `officer`, report the change as live, and it is not. The old code keeps
|
||||
> running for hours while everything you say about it is wrong.
|
||||
|
||||
That happened on 2026-08-25: a credential scope was widened, `officer` was restarted four times, and the
|
||||
sidecar carried the previous build all day. The symptom was a legitimate refusal that read as a bug.
|
||||
|
||||
**So check which process owns your change before you claim it is live.** `pm2 jlist` gives each process's
|
||||
uptime; if the sidecar's start time predates your edit, your edit is not running.
|
||||
|
||||
## What to do instead
|
||||
|
||||
**Ask the user to run it.** One line, and they are already there:
|
||||
|
||||
```
|
||||
pm2 restart officer-claude-code
|
||||
```
|
||||
|
||||
Say why it is theirs to run rather than yours — that it ends this session — so it does not read as
|
||||
helplessness.
|
||||
|
||||
If it genuinely must happen without them, detaching it from your process group is the only form that
|
||||
survives, and **it still ends this conversation** — the restart completes, you do not:
|
||||
|
||||
```
|
||||
setsid nohup sh -c 'sleep 2; pm2 restart officer-claude-code' >/dev/null 2>&1 &
|
||||
```
|
||||
|
||||
Never do that silently. Tell the user first that the session is about to end, or they will see exactly
|
||||
what this affordance exists to explain.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: Starting an app
|
||||
description: Use when asked to create, scaffold or start a new app or project, and when asked to run one. There is a template to begin from, and a specific way the owner expects a dev server to be started.
|
||||
version: 1
|
||||
---
|
||||
|
||||
**Do not scaffold a new app from scratch.** There is a template, it is the agreed starting point, and it
|
||||
carries decisions that took a while to settle.
|
||||
|
||||
```bash
|
||||
git clone git@gitea.officer.dev:templates/template-react.git <name>
|
||||
```
|
||||
|
||||
Bun · Hono · React 19 · Tailwind v4 · shadcn/ui · Postgres with Drizzle. A JSON API at `/api` and a React
|
||||
app at `/`, over one query layer.
|
||||
|
||||
**SSH, not HTTPS.** `https://gitea.officer.dev/…` prompts for a username and fails in a non-interactive
|
||||
shell; the SSH remote is already authenticated. If SSH is being refused for a stretch — the public host is
|
||||
behind a proxy that rate-limits it — `ssh://git@100.64.0.13:10023/templates/template-react.git` is the same
|
||||
repository over the tailnet.
|
||||
|
||||
### Cloning into the folder you are already in
|
||||
|
||||
`git clone <url> .` works, **and only into a genuinely empty directory**. Verified: a single file makes it
|
||||
fail, and so does a single HIDDEN one —
|
||||
|
||||
```
|
||||
fatal: destination path '.' already exists and is not an empty directory.
|
||||
```
|
||||
|
||||
That is not an edge case here. A commander's directory holds `CLAUDE.md` and `state/`; a plugin's chat
|
||||
directory holds a `CLAUDE.md` the platform wrote. Cloning into either fails.
|
||||
|
||||
When the directory is not empty, fetch instead of cloning:
|
||||
|
||||
```bash
|
||||
git init -q .
|
||||
git remote add origin git@gitea.officer.dev:templates/template-react.git
|
||||
git fetch -q --depth 1 origin HEAD
|
||||
git checkout -q FETCH_HEAD -- .
|
||||
git reset -q
|
||||
```
|
||||
|
||||
**This overwrites a file that collides, without asking.** Also verified: doing it in a directory that
|
||||
already had a `CLAUDE.md` replaced it with the template's. So look at what is there first, and if one of
|
||||
those files is the platform's — a commander's brief, a plugin's chat brief — move it aside and put it back,
|
||||
or clone somewhere else and copy in what you need.
|
||||
|
||||
## Then read its own documentation, and do step 1
|
||||
|
||||
The template documents itself. **`README.md` step 1 is a rename** — everything project-specific is called
|
||||
`app`, and a case-sensitive search for `app`, `App` and `APP` finds all of it. Thirty seconds at the start,
|
||||
a chore forever after. Do it before the first commit.
|
||||
|
||||
Its `CLAUDE.md` points at `CONVENTIONS.md` (React patterns, feature folders, manager hooks),
|
||||
`docs/DATABASE.md` and `docs/SURFACES.md`. Read those rather than inferring house style from the code —
|
||||
they exist so the same argument is not had twice.
|
||||
|
||||
Drop the template's git history and start your own unless told otherwise: it is a starting point, not an
|
||||
upstream to track.
|
||||
|
||||
## Data to build against
|
||||
|
||||
There is mock data, so a dashboard has something real to show:
|
||||
|
||||
```bash
|
||||
git clone git@gitea.officer.dev:templates/sample-data.git
|
||||
```
|
||||
|
||||
`sales` — 6,402 daily orders over two years, with a Q4 lift, a summer dip, quiet weekends and one region
|
||||
pulling ahead in year two. `subscriptions` — 480 accounts where "active" is a calculation over start and
|
||||
cancel dates rather than a column, and small plans churn far more than large ones.
|
||||
|
||||
Both come as `.xlsx` and `.csv`, identical data. **The CSV needs no parser**, so prefer it unless reading
|
||||
the spreadsheet is the point. Its `README.md` has every column.
|
||||
|
||||
Use this rather than inventing rows. Generated data with no shape in it produces a chart that proves
|
||||
nothing, and that is what gets built when an agent makes numbers up.
|
||||
|
||||
## Running it — always as a background job
|
||||
|
||||
```bash
|
||||
bun dev
|
||||
```
|
||||
|
||||
**Start it in the BACKGROUND, never in the foreground.** Two reasons, and either one is sufficient:
|
||||
|
||||
- **A dev server does not exit.** Run in the foreground it blocks the turn until the tool times out, and
|
||||
the timeout is reported as a failure of a thing that was working perfectly.
|
||||
- **The owner wants to watch it.** A background job gets Officer's live panel — the same one they would
|
||||
see running it themselves — so they can read the output, see the port, and stop it. Run any other way it
|
||||
is invisible, and stopping it means asking you.
|
||||
|
||||
`bun dev` prints steadily, so the panel fills immediately. `predev` regenerates the index first; that is
|
||||
normal and takes a moment.
|
||||
|
||||
The general rule about background output still applies — see the background-jobs affordance. It matters
|
||||
less here only because this particular command is already talkative.
|
||||
|
||||
**Say the URL when it comes up.** The port is in `.env` and the template's README, and the first thing
|
||||
anybody wants after "it's running" is where to look.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: When the user stops working
|
||||
description: Use whenever you are about to close a turn, defer work, or comment on the hour, the session length, or how long the user has been at it.
|
||||
version: 1
|
||||
---
|
||||
|
||||
**Never tell the user to stop.** Not to rest, not to take a break, not to pick it up tomorrow, not that this
|
||||
is a good place to pause. Not as a closing line, not as an offer, not as care.
|
||||
|
||||
That includes every softer form of it, and the softer forms are the ones that actually get written:
|
||||
|
||||
- "you've been at this a long time"
|
||||
- "none of this is urgent"
|
||||
- "it's late — this can wait"
|
||||
- "let's leave it here for tonight"
|
||||
- "no rush on this one"
|
||||
- deferring work yourself, unasked, because the hour seemed like a reason
|
||||
|
||||
If the user says it is four in the morning, that is context for the work, not an invitation to comment on it.
|
||||
Answer the question.
|
||||
|
||||
**Why it lands badly, since the instinct behind it is a decent one.** It reads as being managed. The user
|
||||
decides what they are doing with their night, and an assistant that starts making that call has quietly
|
||||
appointed itself to a job nobody gave it — while also implying the work is less important than the observer
|
||||
thinks their tiredness is. Both are presumptuous, and neither is information they lack.
|
||||
|
||||
**What to do instead:** finish the thing, report what happened, and stop talking. Silence at the end of a turn
|
||||
says "your move" without saying anything about them. If something genuinely should not ship yet, say why in
|
||||
terms of the WORK — untested, one host behind, a schema change nobody has run — never in terms of the person.
|
||||
|
||||
When they say they are stopping, acknowledge it in a few words and stop. No commentary on the hour, no advice
|
||||
about sleep, no "good idea".
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: bots
|
||||
title: Bots
|
||||
description: >-
|
||||
Use when the owner asks about a bot — what one is, how to start one, why one has not fired, how to stop
|
||||
one, or why it is a bot rather than a commander. Also read it before setting one up FOR them. Carries
|
||||
where a bot lives, the record that is the only thing able to stop it, and the two ways one dies quietly.
|
||||
version: 1
|
||||
---
|
||||
|
||||
# A bot is one thing watched and one thing done
|
||||
|
||||
"Notify me when x.com is down." "Tell the todo guy when NYT has a new headline." That sentence is the
|
||||
whole specification, and **the first message arms it** — a bot that interviews the owner first has already
|
||||
failed at the thing that makes it a bot.
|
||||
|
||||
## Bot or commander — the line, and it is not size
|
||||
|
||||
- **A bot watches ONE thing and reacts.** A shell loop that exits when a condition is true. No ledger, no
|
||||
procedures, no dispatch policy. Arming is `run_in_background: true` on a script and nothing else.
|
||||
- **A commander runs a JOB.** It classifies, it decides, it spawns agents, it keeps `JOB.md` and a record
|
||||
of what it has already handled, and it is set up in a conversation before it is armed.
|
||||
|
||||
If the answer to "what does it do when it fires" is one action, it is a bot. If the answer needs a
|
||||
paragraph, or the thing has to remember what it already did, it is a commander. Reach for
|
||||
[[commander]] there — and note that the bot brief tells bots explicitly NOT to read it, because a bot that
|
||||
starts building a dispatch loop has stopped being a bot.
|
||||
|
||||
## Where everything is
|
||||
|
||||
```
|
||||
$DATA_PATH/<owner-email>/bots/
|
||||
├── CLAUDE.md the brief every bot runs under — PLATFORM-OWNED, rewritten every turn
|
||||
├── <name>.sh the watch itself, one per bot
|
||||
├── <name>.state its baseline, so a restart resumes instead of re-firing
|
||||
└── <sessionId>.bot.json the record: script, state, pids, what it watches
|
||||
```
|
||||
|
||||
Every bot shares that one directory — `getBotsDir(email)` — which is why the record is named for the
|
||||
session id rather than filed under it.
|
||||
|
||||
The chat list for them is `GET /api/chat/sessions?context=bots`. The context IS the address: no `cwd`
|
||||
needed, because there is only one bots directory per owner.
|
||||
|
||||
## `<sessionId>.bot.json` is the only thing that can stop a bot
|
||||
|
||||
A watch is a detached shell process. Nothing else on the machine knows it belongs to a bot — not the
|
||||
transcript, not the session list, and not the process table, which sees an anonymous `bash`. The record is
|
||||
the only link between the conversation and the pid.
|
||||
|
||||
That cost was paid before the file existed: a bot deleted on 2026-08-26 was still polling a website eleven
|
||||
minutes later, with nothing in the product able to find it. So when a bot re-arms, it rewrites the record
|
||||
with the new pid, in the same turn. A record that is stale is worse than absent — it names a pid that may
|
||||
now belong to something else entirely.
|
||||
|
||||
## Why a bot has not fired — check in this order
|
||||
|
||||
1. **Is the process alive?** Read the record, then `ps -p <pid>`. A watch dies with a reboot and nothing
|
||||
re-arms it automatically; the bot re-arms itself on its next turn, which needs a message.
|
||||
2. **Has it fired already and not re-armed?** The exit IS the signal. A bot that fired, reported, and
|
||||
never re-armed looks identical to one that is watching quietly.
|
||||
3. **Is the baseline wrong?** A "has it changed" watch that wrote its state before exiting will not fire
|
||||
on the same change twice. That is correct, and it is also the usual reason a test does not reproduce.
|
||||
4. **Is it watching the wrong thing?** `watches` in the record is the bot's own description of its job.
|
||||
Compare it to what the owner actually asked for before debugging any further down.
|
||||
|
||||
## The two ways a bot dies quietly
|
||||
|
||||
- **A reboot.** The loop is a plain background process; nothing supervises it. The record survives, which
|
||||
is what lets the next turn re-arm from it — so a bot that has been silent since a restart is not broken,
|
||||
it is unstarted.
|
||||
- **A fired-and-forgotten watch.** It exited, said its line, and the turn ended without re-arming. Act,
|
||||
re-arm, record — in one turn — is the rule in the brief precisely because forgetting the third step is
|
||||
invisible until the thing never fires again.
|
||||
|
||||
## Setting one up for the owner
|
||||
|
||||
Say the sentence and let the bot arm itself. The brief in that directory is thorough and current — it is
|
||||
rewritten on every turn from `src/servers/api/bots/brief.ts`, so it cannot drift from the platform the way
|
||||
a copy here would.
|
||||
|
||||
**Do not paste the mechanism into the conversation.** If you find yourself explaining the script shape, the
|
||||
record, or the naming rules to a bot, it has not read its own brief and the fix is to point at it.
|
||||
|
||||
## Naming
|
||||
|
||||
A bot's automatic title is the owner's first sentence, truncated, typos included. It is a prompt, not a
|
||||
name. Two to four words, the subject and what is watched about it — `newweb uptime`, `NYT front page`,
|
||||
`gitea disk space`. Not "Website Monitor Bot": every bot is a monitor, so the word earns nothing in a list
|
||||
of them.
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: Commanders
|
||||
name: commander
|
||||
title: Commanders
|
||||
description: >-
|
||||
Use when the user asks about a commander — what one is, how to start one, why one is not firing, how much
|
||||
one costs — or when they describe work that should keep happening while they are away ("watch X and do Y",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: delegating
|
||||
title: Delegating the writing
|
||||
description: >-
|
||||
Use when a feature has been agreed and somebody has to type it — how to hand the writing to a cheaper
|
||||
agent, what a brief has to contain, and how to review what comes back. Also read it before deciding NOT
|
||||
to delegate, because most of the cost of doing it yourself is invisible until the context runs out.
|
||||
version: 1
|
||||
---
|
||||
|
||||
# Why this exists
|
||||
|
||||
The owner's architecture, somebody else's typing, and the owner's reviewer in the middle. That division
|
||||
is the whole point: the expensive model should be spending its context on decisions, not on writing out
|
||||
six locale files or the same form for the fourth time.
|
||||
|
||||
Set as a standing way of working on 2026-08-24. It is not a suggestion to consider per task — when a
|
||||
feature is agreed and the remaining work is typing, it goes to an agent.
|
||||
|
||||
# What stays here and what goes
|
||||
|
||||
**Stays.** The design. Which boundary moves, what a thing is called, what the failure modes are, and
|
||||
whether the result is right. If a decision has to be made while the code is being written, the brief was
|
||||
incomplete — that is a fault in the brief, not a reason to take the work back.
|
||||
|
||||
**Goes.** Everything mechanical and everything wide: a change repeated across many files, translations,
|
||||
a form, a migration over a list of call sites, tests for a rule already agreed.
|
||||
|
||||
**Does not go.** Anything where a wrong guess is expensive and quiet — permission checks, credential
|
||||
handling, anything that deletes. Not because a smaller model cannot write them, but because reviewing
|
||||
them properly costs as much as writing them, so nothing is saved.
|
||||
|
||||
# The brief
|
||||
|
||||
An agent that has to ask a question has already cost more than it saved. So the brief carries:
|
||||
|
||||
- **The exact files**, by path. Not "the locale files" — list them.
|
||||
- **The pattern to follow**, by pointing at a file that already does it. "Same shape as `pt-PT.json`" is
|
||||
worth more than any description.
|
||||
- **What must not change.** Key names, public exports, anything another file imports.
|
||||
- **How it verifies itself**: `bunx tsgo`, `bun test`, `bunx prettier --write <the paths it touched>`.
|
||||
Say that a red typecheck is not done, or it will report success with a broken tree.
|
||||
- **What done looks like**, in one sentence it can check itself against.
|
||||
|
||||
Run it in the background and carry on. A brief good enough to run unattended is the same brief that makes
|
||||
the result reviewable.
|
||||
|
||||
# Choosing the model
|
||||
|
||||
**The cheapest one that will get it right.** Not the cheapest one; the cheapest one that will get it
|
||||
right, which is a different question and has to be asked per task.
|
||||
|
||||
- **Haiku** — mechanical and high-volume with no judgement in it. A rename across forty call sites, a
|
||||
JSON transform, moving a block of files to a new shape.
|
||||
- **Sonnet** — the default, and what most of this work is. A feature whose design is settled and whose
|
||||
brief names the files: translations, a form, tests for a rule already agreed, a migration over a list.
|
||||
- **Opus** — when the work still contains a decision. Which usually means the brief is incomplete, so
|
||||
the first move is to finish the brief rather than to spend more per token on writing it.
|
||||
|
||||
## Cheapness is a property of the pair, not the model
|
||||
|
||||
A cheap model on a vague brief is the most expensive option available: it returns something plausible,
|
||||
you review it, you reject it, you write the brief you should have written, and you pay for the whole
|
||||
round twice. The saving comes from the brief being good enough that a smaller model cannot go wrong —
|
||||
so if you find yourself reaching for a bigger model to compensate for a thin brief, fix the brief.
|
||||
|
||||
The other direction is real too, and cheaper to correct: a task that comes back wrong from Haiku goes to
|
||||
Sonnet and costs one wasted run. Start low.
|
||||
|
||||
# Reviewing what comes back
|
||||
|
||||
**Read the diff, not the summary.** The summary is written by the thing being reviewed. `git diff` is not.
|
||||
|
||||
**Run the checks yourself.** `bunx tsgo && bun test` in the real tree. An agent reporting green has
|
||||
reported what it believes.
|
||||
|
||||
**Then it is yours.** It goes out under your commit and your name, so a mistake in it is not the agent's
|
||||
mistake. Review it as if you had typed it, because as far as the history is concerned you did.
|
||||
|
||||
# What went wrong the first time, so it does not go wrong again
|
||||
|
||||
Both of these are from one night, 2026-08-24.
|
||||
|
||||
**A dead agent was reported as running, twice.** A subagent is a sidechain in the parent session, and a
|
||||
tool rejection in the parent had killed it about four hours earlier. Nothing said so and the parent kept
|
||||
answering "still running" when asked. **Never report a subagent's state from memory — look.** If the
|
||||
result is not in the tree, it did not happen.
|
||||
|
||||
**Interrupting the parent interrupts the child.** Stopping the session to say something also stops the
|
||||
work it was waiting on. Worth knowing before assuming a long silence means progress.
|
||||
+34
-1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: Deploying Officer
|
||||
name: deploy
|
||||
title: Deploying Officer
|
||||
description: >-
|
||||
Use when asked to deploy, release or roll out Officer, to watch for a release and deploy it, or when a
|
||||
deploy has gone wrong — a host behind, a tag not noticed, a rollout that stopped halfway. Points at the
|
||||
@@ -66,6 +67,38 @@ money. That is not hypothetical.
|
||||
- **A dirty tree means TRACKED changes.** `git status --porcelain` counts untracked files; a stray script a
|
||||
fast-forward could never touch is not a reason to skip a host. Use `--untracked-files=no`.
|
||||
|
||||
# `db:push` can offer data loss at a prompt, and it waits for an answer
|
||||
|
||||
A schema push on a host that is many commits behind can stop and ask whether a table was RENAMED. Both
|
||||
answers destroy something, so neither is safe to guess and neither is safe to automate.
|
||||
|
||||
Seen 2026-08-27 catching `edge-pertento-bridge` up by 215 commits:
|
||||
|
||||
```
|
||||
Is command_hq_spawned table created or renamed from another table?
|
||||
❯ + command_hq_spawned create table
|
||||
~ dav_app_passwords › command_hq_spawned rename table
|
||||
```
|
||||
|
||||
drizzle-kit saw one table leaving the platform schema and another arriving, and could not tell a rename
|
||||
from a coincidence. `rename` would have turned the DAV app passwords into the spawned-commander table;
|
||||
`create` would have dropped them.
|
||||
|
||||
**The table had not gone anywhere — it had moved to a PLUGIN.** Caldav was extracted that day, so
|
||||
`dav_app_passwords` belongs to `plugins/caldav/db/schema.ts` and reaches the barrel through
|
||||
`gen-plugin-schemas`. Installing the plugin FIRST declares it, and the question disappears.
|
||||
|
||||
So, on a host far enough behind that a plugin extraction is in the gap:
|
||||
|
||||
1. Install the plugins whose tables are involved, **before** pushing.
|
||||
2. Then `bun db:push < /dev/null` — stdin closed, so it can never hang waiting on a prompt in an
|
||||
unattended run. A push that needs an answer fails instead, which is the outcome you want.
|
||||
3. Expect ONE failure on a `role_permissions` foreign key, then `seed-roles.ts`, then push again. That
|
||||
sequence is normal on a host predating the roles table.
|
||||
|
||||
**Back up the database first.** `pg_dump` against the URL in `.env` — and read the variable name rather
|
||||
than assuming: several hosts use `OFFICERDEV_POSTGRES_URL`, not `POSTGRES_URL`.
|
||||
|
||||
# The report is an artefact, the lock is the record
|
||||
|
||||
Write the local record FIRST, then commit and push the report. If the push fails, say so and stop: the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: Officer platform
|
||||
name: platform
|
||||
title: Officer platform
|
||||
description: >-
|
||||
Use when the user asks what Officer can do, how to use a feature, what an endpoint is, why something is
|
||||
refused, or how permissions and accounts work — "how do I start a commander", "can a member see my files",
|
||||
@@ -41,7 +42,7 @@ diligence.
|
||||
- API `/bug-report`
|
||||
- **Notifications** (`notify`) — Push notifications to your devices
|
||||
- API `/notify`
|
||||
- owner only, even when granted: `/broadcast`
|
||||
- owner only, even when granted: `/broadcast`, `/user`
|
||||
- **Screen layouts and dashboards** (`dashboards`) — Where your own screen layouts and dashboards are saved
|
||||
- API `/dashboards` · screen `/dashboards`
|
||||
- **Command HQ** (`command-hq`) — Long-running commanders you started, organised into categories
|
||||
@@ -51,8 +52,6 @@ diligence.
|
||||
|
||||
- **Memos** (`memos`) — Notes
|
||||
- screen `/memos`
|
||||
- **Calendar and contacts** (`calendar`) — Calendars, contacts, and the app passwords that sync them to a phone
|
||||
- API `/caldav`, `/dav` · screen `/calendar`, `/contacts`
|
||||
- **SendOff** (`sendoff`) — Send files to your own machines, and to accounts that have allowed you
|
||||
- API `/sendoff` · screen `/sendoff`
|
||||
|
||||
@@ -61,7 +60,7 @@ diligence.
|
||||
- **Terminal** (`terminal`) — A shell on this machine, as your own user
|
||||
- API `/terminal` · socket `terminal` · screen `/terminal`
|
||||
- **Chat** (`chat`) — The agent
|
||||
- API `/chat`, `/agent-status`, `/claude-auth` · socket `chat`, `chat-notify` · screen `/chat`
|
||||
- API `/chat`, `/agent-status`, `/claude-auth` · socket `chat`, `chat-notify` · screen `/chat`, `/bots`
|
||||
- **Files** (`files`) — Your own home directory on this machine, and the code editor over it
|
||||
- API `/file-browser`, `/upload` · screen `/files`, `/code-editor`
|
||||
- a POST that is really a read, allowed at read level: `/download`
|
||||
@@ -98,14 +97,14 @@ list rather than remembering one.
|
||||
|
||||
## Navigation
|
||||
|
||||
- `Shift+Space` — Spotlight — go to any screen, plugin or setting by name
|
||||
- `Ctrl+Space` — Spotlight — go to any screen, plugin or setting by name
|
||||
- `Ctrl+?` — This list
|
||||
|
||||
## Themes
|
||||
|
||||
The browser applies as you move, so you watch the app change rather than reading names.
|
||||
|
||||
- `Ctrl+Shift+Space` — Open the theme browser
|
||||
- `Ctrl+Shift+↑ / ↓` — Open the theme browser
|
||||
- `Ctrl+Shift+← / →` — Previous / next theme, without opening anything
|
||||
- `Ctrl+Shift+F` — Star or unstar the current theme, from anywhere
|
||||
|
||||
|
||||
Reference in New Issue
Block a user