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.
- package/README.md +8 -5
- package/assets/agents/gemini-researcher.md +13 -0
- package/assets/agents/grok-researcher.md +13 -0
- package/assets/agents/multi-perspective-researcher.md +12 -0
- package/assets/agents/perplexity-researcher.md +13 -0
- package/assets/agents/skill-author.md +17 -0
- package/assets/agents/subagent-author.md +18 -1
- package/assets/skills/create-subagent/SKILL.md +3 -2
- package/assets/skills/create-subagent/authoring-guide.md +16 -0
- package/assets/templates/PAL/MEMORY_SYSTEM.md +1 -1
- package/assets/templates/PAL/README.md +6 -5
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +4 -1
- package/assets/templates/hooks.antigravity.json +42 -0
- package/assets/templates/settings.antigravity.json +44 -0
- package/package.json +1 -1
- package/src/cli/builtin-tools.ts +28 -23
- package/src/cli/doctor/agents.ts +64 -64
- package/src/cli/identity.ts +26 -20
- package/src/cli/index.ts +109 -312
- package/src/cli/knowledge.ts +160 -201
- package/src/cli/ledger-command.ts +69 -0
- package/src/cli/ledger.ts +20 -101
- package/src/cli/personal-context.ts +15 -12
- package/src/cli/rule.ts +29 -29
- package/src/cli/server.ts +33 -52
- package/src/cli/session-agent.ts +7 -9
- package/src/cli/skill.ts +73 -64
- package/src/cli/subagent.ts +51 -58
- package/src/cli/tree.ts +177 -0
- package/src/hooks/InvocationContext.ts +26 -0
- package/src/hooks/LedgerCommit.ts +4 -2
- package/src/hooks/LedgerSnapshot.ts +2 -0
- package/src/hooks/SecurityValidator.ts +2 -0
- package/src/hooks/StopOrchestrator.ts +12 -2
- package/src/hooks/UserPromptOrchestrator.ts +3 -15
- package/src/hooks/handlers/context-digests.ts +21 -21
- package/src/hooks/handlers/inject-retrieval.ts +7 -5
- package/src/hooks/handlers/readme-sync.ts +6 -2
- package/src/hooks/handlers/session-name.ts +3 -12
- package/src/hooks/lib/agent-definition.ts +6 -16
- package/src/hooks/lib/agent-registry.ts +88 -0
- package/src/hooks/lib/agent.ts +47 -23
- package/src/hooks/lib/antigravity-inference.ts +84 -0
- package/src/hooks/lib/antigravity-transcript.ts +129 -0
- package/src/hooks/lib/claim-check.ts +13 -2
- package/src/hooks/lib/claim-log.ts +5 -1
- package/src/hooks/lib/claude-md.ts +21 -1
- package/src/hooks/lib/context.ts +5 -1
- package/src/hooks/lib/hook-turn.ts +25 -2
- package/src/hooks/lib/inference.ts +77 -1
- package/src/hooks/lib/invocation-context.ts +99 -0
- package/src/hooks/lib/ledger-hook.ts +14 -6
- package/src/hooks/lib/models.ts +34 -17
- package/src/hooks/lib/paths.ts +39 -6
- package/src/hooks/lib/prompt-capture.ts +28 -0
- package/src/hooks/lib/prompt-text.ts +1 -0
- package/src/hooks/lib/readme-sync.ts +3 -31
- package/src/hooks/lib/security-gate.ts +10 -3
- package/src/hooks/lib/semi-static.ts +13 -3
- package/src/hooks/lib/session-context.ts +3 -1
- package/src/hooks/lib/session-names.ts +20 -4
- package/src/hooks/lib/stop.ts +1 -0
- package/src/hooks/lib/transcript.ts +6 -3
- package/src/targets/agent-render.ts +3 -3
- package/src/targets/antigravity/install.ts +68 -0
- package/src/targets/antigravity/uninstall.ts +66 -0
- package/src/targets/lib.ts +81 -64
- package/src/targets/opencode/install.ts +2 -0
- package/src/targets/opencode/permission.ts +39 -0
- package/src/targets/opencode/uninstall.ts +9 -8
- package/src/tools/agent/algorithm-reflect.ts +70 -62
- package/src/tools/agent/algorithm-synthesize.ts +39 -14
- package/src/tools/agent/analyze.ts +31 -36
- package/src/tools/agent/handoff-note.ts +51 -54
- package/src/tools/agent/interaction.ts +32 -28
- package/src/tools/agent/project.ts +300 -287
- package/src/tools/agent/relationship-note.ts +40 -36
- package/src/tools/agent/relationship-reflect.ts +23 -32
- package/src/tools/agent/synthesize.ts +26 -27
- package/src/tools/agent/thread.ts +42 -51
- package/src/tools/agent/wisdom-frame.ts +45 -38
- package/src/tools/control-room/server.ts +0 -2
- package/src/tools/control-room/ui/dist/assets/{index-BZMDzWFa.js → index-mFpXpiBj.js} +1 -1
- package/src/tools/control-room/ui/dist/index.html +1 -1
- package/src/tools/control-room/ui/screens/log.tsx +10 -1
- package/src/tools/lib/command.ts +275 -0
- package/src/tools/subagent-doctor.ts +74 -9
- package/src/tools/token-cost.ts +18 -17
package/src/cli/knowledge.ts
CHANGED
|
@@ -3,26 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Thin presentation layer over src/tools/knowledge/{lib,graph}.ts. Owns
|
|
5
5
|
* formatting + argv parsing only; all entity logic lives in the tools.
|
|
6
|
-
*
|
|
7
|
-
* Subcommands:
|
|
8
|
-
* search <query> Substring search across title/tags/body
|
|
9
|
-
* graph <slug> [--hops N] BFS traversal from a slug (default 2 hops)
|
|
10
|
-
* stats Counts, hubs, isolated nodes
|
|
11
|
-
* hubs Top 10 most-connected entities
|
|
12
|
-
* find <tag> Entities tagged with <tag>
|
|
13
|
-
* show <slug> Print one entity (frontmatter + body)
|
|
14
|
-
* add <domain> <name> Create entity (interactive unless flags given)
|
|
15
|
-
* --tags ai,research Comma-separated tags
|
|
16
|
-
* --related slug:type Repeatable; type ∈ RELATION_TYPES
|
|
17
|
-
* --quality 0-10
|
|
18
|
-
* --status seedling|budding|evergreen
|
|
19
|
-
* --type <subtype> Free-form sub-type (default by domain)
|
|
20
|
-
* ls [domain] List entities, optionally by one domain
|
|
21
6
|
*/
|
|
22
7
|
|
|
23
8
|
import { readFileSync } from "node:fs";
|
|
24
|
-
import { parseArgs } from "node:util";
|
|
25
|
-
import * as clack from "@clack/prompts";
|
|
26
9
|
import { toPath } from "../hooks/lib/paths";
|
|
27
10
|
import { buildGraph, resolveSlug, stats, traverse } from "../tools/knowledge/graph";
|
|
28
11
|
import {
|
|
@@ -43,76 +26,111 @@ import {
|
|
|
43
26
|
STATUSES,
|
|
44
27
|
type Status,
|
|
45
28
|
} from "../tools/knowledge/lib";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
29
|
+
import { group, leaf, runCommand, UsageError } from "../tools/lib/command";
|
|
30
|
+
|
|
31
|
+
const VOCABULARY = `Domains: ${DOMAINS.join(", ")}
|
|
32
|
+
Relation types: ${RELATION_TYPES.join(", ")}`;
|
|
33
|
+
|
|
34
|
+
export const knowledgeCommand = group({
|
|
35
|
+
summary: "Query and manage the knowledge store",
|
|
36
|
+
commands: {
|
|
37
|
+
search: leaf({
|
|
38
|
+
summary: "Substring search across title, tags, body",
|
|
39
|
+
args: "<query>",
|
|
40
|
+
run: ({ positionals }) => cmdSearch(positionals[0]),
|
|
41
|
+
}),
|
|
42
|
+
graph: leaf({
|
|
43
|
+
summary: "BFS traversal from a slug (default 2 hops)",
|
|
44
|
+
args: "<slug>",
|
|
45
|
+
options: {
|
|
46
|
+
hops: { type: "string", value: "<n>", description: "Hops to follow (default 2)" },
|
|
47
|
+
},
|
|
48
|
+
run: ({ positionals, values }) => cmdGraph(positionals[0], parseHops(values.hops)),
|
|
49
|
+
}),
|
|
50
|
+
stats: leaf({ summary: "Counts, hubs, isolated nodes", run: cmdStats }),
|
|
51
|
+
hubs: leaf({ summary: "Top 10 most-connected entities", run: cmdHubs }),
|
|
52
|
+
find: leaf({
|
|
53
|
+
summary: "Entities tagged with <tag>",
|
|
54
|
+
args: "<tag>",
|
|
55
|
+
run: ({ positionals }) => cmdFind(positionals[0]),
|
|
56
|
+
}),
|
|
57
|
+
show: leaf({
|
|
58
|
+
summary: "Print one entity (frontmatter + body)",
|
|
59
|
+
args: "<slug>",
|
|
60
|
+
run: ({ positionals }) => cmdShow(positionals[0]),
|
|
61
|
+
}),
|
|
62
|
+
add: leaf({
|
|
63
|
+
summary: "Create an entity (interactive unless flags are given)",
|
|
64
|
+
args: "<domain> <name>",
|
|
65
|
+
options: {
|
|
66
|
+
tags: { type: "string", value: "<a,b>", description: "Comma-separated tags" },
|
|
67
|
+
related: {
|
|
68
|
+
type: "string",
|
|
69
|
+
multiple: true,
|
|
70
|
+
value: "<slug:type>",
|
|
71
|
+
description: "Typed relation (repeatable)",
|
|
72
|
+
},
|
|
73
|
+
quality: { type: "string", value: "<0-10>", description: "Default 5" },
|
|
74
|
+
status: {
|
|
75
|
+
type: "string",
|
|
76
|
+
value: `<${STATUSES.join("|")}>`,
|
|
77
|
+
description: "Default seedling",
|
|
78
|
+
},
|
|
79
|
+
type: { type: "string", value: "<subtype>", description: "Free-form sub-type" },
|
|
80
|
+
body: { type: "string", value: "<text>", description: "Markdown body" },
|
|
81
|
+
},
|
|
82
|
+
details: VOCABULARY,
|
|
83
|
+
run: ({ positionals, values }) =>
|
|
84
|
+
cmdAdd(
|
|
85
|
+
parseDomain(positionals[0]),
|
|
86
|
+
positionals[1],
|
|
87
|
+
parseAddFlags(values),
|
|
88
|
+
Object.keys(values).length > 0
|
|
89
|
+
),
|
|
90
|
+
}),
|
|
91
|
+
ls: leaf({
|
|
92
|
+
summary: "List entities, optionally by one domain",
|
|
93
|
+
args: "[domain]",
|
|
94
|
+
details: VOCABULARY,
|
|
95
|
+
run: ({ positionals }) =>
|
|
96
|
+
cmdLs(positionals[0] === undefined ? undefined : parseDomain(positionals[0])),
|
|
97
|
+
}),
|
|
98
|
+
ingest: leaf({
|
|
99
|
+
summary: "Upsert people and companies from JSON on stdin (or --file)",
|
|
100
|
+
options: {
|
|
101
|
+
source: {
|
|
102
|
+
type: "string",
|
|
103
|
+
short: "s",
|
|
104
|
+
value: "<id>",
|
|
105
|
+
description: 'Provenance tag (default "manual")',
|
|
106
|
+
},
|
|
107
|
+
file: {
|
|
108
|
+
type: "string",
|
|
109
|
+
short: "f",
|
|
110
|
+
value: "<path>",
|
|
111
|
+
description: "Read the JSON from a file instead of stdin",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
run: ({ values }) => cmdIngest(values.source ?? "manual", values.file),
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
details: VOCABULARY,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export function runKnowledge(args: string[]): Promise<number> {
|
|
121
|
+
return runCommand(knowledgeCommand, args, ["pal", "cli", "knowledge"]);
|
|
110
122
|
}
|
|
111
123
|
|
|
112
124
|
function isDomain(s: string): s is Domain {
|
|
113
125
|
return (DOMAINS as readonly string[]).includes(s);
|
|
114
126
|
}
|
|
115
127
|
|
|
128
|
+
function parseDomain(value: string): Domain {
|
|
129
|
+
if (!isDomain(value))
|
|
130
|
+
throw new UsageError(`domain must be one of: ${DOMAINS.join(", ")}`);
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
function isStatus(s: string): s is Status {
|
|
117
135
|
return (STATUSES as readonly string[]).includes(s);
|
|
118
136
|
}
|
|
@@ -171,12 +189,7 @@ function scoreEntity(entity: Entity, q: string): number {
|
|
|
171
189
|
return score;
|
|
172
190
|
}
|
|
173
191
|
|
|
174
|
-
function cmdSearch(
|
|
175
|
-
const q = args[0];
|
|
176
|
-
if (!q) {
|
|
177
|
-
console.error("Usage: pal cli knowledge search <query>");
|
|
178
|
-
return 1;
|
|
179
|
-
}
|
|
192
|
+
function cmdSearch(q: string): number {
|
|
180
193
|
const hits: SearchHit[] = [];
|
|
181
194
|
for (const e of list()) {
|
|
182
195
|
const s = scoreEntity(e, q);
|
|
@@ -195,23 +208,15 @@ function cmdSearch(args: string[]): number {
|
|
|
195
208
|
|
|
196
209
|
// ── graph ──────────────────────────────────────────────────────────
|
|
197
210
|
|
|
198
|
-
function
|
|
199
|
-
const
|
|
200
|
-
args,
|
|
201
|
-
options: { hops: { type: "string" } },
|
|
202
|
-
allowPositionals: true,
|
|
203
|
-
strict: false,
|
|
204
|
-
});
|
|
205
|
-
const query = positionals[0];
|
|
206
|
-
if (!query) {
|
|
207
|
-
console.error("Usage: pal cli knowledge graph <slug> [--hops N]");
|
|
208
|
-
return 1;
|
|
209
|
-
}
|
|
210
|
-
const hops = values.hops ? Number(values.hops) : 2;
|
|
211
|
+
function parseHops(value: string | undefined): number {
|
|
212
|
+
const hops = value === undefined ? 2 : Number(value);
|
|
211
213
|
if (!Number.isInteger(hops) || hops < 1) {
|
|
212
|
-
|
|
213
|
-
return 1;
|
|
214
|
+
throw new UsageError("--hops must be a positive integer");
|
|
214
215
|
}
|
|
216
|
+
return hops;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function cmdGraph(query: string, hops: number): number {
|
|
215
220
|
const g = buildGraph();
|
|
216
221
|
const slug = resolveSlug(g, query);
|
|
217
222
|
if (!slug) {
|
|
@@ -302,12 +307,8 @@ function cmdHubs(): number {
|
|
|
302
307
|
|
|
303
308
|
// ── find ───────────────────────────────────────────────────────────
|
|
304
309
|
|
|
305
|
-
function cmdFind(
|
|
306
|
-
const tag =
|
|
307
|
-
if (!tag) {
|
|
308
|
-
console.error("Usage: pal cli knowledge find <tag>");
|
|
309
|
-
return 1;
|
|
310
|
-
}
|
|
310
|
+
function cmdFind(rawTag: string): number {
|
|
311
|
+
const tag = rawTag.toLowerCase();
|
|
311
312
|
// Accept both the bare tag and the topic-prefixed form so users don't
|
|
312
313
|
// need to know which kind a given concept was stored as.
|
|
313
314
|
const prefixedTag = tag.startsWith("topic:") ? tag : `topic:${tag}`;
|
|
@@ -331,12 +332,7 @@ function cmdFind(args: string[]): number {
|
|
|
331
332
|
|
|
332
333
|
// ── show ───────────────────────────────────────────────────────────
|
|
333
334
|
|
|
334
|
-
function cmdShow(
|
|
335
|
-
const query = args[0];
|
|
336
|
-
if (!query) {
|
|
337
|
-
console.error("Usage: pal cli knowledge show <slug>");
|
|
338
|
-
return 1;
|
|
339
|
-
}
|
|
335
|
+
function cmdShow(query: string): number {
|
|
340
336
|
const g = buildGraph();
|
|
341
337
|
const slug = resolveSlug(g, query);
|
|
342
338
|
if (!slug) {
|
|
@@ -385,91 +381,74 @@ interface AddFlags {
|
|
|
385
381
|
body?: string;
|
|
386
382
|
}
|
|
387
383
|
|
|
384
|
+
interface AddFlagValues {
|
|
385
|
+
tags?: string;
|
|
386
|
+
related?: string[];
|
|
387
|
+
quality?: string;
|
|
388
|
+
status?: string;
|
|
389
|
+
type?: string;
|
|
390
|
+
body?: string;
|
|
391
|
+
}
|
|
392
|
+
|
|
388
393
|
function parseRelatedFlag(value: string): Related {
|
|
389
394
|
const [slug, type] = value.split(":");
|
|
390
395
|
if (!slug || !type) {
|
|
391
|
-
throw new
|
|
396
|
+
throw new UsageError(`--related must be slug:type, got "${value}"`);
|
|
392
397
|
}
|
|
393
398
|
if (!isRelationType(type)) {
|
|
394
|
-
throw new
|
|
399
|
+
throw new UsageError(
|
|
395
400
|
`--related type must be one of: ${RELATION_TYPES.join(", ")} (got "${type}")`
|
|
396
401
|
);
|
|
397
402
|
}
|
|
398
403
|
return { slug, type };
|
|
399
404
|
}
|
|
400
405
|
|
|
401
|
-
function
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
related: { type: "string", multiple: true },
|
|
407
|
-
quality: { type: "string" },
|
|
408
|
-
status: { type: "string" },
|
|
409
|
-
type: { type: "string" },
|
|
410
|
-
body: { type: "string" },
|
|
411
|
-
},
|
|
412
|
-
allowPositionals: true,
|
|
413
|
-
strict: false,
|
|
414
|
-
});
|
|
415
|
-
const tags = values.tags
|
|
416
|
-
? String(values.tags)
|
|
417
|
-
.split(",")
|
|
418
|
-
.map((t) => t.trim())
|
|
419
|
-
.filter(Boolean)
|
|
420
|
-
: [];
|
|
421
|
-
const related = ((values.related as string[] | undefined) ?? []).map(parseRelatedFlag);
|
|
422
|
-
const quality = values.quality != null ? Number(values.quality) : undefined;
|
|
423
|
-
if (quality != null && (!Number.isInteger(quality) || quality < 0 || quality > 10)) {
|
|
424
|
-
throw new Error("--quality must be an integer 0-10");
|
|
425
|
-
}
|
|
426
|
-
const status = values.status as string | undefined;
|
|
427
|
-
if (status && !isStatus(status)) {
|
|
428
|
-
throw new Error(`--status must be one of: ${STATUSES.join(", ")}`);
|
|
406
|
+
function parseQuality(value: string | undefined): number | undefined {
|
|
407
|
+
if (value === undefined) return undefined;
|
|
408
|
+
const quality = Number(value);
|
|
409
|
+
if (!Number.isInteger(quality) || quality < 0 || quality > 10) {
|
|
410
|
+
throw new UsageError("--quality must be an integer 0-10");
|
|
429
411
|
}
|
|
412
|
+
return quality;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function parseStatus(value: string | undefined): Status | undefined {
|
|
416
|
+
if (value === undefined) return undefined;
|
|
417
|
+
if (!isStatus(value))
|
|
418
|
+
throw new UsageError(`--status must be one of: ${STATUSES.join(", ")}`);
|
|
419
|
+
return value;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function parseAddFlags(values: AddFlagValues): AddFlags {
|
|
423
|
+
const tags = (values.tags ?? "")
|
|
424
|
+
.split(",")
|
|
425
|
+
.map((t) => t.trim())
|
|
426
|
+
.filter(Boolean);
|
|
430
427
|
return {
|
|
431
428
|
tags,
|
|
432
|
-
related,
|
|
433
|
-
quality,
|
|
434
|
-
status: status
|
|
435
|
-
type: values.type
|
|
436
|
-
body: values.body
|
|
429
|
+
related: (values.related ?? []).map(parseRelatedFlag),
|
|
430
|
+
quality: parseQuality(values.quality),
|
|
431
|
+
status: parseStatus(values.status),
|
|
432
|
+
type: values.type,
|
|
433
|
+
body: values.body,
|
|
437
434
|
};
|
|
438
435
|
}
|
|
439
436
|
|
|
440
|
-
async function cmdAdd(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
if (!name) {
|
|
450
|
-
console.error("Missing <name>");
|
|
451
|
-
return 1;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const flagArgs = args.slice(args.indexOf(name) + 1);
|
|
455
|
-
const hasAnyFlag = flagArgs.some((a) => a.startsWith("--"));
|
|
456
|
-
let flags: AddFlags;
|
|
457
|
-
try {
|
|
458
|
-
flags = parseAddFlags(flagArgs);
|
|
459
|
-
} catch (e) {
|
|
460
|
-
console.error(e instanceof Error ? e.message : String(e));
|
|
461
|
-
return 1;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Interactive mode only when no flags and we have a TTY.
|
|
465
|
-
if (!hasAnyFlag && process.stdin.isTTY) {
|
|
437
|
+
async function cmdAdd(
|
|
438
|
+
domain: Domain,
|
|
439
|
+
name: string,
|
|
440
|
+
given: AddFlags,
|
|
441
|
+
anyFlagGiven: boolean
|
|
442
|
+
): Promise<number> {
|
|
443
|
+
let flags = given;
|
|
444
|
+
if (!anyFlagGiven && process.stdin.isTTY) {
|
|
466
445
|
const enriched = await runInteractiveAdd(flags);
|
|
467
446
|
if (enriched === null) return 1;
|
|
468
447
|
flags = enriched;
|
|
469
448
|
}
|
|
470
449
|
|
|
471
450
|
const entity = getOrCreate({
|
|
472
|
-
domain
|
|
451
|
+
domain,
|
|
473
452
|
name,
|
|
474
453
|
tags: flags.tags,
|
|
475
454
|
related: flags.related,
|
|
@@ -483,6 +462,7 @@ async function cmdAdd(args: string[]): Promise<number> {
|
|
|
483
462
|
}
|
|
484
463
|
|
|
485
464
|
async function runInteractiveAdd(prefilled: AddFlags): Promise<AddFlags | null> {
|
|
465
|
+
const clack = await import("@clack/prompts");
|
|
486
466
|
clack.intro("Add knowledge entry");
|
|
487
467
|
const tagsInput = await clack.text({
|
|
488
468
|
message: "Tags (comma-separated, optional):",
|
|
@@ -528,13 +508,7 @@ async function runInteractiveAdd(prefilled: AddFlags): Promise<AddFlags | null>
|
|
|
528
508
|
|
|
529
509
|
// ── ls ─────────────────────────────────────────────────────────────
|
|
530
510
|
|
|
531
|
-
function cmdLs(
|
|
532
|
-
const domainArg = args[0];
|
|
533
|
-
if (domainArg && !isDomain(domainArg)) {
|
|
534
|
-
console.error(`Domain must be one of: ${DOMAINS.join(", ")}`);
|
|
535
|
-
return 1;
|
|
536
|
-
}
|
|
537
|
-
const target = domainArg ? (domainArg as Domain) : undefined;
|
|
511
|
+
function cmdLs(target: Domain | undefined): number {
|
|
538
512
|
const entries = list(target).sort(
|
|
539
513
|
(a, b) => a.domain.localeCompare(b.domain) || a.slug.localeCompare(b.slug)
|
|
540
514
|
);
|
|
@@ -559,25 +533,10 @@ async function readIngestInput(file: string | undefined): Promise<string | null>
|
|
|
559
533
|
return await Bun.stdin.text();
|
|
560
534
|
}
|
|
561
535
|
|
|
562
|
-
async function cmdIngest(
|
|
563
|
-
const
|
|
564
|
-
args,
|
|
565
|
-
options: {
|
|
566
|
-
source: { type: "string", short: "s", default: "manual" },
|
|
567
|
-
file: { type: "string", short: "f" },
|
|
568
|
-
},
|
|
569
|
-
strict: true,
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
const sourceId = values.source ?? "manual";
|
|
573
|
-
const raw = await readIngestInput(values.file);
|
|
574
|
-
|
|
536
|
+
async function cmdIngest(sourceId: string, file: string | undefined): Promise<number> {
|
|
537
|
+
const raw = await readIngestInput(file);
|
|
575
538
|
if (raw === null || !raw.trim()) {
|
|
576
|
-
|
|
577
|
-
"Usage: echo '<JSON>' | pal cli knowledge ingest --source <id>\n" +
|
|
578
|
-
" or: pal cli knowledge ingest --file <path> --source <id>"
|
|
579
|
-
);
|
|
580
|
-
return 1;
|
|
539
|
+
throw new UsageError("pipe the JSON on stdin or pass --file <path>");
|
|
581
540
|
}
|
|
582
541
|
|
|
583
542
|
let data: IngestPayload;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `pal cli ledger` command tree. The queries load only when one runs, so a
|
|
3
|
+
* missing ledger dependency cannot stop the rest of the CLI, the doctor included.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { group, leaf } from "../tools/lib/command";
|
|
7
|
+
|
|
8
|
+
const JSON_OPTION = { type: "boolean", description: "Machine-readable output" } as const;
|
|
9
|
+
|
|
10
|
+
const FILTER_OPTIONS = {
|
|
11
|
+
project: {
|
|
12
|
+
type: "string",
|
|
13
|
+
value: "<slug>",
|
|
14
|
+
description: "Actions against a registered project",
|
|
15
|
+
},
|
|
16
|
+
since: {
|
|
17
|
+
type: "string",
|
|
18
|
+
value: "<7d|2026-09-01>",
|
|
19
|
+
description: "A duration back from now, or a date",
|
|
20
|
+
},
|
|
21
|
+
until: { type: "string", value: "<date>", description: "Upper bound on the timestamp" },
|
|
22
|
+
actor: { type: "string", value: "<id>", description: "Who caused it" },
|
|
23
|
+
machine: { type: "string", value: "<id>", description: "Which install wrote it" },
|
|
24
|
+
runtime: {
|
|
25
|
+
type: "string",
|
|
26
|
+
value: "<agent>",
|
|
27
|
+
description: "claude, cursor, codex, copilot, opencode",
|
|
28
|
+
},
|
|
29
|
+
outcome: {
|
|
30
|
+
type: "string",
|
|
31
|
+
value: "<applied|failed|denied>",
|
|
32
|
+
description: "What became of the action",
|
|
33
|
+
},
|
|
34
|
+
tool: { type: "string", value: "<Edit|Write>", description: "The tool that acted" },
|
|
35
|
+
target: {
|
|
36
|
+
type: "string",
|
|
37
|
+
value: "<substring>",
|
|
38
|
+
description: "Match anywhere in the recorded path",
|
|
39
|
+
},
|
|
40
|
+
limit: { type: "string", value: "<n>", description: "Keep the newest n matches" },
|
|
41
|
+
json: JSON_OPTION,
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
export const ledgerCommand = group({
|
|
45
|
+
summary: "Query the action ledger",
|
|
46
|
+
details: `Examples:
|
|
47
|
+
pal cli ledger log --project portable-agent-layer --since 7d
|
|
48
|
+
pal cli ledger log --target memory/ --outcome applied
|
|
49
|
+
pal cli ledger stats --since 24h`,
|
|
50
|
+
commands: {
|
|
51
|
+
log: leaf({
|
|
52
|
+
summary: "Matching actions, oldest first",
|
|
53
|
+
options: FILTER_OPTIONS,
|
|
54
|
+
run: async ({ values }) => (await import("./ledger")).ledgerLog(values),
|
|
55
|
+
}),
|
|
56
|
+
show: leaf({
|
|
57
|
+
summary: "One action in full: change, target, standing",
|
|
58
|
+
args: "<id>",
|
|
59
|
+
options: { json: JSON_OPTION },
|
|
60
|
+
run: async ({ positionals, values }) =>
|
|
61
|
+
(await import("./ledger")).ledgerShow(positionals[0], values.json === true),
|
|
62
|
+
}),
|
|
63
|
+
stats: leaf({
|
|
64
|
+
summary: "Counts by outcome, runtime, actor, tool, target",
|
|
65
|
+
options: FILTER_OPTIONS,
|
|
66
|
+
run: async ({ values }) => (await import("./ledger")).ledgerStats(values),
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
});
|