portable-agent-layer 0.10.0 → 0.12.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/skills/{analyze-pdf.md → analyze-pdf/SKILL.md} +4 -4
- package/{src → assets/skills/analyze-pdf}/tools/pdf-download.ts +3 -3
- package/assets/skills/{analyze-youtube.md → analyze-youtube/SKILL.md} +4 -4
- package/{src → assets/skills/analyze-youtube}/tools/youtube-analyze.ts +2 -2
- package/assets/skills/{council.md → council/SKILL.md} +3 -2
- package/assets/skills/{create-skill.md → create-skill/SKILL.md} +2 -1
- package/assets/skills/{extract-entities.md → extract-entities/SKILL.md} +4 -5
- package/{src → assets/skills/extract-entities}/tools/entity-save.ts +3 -3
- package/assets/skills/{extract-wisdom.md → extract-wisdom/SKILL.md} +3 -2
- package/assets/skills/{first-principles.md → first-principles/SKILL.md} +3 -2
- package/assets/skills/{fyzz-chat-api.md → fyzz-chat-api/SKILL.md} +6 -6
- package/{src → assets/skills/fyzz-chat-api}/tools/fyzz-api.ts +6 -6
- package/assets/skills/{reflect.md → reflect/SKILL.md} +2 -1
- package/assets/skills/{research.md → research/SKILL.md} +2 -1
- package/assets/skills/{review.md → review/SKILL.md} +2 -1
- package/assets/skills/{summarize.md → summarize/SKILL.md} +3 -2
- package/assets/skills/telos/SKILL.md +94 -0
- package/assets/skills/telos/tools/update-telos.ts +100 -0
- package/assets/skills/think/SKILL.md +47 -0
- package/assets/templates/AGENTS.md.template +51 -32
- package/assets/templates/PAL/ALGORITHM.md +120 -0
- package/assets/templates/PAL/CONTEXT_ROUTING.md +28 -0
- package/assets/templates/PAL/MEMORY_SYSTEM.md +26 -0
- package/assets/templates/PAL/OPINION_TRACKING.md +3 -0
- package/assets/templates/PAL/STEERING_RULES.md +43 -0
- package/assets/templates/PAL/WORK_TRACKING.md +14 -0
- package/assets/templates/pal-settings.json +32 -0
- package/assets/templates/settings.claude.json +80 -0
- package/package.json +4 -7
- package/src/cli/index.ts +7 -0
- package/src/cli/setup-identity.ts +119 -0
- package/src/hooks/lib/claude-md.ts +52 -26
- package/src/hooks/lib/context.ts +49 -25
- package/src/hooks/lib/paths.ts +2 -0
- package/src/hooks/lib/security.ts +2 -0
- package/src/hooks/lib/setup.ts +4 -16
- package/src/targets/claude/install.ts +20 -93
- package/src/targets/claude/uninstall.ts +22 -47
- package/src/targets/lib.ts +207 -48
- package/src/targets/opencode/install.ts +13 -2
- package/src/targets/opencode/uninstall.ts +4 -1
- package/assets/templates/STEERING-RULES.md +0 -23
- package/assets/templates/telos/IDENTITY.md +0 -4
- package/src/cli/install.ts +0 -86
- package/src/cli/uninstall.ts +0 -45
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
writeFileSync,
|
|
18
18
|
} from "node:fs";
|
|
19
19
|
import { dirname, relative, resolve } from "node:path";
|
|
20
|
-
import { loadTelos } from "./context";
|
|
21
20
|
import { assets, ensureDir, palHome, paths, platform } from "./paths";
|
|
22
21
|
import { buildSetupPrompt, readSetupState } from "./setup";
|
|
23
22
|
|
|
@@ -71,54 +70,81 @@ export function needsRebuild(): boolean {
|
|
|
71
70
|
|
|
72
71
|
const outputMtime = statSync(outputPath).mtimeMs;
|
|
73
72
|
|
|
74
|
-
// Collect source files: template + setup.json +
|
|
73
|
+
// Collect source files: template + setup.json + identity + PAL docs
|
|
75
74
|
const sources: string[] = [
|
|
76
75
|
TEMPLATE_PATH,
|
|
77
|
-
resolve(dirname(TEMPLATE_PATH), "STEERING-RULES.md"),
|
|
78
76
|
resolve(paths.state(), "setup.json"),
|
|
77
|
+
palSettingsPath(),
|
|
79
78
|
];
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
// Track PAL doc sources for rebuild detection
|
|
81
|
+
const palDocsDir = assets.palDocs();
|
|
82
|
+
if (existsSync(palDocsDir)) {
|
|
83
|
+
for (const f of readdirSync(palDocsDir).filter((f) => f.endsWith(".md"))) {
|
|
84
|
+
sources.push(resolve(palDocsDir, f));
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
return latestMtime(...sources) > outputMtime;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
interface Identity {
|
|
92
|
+
ai: { name: string; displayName: string; catchphrase: string };
|
|
93
|
+
principal: { name: string };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const IDENTITY_DEFAULTS: Identity = {
|
|
97
|
+
ai: { name: "Assistant", displayName: "ASSISTANT", catchphrase: "" },
|
|
98
|
+
principal: { name: "" },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function palSettingsPath(): string {
|
|
102
|
+
return resolve(palHome(), "memory", "pal-settings.json");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Load identity from pal-settings.json */
|
|
106
|
+
export function loadIdentity(): Identity {
|
|
107
|
+
const p = palSettingsPath();
|
|
108
|
+
if (!existsSync(p)) return IDENTITY_DEFAULTS;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const data = JSON.parse(readFileSync(p, "utf-8"));
|
|
112
|
+
const ai = data.identity?.ai ?? {};
|
|
113
|
+
const principal = data.identity?.principal ?? {};
|
|
114
|
+
const name = ai.name || IDENTITY_DEFAULTS.ai.name;
|
|
115
|
+
const catchphrase = (ai.catchphrase || "").replace("{name}", name);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
ai: {
|
|
119
|
+
name,
|
|
120
|
+
displayName: ai.displayName || IDENTITY_DEFAULTS.ai.displayName,
|
|
121
|
+
catchphrase,
|
|
122
|
+
},
|
|
123
|
+
principal: {
|
|
124
|
+
name: principal.name || IDENTITY_DEFAULTS.principal.name,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
} catch {
|
|
128
|
+
return IDENTITY_DEFAULTS;
|
|
129
|
+
}
|
|
100
130
|
}
|
|
101
131
|
|
|
102
132
|
/** Render AGENTS.md from the template using current state */
|
|
103
133
|
export function buildClaudeMd(): string {
|
|
104
134
|
const template = existsSync(TEMPLATE_PATH)
|
|
105
135
|
? readFileSync(TEMPLATE_PATH, "utf-8")
|
|
106
|
-
: "# PAL Context\n\n{{SETUP_PROMPT}}\n
|
|
136
|
+
: "# PAL Context\n\n{{SETUP_PROMPT}}\n";
|
|
107
137
|
|
|
108
138
|
const state = readSetupState();
|
|
109
139
|
const setupPrompt = state ? buildSetupPrompt(state) : null;
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const steeringPath = resolve(dirname(TEMPLATE_PATH), "STEERING-RULES.md");
|
|
113
|
-
const steeringRules = existsSync(steeringPath)
|
|
114
|
-
? readFileSync(steeringPath, "utf-8").trim()
|
|
115
|
-
: "";
|
|
140
|
+
const identity = loadIdentity();
|
|
116
141
|
|
|
117
142
|
return template
|
|
118
143
|
.replace("{{SETUP_PROMPT}}", setupPrompt ? `${setupPrompt}\n` : "")
|
|
119
|
-
.
|
|
120
|
-
.
|
|
121
|
-
.
|
|
144
|
+
.replaceAll("{{IDENTITY_NAME}}", identity.ai.name)
|
|
145
|
+
.replaceAll("{{IDENTITY_DISPLAY}}", identity.ai.displayName)
|
|
146
|
+
.replaceAll("{{IDENTITY_CATCHPHRASE}}", identity.ai.catchphrase)
|
|
147
|
+
.replaceAll("{{PRINCIPAL_NAME}}", identity.principal.name);
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
/** Regenerate AGENTS.md if any source file is newer, and ensure CLAUDE.md symlink exists. Returns true if rebuilt. */
|
package/src/hooks/lib/context.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
7
8
|
import { resolve } from "node:path";
|
|
8
9
|
import { parse } from "./frontmatter";
|
|
9
10
|
import { readFailures, readLearnings } from "./learning-store";
|
|
@@ -21,28 +22,44 @@ import {
|
|
|
21
22
|
staleProjects,
|
|
22
23
|
} from "./work-tracking";
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
interface PalSettings {
|
|
26
|
+
loadAtStartup?: { files?: string[] };
|
|
27
|
+
dynamicContext?: Record<string, boolean>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Load pal-settings.json from memory/ */
|
|
31
|
+
function loadPalSettings(): PalSettings {
|
|
32
|
+
const p = resolve(paths.memory(), "pal-settings.json");
|
|
33
|
+
if (!existsSync(p)) return {};
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Check if a dynamic context section is enabled (defaults to true) */
|
|
42
|
+
function isEnabled(settings: PalSettings, key: string): boolean {
|
|
43
|
+
return settings.dynamicContext?.[key] !== false;
|
|
44
|
+
}
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
/** Load and concatenate loadAtStartup files */
|
|
47
|
+
function loadStartupFiles(settings: PalSettings): string {
|
|
48
|
+
const files = settings.loadAtStartup?.files;
|
|
49
|
+
if (!files || files.length === 0) return "";
|
|
32
50
|
|
|
51
|
+
const home = homedir();
|
|
33
52
|
const sections: string[] = [];
|
|
34
53
|
|
|
35
54
|
for (const file of files) {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (realLines.length === 0) continue;
|
|
45
|
-
sections.push(content);
|
|
55
|
+
const resolved = file.replace("~", home);
|
|
56
|
+
if (!existsSync(resolved)) continue;
|
|
57
|
+
try {
|
|
58
|
+
const content = readFileSync(resolved, "utf-8").trim();
|
|
59
|
+
if (content) sections.push(content);
|
|
60
|
+
} catch {
|
|
61
|
+
/* skip unreadable files */
|
|
62
|
+
}
|
|
46
63
|
}
|
|
47
64
|
|
|
48
65
|
return sections.join("\n\n---\n\n");
|
|
@@ -348,15 +365,22 @@ export function loadRelationshipContext(): string {
|
|
|
348
365
|
* things that change per-session and can't live in a static file.
|
|
349
366
|
*/
|
|
350
367
|
export function buildSystemReminder(): string {
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
368
|
+
const settings = loadPalSettings();
|
|
369
|
+
const startup = loadStartupFiles(settings);
|
|
370
|
+
const work = isEnabled(settings, "activeWork") ? loadActiveWork() : null;
|
|
371
|
+
const wisdom = isEnabled(settings, "wisdom") ? loadWisdomContext() : "";
|
|
372
|
+
const relationship = isEnabled(settings, "relationship")
|
|
373
|
+
? loadRelationshipContext()
|
|
374
|
+
: "";
|
|
375
|
+
const digest = isEnabled(settings, "learningDigest") ? loadLearningDigest() : "";
|
|
376
|
+
const trends = isEnabled(settings, "signalTrends") ? loadSignalTrends() : "";
|
|
377
|
+
const failures = isEnabled(settings, "failurePatterns") ? loadFailurePatterns() : "";
|
|
378
|
+
const synthesis = isEnabled(settings, "synthesis")
|
|
379
|
+
? loadSynthesisRecommendations()
|
|
380
|
+
: "";
|
|
381
|
+
const opinions = isEnabled(settings, "opinions") ? loadOpinionContext() : "";
|
|
359
382
|
const parts: string[] = [];
|
|
383
|
+
if (startup) parts.push(startup);
|
|
360
384
|
if (wisdom) parts.push(wisdom);
|
|
361
385
|
if (opinions) parts.push(opinions);
|
|
362
386
|
if (relationship) parts.push(relationship);
|
package/src/hooks/lib/paths.ts
CHANGED
|
@@ -77,4 +77,6 @@ export const assets = {
|
|
|
77
77
|
hooks: () => pkg("src", "hooks"),
|
|
78
78
|
telosTemplates: () => pkg("assets", "templates", "telos"),
|
|
79
79
|
agentsMdTemplate: () => pkg("assets", "templates", "AGENTS.md.template"),
|
|
80
|
+
claudeSettingsTemplate: () => pkg("assets", "templates", "settings.claude.json"),
|
|
81
|
+
palDocs: () => pkg("assets", "templates", "PAL"),
|
|
80
82
|
} as const;
|
|
@@ -33,6 +33,7 @@ export const HOOK_MANAGED_FILES = [
|
|
|
33
33
|
"update-available.json",
|
|
34
34
|
"debug.log.prev",
|
|
35
35
|
"opinions.json",
|
|
36
|
+
"pal-settings.json",
|
|
36
37
|
];
|
|
37
38
|
|
|
38
39
|
/** Hook-managed directories — AI must not write to or delete from these */
|
|
@@ -43,6 +44,7 @@ export const HOOK_MANAGED_DIRS = [
|
|
|
43
44
|
"memory/learning/synthesis",
|
|
44
45
|
"memory/relationship",
|
|
45
46
|
"memory/wisdom/state",
|
|
47
|
+
".agents/PAL",
|
|
46
48
|
];
|
|
47
49
|
|
|
48
50
|
/** Escape a string for use in a RegExp */
|
package/src/hooks/lib/setup.ts
CHANGED
|
@@ -26,20 +26,8 @@ export interface SetupState {
|
|
|
26
26
|
const SETUP_STEPS: Record<string, Omit<SetupStep, "done">> = {
|
|
27
27
|
mission: {
|
|
28
28
|
file: "telos/MISSION.md",
|
|
29
|
-
question: "What's your
|
|
30
|
-
hint: "Write their
|
|
31
|
-
},
|
|
32
|
-
ai_name: {
|
|
33
|
-
file: "telos/IDENTITY.md",
|
|
34
|
-
question:
|
|
35
|
-
"What would you like to call your AI? (Pick a name — this is how I'll identify myself.)",
|
|
36
|
-
hint: "Write the chosen AI name and identity to telos/IDENTITY.md with fields: name, fullName (name — Personal AI), displayName (UPPERCASED)",
|
|
37
|
-
},
|
|
38
|
-
catchphrase: {
|
|
39
|
-
file: "telos/IDENTITY.md",
|
|
40
|
-
question:
|
|
41
|
-
'What should your AI\'s startup catchphrase be? (e.g. "{name} here, ready to go" — {name} gets replaced with the AI name.)',
|
|
42
|
-
hint: "Append the catchphrase to telos/IDENTITY.md under a ## Catchphrase heading. Support {name} as a placeholder.",
|
|
29
|
+
question: "What do you do? What's your role and core purpose?",
|
|
30
|
+
hint: "Write their role and core purpose to telos/MISSION.md",
|
|
43
31
|
},
|
|
44
32
|
goals: {
|
|
45
33
|
file: "telos/GOALS.md",
|
|
@@ -152,8 +140,8 @@ export function buildSetupPrompt(state: SetupState): string | null {
|
|
|
152
140
|
const lines: string[] = [
|
|
153
141
|
"## IMPORTANT: PAL First-Run Setup Required",
|
|
154
142
|
"",
|
|
155
|
-
"TELOS files are empty —
|
|
156
|
-
"You MUST start the setup process immediately
|
|
143
|
+
"TELOS files are empty — the user's identity is already configured (via the installer),",
|
|
144
|
+
"but personal context is still needed. You MUST start the setup process immediately.",
|
|
157
145
|
"Greet them, explain that PAL needs to learn about them to personalize future sessions,",
|
|
158
146
|
"and ask the first remaining question below. Do NOT wait for the user to ask about setup.",
|
|
159
147
|
"",
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PAL — Claude Code target installer (TypeScript)
|
|
3
|
-
* Merges
|
|
4
|
-
* Copies skills
|
|
3
|
+
* Merges settings template into existing settings.json (never overwrites).
|
|
4
|
+
* Copies skills, agents, and PAL docs. Generates CLAUDE.md from TELOS.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
9
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
10
|
-
import { palHome, palPkg, platform } from "../../hooks/lib/paths";
|
|
10
|
+
import { assets, palHome, palPkg, platform } from "../../hooks/lib/paths";
|
|
11
11
|
import {
|
|
12
12
|
copyAgents,
|
|
13
|
+
copyPalDocs,
|
|
13
14
|
copySkills,
|
|
14
15
|
countAgents,
|
|
15
16
|
countMd,
|
|
16
17
|
countSkills,
|
|
18
|
+
loadSettingsTemplate,
|
|
17
19
|
log,
|
|
20
|
+
mergeSettings,
|
|
18
21
|
readJson,
|
|
22
|
+
scaffoldPalSettings,
|
|
19
23
|
writeJson,
|
|
20
24
|
} from "../lib";
|
|
21
25
|
|
|
@@ -35,96 +39,13 @@ const backup = `${SETTINGS}.bak.${Date.now()}`;
|
|
|
35
39
|
copyFileSync(SETTINGS, backup);
|
|
36
40
|
log.info("Backed up settings.json");
|
|
37
41
|
|
|
38
|
-
// ---
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
matcher: "",
|
|
44
|
-
hooks: [
|
|
45
|
-
{ type: "command", command: `bun run ${PKG_ROOT}/src/hooks/LoadContext.ts` },
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
UserPromptSubmit: [
|
|
50
|
-
{
|
|
51
|
-
matcher: "",
|
|
52
|
-
hooks: [
|
|
53
|
-
{
|
|
54
|
-
type: "command",
|
|
55
|
-
command: `bun run ${PKG_ROOT}/src/hooks/UserPromptOrchestrator.ts`,
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
PreToolUse: [
|
|
61
|
-
{
|
|
62
|
-
matcher: "Bash|Write|Edit",
|
|
63
|
-
hooks: [
|
|
64
|
-
{
|
|
65
|
-
type: "command",
|
|
66
|
-
command: `bun run ${PKG_ROOT}/src/hooks/SecurityValidator.ts`,
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
matcher: "Skill",
|
|
72
|
-
hooks: [
|
|
73
|
-
{ type: "command", command: `bun run ${PKG_ROOT}/src/hooks/SkillGuard.ts` },
|
|
74
|
-
],
|
|
75
|
-
},
|
|
76
|
-
],
|
|
77
|
-
Stop: [
|
|
78
|
-
{
|
|
79
|
-
matcher: "",
|
|
80
|
-
hooks: [
|
|
81
|
-
{
|
|
82
|
-
type: "command",
|
|
83
|
-
command: `bun run ${PKG_ROOT}/src/hooks/StopOrchestrator.ts`,
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
};
|
|
42
|
+
// --- Load template and merge into existing settings ---
|
|
43
|
+
const template = loadSettingsTemplate(assets.claudeSettingsTemplate(), PKG_ROOT);
|
|
44
|
+
const existing = readJson<Record<string, unknown>>(SETTINGS, {});
|
|
45
|
+
const merged = mergeSettings(existing, template);
|
|
90
46
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
type Settings = { hooks?: Record<string, HookEntry[]>; env?: Record<string, string> };
|
|
94
|
-
|
|
95
|
-
const settings = readJson<Settings>(SETTINGS, {});
|
|
96
|
-
if (!settings.hooks) settings.hooks = {};
|
|
97
|
-
|
|
98
|
-
for (const [event, entries] of Object.entries(hooksPayload.hooks)) {
|
|
99
|
-
const existing = settings.hooks[event] ?? [];
|
|
100
|
-
for (const entry of entries) {
|
|
101
|
-
const cmd = entry.hooks[0]?.command;
|
|
102
|
-
const alreadyPresent = existing.some((e) => e.hooks?.[0]?.command === cmd);
|
|
103
|
-
if (!alreadyPresent) existing.push(entry);
|
|
104
|
-
}
|
|
105
|
-
settings.hooks[event] = existing;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// --- Add PAL tool permissions (auto-allow ai: scripts) ---
|
|
109
|
-
type SettingsWithPermissions = Settings & { permissions?: { allow?: string[] } };
|
|
110
|
-
const s = settings as SettingsWithPermissions;
|
|
111
|
-
if (!s.permissions) s.permissions = {};
|
|
112
|
-
if (!s.permissions.allow) s.permissions.allow = [];
|
|
113
|
-
const aiTools = [
|
|
114
|
-
"ai:entity-save",
|
|
115
|
-
"ai:fyzz-api",
|
|
116
|
-
"ai:pdf-download",
|
|
117
|
-
"ai:youtube-analyze",
|
|
118
|
-
];
|
|
119
|
-
for (const tool of aiTools) {
|
|
120
|
-
const perm = `Bash(bun run ${tool} *)`;
|
|
121
|
-
if (!s.permissions.allow.includes(perm)) {
|
|
122
|
-
s.permissions.allow.push(perm);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
writeJson(SETTINGS, settings);
|
|
127
|
-
log.success("Merged hooks into settings.json");
|
|
47
|
+
writeJson(SETTINGS, merged);
|
|
48
|
+
log.success("Merged PAL settings into settings.json");
|
|
128
49
|
|
|
129
50
|
// --- Copy skills ---
|
|
130
51
|
const skillsDir = resolve(CLAUDE_DIR, "skills");
|
|
@@ -133,13 +54,19 @@ copySkills(skillsDir);
|
|
|
133
54
|
// --- Copy agents ---
|
|
134
55
|
copyAgents();
|
|
135
56
|
|
|
57
|
+
// --- Copy PAL system docs ---
|
|
58
|
+
const palDocsCount = copyPalDocs();
|
|
59
|
+
log.success(`Installed ${palDocsCount} PAL docs to ~/.agents/PAL/`);
|
|
60
|
+
|
|
61
|
+
// --- Scaffold PAL settings ---
|
|
62
|
+
scaffoldPalSettings();
|
|
63
|
+
|
|
136
64
|
// --- Generate ~/.claude/AGENTS.md and symlink ~/.claude/CLAUDE.md → AGENTS.md ---
|
|
137
65
|
regenerateIfNeeded();
|
|
138
66
|
log.success("Generated ~/.config/opencode/AGENTS.md (→ ~/.claude/CLAUDE.md symlink)");
|
|
139
67
|
|
|
140
68
|
log.success("Claude Code installation complete");
|
|
141
69
|
console.log("");
|
|
142
|
-
log.info(`Hooks: 5 (SessionStart, UserPromptSubmit, PreToolUse×2, Stop)`);
|
|
143
70
|
log.info(`Skills: ${countSkills()}`);
|
|
144
71
|
log.info(`Agents: ${countAgents()}`);
|
|
145
72
|
log.info(`TELOS: ${countMd(resolve(palHome(), "telos"))} files`);
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PAL — Claude Code uninstaller (TypeScript)
|
|
3
|
-
* Removes
|
|
3
|
+
* Removes exactly what the settings template added, plus skills, agents, and PAL docs.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { copyFileSync, existsSync, unlinkSync } from "node:fs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
|
-
import { palPkg, platform } from "../../hooks/lib/paths";
|
|
9
|
-
import {
|
|
8
|
+
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
9
|
+
import {
|
|
10
|
+
loadSettingsTemplate,
|
|
11
|
+
log,
|
|
12
|
+
readJson,
|
|
13
|
+
removeAgents,
|
|
14
|
+
removePalDocs,
|
|
15
|
+
removeSkills,
|
|
16
|
+
unmergeSettings,
|
|
17
|
+
writeJson,
|
|
18
|
+
} from "../lib";
|
|
10
19
|
|
|
11
|
-
const PKG_ROOT = palPkg();
|
|
20
|
+
const PKG_ROOT = palPkg().replaceAll("\\", "/");
|
|
12
21
|
const CLAUDE_DIR = platform.claudeDir();
|
|
13
22
|
const SETTINGS = resolve(CLAUDE_DIR, "settings.json");
|
|
14
23
|
|
|
@@ -21,50 +30,13 @@ if (!existsSync(SETTINGS)) {
|
|
|
21
30
|
copyFileSync(SETTINGS, `${SETTINGS}.bak.${Date.now()}`);
|
|
22
31
|
log.info("Backed up settings.json");
|
|
23
32
|
|
|
24
|
-
// ---
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
command?: string;
|
|
29
|
-
};
|
|
30
|
-
type Settings = { hooks?: Record<string, HookEntry[]>; env?: Record<string, string> };
|
|
33
|
+
// --- Load template and unmerge from existing settings ---
|
|
34
|
+
const template = loadSettingsTemplate(assets.claudeSettingsTemplate(), PKG_ROOT);
|
|
35
|
+
const existing = readJson<Record<string, unknown>>(SETTINGS, {});
|
|
36
|
+
const cleaned = unmergeSettings(existing, template);
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (settings.hooks) {
|
|
35
|
-
for (const [event, entries] of Object.entries(settings.hooks)) {
|
|
36
|
-
settings.hooks[event] = entries.filter((entry) => {
|
|
37
|
-
// New format: { matcher, hooks: [{ command }] }
|
|
38
|
-
if (entry.hooks) return !entry.hooks.some((h) => h.command?.includes(PKG_ROOT));
|
|
39
|
-
// Old flat format: { type, command }
|
|
40
|
-
if (entry.command) return !entry.command.includes(PKG_ROOT);
|
|
41
|
-
return true;
|
|
42
|
-
});
|
|
43
|
-
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
44
|
-
}
|
|
45
|
-
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// --- Remove env ---
|
|
49
|
-
if (settings.env) {
|
|
50
|
-
// Clean up env vars
|
|
51
|
-
delete settings.env.PAL_DIR;
|
|
52
|
-
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// --- Remove PAL tool permissions ---
|
|
56
|
-
type SettingsWithPermissions = Settings & { permissions?: { allow?: string[] } };
|
|
57
|
-
const s = settings as SettingsWithPermissions;
|
|
58
|
-
if (s.permissions?.allow) {
|
|
59
|
-
s.permissions.allow = s.permissions.allow.filter(
|
|
60
|
-
(p) => !p.includes(PKG_ROOT) && !p.startsWith("Bash(bun run ai:")
|
|
61
|
-
);
|
|
62
|
-
if (s.permissions.allow.length === 0) delete s.permissions.allow;
|
|
63
|
-
if (Object.keys(s.permissions).length === 0) delete s.permissions;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
writeJson(SETTINGS, settings);
|
|
67
|
-
log.success("Removed PAL hooks and env from settings.json");
|
|
38
|
+
writeJson(SETTINGS, cleaned);
|
|
39
|
+
log.success("Removed PAL settings from settings.json");
|
|
68
40
|
|
|
69
41
|
// --- Remove PAL skills ---
|
|
70
42
|
const removed = removeSkills(resolve(CLAUDE_DIR, "skills"));
|
|
@@ -82,6 +54,9 @@ if (removedAgents.length > 0) {
|
|
|
82
54
|
log.info("No PAL agents found");
|
|
83
55
|
}
|
|
84
56
|
|
|
57
|
+
// --- Remove PAL system docs ---
|
|
58
|
+
removePalDocs();
|
|
59
|
+
|
|
85
60
|
// --- Remove AGENTS.md and CLAUDE.md symlink ---
|
|
86
61
|
const agentsMd = resolve(platform.opencodeDir(), "AGENTS.md");
|
|
87
62
|
const claudeMd = resolve(CLAUDE_DIR, "CLAUDE.md");
|