portable-agent-layer 0.22.0 → 0.23.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.
- package/assets/agents/gemini-researcher.md +17 -3
- package/assets/agents/grok-researcher.md +19 -5
- package/assets/agents/multi-perspective-researcher.md +16 -2
- package/assets/agents/perplexity-researcher.md +17 -3
- package/assets/skills/analyze-pdf/SKILL.md +1 -1
- package/assets/skills/analyze-youtube/SKILL.md +1 -1
- package/assets/skills/extract-entities/SKILL.md +1 -1
- package/assets/skills/fyzz-chat-api/SKILL.md +3 -3
- package/assets/skills/reflect/SKILL.md +2 -2
- package/assets/skills/telos/SKILL.md +6 -6
- package/assets/templates/AGENTS.md.template +2 -2
- package/assets/templates/PAL/ALGORITHM.md +93 -10
- package/assets/templates/PAL/CONTEXT_ROUTING.md +17 -17
- package/assets/templates/PAL/MEMORY_SYSTEM.md +5 -5
- package/assets/templates/PAL/README.md +12 -9
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +1 -1
- package/assets/templates/pal-settings.json +6 -3
- package/assets/templates/settings.claude.json +2 -2
- package/package.json +3 -1
- package/src/cli/index.ts +4 -11
- package/src/hooks/handlers/failure.ts +3 -1
- package/src/hooks/handlers/rating.ts +17 -2
- package/src/hooks/handlers/reflect-trigger.ts +4 -4
- package/src/hooks/handlers/relationship.ts +1 -1
- package/src/hooks/handlers/session-intelligence.ts +324 -0
- package/src/hooks/handlers/session-name.ts +2 -2
- package/src/hooks/handlers/synthesis.ts +36 -0
- package/src/hooks/handlers/update-check.ts +2 -2
- package/src/hooks/handlers/work-learning.ts +1 -1
- package/src/hooks/lib/context.ts +119 -2
- package/src/hooks/lib/paths.ts +4 -12
- package/src/hooks/lib/security.ts +39 -28
- package/src/hooks/lib/stop.ts +56 -7
- package/src/hooks/lib/token-usage.ts +1 -0
- package/src/targets/claude/install.ts +1 -1
- package/src/targets/cursor/install.ts +7 -1
- package/src/targets/cursor/uninstall.ts +7 -0
- package/src/targets/lib.ts +125 -115
- package/src/targets/opencode/install.ts +4 -4
- package/src/tools/agent/algorithm-reflect.ts +2 -0
- package/src/tools/agent/synthesize.ts +361 -0
- package/src/tools/agent/thread.ts +162 -0
package/src/hooks/lib/context.ts
CHANGED
|
@@ -231,10 +231,11 @@ export function loadFailurePatterns(): string {
|
|
|
231
231
|
|
|
232
232
|
const lines = entries.map((e) => {
|
|
233
233
|
const label = e.rating ? `[${e.rating}/10]` : "";
|
|
234
|
-
|
|
234
|
+
const text = e.principle || e.context;
|
|
235
|
+
return `- ${label} ${text}`.trim();
|
|
235
236
|
});
|
|
236
237
|
|
|
237
|
-
return ["## Recent
|
|
238
|
+
return ["## Lessons from Recent Failures — Apply These Now", ...lines].join("\n");
|
|
238
239
|
} catch {
|
|
239
240
|
return "";
|
|
240
241
|
}
|
|
@@ -333,6 +334,116 @@ export function loadRelationshipContext(): string {
|
|
|
333
334
|
}
|
|
334
335
|
}
|
|
335
336
|
|
|
337
|
+
/** Load session intelligence from compact synthesis state */
|
|
338
|
+
export function loadSessionIntelligence(): string {
|
|
339
|
+
try {
|
|
340
|
+
const p = resolve(paths.state(), "synthesis.json");
|
|
341
|
+
if (!existsSync(p)) return "";
|
|
342
|
+
const state = JSON.parse(readFileSync(p, "utf-8"));
|
|
343
|
+
|
|
344
|
+
const lines: string[] = ["## Session Intelligence"];
|
|
345
|
+
|
|
346
|
+
// Open Threads — project-specific first, then global
|
|
347
|
+
if (state.threads?.length > 0) {
|
|
348
|
+
const cwd = process.cwd();
|
|
349
|
+
const here = state.threads.filter((t: { cwd?: string }) => t.cwd === cwd);
|
|
350
|
+
const other = state.threads.filter((t: { cwd?: string }) => t.cwd !== cwd);
|
|
351
|
+
|
|
352
|
+
if (here.length > 0) {
|
|
353
|
+
lines.push("");
|
|
354
|
+
lines.push(`**Open threads — this project (${here.length}):**`);
|
|
355
|
+
for (const t of here) {
|
|
356
|
+
lines.push(`- ${t.title} (opened ${t.opened})`);
|
|
357
|
+
if (t.context) lines.push(` ${t.context}`);
|
|
358
|
+
}
|
|
359
|
+
lines.push("→ These are directly relevant to your current work.");
|
|
360
|
+
}
|
|
361
|
+
if (other.length > 0) {
|
|
362
|
+
lines.push("");
|
|
363
|
+
lines.push(`**Open threads — other projects (${other.length}):**`);
|
|
364
|
+
for (const t of other) {
|
|
365
|
+
lines.push(`- ${t.title} (opened ${t.opened})`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Rating Trend
|
|
371
|
+
if (state.ratings?.count > 0) {
|
|
372
|
+
const r = state.ratings;
|
|
373
|
+
lines.push("");
|
|
374
|
+
lines.push(
|
|
375
|
+
`**Rating trend:** ${r.avg}/10 avg (last 10: ${r.recentAvg}/10, ${r.trend}).${r.lowCount > 0 ? ` ${r.lowCount} low ratings.` : ""}`
|
|
376
|
+
);
|
|
377
|
+
if (r.trend === "declining") {
|
|
378
|
+
lines.push(
|
|
379
|
+
"→ Trend is declining. Be extra careful with assumptions. Confirm before acting."
|
|
380
|
+
);
|
|
381
|
+
} else if (r.trend === "improving") {
|
|
382
|
+
lines.push("→ Trend is improving. Maintain current approach.");
|
|
383
|
+
} else if (r.lowCount > 5) {
|
|
384
|
+
lines.push(
|
|
385
|
+
"→ Multiple low ratings. Slow down, verify before acting, ask when uncertain."
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Algorithm Performance
|
|
391
|
+
if (state.algorithm?.reflectionCount > 0) {
|
|
392
|
+
const a = state.algorithm;
|
|
393
|
+
lines.push("");
|
|
394
|
+
lines.push(
|
|
395
|
+
`**Algorithm:** ${a.reflectionCount} reflections, ${a.passRate}% criteria pass rate, ${a.avgSentiment}/10 sentiment.`
|
|
396
|
+
);
|
|
397
|
+
if (a.passRate < 80) {
|
|
398
|
+
lines.push(
|
|
399
|
+
"→ Criteria pass rate is low. Invest more time in OBSERVE and PLAN phases."
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (a.recentObservations?.length > 0) {
|
|
403
|
+
const cwd = process.cwd();
|
|
404
|
+
const relevant = a.recentObservations.filter(
|
|
405
|
+
(o: { cwd?: string }) => !o.cwd || o.cwd === cwd
|
|
406
|
+
);
|
|
407
|
+
if (relevant.length > 0) {
|
|
408
|
+
lines.push("Recent self-observations (this project):");
|
|
409
|
+
for (const o of relevant) {
|
|
410
|
+
lines.push(`- [${o.date}] ${o.task}: "${o.observation}"`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return lines.length > 1 ? lines.join("\n") : "";
|
|
417
|
+
} catch {
|
|
418
|
+
return "";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Load handoff state for the current project */
|
|
423
|
+
export function loadHandoff(): string {
|
|
424
|
+
try {
|
|
425
|
+
const p = resolve(paths.state(), "last-handoff.json");
|
|
426
|
+
if (!existsSync(p)) return "";
|
|
427
|
+
const handoffs = JSON.parse(readFileSync(p, "utf-8"));
|
|
428
|
+
const cwd = process.cwd();
|
|
429
|
+
const entry = handoffs[cwd];
|
|
430
|
+
if (!entry?.handoff || entry.status !== "in-progress") return "";
|
|
431
|
+
|
|
432
|
+
const age = Date.now() - new Date(entry.timestamp).getTime();
|
|
433
|
+
if (age > 7 * 24 * 60 * 60 * 1000) return ""; // stale after 7 days
|
|
434
|
+
|
|
435
|
+
return [
|
|
436
|
+
"## Pick Up Where You Left Off",
|
|
437
|
+
`*Previous session: ${entry.title}*`,
|
|
438
|
+
"",
|
|
439
|
+
entry.handoff,
|
|
440
|
+
"→ Continue this work or explicitly close it before starting something new.",
|
|
441
|
+
].join("\n");
|
|
442
|
+
} catch {
|
|
443
|
+
return "";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
336
447
|
/**
|
|
337
448
|
* Build the <system-reminder> content for the AI.
|
|
338
449
|
*
|
|
@@ -358,10 +469,16 @@ export function buildSystemReminder(): string {
|
|
|
358
469
|
? loadSynthesisRecommendations()
|
|
359
470
|
: "";
|
|
360
471
|
const opinions = isEnabled(settings, "opinions") ? loadOpinionContext() : "";
|
|
472
|
+
const intelligence = isEnabled(settings, "sessionIntelligence")
|
|
473
|
+
? loadSessionIntelligence()
|
|
474
|
+
: "";
|
|
475
|
+
const handoff = isEnabled(settings, "handoff") ? loadHandoff() : "";
|
|
361
476
|
const parts: string[] = [];
|
|
362
477
|
if (startup) parts.push(startup);
|
|
478
|
+
if (handoff) parts.push(handoff);
|
|
363
479
|
if (wisdom) parts.push(wisdom);
|
|
364
480
|
if (opinions) parts.push(opinions);
|
|
481
|
+
if (intelligence) parts.push(intelligence);
|
|
365
482
|
if (relationship) parts.push(relationship);
|
|
366
483
|
if (projectHistory) parts.push(projectHistory);
|
|
367
484
|
if (digest) parts.push(digest);
|
package/src/hooks/lib/paths.ts
CHANGED
|
@@ -12,20 +12,12 @@ export function palPkg(): string {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Root of the user's personal state (telos, memory,
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* Repo mode is detected by the presence of .palroot next to the package.
|
|
20
|
-
* This file is not included in the npm package, so it only exists in cloned repos.
|
|
15
|
+
* Root of the user's personal state (telos, memory, docs, tools, skills).
|
|
16
|
+
* Always resolves to ~/.pal/ regardless of where the package lives.
|
|
17
|
+
* Power users who want memory/telos versioned in a repo can override via PAL_HOME.
|
|
21
18
|
*/
|
|
22
19
|
export function palHome(): string {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const pkgRoot = palPkg();
|
|
26
|
-
if (existsSync(resolve(pkgRoot, ".palroot"))) return pkgRoot;
|
|
27
|
-
|
|
28
|
-
return resolve(homedir(), ".pal");
|
|
20
|
+
return process.env.PAL_HOME || resolve(homedir(), ".pal");
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
/** Ensure a directory exists, creating it recursively if needed */
|
|
@@ -47,8 +47,6 @@ export const HOOK_MANAGED_DIRS = [
|
|
|
47
47
|
"memory/relationship",
|
|
48
48
|
"memory/wisdom/state",
|
|
49
49
|
"memory/projects",
|
|
50
|
-
".agents/PAL/memory",
|
|
51
|
-
".agents/PAL/telos",
|
|
52
50
|
];
|
|
53
51
|
|
|
54
52
|
/** Escape a string for use in a RegExp */
|
|
@@ -63,8 +61,11 @@ export const PROTECTED_PATHS: RegExp[] = [
|
|
|
63
61
|
/^\/System\//,
|
|
64
62
|
/\.ssh\/(?!config)/,
|
|
65
63
|
/\.gnupg\//,
|
|
66
|
-
// Derived from HOOK_MANAGED_FILES
|
|
67
|
-
...HOOK_MANAGED_FILES.map(
|
|
64
|
+
// Derived from HOOK_MANAGED_FILES — scoped to managed roots only
|
|
65
|
+
...HOOK_MANAGED_FILES.map(
|
|
66
|
+
(name) =>
|
|
67
|
+
new RegExp(`[/\\\\]\\.(?:pal|claude|agents|cursor)[/\\\\].*${escapeRegExp(name)}$`)
|
|
68
|
+
),
|
|
68
69
|
];
|
|
69
70
|
|
|
70
71
|
/** Patterns that warrant a warning (logged but not blocked) */
|
|
@@ -75,38 +76,44 @@ export const WARN_COMMANDS: RegExp[] = [
|
|
|
75
76
|
/truncate\s+table/i,
|
|
76
77
|
];
|
|
77
78
|
|
|
79
|
+
/** Roots where managed files/dirs are protected (user state, not repo templates) */
|
|
80
|
+
const MANAGED_ROOTS = [".pal/", ".claude/", ".agents/", ".config/opencode/", ".cursor/"];
|
|
81
|
+
|
|
82
|
+
function isUnderManagedRoot(path: string): boolean {
|
|
83
|
+
const normalized = path.replace(/\\/g, "/");
|
|
84
|
+
return MANAGED_ROOTS.some(
|
|
85
|
+
(root) => normalized.includes(`/${root}`) || normalized.includes(`\\.${root}`)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
/** Read-only commands allowed to reference protected files */
|
|
79
90
|
const READ_ONLY_COMMANDS =
|
|
80
|
-
/^\s*(?:cat|head|tail|less|more|grep|rg|wc|diff|stat|file|ls|dir|git\s+(?:log|diff|blame|show|status)|bat)\b/;
|
|
91
|
+
/^\s*(?:cat|head|tail|less|more|grep|rg|wc|diff|stat|file|ls|dir|find|git\s+(?:log|diff|blame|show|status)|bat)\b/;
|
|
81
92
|
|
|
82
93
|
/** Check a bash command against blocked patterns. Returns reason string or null. */
|
|
83
94
|
export function checkBashCommand(cmd: string): string | null {
|
|
84
95
|
for (const [pattern, reason] of BLOCKED_COMMANDS) {
|
|
85
96
|
if (pattern.test(cmd)) return reason;
|
|
86
97
|
}
|
|
87
|
-
// If command
|
|
98
|
+
// If command references a managed file in a managed root path, block unless read-only.
|
|
99
|
+
// The filename must appear IN the same path as the managed root (e.g. .pal/.../file.json).
|
|
100
|
+
const segments = cmd.split(/[|;&&]/).map((s) => s.trim());
|
|
88
101
|
for (const name of HOOK_MANAGED_FILES) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!allReadOnly) {
|
|
96
|
-
return `${name} is managed automatically by hooks — do not edit directly`;
|
|
97
|
-
}
|
|
102
|
+
const pattern = new RegExp(
|
|
103
|
+
`\\.(?:pal|claude|agents|cursor|config/opencode)[/\\\\]\\S*${escapeRegExp(name)}`
|
|
104
|
+
);
|
|
105
|
+
const managed = segments.filter((s) => pattern.test(s));
|
|
106
|
+
if (managed.length > 0 && !managed.every((s) => READ_ONLY_COMMANDS.test(s))) {
|
|
107
|
+
return `${name} is managed automatically by hooks — do not edit directly`;
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
|
-
// If command mentions a hook-managed directory, block unless it's read-only
|
|
101
110
|
for (const dir of HOOK_MANAGED_DIRS) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return `${dir} is managed automatically by hooks — do not edit directly`;
|
|
109
|
-
}
|
|
111
|
+
const pattern = new RegExp(
|
|
112
|
+
`\\.(?:pal|claude|agents|cursor|config/opencode)[/\\\\]\\S*${escapeRegExp(dir)}`
|
|
113
|
+
);
|
|
114
|
+
const managed = segments.filter((s) => pattern.test(s));
|
|
115
|
+
if (managed.length > 0 && !managed.every((s) => READ_ONLY_COMMANDS.test(s))) {
|
|
116
|
+
return `${dir} is managed automatically by hooks — do not edit directly`;
|
|
110
117
|
}
|
|
111
118
|
}
|
|
112
119
|
return null;
|
|
@@ -115,10 +122,14 @@ export function checkBashCommand(cmd: string): string | null {
|
|
|
115
122
|
/** Check a file path against protected patterns. Returns a reason string or null. */
|
|
116
123
|
export function checkFilePath(filePath: string): string | null {
|
|
117
124
|
const normalized = filePath.replace(/\\/g, "/");
|
|
118
|
-
// Check hook-managed files
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
// Check hook-managed files — only under managed roots (not repo templates)
|
|
126
|
+
if (isUnderManagedRoot(normalized)) {
|
|
127
|
+
const matchedFile = HOOK_MANAGED_FILES.find((name) =>
|
|
128
|
+
normalized.endsWith(`/${name}`)
|
|
129
|
+
);
|
|
130
|
+
if (matchedFile) {
|
|
131
|
+
return `${matchedFile} is managed automatically by hooks — do not edit directly`;
|
|
132
|
+
}
|
|
122
133
|
}
|
|
123
134
|
// Check hook-managed directories
|
|
124
135
|
const matchedDir = HOOK_MANAGED_DIRS.find((dir) => normalized.includes(`/${dir}/`));
|
package/src/hooks/lib/stop.ts
CHANGED
|
@@ -8,11 +8,12 @@ import { resolve } from "node:path";
|
|
|
8
8
|
import { autoBackup } from "../handlers/backup";
|
|
9
9
|
import { captureFailure } from "../handlers/failure";
|
|
10
10
|
import { checkReflectTrigger } from "../handlers/reflect-trigger";
|
|
11
|
-
import {
|
|
11
|
+
import { captureSessionIntelligence } from "../handlers/session-intelligence";
|
|
12
|
+
import { runSynthesis } from "../handlers/synthesis";
|
|
12
13
|
import { resetTab } from "../handlers/tab";
|
|
13
14
|
import { updateCounts } from "../handlers/update-counts";
|
|
14
|
-
import { captureWorkLearning } from "../handlers/work-learning";
|
|
15
15
|
import { captureWorkSession } from "../handlers/work-session";
|
|
16
|
+
import { inference } from "./inference";
|
|
16
17
|
import { logDebug, logError } from "./log";
|
|
17
18
|
import { ensureDir, paths } from "./paths";
|
|
18
19
|
import { extractContent, extractLastAssistant, parseMessages } from "./transcript";
|
|
@@ -39,23 +40,23 @@ export async function runStopHandlers(
|
|
|
39
40
|
const results = await Promise.allSettled([
|
|
40
41
|
captureWorkSession(transcript, options.sessionId),
|
|
41
42
|
resetTab(),
|
|
42
|
-
|
|
43
|
-
captureWorkLearning(transcript, options.sessionId),
|
|
43
|
+
captureSessionIntelligence(transcript, options.sessionId),
|
|
44
44
|
checkPendingFailure(transcript),
|
|
45
45
|
updateCounts(),
|
|
46
46
|
autoBackup(),
|
|
47
47
|
checkReflectTrigger(),
|
|
48
|
+
runSynthesis(),
|
|
48
49
|
]);
|
|
49
50
|
|
|
50
51
|
const handlerNames = [
|
|
51
52
|
"work-session",
|
|
52
53
|
"tab",
|
|
53
|
-
"
|
|
54
|
-
"work-learning",
|
|
54
|
+
"session-intelligence",
|
|
55
55
|
"pending-failure",
|
|
56
56
|
"update-counts",
|
|
57
57
|
"backup",
|
|
58
58
|
"reflect-trigger",
|
|
59
|
+
"synthesis",
|
|
59
60
|
];
|
|
60
61
|
for (let i = 0; i < results.length; i++) {
|
|
61
62
|
const r = results[i];
|
|
@@ -137,15 +138,63 @@ async function checkPendingFailure(transcript: string): Promise<void> {
|
|
|
137
138
|
rating: number;
|
|
138
139
|
context: string;
|
|
139
140
|
detailedContext?: string;
|
|
141
|
+
principle?: string;
|
|
140
142
|
responsePreview?: string;
|
|
141
143
|
userPreview?: string;
|
|
142
144
|
};
|
|
143
145
|
unlinkSync(pendingPath);
|
|
146
|
+
|
|
147
|
+
// Extract principle from full transcript if not already present
|
|
148
|
+
let { principle, detailedContext } = pending;
|
|
149
|
+
if (!principle) {
|
|
150
|
+
try {
|
|
151
|
+
const msgs = parseMessages(transcript);
|
|
152
|
+
const recent = msgs
|
|
153
|
+
.slice(-10)
|
|
154
|
+
.map((m) => `${m.role.toUpperCase()}: ${extractContent(m).slice(0, 300)}`)
|
|
155
|
+
.join("\n\n");
|
|
156
|
+
|
|
157
|
+
const result = await inference({
|
|
158
|
+
system: `Analyze this failed AI interaction. The user rated it ${pending.rating}/10.
|
|
159
|
+
|
|
160
|
+
Return JSON:
|
|
161
|
+
{
|
|
162
|
+
"principle": "<one actionable rule the AI should follow, 10-20 words. Start with a verb: 'Verify...', 'Always...', 'Never...', 'Ask before...'>",
|
|
163
|
+
"detailed_context": "<what went wrong and why, 50-150 words>"
|
|
164
|
+
}`,
|
|
165
|
+
user: `User feedback: ${pending.context}\n\nConversation:\n${recent}`,
|
|
166
|
+
maxTokens: 400,
|
|
167
|
+
timeout: 10000,
|
|
168
|
+
jsonSchema: {
|
|
169
|
+
type: "object" as const,
|
|
170
|
+
properties: {
|
|
171
|
+
principle: { type: "string" as const },
|
|
172
|
+
detailed_context: { type: "string" as const },
|
|
173
|
+
},
|
|
174
|
+
required: ["principle", "detailed_context"],
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (result.success && result.output) {
|
|
180
|
+
const parsed = JSON.parse(result.output) as {
|
|
181
|
+
principle?: string;
|
|
182
|
+
detailed_context?: string;
|
|
183
|
+
};
|
|
184
|
+
principle = parsed.principle || undefined;
|
|
185
|
+
if (!detailedContext) detailedContext = parsed.detailed_context || undefined;
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
/* graceful fallback — capture without principle */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
144
192
|
await captureFailure(
|
|
145
193
|
pending.rating,
|
|
146
194
|
pending.context,
|
|
147
195
|
transcript,
|
|
148
|
-
|
|
196
|
+
detailedContext,
|
|
197
|
+
principle
|
|
149
198
|
);
|
|
150
199
|
} catch {
|
|
151
200
|
// Non-critical
|
|
@@ -58,7 +58,7 @@ copyAgents();
|
|
|
58
58
|
|
|
59
59
|
// --- Copy PAL system docs ---
|
|
60
60
|
const palDocsCount = copyPalDocs();
|
|
61
|
-
log.success(`Installed ${palDocsCount} PAL docs to ~/.
|
|
61
|
+
log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
|
|
62
62
|
|
|
63
63
|
// --- Scaffold PAL settings ---
|
|
64
64
|
scaffoldPalSettings();
|
|
@@ -9,6 +9,7 @@ import { resolve } from "node:path";
|
|
|
9
9
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
10
10
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
11
11
|
import {
|
|
12
|
+
copyAgentsForCursor,
|
|
12
13
|
copyPalDocs,
|
|
13
14
|
copySkills,
|
|
14
15
|
countSkills,
|
|
@@ -47,9 +48,14 @@ const cursorSkillsDir = resolve(CURSOR_DIR, "skills");
|
|
|
47
48
|
copySkills(cursorSkillsDir);
|
|
48
49
|
generateSkillIndex();
|
|
49
50
|
|
|
51
|
+
// --- Copy agents to ~/.cursor/agents/ ---
|
|
52
|
+
const cursorAgentsDir = resolve(CURSOR_DIR, "agents");
|
|
53
|
+
const agentCount = copyAgentsForCursor(cursorAgentsDir);
|
|
54
|
+
if (agentCount > 0) log.success(`Installed ${agentCount} agents to ~/.cursor/agents/`);
|
|
55
|
+
|
|
50
56
|
// --- Copy PAL system docs ---
|
|
51
57
|
const palDocsCount = copyPalDocs();
|
|
52
|
-
log.success(`Installed ${palDocsCount} PAL docs to ~/.
|
|
58
|
+
log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
|
|
53
59
|
|
|
54
60
|
// --- Scaffold PAL settings ---
|
|
55
61
|
scaffoldPalSettings();
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
loadCursorHooksTemplate,
|
|
12
12
|
log,
|
|
13
13
|
readJson,
|
|
14
|
+
removeAgentsFromCursor,
|
|
14
15
|
removePalDocs,
|
|
15
16
|
removeSkills,
|
|
16
17
|
unmergeCursorHooks,
|
|
@@ -46,6 +47,12 @@ if (removed.length > 0) {
|
|
|
46
47
|
log.info("No PAL skills found");
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// --- Remove PAL agents ---
|
|
51
|
+
const removedAgents = removeAgentsFromCursor(resolve(CURSOR_DIR, "agents"));
|
|
52
|
+
if (removedAgents.length > 0) {
|
|
53
|
+
log.success(`Removed ${removedAgents.length} agent(s): ${removedAgents.join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
// --- Remove PAL system docs ---
|
|
50
57
|
removePalDocs();
|
|
51
58
|
|