portable-agent-layer 0.62.2 → 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.
- package/assets/statusline.ps1 +31 -8
- package/assets/statusline.sh +45 -11
- package/assets/templates/hooks.copilot.json +12 -6
- package/package.json +1 -1
- package/src/cli/index.ts +32 -6
- package/src/targets/copilot/uninstall.ts +8 -15
- package/src/targets/cursor/uninstall.ts +4 -16
- package/src/targets/lib.ts +23 -0
package/assets/statusline.ps1
CHANGED
|
@@ -87,14 +87,6 @@ if (Test-Path $projectsDir) {
|
|
|
87
87
|
} catch { $OPEN_ISCS = 0 }
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
# Rate limits (Pro/Max only - absent for other plans)
|
|
91
|
-
$FIVE_H_RAW = $data.rate_limits.five_hour.used_percentage
|
|
92
|
-
$SEVEN_D_RAW = $data.rate_limits.seven_day.used_percentage
|
|
93
|
-
$RATE_PARTS = @()
|
|
94
|
-
if ($FIVE_H_RAW -ne $null) { $RATE_PARTS += "5h: $([int]$FIVE_H_RAW)%" }
|
|
95
|
-
if ($SEVEN_D_RAW -ne $null) { $RATE_PARTS += "7d: $([int]$SEVEN_D_RAW)%" }
|
|
96
|
-
$RATE_STR = if ($RATE_PARTS.Count -gt 0) { " - " + ($RATE_PARTS -join " | ") } else { "" }
|
|
97
|
-
|
|
98
90
|
# Create context progress bar - if both are 0, data not yet available (pre-first API call)
|
|
99
91
|
$NO_DATA = ($USED_RAW -eq $null -and $REM_RAW -eq $null)
|
|
100
92
|
if ($NO_DATA) { $USED = 0; $REM = 100 }
|
|
@@ -115,6 +107,37 @@ $RESET = $ESC + "[0m"
|
|
|
115
107
|
# Choose bar color based on context usage
|
|
116
108
|
$BAR_COLOR = if ($USED -gt 80) { $RED } elseif ($USED -gt 60) { $YELLOW } else { $GREEN }
|
|
117
109
|
|
|
110
|
+
# Rate limits (Pro/Max only - absent for other plans)
|
|
111
|
+
$FIVE_H_RAW = $data.rate_limits.five_hour.used_percentage
|
|
112
|
+
$SEVEN_D_RAW = $data.rate_limits.seven_day.used_percentage
|
|
113
|
+
|
|
114
|
+
# Daily soft limit - spending the weekly budget evenly is 100/7 ~ 14% per day.
|
|
115
|
+
# Shows what is still sustainable per day for the rest of the window: remaining
|
|
116
|
+
# budget divided by days left. Dropping under the even-pace ideal means future
|
|
117
|
+
# days have already been eaten into. Both inputs are server-reported for the
|
|
118
|
+
# whole account, so every machine on the account computes the same figure.
|
|
119
|
+
# resets_at is Unix epoch seconds.
|
|
120
|
+
$DAY_STR = ""
|
|
121
|
+
$RESETS_AT = $data.rate_limits.seven_day.resets_at
|
|
122
|
+
if (($SEVEN_D_RAW -ne $null) -and ($RESETS_AT -ne $null)) {
|
|
123
|
+
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
|
|
124
|
+
$daysLeft = ([double]$RESETS_AT - $now) / 86400
|
|
125
|
+
if ($daysLeft -gt 0) {
|
|
126
|
+
$dayIdeal = 100 / 7
|
|
127
|
+
$dayRate = (100 - [double]$SEVEN_D_RAW) / $daysLeft
|
|
128
|
+
if ($dayRate -lt 0) { $dayRate = 0 }
|
|
129
|
+
$DAY_COLOR = if ($dayRate -lt $dayIdeal) { $RED } elseif ($dayRate -lt ($dayIdeal * 1.1)) { $YELLOW } else { $GREEN }
|
|
130
|
+
$DAY_FLAG = if ($dayRate -lt $dayIdeal) { " !" } else { "" }
|
|
131
|
+
$DAY_STR = $DAY_COLOR + "day: $([int][math]::Round($dayRate))/$([int][math]::Round($dayIdeal))%$DAY_FLAG" + $RESET + $DIM
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
$RATE_PARTS = @()
|
|
136
|
+
if ($FIVE_H_RAW -ne $null) { $RATE_PARTS += "5h: $([int]$FIVE_H_RAW)%" }
|
|
137
|
+
if ($SEVEN_D_RAW -ne $null) { $RATE_PARTS += "7d: $([int]$SEVEN_D_RAW)%" }
|
|
138
|
+
if ($DAY_STR) { $RATE_PARTS += $DAY_STR }
|
|
139
|
+
$RATE_STR = if ($RATE_PARTS.Count -gt 0) { " - " + ($RATE_PARTS -join " | ") } else { "" }
|
|
140
|
+
|
|
118
141
|
# PAL: Signal trend (reads 10-min cache written by session intelligence)
|
|
119
142
|
$SIGNAL_STR = ""
|
|
120
143
|
$signalCache = Join-Path $env:USERPROFILE ".pal\memory\state\signal-cache.json"
|
package/assets/statusline.sh
CHANGED
|
@@ -107,17 +107,6 @@ if [ -d "$PROJECTS_DIR" ]; then
|
|
|
107
107
|
done
|
|
108
108
|
fi
|
|
109
109
|
|
|
110
|
-
# Rate limits (Pro/Max only — absent for other plans and on Cursor)
|
|
111
|
-
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
|
112
|
-
SEVEN_D=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
|
113
|
-
RATE_PARTS=()
|
|
114
|
-
[ -n "$FIVE_H" ] && RATE_PARTS+=("5h: ${FIVE_H%.*}%")
|
|
115
|
-
[ -n "$SEVEN_D" ] && RATE_PARTS+=("7d: ${SEVEN_D%.*}%")
|
|
116
|
-
RATE_STR=""
|
|
117
|
-
if [ ${#RATE_PARTS[@]} -gt 0 ]; then
|
|
118
|
-
RATE_STR=" │ $(IFS=" | "; echo "${RATE_PARTS[*]}")"
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
110
|
# Create context progress bar (20 chars wide)
|
|
122
111
|
FILLED=$((USED / 5))
|
|
123
112
|
EMPTY=$((20 - FILLED))
|
|
@@ -147,6 +136,51 @@ else
|
|
|
147
136
|
CONTEXT_COLOR=$GREEN
|
|
148
137
|
fi
|
|
149
138
|
|
|
139
|
+
# Rate limits (Pro/Max only — absent for other plans and on Cursor)
|
|
140
|
+
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
|
141
|
+
SEVEN_D=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
|
142
|
+
|
|
143
|
+
# Daily soft limit — spending the weekly budget evenly is 100/7 ≈ 14% per day.
|
|
144
|
+
# Shows what is still sustainable per day for the rest of the window: remaining
|
|
145
|
+
# budget divided by days left. Dropping under the even-pace ideal means future
|
|
146
|
+
# days have already been eaten into. Both inputs are server-reported for the
|
|
147
|
+
# whole account, so every machine on the account computes the same figure.
|
|
148
|
+
# resets_at is Unix epoch seconds.
|
|
149
|
+
DAY_STR=""
|
|
150
|
+
RESETS_AT=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
|
|
151
|
+
if [ -n "$SEVEN_D" ] && [ -n "$RESETS_AT" ]; then
|
|
152
|
+
DAYS_LEFT=$(echo "($RESETS_AT - $(date +%s)) / 86400" | bc -l)
|
|
153
|
+
if (( $(echo "$DAYS_LEFT > 0" | bc -l) )); then
|
|
154
|
+
DAY_IDEAL=$(echo "100 / 7" | bc -l)
|
|
155
|
+
DAY_RATE=$(echo "(100 - $SEVEN_D) / $DAYS_LEFT" | bc -l)
|
|
156
|
+
(( $(echo "$DAY_RATE < 0" | bc -l) )) && DAY_RATE=0
|
|
157
|
+
if (( $(echo "$DAY_RATE < $DAY_IDEAL" | bc -l) )); then
|
|
158
|
+
DAY_COLOR=$RED
|
|
159
|
+
DAY_FLAG=" ⚠️"
|
|
160
|
+
elif (( $(echo "$DAY_RATE < $DAY_IDEAL * 1.1" | bc -l) )); then
|
|
161
|
+
DAY_COLOR=$YELLOW
|
|
162
|
+
DAY_FLAG=""
|
|
163
|
+
else
|
|
164
|
+
DAY_COLOR=$GREEN
|
|
165
|
+
DAY_FLAG=""
|
|
166
|
+
fi
|
|
167
|
+
DAY_STR="${DAY_COLOR}day: $(printf '%.0f' "$DAY_RATE")/$(printf '%.0f' "$DAY_IDEAL")%${DAY_FLAG}${RESET}${DIM}"
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
RATE_PARTS=()
|
|
172
|
+
[ -n "$FIVE_H" ] && RATE_PARTS+=("5h: ${FIVE_H%.*}%")
|
|
173
|
+
[ -n "$SEVEN_D" ] && RATE_PARTS+=("7d: ${SEVEN_D%.*}%")
|
|
174
|
+
[ -n "$DAY_STR" ] && RATE_PARTS+=("$DAY_STR")
|
|
175
|
+
RATE_STR=""
|
|
176
|
+
if [ ${#RATE_PARTS[@]} -gt 0 ]; then
|
|
177
|
+
RATE_JOINED="${RATE_PARTS[0]}"
|
|
178
|
+
for PART in "${RATE_PARTS[@]:1}"; do
|
|
179
|
+
RATE_JOINED="${RATE_JOINED} | ${PART}"
|
|
180
|
+
done
|
|
181
|
+
RATE_STR=" │ ${RATE_JOINED}"
|
|
182
|
+
fi
|
|
183
|
+
|
|
150
184
|
# PAL: Signal trend (reads 10-min cache written by session intelligence)
|
|
151
185
|
SIGNAL_STR=""
|
|
152
186
|
SIGNAL_CACHE="$HOME/.pal/memory/state/signal-cache.json"
|
|
@@ -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();
|