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
@@ -6,85 +6,93 @@
6
6
  * Records algorithm performance data after each LEARN phase.
7
7
  * Creates a queryable dataset for improving the algorithm over time.
8
8
  *
9
- * Usage:
10
- * bun run tool:algorithm-reflect --task "description" --criteria 5 --passed 4 --failed 1 --sentiment 7 \
11
- * --q1 "Should have read the file before planning" \
12
- * --q2 "Could have parallelized the explore agents" \
13
- * --q3 "Missed the implicit constraint about cross-platform"
9
+ * Usage: pal cli algorithm-reflect --task "…" --q1 "…" --q2 "…" --q3 "…" [options]
14
10
  */
15
11
 
16
12
  import { appendFileSync } from "node:fs";
17
- import { parseArgs } from "node:util";
18
13
  import { paths } from "../../hooks/lib/paths";
19
14
  import { buildReflection, intOr, reflectionLine } from "../lib/algorithm-reflect";
15
+ import { leaf, runCommand, UsageError } from "../lib/command";
20
16
  import { emit } from "../lib/emit";
21
17
  import { scriptArgs } from "../lib/script-args";
22
18
 
23
- const HELP = `
24
- AlgorithmReflect — Log algorithm performance after LEARN phase
19
+ const DETAILS = `Required: --task, --q1, --q2, --q3.
25
20
 
26
- Usage:
27
- bun run tool:algorithm-reflect --task "description" --criteria N --passed N --failed N --sentiment 1-10 \\
21
+ Example:
22
+ pal cli algorithm-reflect --task "description" --criteria N --passed N --failed N --sentiment 1-10 \\
28
23
  --q1 "self reflection" --q2 "algorithm reflection" --q3 "AI reflection"
29
24
 
30
- Arguments:
31
- --task Brief task description
32
- --criteria Total criteria count
33
- --passed Criteria passed
34
- --failed Criteria failed
35
- --sentiment Implied satisfaction 1-10
36
- --q1 Q1 — Self: what I'd do differently
37
- --q2 Q2 — Algorithm: structural improvement
38
- --q3 Q3 — AI: reasoning blind spot
39
- --scope general (default) | task-specific — is the algorithm idea reusable or task-bound?
25
+ Output: algorithm-reflections.jsonl in memory/learning/reflections/`;
40
26
 
41
- Output: algorithm-reflections.jsonl in memory/learning/reflections/
42
- `;
27
+ const REQUIRED = ["task", "q1", "q2", "q3"] as const;
43
28
 
