portable-agent-layer 0.88.0 → 0.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +8 -5
  2. package/assets/agents/gemini-researcher.md +13 -0
  3. package/assets/agents/grok-researcher.md +13 -0
  4. package/assets/agents/multi-perspective-researcher.md +12 -0
  5. package/assets/agents/perplexity-researcher.md +13 -0
  6. package/assets/agents/skill-author.md +17 -0
  7. package/assets/agents/subagent-author.md +18 -1
  8. package/assets/skills/create-subagent/SKILL.md +3 -2
  9. package/assets/skills/create-subagent/authoring-guide.md +16 -0
  10. package/assets/templates/PAL/MEMORY_SYSTEM.md +1 -1
  11. package/assets/templates/PAL/README.md +6 -5
  12. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +4 -1
  13. package/assets/templates/hooks.antigravity.json +42 -0
  14. package/assets/templates/settings.antigravity.json +44 -0
  15. package/package.json +1 -1
  16. package/src/cli/builtin-tools.ts +28 -23
  17. package/src/cli/doctor/agents.ts +64 -64
  18. package/src/cli/identity.ts +26 -20
  19. package/src/cli/index.ts +109 -312
  20. package/src/cli/knowledge.ts +160 -201
  21. package/src/cli/ledger-command.ts +69 -0
  22. package/src/cli/ledger.ts +20 -101
  23. package/src/cli/personal-context.ts +15 -12
  24. package/src/cli/rule.ts +29 -29
  25. package/src/cli/server.ts +33 -52
  26. package/src/cli/session-agent.ts +7 -9
  27. package/src/cli/skill.ts +73 -64
  28. package/src/cli/subagent.ts +51 -58
  29. package/src/cli/tree.ts +177 -0
  30. package/src/hooks/InvocationContext.ts +26 -0
  31. package/src/hooks/LedgerCommit.ts +4 -2
  32. package/src/hooks/LedgerSnapshot.ts +2 -0
  33. package/src/hooks/SecurityValidator.ts +2 -0
  34. package/src/hooks/StopOrchestrator.ts +12 -2
  35. package/src/hooks/UserPromptOrchestrator.ts +3 -15
  36. package/src/hooks/handlers/context-digests.ts +21 -21
  37. package/src/hooks/handlers/inject-retrieval.ts +7 -5
  38. package/src/hooks/handlers/readme-sync.ts +6 -2
  39. package/src/hooks/handlers/session-name.ts +3 -12
  40. package/src/hooks/lib/agent-definition.ts +6 -16
  41. package/src/hooks/lib/agent-registry.ts +88 -0
  42. package/src/hooks/lib/agent.ts +47 -23
  43. package/src/hooks/lib/antigravity-inference.ts +84 -0
  44. package/src/hooks/lib/antigravity-transcript.ts +129 -0
  45. package/src/hooks/lib/claim-check.ts +13 -2
  46. package/src/hooks/lib/claim-log.ts +5 -1
  47. package/src/hooks/lib/claude-md.ts +21 -1
  48. package/src/hooks/lib/context.ts +5 -1
  49. package/src/hooks/lib/hook-turn.ts +25 -2
  50. package/src/hooks/lib/inference.ts +77 -1
  51. package/src/hooks/lib/invocation-context.ts +99 -0
  52. package/src/hooks/lib/ledger-hook.ts +14 -6
  53. package/src/hooks/lib/models.ts +34 -17
  54. package/src/hooks/lib/paths.ts +39 -6
  55. package/src/hooks/lib/prompt-capture.ts +28 -0
  56. package/src/hooks/lib/prompt-text.ts +1 -0
  57. package/src/hooks/lib/readme-sync.ts +3 -31
  58. package/src/hooks/lib/security-gate.ts +10 -3
  59. package/src/hooks/lib/semi-static.ts +13 -3
  60. package/src/hooks/lib/session-context.ts +3 -1
  61. package/src/hooks/lib/session-names.ts +20 -4
  62. package/src/hooks/lib/stop.ts +1 -0
  63. package/src/hooks/lib/transcript.ts +6 -3
  64. package/src/targets/agent-render.ts +3 -3
  65. package/src/targets/antigravity/install.ts +68 -0
  66. package/src/targets/antigravity/uninstall.ts +66 -0
  67. package/src/targets/lib.ts +81 -64
  68. package/src/targets/opencode/install.ts +2 -0
  69. package/src/targets/opencode/permission.ts +39 -0
  70. package/src/targets/opencode/uninstall.ts +9 -8
  71. package/src/tools/agent/algorithm-reflect.ts +70 -62
  72. package/src/tools/agent/algorithm-synthesize.ts +39 -14
  73. package/src/tools/agent/analyze.ts +31 -36
  74. package/src/tools/agent/handoff-note.ts +51 -54
  75. package/src/tools/agent/interaction.ts +32 -28
  76. package/src/tools/agent/project.ts +300 -287
  77. package/src/tools/agent/relationship-note.ts +40 -36
  78. package/src/tools/agent/relationship-reflect.ts +23 -32
  79. package/src/tools/agent/synthesize.ts +26 -27
  80. package/src/tools/agent/thread.ts +42 -51
  81. package/src/tools/agent/wisdom-frame.ts +45 -38
  82. package/src/tools/control-room/server.ts +0 -2
  83. package/src/tools/control-room/ui/dist/assets/{index-BZMDzWFa.js → index-mFpXpiBj.js} +1 -1
  84. package/src/tools/control-room/ui/dist/index.html +1 -1
  85. package/src/tools/control-room/ui/screens/log.tsx +10 -1
  86. package/src/tools/lib/command.ts +275 -0
  87. package/src/tools/subagent-doctor.ts +74 -9
  88. package/src/tools/token-cost.ts +18 -17
