portable-agent-layer 0.75.1 → 0.76.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/assets/agents/gemini-researcher.md +1 -1
- package/assets/agents/grok-researcher.md +3 -3
- package/assets/agents/perplexity-researcher.md +1 -1
- package/assets/skills/analyze-pdf/SKILL.md +2 -2
- package/assets/skills/analyze-youtube/SKILL.md +1 -1
- package/assets/skills/consulting-report/SKILL.md +5 -5
- package/assets/skills/consulting-report/tools/dev.ts +1 -1
- package/assets/skills/consulting-report/tools/generate-pdf.ts +1 -1
- package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
- package/assets/skills/create-pdf/SKILL.md +3 -3
- package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +1 -1
- package/assets/skills/fyzz-chat-api/SKILL.md +3 -3
- package/assets/skills/onboarding/SKILL.md +1 -1
- package/assets/skills/pal-analyze/SKILL.md +1 -1
- package/assets/skills/pal-reflect/SKILL.md +2 -2
- package/assets/skills/playwright/SKILL.md +1 -1
- package/assets/skills/playwright/tools/shot.ts +1 -1
- package/assets/skills/presentation/README.md +3 -3
- package/assets/skills/presentation/SKILL.md +8 -8
- package/assets/skills/presentation/template/README.md +1 -1
- package/assets/skills/presentation/tools/new-deck.ts +1 -1
- package/assets/skills/presentation/tools/setup-template.ts +1 -1
- package/assets/skills/projects/SKILL.md +17 -8
- package/assets/skills/telos/SKILL.md +1 -1
- package/assets/templates/AGENTS.md.template +1 -1
- package/assets/templates/PAL/ALGORITHM.md +18 -18
- package/assets/templates/PAL/PROJECT_LIFECYCLE.md +1 -1
- package/assets/templates/PAL/WORK_TRACKING.md +1 -1
- package/assets/templates/rules.codex.rules +65 -0
- package/assets/templates/settings.claude.json +11 -1
- package/package.json +1 -1
- package/src/cli/builtin-tools.ts +30 -0
- package/src/cli/index.ts +17 -9
- package/src/cli/knowledge.ts +2 -1
- package/src/cli/skill.ts +56 -4
- package/src/cli/subagent.ts +1 -1
- package/src/hooks/lib/bindings.ts +2 -2
- package/src/hooks/lib/context.ts +2 -4
- package/src/hooks/lib/paths.ts +48 -8
- package/src/hooks/lib/projects.ts +1 -1
- package/src/tools/agent/algorithm-reflect.ts +3 -2
- package/src/tools/agent/algorithm-synthesize.ts +5 -2
- package/src/tools/agent/analyze.ts +2 -1
- package/src/tools/agent/handoff-note.ts +7 -6
- package/src/tools/agent/project.ts +21 -20
- package/src/tools/agent/relationship-note.ts +11 -8
- package/src/tools/agent/synthesize.ts +4 -3
- package/src/tools/agent/thread.ts +6 -5
- package/src/tools/agent/wisdom-frame.ts +3 -2
- package/src/tools/lib/script-args.ts +4 -0
- package/src/tools/lib/skill-doctor.ts +2 -2
- package/src/tools/self-model.ts +1 -1
- package/src/tools/skill-doctor.ts +1 -1
- package/src/tools/subagent-doctor.ts +4 -4
|
@@ -61,4 +61,69 @@ prefix_rule(
|
|
|
61
61
|
"node ~/.pal/skills/consulting-report/tools/generate-pdf.ts ./report",
|
|
62
62
|
],
|
|
63
63
|
)
|
|
64
|
+
|
|
65
|
+
prefix_rule(
|
|
66
|
+
pattern = ["pal", "cli", "project"],
|
|
67
|
+
decision = "allow",
|
|
68
|
+
justification = "PAL project state commands are part of the installed PAL workflow",
|
|
69
|
+
match = [
|
|
70
|
+
"pal cli project resume portable-agent-layer",
|
|
71
|
+
"pal cli project list-isc portable-agent-layer",
|
|
72
|
+
"pal cli project add-next portable-agent-layer note",
|
|
73
|
+
],
|
|
74
|
+
not_match = [
|
|
75
|
+
"pal cli install",
|
|
76
|
+
],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
prefix_rule(
|
|
80
|
+
pattern = ["pal", "cli", "algorithm-reflect"],
|
|
81
|
+
decision = "allow",
|
|
82
|
+
justification = "PAL Algorithm reflection logging is part of the installed PAL workflow",
|
|
83
|
+
match = [
|
|
84
|
+
"pal cli algorithm-reflect --task work --criteria 1 --passed 1 --failed 0 --sentiment 8",
|
|
85
|
+
],
|
|
86
|
+
not_match = [
|
|
87
|
+
"pal cli project list",
|
|
88
|
+
],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
prefix_rule(
|
|
92
|
+
pattern = [
|
|
93
|
+
"pal",
|
|
94
|
+
"cli",
|
|
95
|
+
[
|
|
96
|
+
"algorithm-synthesize",
|
|
97
|
+
"analyze",
|
|
98
|
+
"handoff-note",
|
|
99
|
+
"relationship-note",
|
|
100
|
+
"synthesize",
|
|
101
|
+
"thread",
|
|
102
|
+
"wisdom-frame",
|
|
103
|
+
],
|
|
104
|
+
],
|
|
105
|
+
decision = "allow",
|
|
106
|
+
justification = "PAL installed agent tools are safe to run without repeated approval",
|
|
107
|
+
match = [
|
|
108
|
+
"pal cli thread --list",
|
|
109
|
+
"pal cli wisdom-frame --help",
|
|
110
|
+
],
|
|
111
|
+
not_match = [
|
|
112
|
+
"pal cli project list",
|
|
113
|
+
"pal cli uninstall",
|
|
114
|
+
],
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
prefix_rule(
|
|
118
|
+
pattern = ["pal", "cli", "skill", "run"],
|
|
119
|
+
decision = "allow",
|
|
120
|
+
justification = "A skill's own tools ship with PAL and are safe to run without repeated approval",
|
|
121
|
+
match = [
|
|
122
|
+
"pal cli skill run presentation build ./deck",
|
|
123
|
+
"pal cli skill run consulting-report generate-pdf.mjs ./report",
|
|
124
|
+
],
|
|
125
|
+
not_match = [
|
|
126
|
+
"pal cli skill link my-skill",
|
|
127
|
+
],
|
|
128
|
+
)
|
|
64
129
|
# END PAL MANAGED CODEX RULES
|
|
@@ -34,7 +34,17 @@
|
|
|
34
34
|
"Bash(rtk gain *)",
|
|
35
35
|
"Bash(bun ~/.pal/skills/*/tools/*.ts *)",
|
|
36
36
|
"Bash(bun ~/.pal/tools/*.ts *)",
|
|
37
|
-
"Bash(node --experimental-strip-types ~/.pal/skills/consulting-report/tools/generate-pdf.ts *)"
|
|
37
|
+
"Bash(node --experimental-strip-types ~/.pal/skills/consulting-report/tools/generate-pdf.ts *)",
|
|
38
|
+
"Bash(pal cli skill run *)",
|
|
39
|
+
"Bash(pal cli algorithm-reflect *)",
|
|
40
|
+
"Bash(pal cli algorithm-synthesize *)",
|
|
41
|
+
"Bash(pal cli analyze *)",
|
|
42
|
+
"Bash(pal cli handoff-note *)",
|
|
43
|
+
"Bash(pal cli project *)",
|
|
44
|
+
"Bash(pal cli relationship-note *)",
|
|
45
|
+
"Bash(pal cli synthesize *)",
|
|
46
|
+
"Bash(pal cli thread *)",
|
|
47
|
+
"Bash(pal cli wisdom-frame *)"
|
|
38
48
|
]
|
|
39
49
|
},
|
|
40
50
|
"skillOverrides": {
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAL's own agent tools, surfaced as `pal cli <verb>`.
|
|
3
|
+
*
|
|
4
|
+
* Instruction text names the verb rather than `bun ~/.pal/tools/<tool>.ts`, so
|
|
5
|
+
* no shell has to expand a tilde — which cmd.exe never does and PowerShell
|
|
6
|
+
* passes through literally — and a tool can move without breaking every doc
|
|
7
|
+
* that calls it. Each entry imports lazily, so an unused verb costs nothing.
|
|
8
|
+
*/
|
|
9
|
+
const BUILTIN_TOOLS = {
|
|
10
|
+
"algorithm-reflect": () => import("../tools/agent/algorithm-reflect"),
|
|
11
|
+
"algorithm-synthesize": () => import("../tools/agent/algorithm-synthesize"),
|
|
12
|
+
analyze: () => import("../tools/agent/analyze"),
|
|
13
|
+
"handoff-note": () => import("../tools/agent/handoff-note"),
|
|
14
|
+
project: () => import("../tools/agent/project"),
|
|
15
|
+
"relationship-note": () => import("../tools/agent/relationship-note"),
|
|
16
|
+
synthesize: () => import("../tools/agent/synthesize"),
|
|
17
|
+
thread: () => import("../tools/agent/thread"),
|
|
18
|
+
"wisdom-frame": () => import("../tools/agent/wisdom-frame"),
|
|
19
|
+
} satisfies Record<string, () => Promise<{ run: (argv: string[]) => unknown }>>;
|
|
20
|
+
|
|
21
|
+
export const builtinToolVerbs = Object.keys(BUILTIN_TOOLS).sort();
|
|
22
|
+
|
|
23
|
+
/** Dispatch a built-in verb; false means the command belongs to the main switch. */
|
|
24
|
+
export async function runBuiltinTool(command: string, args: string[]): Promise<boolean> {
|
|
25
|
+
const load = BUILTIN_TOOLS[command as keyof typeof BUILTIN_TOOLS];
|
|
26
|
+
if (!load) return false;
|
|
27
|
+
const { run } = await load();
|
|
28
|
+
await run(args);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* usage Summarize token usage and cost
|
|
19
19
|
* ledger <sub> [filters] Query the action ledger (log · show · stats)
|
|
20
20
|
* server start|stop|restart|status The control room, a local page over ~/.pal
|
|
21
|
+
* <tool> [args] Run a built-in agent tool (project, thread, analyze, …)
|
|
22
|
+
* skill run <skill> <tool> [-- args] Run a skill's own tool by name, not by path
|
|
21
23
|
* skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
22
24
|
* skill doctor <name|--all> Evaluate one skill, or every installed skill, against the authoring best practices
|
|
23
25
|
* subagent link <name> Install a personal ~/.pal/agents/<name>.md into installed agents
|
|
@@ -47,10 +49,12 @@ import {
|
|
|
47
49
|
import { inference, previewInferenceRoute } from "../hooks/lib/inference";
|
|
48
50
|
import { DEBUG_LOG_MAX_ROTATED, logDebug } from "../hooks/lib/log";
|
|
49
51
|
import { ensureRegistered, writeRegistryEntry } from "../hooks/lib/machine";
|
|
50
|
-
import { palHome, palPkg, paths, platform } from "../hooks/lib/paths";
|
|
52
|
+
import { palHome, palPkg, paths, platform, toPath } from "../hooks/lib/paths";
|
|
51
53
|
import { auditBindings, describeBindingIssue } from "../hooks/lib/projects";
|
|
52
54
|
import { telosStatus } from "../hooks/lib/telos-topics";
|
|
55
|
+
import { findBinaryOnPath } from "../hooks/lib/which";
|
|
53
56
|
import { log } from "../targets/lib";
|
|
57
|
+
import { builtinToolVerbs, runBuiltinTool } from "./builtin-tools";
|
|
54
58
|
import { checkPendingMigrations } from "./migrate";
|
|
55
59
|
|
|
56
60
|
const allArgs = process.argv.slice(2);
|
|
@@ -184,6 +188,7 @@ async function session(sessionArgs: string[]) {
|
|
|
184
188
|
// ── CLI dispatcher ──
|
|
185
189
|
|
|
186
190
|
async function runCli(command: string | undefined, args: string[]) {
|
|
191
|
+
if (command && (await runBuiltinTool(command, args))) return;
|
|
187
192
|
switch (command) {
|
|
188
193
|
case "init":
|
|
189
194
|
await init(args);
|
|
@@ -219,11 +224,6 @@ async function runCli(command: string | undefined, args: string[]) {
|
|
|
219
224
|
runMigrate(args);
|
|
220
225
|
break;
|
|
221
226
|
}
|
|
222
|
-
case "analyze": {
|
|
223
|
-
const { run: runAnalyze } = await import("../tools/agent/analyze");
|
|
224
|
-
await runAnalyze(args);
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
227
|
case "usage": {
|
|
228
228
|
const { usage } = await import("../tools/token-cost");
|
|
229
229
|
usage();
|
|
@@ -338,7 +338,8 @@ function showHelp() {
|
|
|
338
338
|
pal cli status Show PAL configuration
|
|
339
339
|
pal cli doctor [--probe-inference] Check prerequisites and health (--probe fires real inference per route)
|
|
340
340
|
pal cli migrate [--list] [--dry-run] Run pending data migrations
|
|
341
|
-
pal cli
|
|
341
|
+
pal cli <tool> [args] Run a built-in agent tool ('<tool> --help' for its flags):
|
|
342
|
+
${builtinToolVerbs.join(" · ")}
|
|
342
343
|
pal cli usage Summarize token usage and cost
|
|
343
344
|
pal cli actor [label <name>] Show or rename this actor (who caused a record)
|
|
344
345
|
pal cli machine [label <name>] Show or rename this install (where it was written)
|
|
@@ -349,6 +350,7 @@ function showHelp() {
|
|
|
349
350
|
pal cli ledger <sub> [filters] Query the action ledger (log · show · stats)
|
|
350
351
|
e.g. ledger log --project X --since 7d
|
|
351
352
|
pal cli server start|stop|restart|status The control room: a local page to open before a terminal
|
|
353
|
+
pal cli skill run <skill> <tool> [-- args] Run ~/.pal/skills/<skill>/tools/<tool> by name
|
|
352
354
|
pal cli skill link <name> Link a personal ~/.pal/skills/<name>/ into installed agents
|
|
353
355
|
pal cli skill doctor <name|--all> Evaluate one skill, or every installed skill
|
|
354
356
|
pal cli skill author-model Print the flagship model that authors skills for the active agent
|
|
@@ -833,6 +835,12 @@ function doctor(silent = false): DoctorResult {
|
|
|
833
835
|
console.log("");
|
|
834
836
|
log.info("Prerequisites");
|
|
835
837
|
ok(`Bun ${bun.version}`);
|
|
838
|
+
const palBin = findBinaryOnPath("pal");
|
|
839
|
+
palBin
|
|
840
|
+
? ok(`pal on PATH — ${palBin}`)
|
|
841
|
+
: fail(
|
|
842
|
+
"pal — not on PATH; skills and docs invoke tools as 'pal cli ...', which will not resolve. Install globally: bun add -g portable-agent-layer"
|
|
843
|
+
);
|
|
836
844
|
const node = checkNode();
|
|
837
845
|
if (!node.available) {
|
|
838
846
|
warn(
|
|
@@ -1361,7 +1369,7 @@ async function exportState(args: string[]) {
|
|
|
1361
1369
|
|
|
1362
1370
|
const dryRun = args.includes("--dry-run");
|
|
1363
1371
|
const pathArg = args.find((a) => !a.startsWith("-"));
|
|
1364
|
-
const resolvedArg = pathArg ?
|
|
1372
|
+
const resolvedArg = pathArg ? toPath(pathArg) : null;
|
|
1365
1373
|
const argIsDir =
|
|
1366
1374
|
resolvedArg !== null &&
|
|
1367
1375
|
existsSync(resolvedArg) &&
|
|
@@ -1425,7 +1433,7 @@ async function importState(args: string[]) {
|
|
|
1425
1433
|
|
|
1426
1434
|
let zipPath: string;
|
|
1427
1435
|
|
|
1428
|
-
const resolvedArg = pathArg ?
|
|
1436
|
+
const resolvedArg = pathArg ? toPath(pathArg) : null;
|
|
1429
1437
|
const argIsDir =
|
|
1430
1438
|
resolvedArg !== null &&
|
|
1431
1439
|
existsSync(resolvedArg) &&
|
package/src/cli/knowledge.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { readFileSync } from "node:fs";
|
|
24
24
|
import { parseArgs } from "node:util";
|
|
25
25
|
import * as clack from "@clack/prompts";
|
|
26
|
+
import { toPath } from "../hooks/lib/paths";
|
|
26
27
|
import { buildGraph, resolveSlug, stats, traverse } from "../tools/knowledge/graph";
|
|
27
28
|
import {
|
|
28
29
|
type CompanyInput,
|
|
@@ -553,7 +554,7 @@ interface IngestPayload {
|
|
|
553
554
|
}
|
|
554
555
|
|
|
555
556
|
async function readIngestInput(file: string | undefined): Promise<string | null> {
|
|
556
|
-
if (file) return readFileSync(file, "utf-8");
|
|
557
|
+
if (file) return readFileSync(toPath(file), "utf-8");
|
|
557
558
|
if (process.stdin.isTTY) return null;
|
|
558
559
|
return await Bun.stdin.text();
|
|
559
560
|
}
|
package/src/cli/skill.ts
CHANGED
|
@@ -8,15 +8,25 @@
|
|
|
8
8
|
* pal cli skill doctor --all Evaluate every installed skill, one line each.
|
|
9
9
|
* pal cli skill author-model Print the flagship model configured to author
|
|
10
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.
|
|
11
15
|
*/
|
|
12
16
|
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
13
18
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
14
19
|
import { resolve } from "node:path";
|
|
15
20
|
import { getActiveAgent } from "../hooks/lib/agent";
|
|
16
21
|
import { flagshipAuthorModel } from "../hooks/lib/models";
|
|
17
22
|
import { palHome } from "../hooks/lib/paths";
|
|
18
23
|
import { linkPersonalSkill, log } from "../targets/lib";
|
|
19
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
formatReport,
|
|
26
|
+
formatSummary,
|
|
27
|
+
lintSkill,
|
|
28
|
+
resolveSkillDir,
|
|
29
|
+
} from "../tools/lib/skill-doctor";
|
|
20
30
|
|
|
21
31
|
/** Entry names under ~/.pal/skills/, sorted; dangling links included. */
|
|
22
32
|
function skillEntries(dir: string): string[] {
|
|
@@ -59,9 +69,51 @@ function doctorAll(): number {
|
|
|
59
69
|
return failing > 0 ? 1 : 0;
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
/** One plain path segment — the containment guarantee for `skill run`. */
|
|
73
|
+
function isPlainSegment(part: string): boolean {
|
|
74
|
+
return (
|
|
75
|
+
part.length > 0 && part !== "." && part !== ".." && !new RegExp(/[/\\\0]/).test(part)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Playwright tools ship compiled as .mjs and need Node; everything else is Bun. */
|
|
80
|
+
function runtimeFor(file: string): string {
|
|
81
|
+
return file.endsWith(".mjs") ? "node" : "bun";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toolFileName(tool: string): string {
|
|
85
|
+
return new RegExp(/\.[a-z]+$/).test(tool) ? tool : `${tool}.ts`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runSkillTool(skill: string, tool: string, toolArgs: string[]): number {
|
|
89
|
+
if (!isPlainSegment(skill) || !isPlainSegment(tool)) {
|
|
90
|
+
log.error("Skill and tool must be plain names — no path separators, no '..'");
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
const file = toolFileName(tool);
|
|
94
|
+
const path = resolve(palHome(), "skills", skill, "tools", file);
|
|
95
|
+
if (!existsSync(path)) {
|
|
96
|
+
log.error(`No tool '${file}' in skill '${skill}' — looked in ${path}`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
const { status } = spawnSync(runtimeFor(file), [path, ...toolArgs], {
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
});
|
|
102
|
+
return status ?? 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
62
105
|
export async function runSkill(args: string[]): Promise<number> {
|
|
63
106
|
const [sub, name] = args;
|
|
64
107
|
|
|
108
|
+
if (sub === "run") {
|
|
109
|
+
const [, skill, tool, ...rest] = args;
|
|
110
|
+
if (!skill || !tool) {
|
|
111
|
+
log.error("Usage: pal cli skill run <skill> <tool> [-- args]");
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
return runSkillTool(skill, tool, rest[0] === "--" ? rest.slice(1) : rest);
|
|
115
|
+
}
|
|
116
|
+
|
|
65
117
|
if (sub === "author-model") {
|
|
66
118
|
const model = flagshipAuthorModel(getActiveAgent());
|
|
67
119
|
if (model) console.log(model);
|
|
@@ -71,10 +123,10 @@ export async function runSkill(args: string[]): Promise<number> {
|
|
|
71
123
|
if (sub === "doctor") {
|
|
72
124
|
if (name === "--all") return doctorAll();
|
|
73
125
|
if (!name) {
|
|
74
|
-
log.error("Usage: pal cli skill doctor <name|--all>");
|
|
126
|
+
log.error("Usage: pal cli skill doctor <skill-dir-or-name|--all>");
|
|
75
127
|
return 1;
|
|
76
128
|
}
|
|
77
|
-
const report = lintSkill(
|
|
129
|
+
const report = lintSkill(resolveSkillDir(name));
|
|
78
130
|
console.log(formatReport(report));
|
|
79
131
|
return report.errors > 0 ? 1 : 0;
|
|
80
132
|
}
|
|
@@ -104,6 +156,6 @@ export async function runSkill(args: string[]): Promise<number> {
|
|
|
104
156
|
}
|
|
105
157
|
}
|
|
106
158
|
|
|
107
|
-
log.error("Usage: pal cli skill <link|doctor|author-model> [name|--all]");
|
|
159
|
+
log.error("Usage: pal cli skill <run|link|doctor|author-model> [name|--all]");
|
|
108
160
|
return 1;
|
|
109
161
|
}
|
package/src/cli/subagent.ts
CHANGED
|
@@ -40,7 +40,7 @@ export async function runSubagent(args: string[]): Promise<number> {
|
|
|
40
40
|
|
|
41
41
|
if (sub === "doctor") {
|
|
42
42
|
if (!name) {
|
|
43
|
-
log.error("Usage: pal cli subagent doctor <name>");
|
|
43
|
+
log.error("Usage: pal cli subagent doctor <file-or-name>");
|
|
44
44
|
return 1;
|
|
45
45
|
}
|
|
46
46
|
const report = lintSubagent(resolveSubagentFile(name));
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { resolve } from "node:path";
|
|
23
|
-
import { palHome } from "./paths";
|
|
23
|
+
import { palHome, toPath } from "./paths";
|
|
24
24
|
|
|
25
25
|
/** Project name → absolute path on this machine. */
|
|
26
26
|
export type Bindings = Record<string, string>;
|
|
@@ -105,7 +105,7 @@ export function writeBinding(
|
|
|
105
105
|
home: string = palHome()
|
|
106
106
|
): void {
|
|
107
107
|
const bindings = readBindings(home);
|
|
108
|
-
bindings[project] =
|
|
108
|
+
bindings[project] = toPath(path);
|
|
109
109
|
writeBindings(bindings, home);
|
|
110
110
|
}
|
|
111
111
|
|
package/src/hooks/lib/context.ts
CHANGED
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
7
|
import { resolve } from "node:path";
|
|
9
8
|
import { loadReflectNudge } from "../handlers/reflect-trigger";
|
|
10
9
|
import { loadAlgorithmReviewNudge } from "./algorithm-review";
|
|
11
10
|
import { loadAnalyzeNudge } from "./analyze-nudge";
|
|
12
11
|
import { readLearnings } from "./learning-store";
|
|
13
12
|
import { loadOpinionContext } from "./opinions";
|
|
14
|
-
import { paths } from "./paths";
|
|
13
|
+
import { paths, toPath } from "./paths";
|
|
15
14
|
import { loadActiveProjectsContext } from "./projects";
|
|
16
15
|
import { loadRecentNotes } from "./relationship";
|
|
17
16
|
import { loadFailurePatterns } from "./semi-static";
|
|
@@ -24,11 +23,10 @@ function loadStartupFiles(): string {
|
|
|
24
23
|
const files = settings.startupFiles();
|
|
25
24
|
if (files.length === 0) return "";
|
|
26
25
|
|
|
27
|
-
const home = homedir();
|
|
28
26
|
const sections: string[] = [];
|
|
29
27
|
|
|
30
28
|
for (const file of files) {
|
|
31
|
-
const resolved = file
|
|
29
|
+
const resolved = toPath(file);
|
|
32
30
|
if (!existsSync(resolved)) continue;
|
|
33
31
|
try {
|
|
34
32
|
const content = readFileSync(resolved, "utf-8").trim();
|
package/src/hooks/lib/paths.ts
CHANGED
|
@@ -2,13 +2,53 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Turn a path string that came from outside — argv, a flag, an env override, a
|
|
7
|
+
* persisted record — into a real absolute path.
|
|
8
|
+
*
|
|
9
|
+
* "~" is a shell feature, not a filesystem one: bash and zsh expand it before a
|
|
10
|
+
* process starts, cmd.exe has no such feature, and PowerShell hands it to native
|
|
11
|
+
* commands untouched. So node:path has nothing for it (`resolve("~/x")` yields
|
|
12
|
+
* `<cwd>/~/x`) and every caller that skips this function is a Windows bug.
|
|
13
|
+
*
|
|
14
|
+
* Only the leading segment counts. `~user` throws instead of resolving, because
|
|
15
|
+
* it needs a passwd lookup and quietly reading it as a relative directory is the
|
|
16
|
+
* exact failure this replaces.
|
|
17
|
+
*/
|
|
18
|
+
export function toPath(input: string, base: string = process.cwd()): string {
|
|
19
|
+
if (input === "~") return homedir();
|
|
20
|
+
if (input.startsWith("~/") || input.startsWith("~\\")) {
|
|
21
|
+
return resolve(homedir(), input.slice(2));
|
|
22
|
+
}
|
|
23
|
+
if (input.startsWith("~")) {
|
|
24
|
+
throw new Error(`Home-relative paths for another user are not supported: ${input}`);
|
|
25
|
+
}
|
|
26
|
+
return resolve(base, input);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether a string names a location rather than a bare identifier. A verb that
|
|
31
|
+
* accepts either `my-skill` or `~/skills/my-skill` has to tell them apart before
|
|
32
|
+
* it looks the argument up in a store — appending a tilde path to a store
|
|
33
|
+
* directory yields a path that can never exist.
|
|
34
|
+
*/
|
|
35
|
+
export function namesAPath(input: string): boolean {
|
|
36
|
+
return input.startsWith("~") || input.includes("/") || input.includes("\\");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** An env override names a path the same way a flag does, so it gets the same treatment. */
|
|
40
|
+
function envPath(name: string, fallback: string): string {
|
|
41
|
+
const override = process.env[name];
|
|
42
|
+
return override ? toPath(override) : fallback;
|
|
43
|
+
}
|
|
44
|
+
|
|
5
45
|
/**
|
|
6
46
|
* Root of the PAL package (engine code + shipped assets).
|
|
7
47
|
* In repo mode: the repo root.
|
|
8
48
|
* In package mode: the global node_modules package directory.
|
|
9
49
|
*/
|
|
10
50
|
export function palPkg(): string {
|
|
11
|
-
return
|
|
51
|
+
return envPath("PAL_PKG", resolve(import.meta.dir, "..", "..", ".."));
|
|
12
52
|
}
|
|
13
53
|
|
|
14
54
|
/**
|
|
@@ -17,7 +57,7 @@ export function palPkg(): string {
|
|
|
17
57
|
* Power users who want memory/telos versioned in a repo can override via PAL_HOME.
|
|
18
58
|
*/
|
|
19
59
|
export function palHome(): string {
|
|
20
|
-
return
|
|
60
|
+
return envPath("PAL_HOME", resolve(homedir(), ".pal"));
|
|
21
61
|
}
|
|
22
62
|
|
|
23
63
|
/** Ensure a directory exists, creating it recursively if needed */
|
|
@@ -68,12 +108,12 @@ export const paths = {
|
|
|
68
108
|
// Platform directories (env override or cross-platform defaults)
|
|
69
109
|
const h = homedir();
|
|
70
110
|
export const platform = {
|
|
71
|
-
claudeDir: () =>
|
|
72
|
-
opencodeDir: () =>
|
|
73
|
-
cursorDir: () =>
|
|
74
|
-
copilotDir: () =>
|
|
75
|
-
codexDir: () =>
|
|
76
|
-
agentsDir: () =>
|
|
111
|
+
claudeDir: () => envPath("PAL_CLAUDE_DIR", resolve(h, ".claude")),
|
|
112
|
+
opencodeDir: () => envPath("PAL_OPENCODE_DIR", resolve(h, ".config", "opencode")),
|
|
113
|
+
cursorDir: () => envPath("PAL_CURSOR_DIR", resolve(h, ".cursor")),
|
|
114
|
+
copilotDir: () => envPath("PAL_COPILOT_DIR", resolve(h, ".copilot")),
|
|
115
|
+
codexDir: () => envPath("PAL_CODEX_DIR", resolve(h, ".codex")),
|
|
116
|
+
agentsDir: () => envPath("PAL_AGENTS_DIR", resolve(h, ".agents")),
|
|
77
117
|
} as const;
|
|
78
118
|
|
|
79
119
|
// Engine/asset paths (in PAL_PKG / repo root)
|
|
@@ -459,7 +459,7 @@ export function loadActiveProjectsContext(cwd: string = process.cwd()): string {
|
|
|
459
459
|
if (showHint) {
|
|
460
460
|
if (visible.length > 0) lines.push("");
|
|
461
461
|
lines.push(
|
|
462
|
-
`💡 \`${projectRoot}\` looks like a project but isn't registered. If substantive work starts here, suggest registering it via \`
|
|
462
|
+
`💡 \`${projectRoot}\` looks like a project but isn't registered. If substantive work starts here, suggest registering it via \`pal cli project create\`.`
|
|
463
463
|
);
|
|
464
464
|
}
|
|
465
465
|
|
|
@@ -18,6 +18,7 @@ import { parseArgs } from "node:util";
|
|
|
18
18
|
import { paths } from "../../hooks/lib/paths";
|
|
19
19
|
import { buildReflection, intOr, reflectionLine } from "../lib/algorithm-reflect";
|
|
20
20
|
import { emit } from "../lib/emit";
|
|
21
|
+
import { scriptArgs } from "../lib/script-args";
|
|
21
22
|
|
|
22
23
|
const HELP = `
|
|
23
24
|
AlgorithmReflect — Log algorithm performance after LEARN phase
|
|
@@ -45,9 +46,9 @@ function reflectionsPath(): string {
|
|
|
45
46
|
return paths.reflectionsFile();
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function run() {
|
|
49
|
+
export function run(argv: string[] = scriptArgs()) {
|
|
49
50
|
const { values } = parseArgs({
|
|
50
|
-
args:
|
|
51
|
+
args: argv,
|
|
51
52
|
options: {
|
|
52
53
|
task: { type: "string" },
|
|
53
54
|
criteria: { type: "string" },
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { parseArgs } from "node:util";
|
|
21
21
|
import { readReflections } from "../../hooks/lib/learning-store";
|
|
22
22
|
import { paths } from "../../hooks/lib/paths";
|
|
23
|
+
import { scriptArgs } from "../lib/script-args";
|
|
23
24
|
|
|
24
25
|
/** Algorithm areas a Q2 idea can target, mapped to ALGORITHM.md structure. */
|
|
25
26
|
const BUCKETS: { key: string; label: string; patterns: RegExp }[] = [
|
|
@@ -202,9 +203,9 @@ export function formatAlgorithmReport(s: AlgorithmSynthesis): string {
|
|
|
202
203
|
return lines.join("\n");
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
export function run(argv: string[] = scriptArgs()) {
|
|
206
207
|
const { values } = parseArgs({
|
|
207
|
-
args:
|
|
208
|
+
args: argv,
|
|
208
209
|
options: {
|
|
209
210
|
since: { type: "string" },
|
|
210
211
|
json: { type: "boolean", default: false },
|
|
@@ -216,3 +217,5 @@ if (import.meta.main) {
|
|
|
216
217
|
values.json ? JSON.stringify(result, null, 2) : formatAlgorithmReport(result)
|
|
217
218
|
);
|
|
218
219
|
}
|
|
220
|
+
|
|
221
|
+
if (import.meta.main) run();
|
|
@@ -14,6 +14,7 @@ import { parseArgs } from "node:util";
|
|
|
14
14
|
import { writeLastAnalyzeDate } from "../../hooks/lib/analyze-nudge";
|
|
15
15
|
import { analyze } from "../../hooks/lib/graduation";
|
|
16
16
|
import { reportLines } from "../lib/analyze-report";
|
|
17
|
+
import { scriptArgs } from "../lib/script-args";
|
|
17
18
|
|
|
18
19
|
const HELP = `
|
|
19
20
|
PAL Learning Analysis — unified graduation + ratings report
|
|
@@ -36,7 +37,7 @@ const HELP = `
|
|
|
36
37
|
Usage: pal cli analyze [--actionable]
|
|
37
38
|
`;
|
|
38
39
|
|
|
39
|
-
export async function run(argv: string[] =
|
|
40
|
+
export async function run(argv: string[] = scriptArgs()) {
|
|
40
41
|
const { values } = parseArgs({
|
|
41
42
|
args: argv,
|
|
42
43
|
options: {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* Written by Claude in-session — no inference call needed.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* pal cli handoff-note --title "what we were doing" --text "what remains + next steps"
|
|
10
|
+
* pal cli handoff-note --done # mark completed, suppress next-session injection
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { writeFileSync } from "node:fs";
|
|
@@ -20,13 +20,14 @@ import {
|
|
|
20
20
|
recordNote,
|
|
21
21
|
statusOf,
|
|
22
22
|
} from "../lib/handoff-note";
|
|
23
|
+
import { scriptArgs } from "../lib/script-args";
|
|
23
24
|
|
|
24
25
|
const HELP = `
|
|
25
26
|
HandoffNote — Write a handoff note for the current project
|
|
26
27
|
|
|
27
28
|
Usage:
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
pal cli handoff-note --title "what we were doing" --text "what remains"
|
|
30
|
+
pal cli handoff-note --done # mark session completed
|
|
30
31
|
|
|
31
32
|
Arguments:
|
|
32
33
|
--title Brief title of what was being worked on (5-10 words)
|
|
@@ -44,9 +45,9 @@ function saveNote(note: NoteInput): void {
|
|
|
44
45
|
emit.receipt(file, { status: statusOf(note), entries: Object.keys(store).length });
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function run() {
|
|
48
|
+
export function run(argv: string[] = scriptArgs()) {
|
|
48
49
|
const { values } = parseArgs({
|
|
49
|
-
args:
|
|
50
|
+
args: argv,
|
|
50
51
|
options: {
|
|
51
52
|
title: { type: "string" },
|
|
52
53
|
text: { type: "string" },
|