44
- function reflectionsPath(): string {
45
- paths.reflections();
46
- return paths.reflectionsFile();
47
- }
48
-
49
- export function run(argv: string[] = scriptArgs()) {
50
- const { values } = parseArgs({
51
- args: argv,
52
- options: {
53
- task: { type: "string" },
54
- criteria: { type: "string" },
55
- passed: { type: "string" },
56
- failed: { type: "string" },
57
- sentiment: { type: "string" },
58
- q1: { type: "string" },
59
- q2: { type: "string" },
60
- q3: { type: "string" },
61
- scope: { type: "string" },
62
- help: { type: "boolean", short: "h" },
29
+ export const command = leaf({
30
+ summary: "Log algorithm performance after the LEARN phase",
31
+ options: {
32
+ task: { type: "string", value: "<text>", description: "Brief task description" },
33
+ criteria: { type: "string", value: "<n>", description: "Total criteria count" },
34
+ passed: { type: "string", value: "<n>", description: "Criteria passed" },
35
+ failed: { type: "string", value: "<n>", description: "Criteria failed" },
36
+ sentiment: {
37
+ type: "string",
38
+ value: "<1-10>",
39
+ description: "Implied satisfaction 1-10",
63
40
  },
64
- });
41
+ q1: {
42
+ type: "string",
43
+ value: "<text>",
44
+ description: "Q1 — Self: what I'd do differently",
45
+ },
46
+ q2: {
47
+ type: "string",
48
+ value: "<text>",
49
+ description: "Q2 — Algorithm: structural improvement",
50
+ },
51
+ q3: { type: "string", value: "<text>", description: "Q3 — AI: reasoning blind spot" },
52
+ scope: {
53
+ type: "string",
54
+ value: "<scope>",
55
+ description:
56
+ "general (default) | task-specific — is the algorithm idea reusable or task-bound?",
57
+ },
58
+ },
59
+ details: DETAILS,
60
+ run: ({ values }) => {
61
+ const { task, q1, q2, q3 } = values;
62
+ if (!task || !q1 || !q2 || !q3) throw new UsageError(missingRequired(values));
63
+ return appendReflection(
64
+ buildReflection({
65
+ task,
66
+ q1,
67
+ q2,
68
+ q3,
69
+ criteria_count: intOr(values.criteria, 0),
70
+ criteria_passed: intOr(values.passed, 0),
71
+ criteria_failed: intOr(values.failed, 0),
72
+ sentiment: intOr(values.sentiment, 5),
73
+ scope: values.scope,
74
+ })
75
+ );
76
+ },
77
+ });
65
78
 
66
- if (values.help) {
67
- console.log(HELP);
68
- process.exit(0);
69
- }
79
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
80
+ return runCommand(command, argv, ["pal", "cli", "algorithm-reflect"]);
81
+ }
70
82
 
71
- if (!values.task || !values.q1 || !values.q2 || !values.q3) {
72
- console.error("Required: --task, --q1, --q2, --q3");
73
- process.exit(1);
74
- }
83
+ function missingRequired(
84
+ values: Partial<Record<(typeof REQUIRED)[number], string>>
85
+ ): string {
86
+ const missing = REQUIRED.filter((name) => !values[name]).map((name) => `--${name}`);
87
+ return `missing ${missing.join(", ")}`;
88
+ }
75
89
 
76
- const reflection = buildReflection({
77
- task: values.task,
78
- q1: values.q1,
79
- q2: values.q2,
80
- q3: values.q3,
81
- criteria_count: intOr(values.criteria, 0),
82
- criteria_passed: intOr(values.passed, 0),
83
- criteria_failed: intOr(values.failed, 0),
84
- sentiment: intOr(values.sentiment, 5),
85
- scope: values.scope,
86
- });
90
+ function reflectionsPath(): string {
91
+ paths.reflections();
92
+ return paths.reflectionsFile();
93
+ }
87
94
 
95
+ function appendReflection(reflection: ReturnType<typeof buildReflection>): undefined {
88
96
  const path = reflectionsPath();
89
97
  appendFileSync(path, reflectionLine(reflection), "utf-8");
90
98
  emit.receipt(path, {
@@ -94,4 +102,4 @@ export function run(argv: string[] = scriptArgs()) {
94
102
  });
95
103
  }
96
104
 
97
- if (import.meta.main) run();
105
+ if (import.meta.main) process.exit(await run());
@@ -14,12 +14,12 @@
14
14
  * invisible even when (especially when) the keyword sort misses or mis-sorts it.
15
15
  *
16
16
  * Library: import { synthesizeAlgorithm, formatAlgorithmReport } from ".../algorithm-synthesize"
17
- * Script: bun src/tools/agent/algorithm-synthesize.ts [--since <ISO>] [--json]
17
+ * Script: pal cli algorithm-synthesize [--since <ISO>] [--json]
18
18
  */
19
19
 
20
- import { parseArgs } from "node:util";
21
20
  import { readReflections } from "../../hooks/lib/learning-store";
22
21
  import { paths } from "../../hooks/lib/paths";
22
+ import { leaf, runCommand, UsageError } from "../lib/command";
23
23
  import { scriptArgs } from "../lib/script-args";
24
24
 
25
25
  /** Algorithm areas a Q2 idea can target, mapped to ALGORITHM.md structure. */
@@ -203,19 +203,44 @@ export function formatAlgorithmReport(s: AlgorithmSynthesis): string {
203
203
  return lines.join("\n");
204
204
  }
205
205
 
206
- export function run(argv: string[] = scriptArgs()) {
207
- const { values } = parseArgs({
208
- args: argv,
209
- options: {
210
- since: { type: "string" },
211
- json: { type: "boolean", default: false },
206
+ const DETAILS = `Reads the Q2 field ("what a smarter algorithm would have done") of every
207
+ algorithm reflection and pre-sorts it into candidate areas of ALGORITHM.md. The
208
+ buckets are a keyword hint only: every Q2 they miss is listed in full under
209
+ "Unbucketed", and task-specific Q2s are listed separately.`;
210
+
211
+ export const command = leaf({
212
+ summary: "Cluster algorithm reflections into candidate ALGORITHM.md changes",
213
+ options: {
214
+ since: {
215
+ type: "string",
216
+ value: "<iso-date>",
217
+ description: "Only reflections from this date on (default: all time)",
212
218
  },
213
- });
214
- const since = values.since ? new Date(values.since) : undefined;
219
+ json: {
220
+ type: "boolean",
221
+ default: false,
222
+ description: "Print the full synthesis as JSON instead of the report",
223
+ },
224
+ },
225
+ details: DETAILS,
226
+ run: ({ values }) => printSynthesis(sinceDate(values.since), values.json),
227
+ });
228
+
229
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
230
+ return runCommand(command, argv, ["pal", "cli", "algorithm-synthesize"]);
231
+ }
232
+
233
+ function sinceDate(raw: string | undefined): Date | undefined {
234
+ if (raw === undefined) return undefined;
235
+ const since = new Date(raw);
236
+ if (Number.isNaN(since.getTime()))
237
+ throw new UsageError(`--since is not a date: ${raw}`);
238
+ return since;
239
+ }
240
+
241
+ function printSynthesis(since: Date | undefined, asJson: boolean): undefined {
215
242
  const result = synthesizeAlgorithm(since);
216
- console.log(
217
- values.json ? JSON.stringify(result, null, 2) : formatAlgorithmReport(result)
218
- );
243
+ console.log(asJson ? JSON.stringify(result, null, 2) : formatAlgorithmReport(result));
219
244
  }
220
245
 
221
- if (import.meta.main) run();
246
+ if (import.meta.main) process.exit(await run());
@@ -7,53 +7,48 @@
7
7
  *
8
8
  * What the report says is in lib/analyze-report.ts.
9
9
  *
10
- * Usage: bun run tool:analyze
10
+ * Usage: pal cli analyze [--actionable]
11
11
  */
12
12
 
13
- import { parseArgs } from "node:util";
14
13
  import { writeLastAnalyzeDate } from "../../hooks/lib/analyze-nudge";
15
14
  import { analyze } from "../../hooks/lib/graduation";
16
15
  import { reportLines } from "../lib/analyze-report";
16
+ import { leaf, runCommand } from "../lib/command";
17
17
  import { scriptArgs } from "../lib/script-args";
18
18
 
19
- const HELP = `
20
- PAL Learning Analysis — unified graduation + ratings report
21
-
22
- Reads all captured failures (rating ≤3) and session learnings,
23
- groups recurring patterns via Dice similarity on context text,
24
- and summarizes rating trends.
25
-
26
- Sections:
27
- Ratings Overall average, low/high counts
28
- Graduation Patterns with 3+ occurrences → ready to crystallize
29
- Emerging Patterns with 2 occurrences → one more to graduate
30
-
31
- Flags:
32
- --actionable, -a Generate actionable recommendations via Haiku inference
33
-
34
- To crystallize a graduated pattern, add it to the target wisdom frame:
35
- - Your principle here [CRYSTAL: 85%]
36
-
37
- Usage: pal cli analyze [--actionable]
38
- `;
39
-
40
- export async function run(argv: string[] = scriptArgs()) {
41
- const { values } = parseArgs({
42
- args: argv,
43
- options: {
44
- help: { type: "boolean", short: "h" },
45
- actionable: { type: "boolean", short: "a" },
19
+ const DETAILS = `Reads all captured failures (rating ≤3) and session learnings,
20
+ groups recurring patterns via Dice similarity on context text,
21
+ and summarizes rating trends.
22
+
23
+ Sections:
24
+ Ratings Overall average, low/high counts
25
+ Graduation Patterns with 3+ occurrences → ready to crystallize
26
+ Emerging Patterns with 2 occurrences → one more to graduate
27
+
28
+ To crystallize a graduated pattern, add it to the target wisdom frame:
29
+ - Your principle here [CRYSTAL: 85%]`;
30
+
31
+ export const command = leaf({
32
+ summary: "PAL Learning Analysis — unified graduation + ratings report",
33
+ options: {
34
+ actionable: {
35
+ type: "boolean",
36
+ short: "a",
37
+ description: "Generate actionable recommendations via Haiku inference",
46
38
  },
47
- });
39
+ },
40
+ details: DETAILS,
41
+ run: ({ values }) => printAnalysis(values.actionable),
42
+ });
48
43
 
49
- if (values.help) {
50
- console.log(HELP);
51
- process.exit(0);
52
- }
44
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
45
+ return runCommand(command, argv, ["pal", "cli", "analyze"]);
46
+ }
53
47
 
54
- const result = await analyze({ actionable: values.actionable });
48
+ async function printAnalysis(actionable: boolean | undefined): Promise<undefined> {
49
+ const result = await analyze({ actionable });
55
50
  for (const line of reportLines(result)) console.log(line);
56
51
  writeLastAnalyzeDate(new Date().toISOString());
57
52
  }
58
53
 
59
- if (import.meta.main) await run();
54
+ if (import.meta.main) process.exit(await run());
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { writeFileSync } from "node:fs";
14
- import { parseArgs } from "node:util";
14
+ import { leaf, runCommand, UsageError } from "../lib/command";
15
15
  import { emit } from "../lib/emit";
16
16
  import {
17
17
  handoffFile,
@@ -22,68 +22,65 @@ import {
22
22
  } from "../lib/handoff-note";
23
23
  import { scriptArgs } from "../lib/script-args";
24
24
 
25
- const HELP = `
26
- HandoffNote — Write a handoff note for the current project
25
+ const DETAILS = `Required: --title and --text, or --done to close.
27
26
 
28
- Usage:
27
+ Examples:
29
28
  pal cli handoff-note --title "what we were doing" --text "what remains"
30
29
  pal cli handoff-note --done # mark session completed
31
30
 
32
- Arguments:
33
- --title Brief title of what was being worked on (5-10 words)
34
- --text What remains unfinished — decisions made, next steps, blockers
35
- --waiting What this needs from you before it can move (a decision, an answer, access)
36
- --done Mark as completed; suppresses "pick up where you left off" injection
31
+ Output: writes to memory/state/last-handoff.json keyed by cwd`;
37
32
 
38
- Output: writes to memory/state/last-handoff.json keyed by cwd
39
- `;
33
+ export const command = leaf({
34
+ summary: "Write a handoff note for the current project",
35
+ options: {
36
+ title: {
37
+ type: "string",
38
+ value: "<text>",
39
+ description: "Brief title of what was being worked on (5-10 words)",
40
+ },
41
+ text: {
42
+ type: "string",
43
+ value: "<text>",
44
+ description: "What remains unfinished — decisions made, next steps, blockers",
45
+ },
46
+ waiting: {
47
+ type: "string",
48
+ value: "<text>",
49
+ description:
50
+ "What this needs from you before it can move (a decision, an answer, access)",
51
+ },
52
+ done: {
53
+ type: "boolean",
54
+ description: `Mark as completed; suppresses "pick up where you left off" injection`,
55
+ },
56
+ },
57
+ details: DETAILS,
58
+ run: ({ values }) => {
59
+ const { title, text, waiting, done } = values;
60
+ if (done) {
61
+ return saveNote({
62
+ cwd: process.cwd(),
63
+ title: title || "session",
64
+ text: text || "",
65
+ done: true,
66
+ });
67
+ }
68
+ if (!title || !text) {
69
+ throw new UsageError("--title and --text are required (or --done to close)");
70
+ }
71
+ return saveNote({ cwd: process.cwd(), title, text, done: false, waitingOn: waiting });
72
+ },
73
+ });
40
74
 
41
- function saveNote(note: NoteInput): void {
75
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
76
+ return runCommand(command, argv, ["pal", "cli", "handoff-note"]);
77
+ }
78
+
79
+ function saveNote(note: NoteInput): undefined {
42
80
  const file = handoffFile();
43
81
  const store = recordNote(readHandoffs(file), note, new Date());
44
82
  writeFileSync(file, JSON.stringify(store, null, 2), "utf-8");
45
83
  emit.receipt(file, { status: statusOf(note), entries: Object.keys(store).length });
46
84
  }
47
85
 
48
- export function run(argv: string[] = scriptArgs()) {
49
- const { values } = parseArgs({
50
- args: argv,
51
- options: {
52
- title: { type: "string" },
53
- text: { type: "string" },
54
- waiting: { type: "string" },
55
- done: { type: "boolean" },
56
- help: { type: "boolean", short: "h" },
57
- },
58
- });
59
-
60
- if (values.help) {
61
- console.log(HELP);
62
- process.exit(0);
63
- }
64
-
65
- if (values.done) {
66
- saveNote({
67
- cwd: process.cwd(),
68
- title: values.title || "session",
69
- text: values.text || "",
70
- done: true,
71
- });
72
- process.exit(0);
73
- }
74
-
75
- if (!values.title || !values.text) {
76
- console.error("Required: --title and --text (or --done to close)");
77
- process.exit(1);
78
- }
79
-
80
- saveNote({
81
- cwd: process.cwd(),
82
- title: values.title,
83
- text: values.text,
84
- done: false,
85
- waitingOn: values.waiting,
86
- });
87
- }
88
-
89
- if (import.meta.main) run();
86
+ if (import.meta.main) process.exit(await run());
@@ -6,42 +6,46 @@
6
6
  * Usage: pal cli interaction report [--days N]
7
7
  */
8
8
 
9
- import { parseArgs } from "node:util";
10
9
  import { claimChecksSince } from "../../hooks/lib/claim-log";
11
10
  import { turnsSince } from "../../hooks/lib/interaction";
11
+ import { group, leaf, runCommand, UsageError } from "../lib/command";
12
12
  import { claimCheckLines, reportLines, summarize } from "../lib/interaction-report";
13
13
  import { scriptArgs } from "../lib/script-args";
14
14
 
15
- const HELP = `
16
- PAL Interaction report
15
+ const DETAILS = `Reads the measured turns in memory/signals/interaction/ (features only, no
16
+ text) and prints turns, channels, reactions, the hints sent, per agent the
17
+ turns, replies filed and hints sent, and for each label (short, long, fast,
18
+ skimming, friction) how the replies written while it was active compare with
19
+ the replies written while none was. Last, the replies that claimed a result
20
+ (memory/signals/claim-checks/), and which of them had no command behind the
21
+ claim or were sent back.`;
17
22
 
18
- Reads the measured turns in memory/signals/interaction/ (features only, no
19
- text) and prints turns, channels, reactions, the hints sent, per agent the
20
- turns, replies filed and hints sent, and for each
21
- label (short, long, fast, skimming, friction) how the replies written while
22
- it was active compare with the replies written while none was. Last, the
23
- replies that claimed a result (memory/signals/claim-checks/), and which of
24
- them had no command behind the claim or were sent back.
23
+ const DAY_MS = 86_400_000;
25
24
 
26
- Usage: pal cli interaction report [--days N] (default 7)
27
- `;
25
+ export const command = group({
26
+ summary: "How the user's turns went, and whether replies changed under each label",
27
+ commands: {
28
+ report: leaf({
29
+ summary: "Print the interaction report",
30
+ options: {
31
+ days: {
32
+ type: "string",
33
+ value: "<n>",
34
+ description: "Days to look back (default 7)",
35
+ },
36
+ },
37
+ details: DETAILS,
38
+ run: ({ values }) => printReport(Number(values.days ?? "7")),
39
+ }),
40
+ },
41
+ });
28
42
 
29
- const DAY_MS = 86_400_000;
43
+ export function run(argv: string[] = scriptArgs()): Promise<number> {
44
+ return runCommand(command, argv, ["pal", "cli", "interaction"]);
45
+ }
30
46
 
31
- export function run(argv: string[] = scriptArgs()) {
32
- const { values, positionals } = parseArgs({
33
- args: argv,
34
- allowPositionals: true,
35
- options: {
36
- help: { type: "boolean", short: "h" },
37
- days: { type: "string", default: "7" },
38
- },
39
- });
40
- const days = Number(values.days);
41
- if (values.help || positionals[0] !== "report" || !(days > 0)) {
42
- console.log(HELP);
43
- return;
44
- }
47
+ function printReport(days: number): undefined {
48
+ if (!(days > 0)) throw new UsageError("--days must be a positive number");
45
49
  const since = new Date(Date.now() - days * DAY_MS);
46
50
  const events = turnsSince(since);
47
51
  const claims = claimCheckLines(claimChecksSince(since));
@@ -49,4 +53,4 @@ export function run(argv: string[] = scriptArgs()) {
49
53
  console.log(line);
50
54
  }
51
55
 
52
- if (import.meta.main) run();
56
+ if (import.meta.main) process.exit(await run());