tell an existing commander how to reach its owner
the voice-memos commander was asked for a notification and reached for its harness's own push tool — a different channel, which refuses to interrupt a live terminal. it did that because nothing had told it Officer has a route of its own. that rule is the harness's; the platform's route has none, and had already pushed to a phone twice with the owner sitting at the terminal. so the notify part is its own section now, appendable on its own, and the backfill lands it on a commander that already has a credential — which is every commander that predates this, i.e. the ones that need it. the backfill also stopped being all-or-nothing. it skipped everything after the credential check, so a commander with an officer.env could never receive anything added later. each section is now appended independently, keyed on a marker the section itself contains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
@@ -21,7 +21,7 @@ import { readFileSync, existsSync, appendFileSync, chmodSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { getUsers, listCommandHqSessions } from 'officerdb';
|
||||
import { writeCommanderCredential, commanderEnvPath } from '../src/servers/api/command-hq/credential';
|
||||
import { credentialSection } from '../src/servers/api/command-hq/directory';
|
||||
import { credentialSection, notifySection } from '../src/servers/api/command-hq/directory';
|
||||
import { getCommanderDir } from '../src/servers/data-path';
|
||||
import { lookupOsUser } from '../src/servers/os-user';
|
||||
|
||||
@@ -64,26 +64,42 @@ for (const row of rows) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const envPath = commanderEnvPath(row.email, row.dirName);
|
||||
if (existsSync(envPath)) {
|
||||
console.log(`ok ${label} — already has a credential`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { path } = await writeCommanderCredential({ userId: row.userId, email: row.email, dirName: row.dirName });
|
||||
chmodSync(path, 0o600);
|
||||
await giveToAccount(row.osUser, path);
|
||||
|
||||
// Appended only when it is not already described. A commander whose CLAUDE.md was hand-edited to
|
||||
// mention its credential must not end up with the section twice.
|
||||
const claudeMd = join(dir, 'CLAUDE.md');
|
||||
const existing = existsSync(claudeMd) ? readFileSync(claudeMd, 'utf8') : '';
|
||||
if (!existing.includes('officer.env')) {
|
||||
appendFileSync(claudeMd, `\n${credentialSection(path).join('\n')}`);
|
||||
|
||||
/**
|
||||
* Append a section only when it is not already described.
|
||||
*
|
||||
* The marker is a string the section itself contains, so a commander whose CLAUDE.md was hand-edited to
|
||||
* describe the same thing does not end up with it twice.
|
||||
*/
|
||||
const appendIfMissing = async (marker: string, lines: string[]): Promise<boolean> => {
|
||||
const existing = existsSync(claudeMd) ? readFileSync(claudeMd, 'utf8') : '';
|
||||
if (existing.includes(marker)) return false;
|
||||
appendFileSync(claudeMd, `\n${lines.join('\n')}`);
|
||||
await giveToAccount(row.osUser, claudeMd);
|
||||
return true;
|
||||
};
|
||||
|
||||
const envPath = commanderEnvPath(row.email, row.dirName);
|
||||
|
||||
if (!existsSync(envPath)) {
|
||||
const { path } = await writeCommanderCredential({ userId: row.userId, email: row.email, dirName: row.dirName });
|
||||
chmodSync(path, 0o600);
|
||||
await giveToAccount(row.osUser, path);
|
||||
await appendIfMissing('officer.env', credentialSection(path));
|
||||
console.log(`MINT ${label} — ${path}`);
|
||||
} else {
|
||||
console.log(`ok ${label} — already has a credential`);
|
||||
}
|
||||
|
||||
console.log(`MINT ${label} — ${path}`);
|
||||
/*
|
||||
Separately, because it arrived later and the commanders that need it most already have a credential.
|
||||
Without it a commander does not know Officer HAS a notification route, so it reaches for whatever its
|
||||
harness offers — a different channel, with its own rules about when it may interrupt.
|
||||
*/
|
||||
if (await appendIfMissing('/api/notify/_officer/notify', notifySection())) {
|
||||
console.log(`NOTIFY ${label} — told how to reach the owner`);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
|
||||
@@ -74,6 +74,21 @@ export function credentialSection(credentialPath: string | null): string[] {
|
||||
'A 403 is an answer, not a problem to route around. It means this commander was not given that, and',
|
||||
'the owner is the one who decides otherwise. Say so rather than finding another way in.',
|
||||
'',
|
||||
...notifySection(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* How a commander tells the owner something, and what it may say.
|
||||
*
|
||||
* Its own function because it lands in two places at different times: inside `credentialSection` for a
|
||||
* new commander, and appended on its own by `scripts/backfill-commander-credentials.ts` to a commander
|
||||
* that already has a credential but predates this. The Voice Memos commander is exactly that case — it
|
||||
* reached for the harness's own push tool, which is a different channel and refuses to interrupt a live
|
||||
* terminal, because nothing had told it Officer has a route of its own.
|
||||
*/
|
||||
export function notifySection(): string[] {
|
||||
return [
|
||||
'### Telling the owner something',
|
||||
'',
|
||||
'A push to their devices, in your own words.',
|
||||
|
||||
Reference in New Issue
Block a user