portable-agent-layer 0.88.1 → 0.89.1
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/readme-sync.ts +6 -2
- 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/export-manifest.ts +23 -0
- package/src/hooks/lib/export.ts +1 -24
- package/src/hooks/lib/hook-turn.ts +25 -2
- package/src/hooks/lib/import-merge.ts +1 -1
- 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/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/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/ledger.ts
CHANGED
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
* Thin presentation layer over src/tools/ledger/query.ts. Owns formatting and
|
|
5
5
|
* argv parsing only; every question about the records themselves is answered
|
|
6
6
|
* there.
|
|
7
|
-
*
|
|
8
|
-
* Subcommands:
|
|
9
|
-
* log [filters] Matching actions, oldest first
|
|
10
|
-
* show <id> One action in full, with its change and current standing
|
|
11
|
-
* stats [filters] Counts by outcome, runtime, actor, tool and target
|
|
12
7
|
*/
|
|
13
8
|
|
|
14
|
-
import { parseArgs } from "node:util";
|
|
15
9
|
import type { LedgerEntry } from "../hooks/lib/ledger";
|
|
16
10
|
import {
|
|
17
11
|
type ChainVerdict,
|
|
@@ -28,75 +22,22 @@ import {
|
|
|
28
22
|
standing,
|
|
29
23
|
summarize,
|
|
30
24
|
} from "../tools/ledger/query";
|
|
25
|
+
import { UsageError } from "../tools/lib/command";
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
since: { type: "string" },
|
|
35
|
-
until: { type: "string" },
|
|
36
|
-
actor: { type: "string" },
|
|
37
|
-
machine: { type: "string" },
|
|
38
|
-
runtime: { type: "string" },
|
|
39
|
-
outcome: { type: "string" },
|
|
40
|
-
tool: { type: "string" },
|
|
41
|
-
target: { type: "string" },
|
|
42
|
-
limit: { type: "string" },
|
|
43
|
-
json: { type: "boolean" },
|
|
44
|
-
} as const;
|
|
45
|
-
|
|
46
|
-
export async function runLedger(args: string[]): Promise<number> {
|
|
47
|
-
const [sub, ...rest] = args;
|
|
48
|
-
switch (sub) {
|
|
49
|
-
case "log":
|
|
50
|
-
return cmdLog(rest);
|
|
51
|
-
case "show":
|
|
52
|
-
return cmdShow(rest);
|
|
53
|
-
case "stats":
|
|
54
|
-
return cmdStats(rest);
|
|
55
|
-
case undefined:
|
|
56
|
-
case "help":
|
|
57
|
-
case "--help":
|
|
58
|
-
case "-h":
|
|
59
|
-
showHelp();
|
|
60
|
-
return 0;
|
|
61
|
-
default:
|
|
62
|
-
console.error(`Unknown subcommand: ${sub}\n`);
|
|
63
|
-
showHelp();
|
|
64
|
-
return 1;
|
|
65
|
-
}
|
|
27
|
+
export function ledgerLog(values: Record<string, unknown>): number {
|
|
28
|
+
return cmdLog(buildFilter(values), values.json === true);
|
|
66
29
|
}
|
|
67
30
|
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
pal cli ledger <subcommand> [filters]
|
|
72
|
-
|
|
73
|
-
Subcommands:
|
|
74
|
-
log [filters] Matching actions, oldest first
|
|
75
|
-
show <id> One action in full: change, target, standing
|
|
76
|
-
stats [filters] Counts by outcome, runtime, actor, tool, target
|
|
77
|
-
|
|
78
|
-
Filters:
|
|
79
|
-
--project <slug> Actions against a registered project
|
|
80
|
-
--since <7d|2026-09-01> A duration back from now, or a date
|
|
81
|
-
--until <date> Upper bound on the timestamp
|
|
82
|
-
--actor <id> Who caused it
|
|
83
|
-
--machine <id> Which install wrote it
|
|
84
|
-
--runtime <agent> claude, cursor, codex, copilot, opencode
|
|
85
|
-
--outcome <applied|failed|denied>
|
|
86
|
-
--tool <Edit|Write>
|
|
87
|
-
--target <substring> Match anywhere in the recorded path
|
|
88
|
-
--limit <n> Keep the newest n matches
|
|
89
|
-
--json Machine-readable output
|
|
31
|
+
export function ledgerShow(id: string, json: boolean): number {
|
|
32
|
+
return cmdShow(id, json);
|
|
33
|
+
}
|
|
90
34
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
pal cli ledger log --target memory/ --outcome applied
|
|
94
|
-
pal cli ledger stats --since 24h
|
|
95
|
-
`);
|
|
35
|
+
export function ledgerStats(values: Record<string, unknown>): number {
|
|
36
|
+
return cmdStats(buildFilter(values), values.json === true);
|
|
96
37
|
}
|
|
97
38
|
|
|
98
39
|
/** A filter that silently ignored an unparseable window would answer the wrong question. */
|
|
99
|
-
function buildFilter(values: Record<string, unknown>): LedgerFilter
|
|
40
|
+
function buildFilter(values: Record<string, unknown>): LedgerFilter {
|
|
100
41
|
const filter: LedgerFilter = {};
|
|
101
42
|
for (const key of [
|
|
102
43
|
"project",
|
|
@@ -115,43 +56,27 @@ function buildFilter(values: Record<string, unknown>): LedgerFilter | string {
|
|
|
115
56
|
const spec = values[key];
|
|
116
57
|
if (typeof spec !== "string") continue;
|
|
117
58
|
const at = parseSince(spec);
|
|
118
|
-
if (!at)
|
|
59
|
+
if (!at)
|
|
60
|
+
throw new UsageError(`Unrecognised --${key}: ${spec} (use 7d, 24h, or a date)`);
|
|
119
61
|
filter[key] = at;
|
|
120
62
|
}
|
|
121
63
|
|
|
122
64
|
if (typeof values.limit === "string") {
|
|
123
65
|
const limit = Number(values.limit);
|
|
124
66
|
if (!Number.isInteger(limit) || limit < 1)
|
|
125
|
-
|
|
67
|
+
throw new UsageError("--limit must be a positive integer");
|
|
126
68
|
filter.limit = limit;
|
|
127
69
|
}
|
|
128
70
|
return filter;
|
|
129
71
|
}
|
|
130
72
|
|
|
131
|
-
function parseFilters(args: string[]): { filter: LedgerFilter; json: boolean } | string {
|
|
132
|
-
try {
|
|
133
|
-
const { values } = parseArgs({
|
|
134
|
-
args,
|
|
135
|
-
options: FILTER_OPTIONS,
|
|
136
|
-
allowPositionals: true,
|
|
137
|
-
});
|
|
138
|
-
const filter = buildFilter(values);
|
|
139
|
-
return typeof filter === "string" ? filter : { filter, json: values.json === true };
|
|
140
|
-
} catch (error) {
|
|
141
|
-
return error instanceof Error ? error.message : String(error);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
73
|
function shortId(id: string): string {
|
|
146
74
|
return id.slice(0, 11).padEnd(11);
|
|
147
75
|
}
|
|
148
76
|
|
|
149
|
-
function cmdLog(
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
const entries = queryLedger(parsed.filter);
|
|
154
|
-
if (parsed.json) {
|
|
77
|
+
function cmdLog(filter: LedgerFilter, json: boolean): number {
|
|
78
|
+
const entries = queryLedger(filter);
|
|
79
|
+
if (json) {
|
|
155
80
|
console.log(JSON.stringify(entries, null, 2));
|
|
156
81
|
return 0;
|
|
157
82
|
}
|
|
@@ -218,14 +143,11 @@ function printChange(entry: LedgerEntry): void {
|
|
|
218
143
|
}
|
|
219
144
|
}
|
|
220
145
|
|
|
221
|
-
function cmdShow(
|
|
222
|
-
const [id, ...rest] = args;
|
|
223
|
-
if (!id) return fail("Usage: pal cli ledger show <id>");
|
|
224
|
-
|
|
146
|
+
function cmdShow(id: string, json: boolean): number {
|
|
225
147
|
const entry = findEntry(id);
|
|
226
148
|
if (!entry) return fail(`No action with id ${id}`);
|
|
227
149
|
|
|
228
|
-
if (
|
|
150
|
+
if (json) {
|
|
229
151
|
console.log(
|
|
230
152
|
JSON.stringify(
|
|
231
153
|
{
|
|
@@ -280,12 +202,9 @@ function printTally(label: string, counts: Record<string, number>): void {
|
|
|
280
202
|
console.log(` ${String(count).padStart(6)} ${key}`);
|
|
281
203
|
}
|
|
282
204
|
|
|
283
|
-
function cmdStats(
|
|
284
|
-
const
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
const stats = summarize(queryLedger(parsed.filter));
|
|
288
|
-
if (parsed.json) {
|
|
205
|
+
function cmdStats(filter: LedgerFilter, json: boolean): number {
|
|
206
|
+
const stats = summarize(queryLedger(filter));
|
|
207
|
+
if (json) {
|
|
289
208
|
console.log(JSON.stringify(stats, null, 2));
|
|
290
209
|
return 0;
|
|
291
210
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pal cli telos / pal cli timezone — the personal context PAL carries about you.
|
|
3
3
|
*
|
|
4
|
-
* pal cli telos Which TELOS topics are answered, in interview order
|
|
5
|
-
* pal cli timezone Show the configured timezone
|
|
6
|
-
* pal cli timezone <zone> Set it, if Intl recognises the name
|
|
7
|
-
*
|
|
8
4
|
* Both exist for the onboarding skill. pal-settings.json is hook-protected, so
|
|
9
5
|
* an agent cannot write the timezone itself, and a skill that restated the
|
|
10
6
|
* answered/unanswered rule in prose would drift from the code that decides it.
|
|
@@ -18,14 +14,21 @@ import {
|
|
|
18
14
|
import { telosStatus } from "../hooks/lib/telos-topics";
|
|
19
15
|
import { canonicalTimeZone } from "../hooks/lib/wall-clock";
|
|
20
16
|
import { log } from "../targets/lib";
|
|
17
|
+
import { leaf } from "../tools/lib/command";
|
|
21
18
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
export const telosCommand = leaf({
|
|
20
|
+
summary: "Which TELOS topics are answered, in interview order",
|
|
21
|
+
run: printTelosStatus,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const timezoneCommand = leaf({
|
|
25
|
+
summary: "Show the configured timezone, or set it to an IANA name",
|
|
26
|
+
args: "[zone...]",
|
|
27
|
+
details: "Example: pal cli timezone Europe/Budapest",
|
|
28
|
+
run: ({ positionals }) => timezone(positionals),
|
|
29
|
+
});
|
|
28
30
|
|
|
31
|
+
function printTelosStatus(): number {
|
|
29
32
|
const topics = telosStatus();
|
|
30
33
|
for (const topic of topics) {
|
|
31
34
|
const mark = topic.answered ? "answered " : "unanswered";
|
|
@@ -39,8 +42,8 @@ export function runTelos(args: string[]): number {
|
|
|
39
42
|
return 0;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
const requested =
|
|
45
|
+
function timezone(words: string[]): number {
|
|
46
|
+
const requested = words.join(" ").trim();
|
|
44
47
|
|
|
45
48
|
if (!requested) {
|
|
46
49
|
const current = identity().principal.timezone;
|
package/src/cli/rule.ts
CHANGED
|
@@ -14,24 +14,39 @@ import {
|
|
|
14
14
|
readRules,
|
|
15
15
|
type TriggerProof,
|
|
16
16
|
} from "../hooks/lib/adaptation-rules";
|
|
17
|
+
import { group, leaf, runCommand } from "../tools/lib/command";
|
|
17
18
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
export const ruleCommand = group({
|
|
20
|
+
summary: "Review the rules PAL drafted from your corrections",
|
|
21
|
+
commands: {
|
|
22
|
+
list: leaf({
|
|
23
|
+
summary: "Drafts waiting for a decision",
|
|
24
|
+
options: {
|
|
25
|
+
all: { type: "boolean", description: "Every rule, not only the drafts" },
|
|
26
|
+
json: { type: "boolean", description: "Machine-readable output" },
|
|
27
|
+
},
|
|
28
|
+
run: ({ values }) => cmdList(values.all === true, values.json === true),
|
|
29
|
+
}),
|
|
30
|
+
approve: leaf({
|
|
31
|
+
summary: "Approve a draft so it steers",
|
|
32
|
+
args: "<id>",
|
|
33
|
+
run: ({ positionals }) => cmdDecide(positionals[0], "approved"),
|
|
34
|
+
}),
|
|
35
|
+
deny: leaf({
|
|
36
|
+
summary: "Deny a draft; it stays on record so it is not drafted again",
|
|
37
|
+
args: "<id>",
|
|
38
|
+
run: ({ positionals }) => cmdDecide(positionals[0], "denied"),
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
});
|
|
22
42
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
if (sub === "list") return cmdList(rest);
|
|
26
|
-
if (sub && sub in DECISIONS) return cmdDecide(rest[0], DECISIONS[sub]);
|
|
27
|
-
showHelp();
|
|
28
|
-
return sub === undefined || sub === "help" ? 0 : 1;
|
|
43
|
+
export function runRule(args: string[]): Promise<number> {
|
|
44
|
+
return runCommand(ruleCommand, args, ["pal", "cli", "rule"]);
|
|
29
45
|
}
|
|
30
46
|
|
|
31
|
-
function cmdList(
|
|
32
|
-
const all = args.includes("--all");
|
|
47
|
+
function cmdList(all: boolean, json: boolean): number {
|
|
33
48
|
const rules = readRules().filter((r) => all || r.status === "draft");
|
|
34
|
-
if (
|
|
49
|
+
if (json) {
|
|
35
50
|
console.log(JSON.stringify(rules, null, 2));
|
|
36
51
|
return 0;
|
|
37
52
|
}
|
|
@@ -64,14 +79,7 @@ function decisionLine(id: string, decision: string, rule: AdaptationRule): strin
|
|
|
64
79
|
return `Rule ${rule.id} now triggers on: ${rule.trigger.pattern} (widened by ${id})`;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
function cmdDecide(
|
|
68
|
-
id: string | undefined,
|
|
69
|
-
decision: Exclude<RuleStatus, "draft">
|
|
70
|
-
): number {
|
|
71
|
-
if (!id) {
|
|
72
|
-
console.error("Give the rule id: pal cli rule approve|deny <id>");
|
|
73
|
-
return 1;
|
|
74
|
-
}
|
|
82
|
+
function cmdDecide(id: string, decision: Exclude<RuleStatus, "draft">): number {
|
|
75
83
|
const result = decideRule(id, decision);
|
|
76
84
|
if (!result.ok) {
|
|
77
85
|
console.error(result.reason);
|
|
@@ -80,11 +88,3 @@ function cmdDecide(
|
|
|
80
88
|
console.log(decisionLine(id, decision, result.rule));
|
|
81
89
|
return 0;
|
|
82
90
|
}
|
|
83
|
-
|
|
84
|
-
function showHelp(): void {
|
|
85
|
-
console.log(`pal cli rule — review adaptation rules drafted from your corrections
|
|
86
|
-
|
|
87
|
-
pal cli rule list [--all] [--json] Drafts waiting for a decision
|
|
88
|
-
pal cli rule approve <id> Approve a draft so it steers
|
|
89
|
-
pal cli rule deny <id> Deny a draft; it is not drafted again`);
|
|
90
|
-
}
|
package/src/cli/server.ts
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
|
-
import { parseArgs } from "node:util";
|
|
12
11
|
import { spawnDetachedInference } from "../hooks/lib/detached-inference";
|
|
13
12
|
import { paths } from "../hooks/lib/paths";
|
|
14
|
-
import {
|
|
13
|
+
import type { ServerStatus } from "../tools/control-room/server";
|
|
14
|
+
import { DEFAULT_PORT, LOOPBACK } from "../tools/control-room/server-config";
|
|
15
15
|
import { BUILD_COMMAND, buildPage, isBuilt } from "../tools/control-room/static";
|
|
16
|
+
import { group, leaf, UsageError } from "../tools/lib/command";
|
|
16
17
|
|
|
17
18
|
interface ServerState {
|
|
18
19
|
pid: number;
|
|
@@ -30,44 +31,29 @@ const SERVER_SCRIPT = resolve(
|
|
|
30
31
|
const STARTUP_TIMEOUT_MS = 3000;
|
|
31
32
|
const PROBE_TIMEOUT_MS = 500;
|
|
32
33
|
|
|
33
|
-
export
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function showHelp(): void {
|
|
58
|
-
console.log(`
|
|
59
|
-
Usage:
|
|
60
|
-
pal cli server <subcommand>
|
|
61
|
-
|
|
62
|
-
Subcommands:
|
|
63
|
-
start [--port <n>] Start the control room in the background (default port ${DEFAULT_PORT})
|
|
64
|
-
stop Stop it
|
|
65
|
-
restart Replace a running one — the API only changes when the process does
|
|
66
|
-
status Show whether it is running, and where
|
|
67
|
-
|
|
68
|
-
The page listens on ${LOOPBACK} only.
|
|
69
|
-
`);
|
|
70
|
-
}
|
|
34
|
+
export const serverCommand = group({
|
|
35
|
+
summary: "Start, stop and inspect the local control room",
|
|
36
|
+
details: `The page listens on ${LOOPBACK} only.`,
|
|
37
|
+
commands: {
|
|
38
|
+
start: leaf({
|
|
39
|
+
summary: "Start the control room in the background",
|
|
40
|
+
options: {
|
|
41
|
+
port: {
|
|
42
|
+
type: "string",
|
|
43
|
+
value: "<n>",
|
|
44
|
+
description: `Port to listen on (default ${DEFAULT_PORT})`,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
run: ({ values }) => cmdStart(parsePort(values.port)),
|
|
48
|
+
}),
|
|
49
|
+
stop: leaf({ summary: "Stop it", run: cmdStop }),
|
|
50
|
+
restart: leaf({
|
|
51
|
+
summary: "Replace a running one — the API only changes when the process does",
|
|
52
|
+
run: cmdRestart,
|
|
53
|
+
}),
|
|
54
|
+
status: leaf({ summary: "Show whether it is running, and where", run: cmdStatus }),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
71
57
|
|
|
72
58
|
function url(port: number): string {
|
|
73
59
|
return `http://${LOOPBACK}:${port}/`;
|
|
@@ -122,19 +108,14 @@ async function waitUntilAnswering(port: number): Promise<ServerStatus | null> {
|
|
|
122
108
|
return null;
|
|
123
109
|
}
|
|
124
110
|
|
|
125
|
-
function parsePort(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
? port
|
|
131
|
-
: `--port must be a port number, got ${values.port}`;
|
|
111
|
+
function parsePort(value: string | undefined): number {
|
|
112
|
+
if (value === undefined) return DEFAULT_PORT;
|
|
113
|
+
const port = Number(value);
|
|
114
|
+
if (Number.isInteger(port) && port > 0 && port < 65536) return port;
|
|
115
|
+
throw new UsageError(`--port must be a port number, got ${value}`);
|
|
132
116
|
}
|
|
133
117
|
|
|
134
|
-
async function cmdStart(
|
|
135
|
-
const port = parsePort(args);
|
|
136
|
-
if (typeof port === "string") return fail(port);
|
|
137
|
-
|
|
118
|
+
async function cmdStart(port: number): Promise<number> {
|
|
138
119
|
const running = await runningServer();
|
|
139
120
|
if (running) {
|
|
140
121
|
console.log(`Already running at ${url(running.port)} (pid ${running.pid})`);
|
|
@@ -171,7 +152,7 @@ async function cmdRestart(): Promise<number> {
|
|
|
171
152
|
return 0;
|
|
172
153
|
}
|
|
173
154
|
await cmdStop();
|
|
174
|
-
return cmdStart(
|
|
155
|
+
return cmdStart(running.port);
|
|
175
156
|
}
|
|
176
157
|
|
|
177
158
|
/**
|
package/src/cli/session-agent.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
+
import { AGENT_REGISTRY, LAUNCH_PRIORITY } from "../hooks/lib/agent-registry";
|
|
1
2
|
import { findBinaryOnPath } from "../hooks/lib/which";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
"
|
|
5
|
-
"codex",
|
|
6
|
-
"cursor-agent",
|
|
7
|
-
"copilot",
|
|
8
|
-
"opencode",
|
|
9
|
-
] as const;
|
|
4
|
+
export type SessionAgent =
|
|
5
|
+
(typeof AGENT_REGISTRY)[(typeof LAUNCH_PRIORITY)[number]]["binary"];
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
const SESSION_AGENT_BINARIES: readonly SessionAgent[] = LAUNCH_PRIORITY.map(
|
|
8
|
+
(agent) => AGENT_REGISTRY[agent].binary
|
|
9
|
+
);
|
|
12
10
|
|
|
13
11
|
export function findSessionAgent(): SessionAgent | null {
|
|
14
12
|
return (
|
|
@@ -17,4 +15,4 @@ export function findSessionAgent(): SessionAgent | null {
|
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
export const NO_SESSION_AGENT_MESSAGE =
|
|
20
|
-
"No supported agent found. Install Claude Code, Codex, Cursor CLI, Copilot CLI or
|
|
18
|
+
"No supported agent found. Install Claude Code, Codex, Cursor CLI, Copilot CLI, opencode or Antigravity CLI.";
|
package/src/cli/skill.ts
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* pal cli skill — manage personal skills under ~/.pal/skills/.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* every installed agent so it is discoverable.
|
|
6
|
-
* pal cli skill doctor <name> Evaluate ~/.pal/skills/<name>/ against the
|
|
7
|
-
* skill-authoring best practices.
|
|
8
|
-
* pal cli skill doctor --all Evaluate every installed skill, one line each.
|
|
9
|
-
* pal cli skill author-model Print the flagship model configured to author
|
|
10
|
-
* skills for the active agent (empty if none).
|
|
11
|
-
* pal cli skill run <skill> <tool> [-- args]
|
|
12
|
-
* Run ~/.pal/skills/<skill>/tools/<tool>, so a
|
|
13
|
-
* SKILL.md can name the tool instead of a path
|
|
14
|
-
* with a tilde no Windows shell expands.
|
|
2
|
+
* pal cli skill — manage personal skills under ~/.pal/skills/. `skill run`
|
|
3
|
+
* exists so a SKILL.md can name a tool instead of a path with a tilde no
|
|
4
|
+
* Windows shell expands.
|
|
15
5
|
*/
|
|
16
6
|
|
|
17
7
|
import { spawnSync } from "node:child_process";
|
|
@@ -22,6 +12,7 @@ import { flagshipAuthorModel } from "../hooks/lib/models";
|
|
|
22
12
|
import { withPalEnv } from "../hooks/lib/pal-env";
|
|
23
13
|
import { isInside, palHome, palPkg } from "../hooks/lib/paths";
|
|
24
14
|
import { linkPersonalSkill, log } from "../targets/lib";
|
|
15
|
+
import { group, leaf, UsageError } from "../tools/lib/command";
|
|
25
16
|
import {
|
|
26
17
|
formatReport,
|
|
27
18
|
formatSummary,
|
|
@@ -105,60 +96,78 @@ function isShippedTool(path: string): boolean {
|
|
|
105
96
|
return existsSync(shipped) && isInside(realpathSync(shipped), realpathSync(path));
|
|
106
97
|
}
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!skill || !tool) {
|
|
114
|
-
log.error("Usage: pal cli skill run <skill> <tool> [-- args]");
|
|
115
|
-
return 1;
|
|
116
|
-
}
|
|
117
|
-
return runSkillTool(skill, tool, rest[0] === "--" ? rest.slice(1) : rest);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (sub === "author-model") {
|
|
121
|
-
const model = flagshipAuthorModel(getActiveAgent());
|
|
122
|
-
if (model) console.log(model);
|
|
123
|
-
return 0;
|
|
124
|
-
}
|
|
99
|
+
function doctorOne(name: string): number {
|
|
100
|
+
const report = lintSkill(resolveSkillDir(name));
|
|
101
|
+
console.log(formatReport(report));
|
|
102
|
+
return report.errors > 0 ? 1 : 0;
|
|
103
|
+
}
|
|
125
104
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const report = lintSkill(resolveSkillDir(name));
|
|
133
|
-
console.log(formatReport(report));
|
|
134
|
-
return report.errors > 0 ? 1 : 0;
|
|
135
|
-
}
|
|
105
|
+
function doctor(name: string | undefined, all: boolean): number {
|
|
106
|
+
if (all && name) throw new UsageError("give a skill name or --all, not both");
|
|
107
|
+
if (all) return doctorAll();
|
|
108
|
+
if (!name) throw new UsageError("missing <name> (or --all)");
|
|
109
|
+
return doctorOne(name);
|
|
110
|
+
}
|
|
136
111
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
log.success(
|
|
151
|
-
`Linked '${name}' into: ${linked.join(", ")} ` +
|
|
152
|
-
"(opencode: auto via ~/.pal/skills/)"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
return 0;
|
|
156
|
-
} catch (e) {
|
|
157
|
-
log.error(e instanceof Error ? e.message : String(e));
|
|
158
|
-
return 1;
|
|
112
|
+
function link(name: string): number {
|
|
113
|
+
try {
|
|
114
|
+
const linked = linkPersonalSkill(name);
|
|
115
|
+
if (linked.length === 0) {
|
|
116
|
+
log.warn(
|
|
117
|
+
`'${name}' linked to no per-skill agents (none installed). ` +
|
|
118
|
+
"It is still discoverable by opencode via ~/.pal/skills/."
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
log.success(
|
|
122
|
+
`Linked '${name}' into: ${linked.join(", ")} ` +
|
|
123
|
+
"(opencode: auto via ~/.pal/skills/)"
|
|
124
|
+
);
|
|
159
125
|
}
|
|
126
|
+
return 0;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
log.error(e instanceof Error ? e.message : String(e));
|
|
129
|
+
return 1;
|
|
160
130
|
}
|
|
131
|
+
}
|
|
161
132
|
|
|
162
|
-
|
|
163
|
-
|
|
133
|
+
export function printAuthorModel(): number {
|
|
134
|
+
const model = flagshipAuthorModel(getActiveAgent());
|
|
135
|
+
if (model) console.log(model);
|
|
136
|
+
return 0;
|
|
164
137
|
}
|
|
138
|
+
|
|
139
|
+
export const skillCommand = group({
|
|
140
|
+
summary: "Manage personal skills under ~/.pal/skills/",
|
|
141
|
+
commands: {
|
|
142
|
+
run: leaf({
|
|
143
|
+
summary:
|
|
144
|
+
"Run ~/.pal/skills/<skill>/tools/<tool>; every later argument goes to the tool",
|
|
145
|
+
args: "<skill> <tool>",
|
|
146
|
+
passThrough: true,
|
|
147
|
+
details:
|
|
148
|
+
"Arguments after <tool> reach the tool untouched; '--' before them is optional.",
|
|
149
|
+
run: ({ positionals, passedThrough }) =>
|
|
150
|
+
runSkillTool(positionals[0], positionals[1], passedThrough),
|
|
151
|
+
}),
|
|
152
|
+
link: leaf({
|
|
153
|
+
summary: "Link ~/.pal/skills/<name>/ into every installed agent",
|
|
154
|
+
args: "<name>",
|
|
155
|
+
run: ({ positionals }) => link(positionals[0]),
|
|
156
|
+
}),
|
|
157
|
+
doctor: leaf({
|
|
158
|
+
summary: "Check a skill against the skill-authoring best practices",
|
|
159
|
+
args: "[name]",
|
|
160
|
+
options: {
|
|
161
|
+
all: {
|
|
162
|
+
type: "boolean",
|
|
163
|
+
description: "Check every installed skill, one line each",
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
run: ({ positionals, values }) => doctor(positionals[0], values.all === true),
|
|
167
|
+
}),
|
|
168
|
+
"author-model": leaf({
|
|
169
|
+
summary: "Print the flagship model that authors skills for the active agent",
|
|
170
|
+
run: printAuthorModel,
|
|
171
|
+
}),
|
|
172
|
+
},
|
|
173
|
+
});
|