@@ -18,56 +18,60 @@
18
18
  * Which flags make which notes is in lib/note-flags.ts.
19
19
  */
20
20
 
21
- import { parseArgs } from "node:util";
22
21
  import { appendNotes } from "../../hooks/lib/relationship";
22
+ import { leaf, runCommand, UsageError } from "../lib/command";
23
23
  import { emit } from "../lib/emit";
24
- import { notesFromFlags } from "../lib/note-flags";
24
+ import { type NoteFlags, notesFromFlags } from "../lib/note-flags";
25
25
  import { scriptArgs } from "../lib/script-args";
26
26
 
27
- const HELP = `
28
- RelationshipNote — Append W/O/Session entries to today's relationship log
27
+ const DETAILS = `Multiple flags may be combined in one call. At least one of --o, --w, --b is required.
29
28
 
30
- Usage:
29
+ Examples:
31
30
  pal cli relationship-note --o "User prefers X" --confidence 0.80
32
31
  pal cli relationship-note --w "User is building X in TypeScript"
33
32
  pal cli relationship-note --b "Debugged the cache split logic"
34
33
 
35
- Flags:
36
- --o TEXT Opinion/behavioral observation about the user
37
- --confidence N Confidence for --o (0.0–1.0, default 0.75)
38
- --w TEXT World fact about the user's situation
39
- --b TEXT Session diary — what the assistant did (first-person)
34
+ Output: appends to memory/relationship/YYYY-MM/YYYY-MM-DD.md`;
40
35
 
41
- Multiple flags may be combined in one call. At least one of --o, --w, --b is required.
42
-
43
- Output: appends to memory/relationship/YYYY-MM/YYYY-MM-DD.md
44
- `;
45
-
46
- export function run(argv: string[] = scriptArgs()) {
47
- const { values } = parseArgs({
48
- args: argv,
49
- options: {
50
- o: { type: "string", multiple: true },
51
- w: { type: "string", multiple: true },
52
- b: { type: "string" },
53
- confidence: { type: "string" },
54
- help: { type: "boolean", short: "h" },
36
+ export const command = leaf({
37
+ summary: "Append W/O/Session entries to today's relationship log",
38
+ options: {
39
+ o: {
40
+ type: "string",
41
+ multiple: true,
42
+ value: "<text>",
43
+ description: "Opinion/behavioral observation about the user (repeatable)",
55
44
  },
56
- });
57
-
58
- if (values.help) {
59
- console.log(HELP);
60
- process.exit(0);
61
- }
45
+ confidence: {
46
+ type: "string",
47
+ value: "<n>",
48
+ description: "Confidence for --o (0.0–1.0, default 0.75)",
49
+ },
50
+ w: {
51
+ type: "string",
52
+ multiple: true,
53
+ value: "<text>",
54
+ description: "World fact about the user's situation (repeatable)",
55
+ },
56
+ b: {
57
+ type: "string",
58
+ value: "<text>",
59
+ description: "Session diary — what the assistant did (first-person)",
60
+ },
61
+ },
62
+ details: DETAILS,
63
+ run: ({ values }) => appendFromFlags(values),
64
+ });
62
65
 
63
- const result = notesFromFlags(values);
64
- if ("error" in result) {
65
- console.error(result.error);
66
- process.exit(1);
67
- }
66
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
67
+ return runCommand(command, argv, ["pal", "cli", "relationship-note"]);
68
+ }
68
69
 
70
+ function appendFromFlags(flags: NoteFlags): undefined {
71
+ const result = notesFromFlags(flags);
72
+ if ("error" in result) throw new UsageError(result.error);
69
73
  const { file, written } = appendNotes(result.notes);
70
74
  emit.receipt(file, { written, deduped: result.notes.length - written });
71
75
  }
72
76
 
73
- if (import.meta.main) run();
77
+ if (import.meta.main) process.exit(await run());
@@ -15,9 +15,9 @@
15
15
 
16
16
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
17
17
  import { resolve } from "node:path";
18
- import { parseArgs } from "node:util";
19
18
  import { readOpinions, saveOpinion, setLastReflectDate } from "../../hooks/lib/opinions";
20
19
  import { palHome } from "../../hooks/lib/paths";
20
+ import { leaf, runCommand } from "../lib/command";
21
21
  import { emit } from "../lib/emit";
22
22
  import {
23
23
  consoleLines,
@@ -30,21 +30,27 @@ import {
30
30
  } from "../lib/relationship-reflect";
31
31
  import { scriptArgs } from "../lib/script-args";
32
32
 
33
- const HELP = `
34
- RelationshipReflect — Periodic reflection + opinion promotion
35
-
36
- Reads recent relationship notes and ratings. Promotes recurring
33
+ const DETAILS = `Reads recent relationship notes and ratings. Promotes recurring
37
34
  observations (O type) into tracked opinions with confidence scoring.
38
-
39
- Usage:
40
- pal cli relationship-reflect Reflect on last 7 days (default)
41
- pal cli relationship-reflect --month Reflect on last 30 days
42
- pal cli relationship-reflect --dry-run Preview without writing
35
+ With no flags, reflects on the last 7 days.
43
36
 
44
37
  Output:
45
38
  - Updates memory/relationship/opinions.json (confidence tracking)
46
- - Creates reflection report in memory/relationship/reflections/
47
- `;
39
+ - Creates reflection report in memory/relationship/reflections/`;
40
+
41
+ export const command = leaf({
42
+ summary: "Periodic reflection + opinion promotion",
43
+ options: {
44
+ month: { type: "boolean", description: "Reflect on last 30 days" },
45
+ "dry-run": { type: "boolean", description: "Preview without writing" },
46
+ },
47
+ details: DETAILS,
48
+ run: ({ values }) => reflect(values.month ?? false, values["dry-run"] ?? false),
49
+ });
50
+
51
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
52
+ return runCommand(command, argv, ["pal", "cli", "relationship-reflect"]);
53
+ }
48
54
 
49
55
  const relationshipDir = () => resolve(palHome(), "memory", "relationship");
50
56
  const ratingsFile = () => resolve(palHome(), "memory", "signals", "ratings.jsonl");
@@ -57,24 +63,9 @@ function saveReport(report: string, period: string): string {
57
63
  return filepath;
58
64
  }
59
65
 
60
- export function run(argv: string[] = scriptArgs()) {
61
- const { values } = parseArgs({
62
- args: argv,
63
- options: {
64
- month: { type: "boolean" },
65
- "dry-run": { type: "boolean" },
66
- help: { type: "boolean", short: "h" },
67
- },
68
- });
69
-
70
- if (values.help) {
71
- console.log(HELP);
72
- process.exit(0);
73
- }
74
-
75
- const daysBack = values.month ? 30 : 7;
76
- const period = values.month ? "Monthly" : "Weekly";
77
- const dryRun = values["dry-run"] ?? false;
66
+ function reflect(month: boolean, dryRun: boolean): undefined {
67
+ const daysBack = month ? 30 : 7;
68
+ const period = month ? "Monthly" : "Weekly";
78
69
 
79
70
  const notes = loadNotes(relationshipDir(), daysBack);
80
71
  const ratings = loadRatings(ratingsFile(), daysBack);
@@ -84,7 +75,7 @@ export function run(argv: string[] = scriptArgs()) {
84
75
 
85
76
  if (notes.length === 0 && ratings.length === 0) {
86
77
  emit.ok("No data to analyze");
87
- process.exit(0);
78
+ return;
88
79
  }
89
80
 
90
81
  const plan = planPromotions(notes, readOpinions());
@@ -109,4 +100,4 @@ export function run(argv: string[] = scriptArgs()) {
109
100
  for (const line of highConfidenceLines(readOpinions())) emit.ok(line);
110
101
  }
111
102
 
112
- if (import.meta.main) run();
103
+ if (import.meta.main) process.exit(await run());
@@ -15,8 +15,8 @@
15
15
 
16
16
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
17
17
  import { resolve } from "node:path";
18
- import { parseArgs } from "node:util";
19
18
  import { ensureDir, paths } from "../../hooks/lib/paths";
19
+ import { leaf, runCommand, UsageError } from "../lib/command";
20
20
  import { scriptArgs } from "../lib/script-args";
21
21
  import { readJsonl } from "../lib/self-model";
22
22
 
@@ -304,33 +304,33 @@ export function synthesize(days: number): SynthesisState {
304
304
 
305
305
  // ── CLI ──
306
306
 
307
- export function run(argv: string[] = scriptArgs()) {
308
- const { values } = parseArgs({
309
- args: argv,
310
- options: {
311
- days: { type: "string", default: "7" },
312
- force: { type: "boolean" },
313
- help: { type: "boolean", short: "h" },
307
+ export const command = leaf({
308
+ summary: "Aggregate recent PAL activity into compact state",
309
+ options: {
310
+ days: {
311
+ type: "string",
312
+ default: "7",
313
+ value: "<n>",
314
+ description: "Lookback window (default: 7)",
314
315
  },
315
- });
316
-
317
- if (values.help) {
318
- console.log(`
319
- Synthesize — Aggregate recent PAL activity into compact state
320
-
321
- Usage:
322
- synthesize.ts [--days 7] [--force]
323
-
324
- Options:
325
- --days Lookback window (default: 7)
326
- --force Skip 24h guard
316
+ force: { type: "boolean", description: "Skip 24h guard" },
317
+ },
318
+ details: "Output: ~/.pal/memory/state/synthesis.json",
319
+ run: ({ values }) =>
320
+ synthesizeUnlessFresh(lookbackDays(values.days), values.force ?? false),
321
+ });
322
+
323
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
324
+ return runCommand(command, argv, ["pal", "cli", "synthesize"]);
325
+ }
327
326
 
328
- Output: ~/.pal/memory/state/synthesis.json
329
- `);
330
- process.exit(0);
331
- }
327
+ function lookbackDays(raw: string): number {
328
+ const days = parseInt(raw, 10);
329
+ if (Number.isNaN(days)) throw new UsageError(`--days must be a number of days: ${raw}`);
330
+ return days;
331
+ }
332
332
 
333
- const force = values.force ?? false;
333
+ function synthesizeUnlessFresh(days: number, force: boolean): undefined {
334
334
  if (!shouldRun(force)) {
335
335
  console.log(
336
336
  JSON.stringify({
@@ -341,7 +341,6 @@ Output: ~/.pal/memory/state/synthesis.json
341
341
  return;
342
342
  }
343
343
 
344
- const days = parseInt(values.days ?? "7", 10);
345
344
  const state = synthesize(days);
346
345
  const sp = synthesisPath();
347
346
 
@@ -363,4 +362,4 @@ Output: ~/.pal/memory/state/synthesis.json
363
362
  );
364
363
  }
365
364
 
366
- if (import.meta.main) run();
365
+ if (import.meta.main) process.exit(await run());
@@ -11,7 +11,7 @@
11
11
  * pal cli thread --list [--all]
12
12
  */
13
13
 
14
- import { parseArgs } from "node:util";
14
+ import { leaf, runCommand, UsageError } from "../lib/command";
15
15
  import { emit } from "../lib/emit";
16
16
  import { scriptArgs } from "../lib/script-args";
17
17
  import {
@@ -23,20 +23,41 @@ import {
23
23
  writeThreads,
24
24
  } from "../lib/thread";
25
25
 
26
- const HELP = `
27
- Thread — Manage open threads across sessions
26
+ const DETAILS = `Modes (one of --add, --resolve, --list is required):
27
+ pal cli thread --add --title "..." [--context "..."]
28
+ pal cli thread --resolve --id <id>
29
+ pal cli thread --list [--all]`;
28
30
 
29
- Usage:
30
- thread.ts --add --title "..." [--context "..."]
31
- thread.ts --resolve --id <id>
32
- thread.ts --list [--all]
33
- `;
31
+ export const command = leaf({
32
+ summary: "Manage open threads across sessions",
33
+ options: {
34
+ add: { type: "boolean", description: "Open a new thread (needs --title)" },
35
+ resolve: { type: "boolean", description: "Mark a thread resolved (needs --id)" },
36
+ list: { type: "boolean", description: "Print open threads as JSON" },
37
+ title: { type: "string", value: "<text>", description: "Title of the thread to add" },
38
+ context: {
39
+ type: "string",
40
+ value: "<text>",
41
+ description: "Why it matters, what needs to happen (with --add)",
42
+ },
43
+ id: { type: "string", value: "<id>", description: "Id of the thread to resolve" },
44
+ all: { type: "boolean", description: "Include resolved threads (with --list)" },
45
+ },
46
+ details: DETAILS,
47
+ run: ({ values }) => {
48
+ if (values.add) return add(values.title, values.context);
49
+ if (values.resolve) return markResolved(values.id);
50
+ if (values.list) return list(values.all ?? false);
51
+ throw new UsageError("one of --add, --resolve, --list is required");
52
+ },
53
+ });
34
54
 
35
- function add(title: string | undefined, context: string | undefined) {
36
- if (!title) {
37
- console.error("--title required");
38
- process.exit(1);
39
- }
55
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
56
+ return runCommand(command, argv, ["pal", "cli", "thread"]);
57
+ }
58
+
59
+ function add(title: string | undefined, context: string | undefined): undefined {
60
+ if (!title) throw new UsageError("--add needs --title");
40
61
  const thread = addThread(title, context ?? "");
41
62
  emit.receipt(threadsFile(), {
42
63
  id: thread.id,
@@ -45,52 +66,22 @@ function add(title: string | undefined, context: string | undefined) {
45
66
  });
46
67
  }
47
68
 
48
- function markResolved(id: string | undefined) {
49
- if (!id) {
50
- console.error("--id required");
51
- process.exit(1);
52
- }
69
+ function markResolved(id: string | undefined): number {
70
+ if (!id) throw new UsageError("--resolve needs --id");
53
71
  const file = threadsFile();
54
72
  const resolution = resolveThreadIn(readThreads(file), id, new Date());
55
73
  if (!resolution) {
56
74
  console.error(`Thread not found: ${id}`);
57
- process.exit(1);
75
+ return 1;
58
76
  }
59
77
  writeThreads(resolution.threads, file);
60
78
  emit.receipt(file, { id, status: "resolved", title: resolution.thread.title });
79
+ return 0;
61
80
  }
62
81
 
63
- export function run(argv: string[] = scriptArgs()) {
64
- const { values } = parseArgs({
65
- args: argv,
66
- options: {
67
- add: { type: "boolean" },
68
- resolve: { type: "boolean" },
69
- list: { type: "boolean" },
70
- title: { type: "string" },
71
- context: { type: "string" },
72
- id: { type: "string" },
73
- all: { type: "boolean" },
74
- help: { type: "boolean", short: "h" },
75
- },
76
- });
77
-
78
- let cmd: string | null = null;
79
- if (values.add) cmd = "add";
80
- else if (values.resolve) cmd = "resolve";
81
- else if (values.list) cmd = "list";
82
-
83
- if (values.help || !cmd) {
84
- console.log(HELP);
85
- process.exit(cmd ? 0 : 1);
86
- }
87
-
88
- if (cmd === "add") add(values.title, values.context);
89
- if (cmd === "resolve") markResolved(values.id);
90
- if (cmd === "list") {
91
- const threads = visibleThreads(readThreads(), values.all ?? false);
92
- emit.data(JSON.stringify({ count: threads.length, threads }, null, 2));
93
- }
82
+ function list(includeResolved: boolean): undefined {
83
+ const threads = visibleThreads(readThreads(), includeResolved);
84
+ emit.data(JSON.stringify({ count: threads.length, threads }, null, 2));
94
85
  }
95
86
 
96
- if (import.meta.main) run();
87
+ if (import.meta.main) process.exit(await run());
@@ -7,18 +7,13 @@
7
7
  * evolution log. Principles are marked [CRYSTAL: N%] manually when
8
8
  * confidence is high enough.
9
9
  *
10
- * Usage:
11
- * bun run tool:wisdom-frame --domain communication --observation "prefers bullet points"
12
- * bun run tool:wisdom-frame --domain development --observation "refactoring without tests caused regressions" --type anti-pattern
13
- * bun run tool:wisdom-frame --domain workflow --observation "always run type-check after edits" --type principle
14
- *
15
- * Types: principle, contextual-rule, anti-pattern, evolution (default)
10
+ * Usage: pal cli wisdom-frame --domain <domain> --observation "…" [--type <type>]
16
11
  */
17
12
 
18
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
14
  import { resolve } from "node:path";
20
- import { parseArgs } from "node:util";
21
15
  import { paths } from "../../hooks/lib/paths";
16
+ import { leaf, runCommand, UsageError } from "../lib/command";
22
17
  import { emit } from "../lib/emit";
23
18
  import { scriptArgs } from "../lib/script-args";
24
19
 
@@ -193,23 +188,7 @@ ${antiPatternEntry}
193
188
 
194
189
  // ── CLI ──
195
190
 
196
- export function run(argv: string[] = scriptArgs()) {
197
- const { values } = parseArgs({
198
- args: argv,
199
- options: {
200
- domain: { type: "string", short: "d" },
201
- observation: { type: "string", short: "o" },
202
- type: { type: "string", short: "t" },
203
- help: { type: "boolean", short: "h" },
204
- },
205
- });
206
-
207
- if (values.help) {
208
- console.log(`
209
- WisdomFrameUpdater — Update wisdom frames with observations
210
-
211
- Usage:
212
- bun run tool:wisdom-frame --domain <domain> --observation "text" [--type <type>]
191
+ const DETAILS = `Required: --domain and --observation.
213
192
 
214
193
  Domains:
215
194
  development, workflow, communication, infrastructure, integration, or any custom domain
@@ -221,21 +200,49 @@ Types:
221
200
  evolution General observation (default)
222
201
 
223
202
  Examples:
224
- bun run tool:wisdom-frame -d workflow -o "always run type-check after edits"
225
- bun run tool:wisdom-frame -d development -o "mocking DB hides migration bugs" -t anti-pattern
226
- bun run tool:wisdom-frame -d communication -o "user prefers terse summaries" -t principle
227
- `);
228
- process.exit(0);
229
- }
230
-
231
- if (!values.domain || !values.observation) {
232
- console.error("Required: --domain and --observation");
233
- process.exit(1);
234
- }
203
+ pal cli wisdom-frame -d workflow -o "always run type-check after edits"
204
+ pal cli wisdom-frame -d development -o "mocking DB hides migration bugs" -t anti-pattern
205
+ pal cli wisdom-frame -d communication -o "user prefers terse summaries" -t principle`;
206
+
207
+ export const command = leaf({
208
+ summary: "Update wisdom frames with observations",
209
+ options: {
210
+ domain: {
211
+ type: "string",
212
+ short: "d",
213
+ value: "<domain>",
214
+ description: "Frame to update; created if it does not exist",
215
+ },
216
+ observation: {
217
+ type: "string",
218
+ short: "o",
219
+ value: "<text>",
220
+ description: "The observation to record",
221
+ },
222
+ type: {
223
+ type: "string",
224
+ short: "t",
225
+ value: "<type>",
226
+ description: "Observation type (default: evolution)",
227
+ },
228
+ },
229
+ details: DETAILS,
230
+ run: ({ values }) => {
231
+ const { domain, observation } = values;
232
+ if (!domain || !observation) {
233
+ throw new UsageError("--domain and --observation are required");
234
+ }
235
+ return recordObservation(domain, observation, values.type || "evolution");
236
+ },
237
+ });
238
+
239
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
240
+ return runCommand(command, argv, ["pal", "cli", "wisdom-frame"]);
241
+ }
235
242
 
236
- const cliType = (values.type || "evolution") as ObservationType;
237
- const result = updateFrame(values.domain, values.observation, cliType);
243
+ function recordObservation(domain: string, observation: string, type: string): undefined {
244
+ const result = updateFrame(domain, observation, type as ObservationType);
238
245
  emit.receipt(result.framePath, { domain: result.domain, type: result.type });
239
246
  }
240
247
 
241
- if (import.meta.main) run();
248
+ if (import.meta.main) process.exit(await run());
@@ -34,8 +34,6 @@ import {
34
34
  writeInstallSettings,
35
35
  } from "./writes";
36
36
 
37
- export { DEFAULT_PORT, LOOPBACK };
38
-
39
37
  /**
40
38
  * Every path the single-page app owns. Listed rather than wildcarded, because a
41
39
  * blanket "/*" outranks the fetch handler and would swallow /api as well.