portable-agent-layer 0.1.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/LICENSE +21 -0
- package/README.md +80 -0
- package/assets/agents/claude-researcher.md +43 -0
- package/assets/agents/investigative-researcher.md +44 -0
- package/assets/agents/multi-perspective-researcher.md +43 -0
- package/assets/skills/analyze-pdf.md +40 -0
- package/assets/skills/analyze-youtube.md +35 -0
- package/assets/skills/council.md +43 -0
- package/assets/skills/create-skill.md +31 -0
- package/assets/skills/extract-entities.md +63 -0
- package/assets/skills/extract-wisdom.md +18 -0
- package/assets/skills/first-principles.md +17 -0
- package/assets/skills/fyzz-chat-api.md +43 -0
- package/assets/skills/reflect.md +87 -0
- package/assets/skills/research.md +68 -0
- package/assets/skills/review.md +19 -0
- package/assets/skills/summarize.md +15 -0
- package/assets/templates/AGENTS.md.template +45 -0
- package/assets/templates/telos/BELIEFS.md +4 -0
- package/assets/templates/telos/CHALLENGES.md +4 -0
- package/assets/templates/telos/GOALS.md +12 -0
- package/assets/templates/telos/IDEAS.md +4 -0
- package/assets/templates/telos/IDENTITY.md +4 -0
- package/assets/templates/telos/LEARNED.md +4 -0
- package/assets/templates/telos/MISSION.md +4 -0
- package/assets/templates/telos/MODELS.md +4 -0
- package/assets/templates/telos/NARRATIVES.md +4 -0
- package/assets/templates/telos/PROJECTS.md +7 -0
- package/assets/templates/telos/STRATEGIES.md +4 -0
- package/bin/pal +24 -0
- package/bin/pal.bat +8 -0
- package/bin/pal.ps1 +30 -0
- package/package.json +82 -0
- package/src/cli/index.ts +344 -0
- package/src/cli/install.ts +86 -0
- package/src/cli/uninstall.ts +45 -0
- package/src/hooks/LoadContext.ts +41 -0
- package/src/hooks/SecurityValidator.ts +52 -0
- package/src/hooks/SkillGuard.ts +41 -0
- package/src/hooks/StopOrchestrator.ts +35 -0
- package/src/hooks/UserPromptOrchestrator.ts +35 -0
- package/src/hooks/handlers/backup.ts +41 -0
- package/src/hooks/handlers/failure.ts +136 -0
- package/src/hooks/handlers/rating.ts +409 -0
- package/src/hooks/handlers/relationship.ts +113 -0
- package/src/hooks/handlers/session-name.ts +121 -0
- package/src/hooks/handlers/synthesis.ts +109 -0
- package/src/hooks/handlers/tab.ts +8 -0
- package/src/hooks/handlers/update-counts.ts +151 -0
- package/src/hooks/handlers/work-learning.ts +183 -0
- package/src/hooks/handlers/work-session.ts +58 -0
- package/src/hooks/lib/claude-md.ts +121 -0
- package/src/hooks/lib/context.ts +433 -0
- package/src/hooks/lib/entities.ts +304 -0
- package/src/hooks/lib/export.ts +76 -0
- package/src/hooks/lib/inference.ts +91 -0
- package/src/hooks/lib/learning-category.ts +14 -0
- package/src/hooks/lib/log.ts +53 -0
- package/src/hooks/lib/models.ts +16 -0
- package/src/hooks/lib/paths.ts +80 -0
- package/src/hooks/lib/relationship.ts +135 -0
- package/src/hooks/lib/security.ts +122 -0
- package/src/hooks/lib/session-names.ts +247 -0
- package/src/hooks/lib/setup.ts +189 -0
- package/src/hooks/lib/signal-trends.ts +117 -0
- package/src/hooks/lib/signals.ts +37 -0
- package/src/hooks/lib/stdin.ts +18 -0
- package/src/hooks/lib/stop.ts +155 -0
- package/src/hooks/lib/time.ts +19 -0
- package/src/hooks/lib/token-usage.ts +42 -0
- package/src/hooks/lib/transcript.ts +76 -0
- package/src/hooks/lib/wisdom.ts +48 -0
- package/src/hooks/lib/work-tracking.ts +193 -0
- package/src/hooks/setup-check.ts +42 -0
- package/src/targets/claude/install.ts +145 -0
- package/src/targets/claude/uninstall.ts +101 -0
- package/src/targets/lib.ts +337 -0
- package/src/targets/opencode/install.ts +59 -0
- package/src/targets/opencode/plugin.ts +328 -0
- package/src/targets/opencode/uninstall.ts +57 -0
- package/src/tools/entity-save.ts +110 -0
- package/src/tools/export.ts +34 -0
- package/src/tools/fyzz-api.ts +104 -0
- package/src/tools/import.ts +123 -0
- package/src/tools/pattern-synthesis.ts +435 -0
- package/src/tools/pdf-download.ts +102 -0
- package/src/tools/relationship-reflect.ts +362 -0
- package/src/tools/session-summary.ts +206 -0
- package/src/tools/token-cost.ts +301 -0
- package/src/tools/youtube-analyze.ts +105 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for PAL installers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
copyFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
lstatSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
symlinkSync,
|
|
14
|
+
unlinkSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { resolve } from "node:path";
|
|
18
|
+
import { assets, palHome, platform } from "../hooks/lib/paths";
|
|
19
|
+
|
|
20
|
+
// --- Colored logging ---
|
|
21
|
+
|
|
22
|
+
export const log = {
|
|
23
|
+
info: (msg: string) => console.log(`\x1b[34m[pal]\x1b[0m ${msg}`),
|
|
24
|
+
success: (msg: string) => console.log(`\x1b[32m[pal]\x1b[0m ${msg}`),
|
|
25
|
+
warn: (msg: string) => console.log(`\x1b[33m[pal]\x1b[0m ${msg}`),
|
|
26
|
+
error: (msg: string) => console.error(`\x1b[31m[pal]\x1b[0m ${msg}`),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// --- JSON helpers ---
|
|
30
|
+
|
|
31
|
+
export function readJson<T = Record<string, unknown>>(path: string, fallback: T): T {
|
|
32
|
+
if (!existsSync(path)) return fallback;
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(path, "utf-8")) as T;
|
|
35
|
+
} catch {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function writeJson(path: string, data: unknown): void {
|
|
41
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- TELOS scaffolding ---
|
|
45
|
+
|
|
46
|
+
/** Copy template files into telos/ without overwriting existing ones */
|
|
47
|
+
export function scaffoldTelos(): void {
|
|
48
|
+
const templatesDir = assets.telosTemplates();
|
|
49
|
+
const telosDir = resolve(palHome(), "telos");
|
|
50
|
+
if (!existsSync(templatesDir)) return;
|
|
51
|
+
|
|
52
|
+
for (const file of readdirSync(templatesDir).filter((f) => f.endsWith(".md"))) {
|
|
53
|
+
const src = resolve(templatesDir, file);
|
|
54
|
+
const dst = resolve(telosDir, file);
|
|
55
|
+
if (!existsSync(dst)) {
|
|
56
|
+
copyFileSync(src, dst);
|
|
57
|
+
log.info(`Created ${file} from template`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- Skills ---
|
|
63
|
+
|
|
64
|
+
const AGENTS_SKILLS_DIR = resolve(platform.agentsDir(), "skills");
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Install PAL skills into the shared ~/.agents/skills/<name>/SKILL.md standard,
|
|
68
|
+
* then symlink ~/.claude/skills/<name> → ../../.agents/skills/<name>.
|
|
69
|
+
* Additive — skips skills already installed.
|
|
70
|
+
*/
|
|
71
|
+
export function copySkills(claudeSkillsDir: string): number {
|
|
72
|
+
const skillsDir = assets.skills();
|
|
73
|
+
if (!existsSync(skillsDir)) return 0;
|
|
74
|
+
|
|
75
|
+
mkdirSync(AGENTS_SKILLS_DIR, { recursive: true });
|
|
76
|
+
mkdirSync(claudeSkillsDir, { recursive: true });
|
|
77
|
+
let count = 0;
|
|
78
|
+
|
|
79
|
+
for (const file of readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
80
|
+
const name = file.replace(/\.md$/, "");
|
|
81
|
+
const src = resolve(skillsDir, file);
|
|
82
|
+
const agentSkillDir = resolve(AGENTS_SKILLS_DIR, name);
|
|
83
|
+
const agentSkillFile = resolve(agentSkillDir, "SKILL.md");
|
|
84
|
+
const claudeLink = resolve(claudeSkillsDir, name);
|
|
85
|
+
|
|
86
|
+
// Install into ~/.agents/skills/<name>/SKILL.md
|
|
87
|
+
if (!existsSync(agentSkillFile)) {
|
|
88
|
+
mkdirSync(agentSkillDir, { recursive: true });
|
|
89
|
+
copyFileSync(src, agentSkillFile);
|
|
90
|
+
log.info(`Added skill: ${name}`);
|
|
91
|
+
count++;
|
|
92
|
+
} else {
|
|
93
|
+
log.warn(`Skill exists, skipping: ${name}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create ~/.claude/skills/<name> symlink if missing or not a symlink
|
|
97
|
+
// Use 'junction' on Windows (no admin required), 'dir' symlink on Unix
|
|
98
|
+
const linkType = process.platform === "win32" ? "junction" : "dir";
|
|
99
|
+
try {
|
|
100
|
+
const st = lstatSync(claudeLink);
|
|
101
|
+
if (!st.isSymbolicLink()) {
|
|
102
|
+
rmSync(claudeLink, { recursive: true, force: true });
|
|
103
|
+
symlinkSync(`../../.agents/skills/${name}`, claudeLink, linkType);
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Entry might exist but lstatSync failed (broken symlink/junction on Windows)
|
|
107
|
+
try {
|
|
108
|
+
rmSync(claudeLink, { recursive: true, force: true });
|
|
109
|
+
} catch {
|
|
110
|
+
/* gone */
|
|
111
|
+
}
|
|
112
|
+
symlinkSync(`../../.agents/skills/${name}`, claudeLink, linkType);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return count;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Remove PAL skills from ~/.agents/skills/ and their symlinks from ~/.claude/skills/ */
|
|
119
|
+
export function removeSkills(claudeSkillsDir: string): string[] {
|
|
120
|
+
const skillsDir = assets.skills();
|
|
121
|
+
if (!existsSync(skillsDir)) return [];
|
|
122
|
+
|
|
123
|
+
const removed: string[] = [];
|
|
124
|
+
for (const file of readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
125
|
+
const name = file.replace(/\.md$/, "");
|
|
126
|
+
|
|
127
|
+
const agentSkillDir = resolve(AGENTS_SKILLS_DIR, name);
|
|
128
|
+
if (existsSync(agentSkillDir)) {
|
|
129
|
+
rmSync(agentSkillDir, { recursive: true });
|
|
130
|
+
removed.push(name);
|
|
131
|
+
log.info(`Removed skill: ${name}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const claudeLink = resolve(claudeSkillsDir, name);
|
|
135
|
+
try {
|
|
136
|
+
unlinkSync(claudeLink);
|
|
137
|
+
} catch {
|
|
138
|
+
/* already gone */
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return removed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// --- Agents ---
|
|
145
|
+
|
|
146
|
+
const CLAUDE_AGENTS_DIR = resolve(platform.claudeDir(), "agents");
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Install PAL agent definitions into ~/.claude/agents/.
|
|
150
|
+
* Additive — skips agents already installed.
|
|
151
|
+
*/
|
|
152
|
+
export function copyAgents(): number {
|
|
153
|
+
const agentsDir = assets.agents();
|
|
154
|
+
if (!existsSync(agentsDir)) return 0;
|
|
155
|
+
|
|
156
|
+
mkdirSync(CLAUDE_AGENTS_DIR, { recursive: true });
|
|
157
|
+
let count = 0;
|
|
158
|
+
|
|
159
|
+
for (const file of readdirSync(agentsDir).filter((f) => f.endsWith(".md"))) {
|
|
160
|
+
const src = resolve(agentsDir, file);
|
|
161
|
+
const dst = resolve(CLAUDE_AGENTS_DIR, file);
|
|
162
|
+
|
|
163
|
+
if (!existsSync(dst)) {
|
|
164
|
+
copyFileSync(src, dst);
|
|
165
|
+
log.info(`Added agent: ${file.replace(/\.md$/, "")}`);
|
|
166
|
+
count++;
|
|
167
|
+
} else {
|
|
168
|
+
log.warn(`Agent exists, skipping: ${file.replace(/\.md$/, "")}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return count;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Remove PAL agents from ~/.claude/agents/ */
|
|
175
|
+
export function removeAgents(): string[] {
|
|
176
|
+
const agentsDir = assets.agents();
|
|
177
|
+
if (!existsSync(agentsDir)) return [];
|
|
178
|
+
|
|
179
|
+
const removed: string[] = [];
|
|
180
|
+
for (const file of readdirSync(agentsDir).filter((f) => f.endsWith(".md"))) {
|
|
181
|
+
const dst = resolve(CLAUDE_AGENTS_DIR, file);
|
|
182
|
+
if (existsSync(dst)) {
|
|
183
|
+
unlinkSync(dst);
|
|
184
|
+
const name = file.replace(/\.md$/, "");
|
|
185
|
+
removed.push(name);
|
|
186
|
+
log.info(`Removed agent: ${name}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return removed;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Count agent .md files in ~/.claude/agents/ */
|
|
193
|
+
export function countAgents(): number {
|
|
194
|
+
if (!existsSync(CLAUDE_AGENTS_DIR)) return 0;
|
|
195
|
+
try {
|
|
196
|
+
return readdirSync(CLAUDE_AGENTS_DIR).filter((f) => f.endsWith(".md")).length;
|
|
197
|
+
} catch {
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Opencode agent translation ---
|
|
203
|
+
|
|
204
|
+
/** Map Claude Code tool names to opencode permission keys */
|
|
205
|
+
const TOOL_TO_PERMISSION: Record<string, string> = {
|
|
206
|
+
WebSearch: "webfetch",
|
|
207
|
+
WebFetch: "webfetch",
|
|
208
|
+
Read: "read",
|
|
209
|
+
Grep: "read",
|
|
210
|
+
Glob: "read",
|
|
211
|
+
Write: "edit",
|
|
212
|
+
Edit: "edit",
|
|
213
|
+
Bash: "bash",
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Translate a Claude Code agent .md file to opencode format.
|
|
218
|
+
* - `tools: X, Y` → `permission: { x: allow, ... }`
|
|
219
|
+
* - Adds `mode: subagent`
|
|
220
|
+
* - Keeps everything else (name, description, model, body)
|
|
221
|
+
*/
|
|
222
|
+
export function translateAgentForOpencode(content: string): string {
|
|
223
|
+
const parts = content.split(/^---\s*$/m);
|
|
224
|
+
if (parts.length < 3) return content;
|
|
225
|
+
|
|
226
|
+
const frontmatter = parts[1];
|
|
227
|
+
const body = parts.slice(2).join("---");
|
|
228
|
+
|
|
229
|
+
// Extract tools line
|
|
230
|
+
const toolsMatch = frontmatter.match(/^tools:\s*(.+)$/m);
|
|
231
|
+
const tools = toolsMatch ? toolsMatch[1].split(",").map((t) => t.trim()) : [];
|
|
232
|
+
|
|
233
|
+
// Build opencode permissions (deduplicated)
|
|
234
|
+
const perms = new Set<string>();
|
|
235
|
+
for (const tool of tools) {
|
|
236
|
+
const perm = TOOL_TO_PERMISSION[tool];
|
|
237
|
+
if (perm) perms.add(perm);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Build new frontmatter: remove tools line, add mode + permission
|
|
241
|
+
let newFrontmatter = frontmatter.replace(/^tools:\s*.+$/m, "").trim();
|
|
242
|
+
newFrontmatter += "\nmode: subagent";
|
|
243
|
+
if (perms.size > 0) {
|
|
244
|
+
const permObj = Object.fromEntries([...perms].map((p) => [p, "allow"]));
|
|
245
|
+
// Inline YAML object
|
|
246
|
+
const permYaml = Object.entries(permObj)
|
|
247
|
+
.map(([k, v]) => ` ${k}: ${v}`)
|
|
248
|
+
.join("\n");
|
|
249
|
+
newFrontmatter += `\npermission:\n${permYaml}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return `---\n${newFrontmatter}\n---\n${body}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Install PAL agent definitions into an opencode agents directory.
|
|
257
|
+
* Translates frontmatter from Claude Code format to opencode format.
|
|
258
|
+
*/
|
|
259
|
+
export function copyAgentsForOpencode(ocAgentsDir: string): number {
|
|
260
|
+
const agentsDir = assets.agents();
|
|
261
|
+
if (!existsSync(agentsDir)) return 0;
|
|
262
|
+
|
|
263
|
+
mkdirSync(ocAgentsDir, { recursive: true });
|
|
264
|
+
let count = 0;
|
|
265
|
+
|
|
266
|
+
for (const file of readdirSync(agentsDir).filter((f) => f.endsWith(".md"))) {
|
|
267
|
+
const dst = resolve(ocAgentsDir, file);
|
|
268
|
+
if (!existsSync(dst)) {
|
|
269
|
+
const src = resolve(agentsDir, file);
|
|
270
|
+
const content = readFileSync(src, "utf-8");
|
|
271
|
+
writeFileSync(dst, translateAgentForOpencode(content), "utf-8");
|
|
272
|
+
log.info(`Added opencode agent: ${file.replace(/\.md$/, "")}`);
|
|
273
|
+
count++;
|
|
274
|
+
} else {
|
|
275
|
+
log.warn(`Opencode agent exists, skipping: ${file.replace(/\.md$/, "")}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return count;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Remove PAL agents from an opencode agents directory */
|
|
282
|
+
export function removeAgentsFromOpencode(ocAgentsDir: string): string[] {
|
|
283
|
+
const agentsDir = assets.agents();
|
|
284
|
+
if (!existsSync(agentsDir)) return [];
|
|
285
|
+
|
|
286
|
+
const removed: string[] = [];
|
|
287
|
+
for (const file of readdirSync(agentsDir).filter((f) => f.endsWith(".md"))) {
|
|
288
|
+
const dst = resolve(ocAgentsDir, file);
|
|
289
|
+
if (existsSync(dst)) {
|
|
290
|
+
unlinkSync(dst);
|
|
291
|
+
const name = file.replace(/\.md$/, "");
|
|
292
|
+
removed.push(name);
|
|
293
|
+
log.info(`Removed opencode agent: ${name}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return removed;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Count skill subdirectories in ~/.agents/skills/ */
|
|
300
|
+
export function countSkills(): number {
|
|
301
|
+
if (!existsSync(AGENTS_SKILLS_DIR)) return 0;
|
|
302
|
+
try {
|
|
303
|
+
return readdirSync(AGENTS_SKILLS_DIR).filter((f) =>
|
|
304
|
+
existsSync(resolve(AGENTS_SKILLS_DIR, f, "SKILL.md"))
|
|
305
|
+
).length;
|
|
306
|
+
} catch {
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Count .md files in a directory */
|
|
312
|
+
export function countMd(dir: string): number {
|
|
313
|
+
if (!existsSync(dir)) return 0;
|
|
314
|
+
try {
|
|
315
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md")).length;
|
|
316
|
+
} catch {
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Read skill frontmatter field */
|
|
322
|
+
export function readSkillField(skillPath: string, field: string): string {
|
|
323
|
+
try {
|
|
324
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
325
|
+
const match = content.match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
|
|
326
|
+
return match?.[1]?.trim() ?? "";
|
|
327
|
+
} catch {
|
|
328
|
+
return "";
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Strip frontmatter from a skill file (content after second ---) */
|
|
333
|
+
export function skillBody(skillPath: string): string {
|
|
334
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
335
|
+
const parts = content.split(/^---\s*$/m);
|
|
336
|
+
return parts.length >= 3 ? parts.slice(2).join("---").trim() : content.trim();
|
|
337
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAL — opencode target installer (TypeScript)
|
|
3
|
+
* Deploys plugin, installs skills, generates AGENTS.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
9
|
+
import { palPkg, platform } from "../../hooks/lib/paths";
|
|
10
|
+
import { copyAgentsForOpencode, copySkills, countSkills, log, writeJson } from "../lib";
|
|
11
|
+
|
|
12
|
+
const PKG_ROOT = palPkg();
|
|
13
|
+
const OC_GLOBAL_DIR = platform.opencodeDir();
|
|
14
|
+
const OC_PLUGINS_DIR = resolve(OC_GLOBAL_DIR, "plugins");
|
|
15
|
+
|
|
16
|
+
mkdirSync(OC_PLUGINS_DIR, { recursive: true });
|
|
17
|
+
|
|
18
|
+
// --- 1. Deploy plugin ---
|
|
19
|
+
const pluginSrc = resolve(PKG_ROOT, "src", "targets", "opencode", "plugin.ts");
|
|
20
|
+
const pluginDst = resolve(OC_PLUGINS_DIR, "pal-plugin.ts");
|
|
21
|
+
// Embed PKG_ROOT as a hardcoded constant so no env config is needed
|
|
22
|
+
const pluginContent = readFileSync(pluginSrc, "utf-8").replace(
|
|
23
|
+
/const PAL_DIR = process\.env\.PAL_DIR \|\| resolve\(import\.meta\.dir, "\.\.\/\.\.\/\.\."\);/,
|
|
24
|
+
`const PAL_DIR = ${JSON.stringify(PKG_ROOT)};`
|
|
25
|
+
);
|
|
26
|
+
writeFileSync(pluginDst, pluginContent, "utf-8");
|
|
27
|
+
log.success(`Deployed plugin to ${pluginDst}`);
|
|
28
|
+
|
|
29
|
+
// --- 2. Ensure package.json for plugin deps ---
|
|
30
|
+
const pkgPath = resolve(OC_PLUGINS_DIR, "package.json");
|
|
31
|
+
if (!existsSync(pkgPath)) {
|
|
32
|
+
writeJson(pkgPath, { dependencies: { "@opencode-ai/plugin": "latest" } });
|
|
33
|
+
log.info("Created package.json for plugin dependencies");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
Bun.spawnSync(["bun", "install", "--silent"], { cwd: OC_PLUGINS_DIR });
|
|
38
|
+
log.success("Installed plugin dependencies");
|
|
39
|
+
} catch {
|
|
40
|
+
log.warn(`Could not install plugin deps — run 'bun install' in ${OC_PLUGINS_DIR}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --- 3. Install skills into ~/.agents/skills/ ---
|
|
44
|
+
const claudeSkillsDir = resolve(platform.claudeDir(), "skills");
|
|
45
|
+
copySkills(claudeSkillsDir);
|
|
46
|
+
log.success("Installed skills to ~/.agents/skills/");
|
|
47
|
+
|
|
48
|
+
// --- 4. Install agents into ~/.config/opencode/agents/ ---
|
|
49
|
+
const ocAgentsDir = resolve(OC_GLOBAL_DIR, "agents");
|
|
50
|
+
copyAgentsForOpencode(ocAgentsDir);
|
|
51
|
+
|
|
52
|
+
// --- 5. Generate ~/.config/opencode/AGENTS.md ---
|
|
53
|
+
regenerateIfNeeded();
|
|
54
|
+
log.success("Generated ~/.config/opencode/AGENTS.md");
|
|
55
|
+
|
|
56
|
+
log.success("opencode installation complete");
|
|
57
|
+
console.log("");
|
|
58
|
+
log.info(`Plugin: ${pluginDst}`);
|
|
59
|
+
log.info(`Skills: ${countSkills()} (native via ~/.agents/skills/)`);
|