portable-agent-layer 0.63.0 → 0.63.2
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/templates/hooks.copilot.json +12 -6
- package/assets/templates/hooks.vscode.json +37 -0
- package/package.json +1 -1
- package/src/cli/index.ts +84 -26
- package/src/hooks/SecurityValidator.ts +83 -32
- package/src/hooks/SkillGuard.ts +8 -12
- package/src/hooks/StopOrchestrator.ts +7 -4
- package/src/hooks/lib/agent.ts +112 -8
- package/src/hooks/lib/security.ts +90 -0
- package/src/hooks/lib/transcript.ts +37 -18
- package/src/targets/claude/install.ts +3 -21
- package/src/targets/codex/install.ts +2 -17
- package/src/targets/copilot/install.ts +13 -32
- package/src/targets/copilot/uninstall.ts +21 -22
- package/src/targets/cursor/install.ts +2 -30
- package/src/targets/cursor/uninstall.ts +4 -16
- package/src/targets/lib.ts +57 -17
- package/src/targets/opencode/install.ts +4 -31
|
@@ -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
|
@@ -741,6 +741,23 @@ export function removePalDocs(): void {
|
|
|
741
741
|
|
|
742
742
|
const PAL_SKILLS_DIR = resolve(palHome(), "skills");
|
|
743
743
|
|
|
744
|
+
/**
|
|
745
|
+
* Run one step of a bulk install, naming it only when it fails.
|
|
746
|
+
*
|
|
747
|
+
* Installs handle dozens of skills and agents; a line each buries the paths,
|
|
748
|
+
* backups and warnings that a reader actually has to act on. Returning false
|
|
749
|
+
* instead of throwing also keeps one unlinkable skill from aborting the rest.
|
|
750
|
+
*/
|
|
751
|
+
function reportOnlyOnFailure(label: string, install: () => void): boolean {
|
|
752
|
+
try {
|
|
753
|
+
install();
|
|
754
|
+
return true;
|
|
755
|
+
} catch (e) {
|
|
756
|
+
log.warn(`Could not install ${label} — ${(e as Error).message}`);
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
744
761
|
/**
|
|
745
762
|
* Install PAL skills by symlinking:
|
|
746
763
|
* ~/.pal/skills/<name> → <repo>/assets/skills/<name> (source of truth)
|
|
@@ -762,16 +779,15 @@ export function copySkills(claudeSkillsDir: string): number {
|
|
|
762
779
|
const srcDir = resolve(skillsDir, name);
|
|
763
780
|
if (!existsSync(resolve(srcDir, "SKILL.md"))) continue;
|
|
764
781
|
|
|
765
|
-
// ~/.pal/skills/<name> → <repo>/assets/skills/<name>
|
|
766
782
|
const palLink = resolve(PAL_SKILLS_DIR, name);
|
|
767
|
-
ensureSymlink(palLink, srcDir, linkType);
|
|
768
|
-
|
|
769
|
-
// ~/.claude/skills/<name> → ~/.pal/skills/<name>
|
|
770
783
|
const claudeLink = resolve(claudeSkillsDir, name);
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
784
|
+
const linked = reportOnlyOnFailure(`skill ${name}`, () => {
|
|
785
|
+
// ~/.pal/skills/<name> → <repo>/assets/skills/<name>
|
|
786
|
+
ensureSymlink(palLink, srcDir, linkType);
|
|
787
|
+
// ~/.claude/skills/<name> → ~/.pal/skills/<name>
|
|
788
|
+
ensureSymlink(claudeLink, palLink, linkType);
|
|
789
|
+
});
|
|
790
|
+
if (linked) count++;
|
|
775
791
|
}
|
|
776
792
|
|
|
777
793
|
// ~/.agents/skills/ → ~/.pal/skills/
|
|
@@ -838,6 +854,29 @@ function ensureSymlink(link: string, target: string, type: "dir" | "junction"):
|
|
|
838
854
|
symlinkSync(target, link, type);
|
|
839
855
|
}
|
|
840
856
|
|
|
857
|
+
/**
|
|
858
|
+
* Remove every `pal-*` context file with the given suffix from a directory.
|
|
859
|
+
*
|
|
860
|
+
* Globs rather than iterating getSemiStaticSources(): a slug retired from that
|
|
861
|
+
* registry keeps its already-written file on disk, and a registry-driven delete
|
|
862
|
+
* can no longer name it — agents then keep loading retired context forever.
|
|
863
|
+
* Also catches the legacy pre-split filenames without needing a special case.
|
|
864
|
+
*/
|
|
865
|
+
export function removePalContextFiles(dir: string, suffix: string): string[] {
|
|
866
|
+
if (!existsSync(dir)) return [];
|
|
867
|
+
const removed: string[] = [];
|
|
868
|
+
for (const file of readdirSync(dir)) {
|
|
869
|
+
if (!file.startsWith("pal-") || !file.endsWith(suffix)) continue;
|
|
870
|
+
try {
|
|
871
|
+
unlinkSync(resolve(dir, file));
|
|
872
|
+
removed.push(file);
|
|
873
|
+
} catch {
|
|
874
|
+
/* gone or not ours to remove */
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return removed;
|
|
878
|
+
}
|
|
879
|
+
|
|
841
880
|
/** Remove PAL skill symlinks from ~/.pal/skills/ and ~/.claude/skills/ */
|
|
842
881
|
export function removeSkills(claudeSkillsDir: string): string[] {
|
|
843
882
|
const skillsDir = assets.skills();
|
|
@@ -977,14 +1016,16 @@ function installAgents(targetDir: string, platform: AgentPlatform): number {
|
|
|
977
1016
|
let count = 0;
|
|
978
1017
|
|
|
979
1018
|
for (const file of readdirSync(agentsDir).filter((f) => f.endsWith(".md"))) {
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
resolve(
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1019
|
+
const name = file.replace(/\.md$/, "");
|
|
1020
|
+
const installed = reportOnlyOnFailure(`${platform} agent ${name}`, () => {
|
|
1021
|
+
const content = readFileSync(resolve(agentsDir, file), "utf-8");
|
|
1022
|
+
writeFileSync(
|
|
1023
|
+
resolve(targetDir, file),
|
|
1024
|
+
extractAgentForPlatform(content, platform),
|
|
1025
|
+
"utf-8"
|
|
1026
|
+
);
|
|
1027
|
+
});
|
|
1028
|
+
if (installed) count++;
|
|
988
1029
|
}
|
|
989
1030
|
return count;
|
|
990
1031
|
}
|
|
@@ -1352,7 +1393,6 @@ export function generateSkillIndex(): number {
|
|
|
1352
1393
|
const stateDir = resolve(palHome(), "memory", "state");
|
|
1353
1394
|
mkdirSync(stateDir, { recursive: true });
|
|
1354
1395
|
writeJson(resolve(stateDir, "skill-index.json"), index);
|
|
1355
|
-
log.info(`Skill index: ${index.totalSkills} skills indexed`);
|
|
1356
1396
|
|
|
1357
1397
|
return index.totalSkills;
|
|
1358
1398
|
}
|
|
@@ -12,18 +12,9 @@ import {
|
|
|
12
12
|
writeFileSync,
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { resolve } from "node:path";
|
|
15
|
-
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
16
15
|
import { palPkg, platform } from "../../hooks/lib/paths";
|
|
17
16
|
import { getSemiStaticSources } from "../../hooks/lib/semi-static";
|
|
18
|
-
import {
|
|
19
|
-
copyAgentsForOpencode,
|
|
20
|
-
copyPalDocs,
|
|
21
|
-
copySkills,
|
|
22
|
-
countSkills,
|
|
23
|
-
generateSkillIndex,
|
|
24
|
-
log,
|
|
25
|
-
writeJson,
|
|
26
|
-
} from "../lib";
|
|
17
|
+
import { copyAgentsForOpencode, copySkills, countSkills, log, writeJson } from "../lib";
|
|
27
18
|
|
|
28
19
|
const PKG_ROOT = palPkg();
|
|
29
20
|
const OC_GLOBAL_DIR = platform.opencodeDir();
|
|
@@ -55,7 +46,6 @@ if (!existsSync(pkgPath)) {
|
|
|
55
46
|
|
|
56
47
|
try {
|
|
57
48
|
Bun.spawnSync(["bun", "install", "--silent"], { cwd: OC_PLUGINS_DIR });
|
|
58
|
-
log.success("Installed plugin dependencies");
|
|
59
49
|
} catch {
|
|
60
50
|
log.warn(`Could not install plugin deps — run 'bun install' in ${OC_PLUGINS_DIR}`);
|
|
61
51
|
}
|
|
@@ -63,22 +53,13 @@ try {
|
|
|
63
53
|
// --- 3. Install skills into ~/.pal/skills/ ---
|
|
64
54
|
const claudeSkillsDir = resolve(platform.claudeDir(), "skills");
|
|
65
55
|
copySkills(claudeSkillsDir);
|
|
66
|
-
generateSkillIndex();
|
|
67
|
-
log.success("Installed skills to ~/.pal/skills/");
|
|
68
56
|
|
|
69
57
|
// --- 4. Install agents into ~/.config/opencode/agents/ ---
|
|
70
58
|
const ocAgentsDir = resolve(OC_GLOBAL_DIR, "agents");
|
|
71
|
-
copyAgentsForOpencode(ocAgentsDir);
|
|
72
|
-
|
|
73
|
-
// --- 5. Copy PAL system docs ---
|
|
74
|
-
const palDocsCount = copyPalDocs();
|
|
75
|
-
log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
|
|
76
|
-
|
|
77
|
-
// --- 6. Generate ~/.config/opencode/AGENTS.md ---
|
|
78
|
-
regenerateIfNeeded();
|
|
79
|
-
log.success("Generated ~/.config/opencode/AGENTS.md");
|
|
59
|
+
const agentCount = copyAgentsForOpencode(ocAgentsDir);
|
|
60
|
+
log.success(`${countSkills()} skills · ${agentCount} agents → ~/.config/opencode/`);
|
|
80
61
|
|
|
81
|
-
// ---
|
|
62
|
+
// --- 5. Add semi-static digest files to instructions[] in config.json ---
|
|
82
63
|
const configPath = resolve(OC_GLOBAL_DIR, "config.json");
|
|
83
64
|
const staticFiles = getSemiStaticSources().map((s) => s.path);
|
|
84
65
|
let ocConfig: Record<string, unknown> = {};
|
|
@@ -94,11 +75,3 @@ const existingInstructions = Array.isArray(ocConfig.instructions)
|
|
|
94
75
|
: [];
|
|
95
76
|
ocConfig.instructions = [...new Set([...existingInstructions, ...staticFiles])];
|
|
96
77
|
writeFileSync(configPath, `${JSON.stringify(ocConfig, null, 2)}\n`, "utf-8");
|
|
97
|
-
log.success(
|
|
98
|
-
`Updated config.json: ${(ocConfig.instructions as string[]).length} instructions`
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
log.success("opencode installation complete");
|
|
102
|
-
console.log("");
|
|
103
|
-
log.info(`Plugin: ${pluginDst}`);
|
|
104
|
-
log.info(`Skills: ${countSkills()} (native via ~/.pal/skills/)`);
|