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 { join } from 'node:path';
|
||||||
import { getUsers, listCommandHqSessions } from 'officerdb';
|
import { getUsers, listCommandHqSessions } from 'officerdb';
|
||||||
import { writeCommanderCredential, commanderEnvPath } from '../src/servers/api/command-hq/credential';
|
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 { getCommanderDir } from '../src/servers/data-path';
|
||||||
import { lookupOsUser } from '../src/servers/os-user';
|
import { lookupOsUser } from '../src/servers/os-user';
|
||||||
|
|
||||||
@@ -64,26 +64,42 @@ for (const row of rows) {
|
|||||||
continue;
|
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 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);
|
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);
|
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',
|
'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.',
|
'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',
|
'### Telling the owner something',
|
||||||
'',
|
'',
|
||||||
'A push to their devices, in your own words.',
|
'A push to their devices, in your own words.',
|
||||||
|
|||||||
Reference in New Issue
Block a user