portable-agent-layer 0.63.0 → 0.63.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.
|
@@ -4,33 +4,39 @@
|
|
|
4
4
|
"sessionStart": [
|
|
5
5
|
{
|
|
6
6
|
"type": "command",
|
|
7
|
-
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts"
|
|
7
|
+
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts",
|
|
8
|
+
"powershell": "$env:PAL_AGENT='copilot'; bun run {{PKG_ROOT}}/src/hooks/LoadContext.ts"
|
|
8
9
|
}
|
|
9
10
|
],
|
|
10
11
|
"userPromptSubmitted": [
|
|
11
12
|
{
|
|
12
13
|
"type": "command",
|
|
13
|
-
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts"
|
|
14
|
+
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts",
|
|
15
|
+
"powershell": "$env:PAL_AGENT='copilot'; bun run {{PKG_ROOT}}/src/hooks/UserPromptOrchestrator.ts"
|
|
14
16
|
}
|
|
15
17
|
],
|
|
16
18
|
"preToolUse": [
|
|
17
19
|
{
|
|
18
20
|
"type": "command",
|
|
19
|
-
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
|
|
21
|
+
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts",
|
|
22
|
+
"powershell": "$env:PAL_AGENT='copilot'; bun run {{PKG_ROOT}}/src/hooks/SecurityValidator.ts"
|
|
20
23
|
},
|
|
21
24
|
{
|
|
22
25
|
"type": "command",
|
|
23
|
-
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/SkillGuard.ts"
|
|
26
|
+
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/SkillGuard.ts",
|
|
27
|
+
"powershell": "$env:PAL_AGENT='copilot'; bun run {{PKG_ROOT}}/src/hooks/SkillGuard.ts"
|
|
24
28
|
},
|
|
25
29
|
{
|
|
26
30
|
"type": "command",
|
|
27
|
-
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/RtkWrap.ts"
|
|
31
|
+
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/RtkWrap.ts",
|
|
32
|
+
"powershell": "$env:PAL_AGENT='copilot'; bun run {{PKG_ROOT}}/src/hooks/RtkWrap.ts"
|
|
28
33
|
}
|
|
29
34
|
],
|
|
30
35
|
"agentStop": [
|
|
31
36
|
{
|
|
32
37
|
"type": "command",
|
|
33
|
-
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts"
|
|
38
|
+
"bash": "PAL_AGENT=copilot bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts",
|
|
39
|
+
"powershell": "$env:PAL_AGENT='copilot'; bun run {{PKG_ROOT}}/src/hooks/StopOrchestrator.ts"
|
|
34
40
|
}
|
|
35
41
|
]
|
|
36
42
|
}
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -80,6 +80,20 @@ function checkTool(cmd: string, versionArgs: string[] = ["--version"]): ToolChec
|
|
|
80
80
|
return { name: cmd, available: false };
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Copilot ships as a VS Code extension as well as a CLI, and the extension puts
|
|
85
|
+
* no `copilot` binary on PATH — but both read hooks, skills and agents out of
|
|
86
|
+
* ~/.copilot. Fall back to that directory so PAL's copilot checks still run.
|
|
87
|
+
*/
|
|
88
|
+
function checkCopilot(): ToolCheck {
|
|
89
|
+
const cli = checkTool("copilot", ["version"]);
|
|
90
|
+
if (cli.available) return cli;
|
|
91
|
+
if (existsSync(platform.copilotDir())) {
|
|
92
|
+
return { name: "copilot", available: true, version: "~/.copilot (no CLI on PATH)" };
|
|
93
|
+
}
|
|
94
|
+
return cli;
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
function detectAgent(): string | null {
|
|
84
98
|
if (checkTool("claude").available) return "claude";
|
|
85
99
|
if (checkTool("opencode").available) return "opencode";
|
|
@@ -443,13 +457,18 @@ function checkCopilotInstructionsPresent(): boolean {
|
|
|
443
457
|
|
|
444
458
|
// ── Install integrity (Tier 2 doctor checks) ──
|
|
445
459
|
|
|
446
|
-
/**
|
|
460
|
+
/** Hook-config keys that carry a shell command: cross-platform and per-shell variants. */
|
|
461
|
+
function isHookCommandField(key: string): boolean {
|
|
462
|
+
return key === "command" || key === "bash" || key === "powershell";
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Recursively collect every command-carrying field value in a hook-config JSON. */
|
|
447
466
|
function extractAllHookCommands(obj: unknown, out: string[] = []): string[] {
|
|
448
467
|
if (Array.isArray(obj)) {
|
|
449
468
|
for (const item of obj) extractAllHookCommands(item, out);
|
|
450
469
|
} else if (obj && typeof obj === "object") {
|
|
451
470
|
for (const [k, v] of Object.entries(obj)) {
|
|
452
|
-
if ((k
|
|
471
|
+
if (isHookCommandField(k) && typeof v === "string") {
|
|
453
472
|
out.push(v);
|
|
454
473
|
} else {
|
|
455
474
|
extractAllHookCommands(v, out);
|
|
@@ -466,14 +485,21 @@ interface HookPrefixCheck {
|
|
|
466
485
|
firstMissing?: string;
|
|
467
486
|
}
|
|
468
487
|
|
|
469
|
-
/**
|
|
488
|
+
/** True when a hook command sets PAL_AGENT up front, in POSIX or PowerShell syntax. */
|
|
489
|
+
function setsAgentEnvPrefix(cmd: string, agentName: string): boolean {
|
|
490
|
+
return (
|
|
491
|
+
cmd.startsWith(`PAL_AGENT=${agentName} `) ||
|
|
492
|
+
cmd.startsWith(`$env:PAL_AGENT='${agentName}'; `)
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Verify every command in an installed hook file sets `PAL_AGENT=<agent>` first. */
|
|
470
497
|
function checkAgentHookPrefix(filePath: string, agentName: string): HookPrefixCheck {
|
|
471
498
|
if (!existsSync(filePath)) return { ok: false, total: 0, missing: 0 };
|
|
472
499
|
try {
|
|
473
500
|
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
474
501
|
const commands = extractAllHookCommands(data.hooks ?? data);
|
|
475
|
-
const
|
|
476
|
-
const missing = commands.filter((c) => !c.startsWith(prefix));
|
|
502
|
+
const missing = commands.filter((c) => !setsAgentEnvPrefix(c, agentName));
|
|
477
503
|
return {
|
|
478
504
|
ok: commands.length > 0 && missing.length === 0,
|
|
479
505
|
total: commands.length,
|
|
@@ -701,7 +727,7 @@ function doctor(silent = false): DoctorResult {
|
|
|
701
727
|
const claude = checkTool("claude");
|
|
702
728
|
const opencode = checkTool("opencode");
|
|
703
729
|
const cursor = checkTool("cursor");
|
|
704
|
-
const copilot =
|
|
730
|
+
const copilot = checkCopilot();
|
|
705
731
|
const codex = checkTool("codex");
|
|
706
732
|
const rtk = checkTool("rtk");
|
|
707
733
|
const hasAgent =
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
import { copyFileSync, existsSync, lstatSync, readlinkSync, unlinkSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
9
|
import { platform } from "../../hooks/lib/paths";
|
|
10
|
-
import { copilotFilename, getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
11
10
|
import {
|
|
12
11
|
log,
|
|
13
12
|
readJson,
|
|
14
13
|
removeAgentsFromCopilot,
|
|
14
|
+
removePalContextFiles,
|
|
15
15
|
removePalDocs,
|
|
16
16
|
removeSkills,
|
|
17
17
|
vscodeSettingsFile,
|
|
@@ -49,20 +49,13 @@ if (removedAgents.length > 0) {
|
|
|
49
49
|
removePalDocs();
|
|
50
50
|
|
|
51
51
|
// --- Remove ~/.copilot/instructions/pal-*.instructions.md ---
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// pal-session.instructions.md is written by LoadContext (not a semi-static source)
|
|
60
|
-
try {
|
|
61
|
-
unlinkSync(resolve(COPILOT_DIR, "instructions", "pal-session.instructions.md"));
|
|
62
|
-
} catch {
|
|
63
|
-
/* gone */
|
|
64
|
-
}
|
|
65
|
-
log.success("Removed ~/.copilot/instructions/pal-*.instructions.md");
|
|
52
|
+
const removedInstructions = removePalContextFiles(
|
|
53
|
+
resolve(COPILOT_DIR, "instructions"),
|
|
54
|
+
".instructions.md"
|
|
55
|
+
);
|
|
56
|
+
log.success(
|
|
57
|
+
`Removed ${removedInstructions.length} ~/.copilot/instructions/pal-*.instructions.md`
|
|
58
|
+
);
|
|
66
59
|
|
|
67
60
|
// --- Backward compat: remove old copilot-instructions.md symlink if present ---
|
|
68
61
|
const legacyPath = resolve(COPILOT_DIR, "copilot-instructions.md");
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
* Removes PAL skill symlinks.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { copyFileSync, existsSync
|
|
7
|
+
import { copyFileSync, existsSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
9
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
10
|
-
import { cursorFilename, getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
11
10
|
import {
|
|
12
11
|
loadCursorHooksTemplate,
|
|
13
12
|
log,
|
|
14
13
|
readJson,
|
|
15
14
|
removeAgentsFromCursor,
|
|
15
|
+
removePalContextFiles,
|
|
16
16
|
removePalDocs,
|
|
17
17
|
removeSkills,
|
|
18
18
|
removeStatusline,
|
|
@@ -73,19 +73,7 @@ if (existsSync(CLI_CONFIG)) {
|
|
|
73
73
|
removeStatusline("cursor");
|
|
74
74
|
|
|
75
75
|
// --- Remove ~/.cursor/rules/pal-*.mdc ---
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
unlinkSync(resolve(CURSOR_DIR, "rules", cursorFilename(src)));
|
|
79
|
-
} catch {
|
|
80
|
-
/* gone */
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Backward compat: remove legacy merged file if present
|
|
84
|
-
try {
|
|
85
|
-
unlinkSync(resolve(CURSOR_DIR, "rules", "pal-context.mdc"));
|
|
86
|
-
} catch {
|
|
87
|
-
/* gone */
|
|
88
|
-
}
|
|
89
|
-
log.success("Removed ~/.cursor/rules/pal-*.mdc");
|
|
76
|
+
const removedRules = removePalContextFiles(resolve(CURSOR_DIR, "rules"), ".mdc");
|
|
77
|
+
log.success(`Removed ${removedRules.length} ~/.cursor/rules/pal-*.mdc`);
|
|
90
78
|
|
|
91
79
|
log.success("Cursor uninstall complete");
|
package/src/targets/lib.ts
CHANGED
|
@@ -838,6 +838,29 @@ function ensureSymlink(link: string, target: string, type: "dir" | "junction"):
|
|
|
838
838
|
symlinkSync(target, link, type);
|
|
839
839
|
}
|
|
840
840
|
|
|
841
|
+
/**
|
|
842
|
+
* Remove every `pal-*` context file with the given suffix from a directory.
|
|
843
|
+
*
|
|
844
|
+
* Globs rather than iterating getSemiStaticSources(): a slug retired from that
|
|
845
|
+
* registry keeps its already-written file on disk, and a registry-driven delete
|
|
846
|
+
* can no longer name it — agents then keep loading retired context forever.
|
|
847
|
+
* Also catches the legacy pre-split filenames without needing a special case.
|
|
848
|
+
*/
|
|
849
|
+
export function removePalContextFiles(dir: string, suffix: string): string[] {
|
|
850
|
+
if (!existsSync(dir)) return [];
|
|
851
|
+
const removed: string[] = [];
|
|
852
|
+
for (const file of readdirSync(dir)) {
|
|
853
|
+
if (!file.startsWith("pal-") || !file.endsWith(suffix)) continue;
|
|
854
|
+
try {
|
|
855
|
+
unlinkSync(resolve(dir, file));
|
|
856
|
+
removed.push(file);
|
|
857
|
+
} catch {
|
|
858
|
+
/* gone or not ours to remove */
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return removed;
|
|
862
|
+
}
|
|
863
|
+
|
|
841
864
|
/** Remove PAL skill symlinks from ~/.pal/skills/ and ~/.claude/skills/ */
|
|
842
865
|
export function removeSkills(claudeSkillsDir: string): string[] {
|
|
843
866
|
const skillsDir = assets.skills();
|