portable-agent-layer 0.49.0 → 0.50.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/STATUSLINE.md +22 -0
- package/assets/statusline.sh +31 -19
- package/package.json +1 -1
- package/src/hooks/handlers/update-check.ts +2 -2
- package/src/targets/codex/install.ts +13 -0
- package/src/targets/codex/uninstall.ts +11 -0
- package/src/targets/cursor/install.ts +17 -1
- package/src/targets/cursor/uninstall.ts +15 -0
- package/src/targets/lib.ts +124 -23
package/assets/STATUSLINE.md
CHANGED
|
@@ -47,6 +47,28 @@ chmod +x ~/.claude/statusline.sh
|
|
|
47
47
|
|
|
48
48
|
The statusline script is at: `portable-agent-layer/assets/statusline.sh`
|
|
49
49
|
|
|
50
|
+
### Cursor CLI
|
|
51
|
+
|
|
52
|
+
1. Make the script executable:
|
|
53
|
+
```bash
|
|
54
|
+
chmod +x ~/.cursor/statusline.sh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
2. Add to `~/.cursor/cli-config.json`:
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"statusLine": {
|
|
61
|
+
"type": "command",
|
|
62
|
+
"command": "~/.cursor/statusline.sh",
|
|
63
|
+
"padding": 2
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Copy `assets/statusline.sh` to `~/.cursor/statusline.sh` (or symlink). Restart the Cursor CLI after editing config.
|
|
69
|
+
|
|
70
|
+
On Cursor, session cost and rate limits are usually absent from stdin — the script omits the cost segment and hides rate limits automatically. PAL indicators (hooks, ISCs, signal, update) still read from `~/.pal/memory/state/`.
|
|
71
|
+
|
|
50
72
|
### On Windows
|
|
51
73
|
|
|
52
74
|
1. Add to `~/.claude/settings.json`:
|
package/assets/statusline.sh
CHANGED
|
@@ -1,49 +1,57 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# PAL Status Line — macOS/Linux
|
|
3
|
-
# Reads JSON from stdin (Claude Code session data) and prints a formatted status line
|
|
3
|
+
# Reads JSON from stdin (Claude Code / Cursor CLI session data) and prints a formatted status line
|
|
4
4
|
|
|
5
5
|
input=$(cat)
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# Empty or invalid stdin — keep previous status line (Cursor may invoke early)
|
|
8
|
+
if [ -z "$input" ] || ! echo "$input" | jq -e . >/dev/null 2>&1; then
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Extract data with fallbacks (Cursor: cwd, worktree.name; Claude: workspace, worktree.branch)
|
|
8
13
|
MODEL=$(echo "$input" | jq -r '.model.display_name // "Unknown"')
|
|
9
14
|
USED_RAW=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
|
|
10
15
|
REM_RAW=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty')
|
|
11
16
|
USED=$( [ -n "$USED_RAW" ] && echo "${USED_RAW%.*}" || echo 0 )
|
|
12
17
|
REMAINING=$( [ -n "$REM_RAW" ] && echo "${REM_RAW%.*}" || echo 0 )
|
|
13
|
-
|
|
14
|
-
CWD=$(echo "$input" | jq -r '.workspace.current_dir // "~"')
|
|
18
|
+
CWD=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // "~"')
|
|
15
19
|
REPO=$(echo "$input" | jq -r '.workspace.repo.name // ""')
|
|
16
20
|
|
|
17
21
|
# No data yet (pre-first API call)
|
|
18
22
|
if [ -z "$USED_RAW" ] && [ -z "$REM_RAW" ]; then
|
|
19
|
-
USED=0
|
|
23
|
+
USED=0
|
|
24
|
+
REMAINING=100
|
|
20
25
|
fi
|
|
21
26
|
|
|
22
|
-
#
|
|
23
|
-
BRANCH=$(echo "$input" | jq -r '.worktree.branch // ""')
|
|
24
|
-
if [ -z "$BRANCH" ]; then
|
|
27
|
+
# Git branch — payload first, then git in cwd
|
|
28
|
+
BRANCH=$(echo "$input" | jq -r '.worktree.branch // .worktree.name // ""')
|
|
29
|
+
if [ -z "$BRANCH" ] || [ "$BRANCH" = "null" ]; then
|
|
25
30
|
BRANCH=$(git -C "$CWD" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
26
31
|
fi
|
|
27
32
|
|
|
28
|
-
#
|
|
29
|
-
if [ -n "$REPO" ]; then
|
|
33
|
+
# Directory label
|
|
34
|
+
if [ -n "$REPO" ] && [ "$REPO" != "null" ]; then
|
|
30
35
|
DIR_DISPLAY="$REPO"
|
|
31
36
|
else
|
|
32
37
|
DIR_DISPLAY="${CWD##*/}"
|
|
33
38
|
fi
|
|
34
39
|
|
|
35
|
-
# Format git branch with emoji indicator
|
|
36
40
|
if [ -n "$BRANCH" ]; then
|
|
37
41
|
GIT_INDICATOR=" (🌿 ${BRANCH})"
|
|
38
42
|
else
|
|
39
43
|
GIT_INDICATOR=""
|
|
40
44
|
fi
|
|
41
45
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
# Session cost — Claude only; omit segment when absent (Cursor does not send cost)
|
|
47
|
+
COST_STR=""
|
|
48
|
+
if echo "$input" | jq -e '.cost.total_cost_usd != null' >/dev/null 2>&1; then
|
|
49
|
+
COST=$(echo "$input" | jq -r '.cost.total_cost_usd')
|
|
50
|
+
if (( $(echo "$COST < 0.01" | bc -l) )); then
|
|
51
|
+
COST_STR="free"
|
|
52
|
+
else
|
|
53
|
+
COST_STR=$(printf '$%.2f' "$COST")
|
|
54
|
+
fi
|
|
47
55
|
fi
|
|
48
56
|
|
|
49
57
|
# PAL: Hook health — count ERROR lines in debug.log from last 24h
|
|
@@ -76,7 +84,7 @@ if [ -d "$PROJECTS_DIR" ]; then
|
|
|
76
84
|
done
|
|
77
85
|
fi
|
|
78
86
|
|
|
79
|
-
# Rate limits (Pro/Max only — absent for other plans)
|
|
87
|
+
# Rate limits (Pro/Max only — absent for other plans and on Cursor)
|
|
80
88
|
FIVE_H=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
|
|
81
89
|
SEVEN_D=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
|
|
82
90
|
RATE_PARTS=()
|
|
@@ -159,8 +167,12 @@ ISC_STR=""
|
|
|
159
167
|
# Line 1: Model, Directory, Git Branch, Hook Health, Open ISCs, Signal
|
|
160
168
|
echo -e "${CYAN}[${MODEL}]${RESET} 📁 ${DIR_DISPLAY}${DIM}${GIT_INDICATOR}${RESET}${HOOK_STR}${ISC_STR}${SIGNAL_STR}"
|
|
161
169
|
|
|
162
|
-
# Line 2: Context bar
|
|
163
|
-
|
|
170
|
+
# Line 2: Context bar; cost segment only when Claude sends it
|
|
171
|
+
if [ -n "$COST_STR" ]; then
|
|
172
|
+
echo -e "${CONTEXT_COLOR}${BAR}${RESET} ${USED}% │ ${COST_STR} │ ${REMAINING}% free${DIM}${RATE_STR}${RESET}"
|
|
173
|
+
else
|
|
174
|
+
echo -e "${CONTEXT_COLOR}${BAR}${RESET} ${USED}% │ ${REMAINING}% free${DIM}${RATE_STR}${RESET}"
|
|
175
|
+
fi
|
|
164
176
|
|
|
165
177
|
# Line 3: Update available (only when cache says available AND versions differ)
|
|
166
178
|
[ -n "$UPDATE_LINE" ] && echo -e "${YELLOW}${UPDATE_LINE}${RESET}"
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Update checker — detects if a newer version of PAL is available.
|
|
3
3
|
*
|
|
4
|
-
* Repo mode (.git exists next to package): git fetch + compare HEAD vs origin/main
|
|
5
4
|
* Package mode: fetch npm registry for latest version vs installed
|
|
5
|
+
* Repo mode: opt in with PAL_UPDATE_MODE=repo to compare HEAD vs origin/main
|
|
6
6
|
*
|
|
7
7
|
* Caches result in state/update-available.json. Checked at most once per hour.
|
|
8
8
|
*/
|
|
@@ -47,7 +47,7 @@ function writeCache(cache: UpdateCache): void {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function isRepoMode(): boolean {
|
|
50
|
-
return existsSync(resolve(palPkg(), ".git"));
|
|
50
|
+
return process.env.PAL_UPDATE_MODE === "repo" && existsSync(resolve(palPkg(), ".git"));
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function getInstalledVersion(): string {
|
|
@@ -15,6 +15,7 @@ import { resolve } from "node:path";
|
|
|
15
15
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
16
16
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
17
17
|
import {
|
|
18
|
+
addCodexStatuslineConfig,
|
|
18
19
|
copySkills,
|
|
19
20
|
countSkills,
|
|
20
21
|
generateSkillIndex,
|
|
@@ -55,6 +56,17 @@ function enableCodexHooks(configPath: string): void {
|
|
|
55
56
|
log.success("Enabled hooks = true in ~/.codex/config.toml");
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
function enableCodexStatusline(configPath: string): void {
|
|
60
|
+
const content = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
|
|
61
|
+
const updated = addCodexStatuslineConfig(content);
|
|
62
|
+
if (updated === content) {
|
|
63
|
+
log.info("Codex status line already configured in config.toml");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
writeFileSync(configPath, updated, "utf-8");
|
|
67
|
+
log.success("Configured Codex TUI status line in ~/.codex/config.toml");
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
const PKG_ROOT = palPkg().replaceAll("\\", "/");
|
|
59
71
|
const CODEX_DIR = platform.codexDir();
|
|
60
72
|
const HOOKS_FILE = resolve(CODEX_DIR, "hooks.json");
|
|
@@ -102,6 +114,7 @@ log.success("Ensured AGENTS.md symlink at ~/.codex/AGENTS.md");
|
|
|
102
114
|
// --- Enable hooks in config.toml ---
|
|
103
115
|
const CONFIG_FILE = resolve(CODEX_DIR, "config.toml");
|
|
104
116
|
enableCodexHooks(CONFIG_FILE);
|
|
117
|
+
enableCodexStatusline(CONFIG_FILE);
|
|
105
118
|
|
|
106
119
|
log.success("Codex installation complete");
|
|
107
120
|
console.log("");
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
loadCodexHooksTemplate,
|
|
12
12
|
log,
|
|
13
13
|
readJson,
|
|
14
|
+
removeCodexStatuslineConfig,
|
|
14
15
|
removeSkills,
|
|
15
16
|
unmergeCodexHooks,
|
|
16
17
|
unmergeCodexRules,
|
|
@@ -36,6 +37,15 @@ function disableCodexHooks(configPath: string): void {
|
|
|
36
37
|
log.success("Removed hooks from ~/.codex/config.toml");
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function disableCodexStatusline(configPath: string): void {
|
|
41
|
+
if (!existsSync(configPath)) return;
|
|
42
|
+
const content = readFileSync(configPath, "utf-8");
|
|
43
|
+
const updated = removeCodexStatuslineConfig(content);
|
|
44
|
+
if (updated === content) return;
|
|
45
|
+
writeFileSync(configPath, updated, "utf-8");
|
|
46
|
+
log.success("Removed PAL Codex status line from ~/.codex/config.toml");
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
const PKG_ROOT = palPkg().replaceAll("\\", "/");
|
|
40
50
|
const CODEX_DIR = platform.codexDir();
|
|
41
51
|
const HOOKS_FILE = resolve(CODEX_DIR, "hooks.json");
|
|
@@ -80,5 +90,6 @@ if (removed.length > 0) {
|
|
|
80
90
|
// --- Disable hooks in config.toml ---
|
|
81
91
|
const CONFIG_FILE = resolve(CODEX_DIR, "config.toml");
|
|
82
92
|
disableCodexHooks(CONFIG_FILE);
|
|
93
|
+
disableCodexStatusline(CONFIG_FILE);
|
|
83
94
|
|
|
84
95
|
log.success("Codex uninstall complete");
|
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
* Symlinks skills. Generates AGENTS.md.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
9
|
import { writeContextDigests } from "../../hooks/handlers/context-digests";
|
|
10
10
|
import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
|
|
11
11
|
import { assets, palPkg, platform } from "../../hooks/lib/paths";
|
|
12
12
|
import {
|
|
13
|
+
addStatuslineConfig,
|
|
13
14
|
copyAgentsForCursor,
|
|
14
15
|
copyPalDocs,
|
|
15
16
|
copySkills,
|
|
17
|
+
copyStatusline,
|
|
16
18
|
countSkills,
|
|
17
19
|
generateSkillIndex,
|
|
18
20
|
loadCursorHooksTemplate,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
const PKG_ROOT = palPkg().replaceAll("\\", "/");
|
|
27
29
|
const CURSOR_DIR = platform.cursorDir();
|
|
28
30
|
const HOOKS_FILE = resolve(CURSOR_DIR, "hooks.json");
|
|
31
|
+
const CLI_CONFIG = resolve(CURSOR_DIR, "cli-config.json");
|
|
29
32
|
|
|
30
33
|
// --- Ensure ~/.cursor/ exists ---
|
|
31
34
|
mkdirSync(CURSOR_DIR, { recursive: true });
|
|
@@ -61,6 +64,19 @@ log.success(`Installed ${palDocsCount} PAL docs to ~/.pal/docs/`);
|
|
|
61
64
|
// --- Scaffold PAL settings ---
|
|
62
65
|
scaffoldPalSettings();
|
|
63
66
|
|
|
67
|
+
// --- Statusline script + cli-config.json statusLine ---
|
|
68
|
+
copyStatusline("cursor");
|
|
69
|
+
if (!existsSync(CLI_CONFIG)) {
|
|
70
|
+
writeFileSync(CLI_CONFIG, "{}\n", "utf-8");
|
|
71
|
+
log.info("Created new cli-config.json");
|
|
72
|
+
} else {
|
|
73
|
+
copyFileSync(CLI_CONFIG, `${CLI_CONFIG}.bak.${Date.now()}`);
|
|
74
|
+
log.info("Backed up cli-config.json");
|
|
75
|
+
}
|
|
76
|
+
const cliConfig = readJson<Record<string, unknown>>(CLI_CONFIG, {});
|
|
77
|
+
writeJson(CLI_CONFIG, addStatuslineConfig(cliConfig, "cursor"));
|
|
78
|
+
log.success("Merged statusLine into cli-config.json");
|
|
79
|
+
|
|
64
80
|
// --- Generate AGENTS.md ---
|
|
65
81
|
regenerateIfNeeded();
|
|
66
82
|
log.success("Generated AGENTS.md");
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
removeAgentsFromCursor,
|
|
16
16
|
removePalDocs,
|
|
17
17
|
removeSkills,
|
|
18
|
+
removeStatusline,
|
|
19
|
+
removeStatuslineConfig,
|
|
18
20
|
unmergeCursorHooks,
|
|
19
21
|
writeJson,
|
|
20
22
|
} from "../lib";
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
const PKG_ROOT = palPkg().replaceAll("\\", "/");
|
|
23
25
|
const CURSOR_DIR = platform.cursorDir();
|
|
24
26
|
const HOOKS_FILE = resolve(CURSOR_DIR, "hooks.json");
|
|
27
|
+
const CLI_CONFIG = resolve(CURSOR_DIR, "cli-config.json");
|
|
25
28
|
|
|
26
29
|
// --- Remove PAL hooks from hooks.json ---
|
|
27
30
|
if (existsSync(HOOKS_FILE)) {
|
|
@@ -57,6 +60,18 @@ if (removedAgents.length > 0) {
|
|
|
57
60
|
// --- Remove PAL system docs ---
|
|
58
61
|
removePalDocs();
|
|
59
62
|
|
|
63
|
+
// --- Remove statusline script and cli-config statusLine ---
|
|
64
|
+
if (existsSync(CLI_CONFIG)) {
|
|
65
|
+
copyFileSync(CLI_CONFIG, `${CLI_CONFIG}.bak.${Date.now()}`);
|
|
66
|
+
log.info("Backed up cli-config.json");
|
|
67
|
+
const cliConfig = readJson<Record<string, unknown>>(CLI_CONFIG, {});
|
|
68
|
+
writeJson(CLI_CONFIG, removeStatuslineConfig(cliConfig, "cursor"));
|
|
69
|
+
log.success("Removed PAL statusLine from cli-config.json");
|
|
70
|
+
} else {
|
|
71
|
+
log.info("No cli-config.json found, skipping statusLine removal");
|
|
72
|
+
}
|
|
73
|
+
removeStatusline("cursor");
|
|
74
|
+
|
|
60
75
|
// --- Remove ~/.cursor/rules/pal-*.mdc ---
|
|
61
76
|
for (const src of getSemiStaticSources()) {
|
|
62
77
|
try {
|
package/src/targets/lib.ts
CHANGED
|
@@ -485,6 +485,69 @@ export function unmergeCodexRules(existing: string): string {
|
|
|
485
485
|
return cleaned ? `${cleaned}\n` : "";
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
// --- Codex TUI status line ---
|
|
489
|
+
|
|
490
|
+
const PAL_CODEX_STATUS_LINE = [
|
|
491
|
+
"model-with-reasoning",
|
|
492
|
+
"context-remaining",
|
|
493
|
+
"context-used",
|
|
494
|
+
"five-hour-limit",
|
|
495
|
+
"weekly-limit",
|
|
496
|
+
"git-branch",
|
|
497
|
+
"used-tokens",
|
|
498
|
+
"thread-id",
|
|
499
|
+
"current-dir",
|
|
500
|
+
"codex-version",
|
|
501
|
+
] as const;
|
|
502
|
+
|
|
503
|
+
function codexStatusLineToml(): string {
|
|
504
|
+
const quotedItems = PAL_CODEX_STATUS_LINE.map((item) => JSON.stringify(item)).join(
|
|
505
|
+
", "
|
|
506
|
+
);
|
|
507
|
+
return `tui.status_line = [${quotedItems}]\ntui.status_line_use_colors = true\n`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function hasCodexStatusLine(content: string): boolean {
|
|
511
|
+
return /^[ \t]*(?:tui\.)?status_line[ \t]*=/m.test(content);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Add Codex TUI status-line defaults unless the user already configured one. */
|
|
515
|
+
export function addCodexStatuslineConfig(content: string): string {
|
|
516
|
+
if (hasCodexStatusLine(content)) return content;
|
|
517
|
+
|
|
518
|
+
const block = codexStatusLineToml();
|
|
519
|
+
if (content.trim() === "") return block;
|
|
520
|
+
|
|
521
|
+
const firstTable = content.search(/^[ \t]*\[/m);
|
|
522
|
+
if (firstTable === -1) {
|
|
523
|
+
return `${content}${content.endsWith("\n") ? "" : "\n"}${block}`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const before = content.slice(0, firstTable);
|
|
527
|
+
const after = content.slice(firstTable);
|
|
528
|
+
return `${before}${before.endsWith("\n") || before === "" ? "" : "\n"}${block}\n${after}`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Remove PAL's default Codex status-line config if it still matches exactly. */
|
|
532
|
+
export function removeCodexStatuslineConfig(content: string): string {
|
|
533
|
+
const lines = content.split("\n");
|
|
534
|
+
const statusLine = codexStatusLineToml().trim().split("\n")[0];
|
|
535
|
+
const colors = codexStatusLineToml().trim().split("\n")[1];
|
|
536
|
+
const filtered: string[] = [];
|
|
537
|
+
|
|
538
|
+
for (let i = 0; i < lines.length; i++) {
|
|
539
|
+
const line = lines[i]?.trim();
|
|
540
|
+
const next = lines[i + 1]?.trim();
|
|
541
|
+
if (line === statusLine && next === colors) {
|
|
542
|
+
i++;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
filtered.push(lines[i] ?? "");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return filtered.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
549
|
+
}
|
|
550
|
+
|
|
488
551
|
// --- TELOS scaffolding ---
|
|
489
552
|
|
|
490
553
|
/** Copy template files into telos/ without overwriting existing ones */
|
|
@@ -892,17 +955,41 @@ export function loadCopilotHooksTemplate(templatePath: string, pkgRoot: string):
|
|
|
892
955
|
|
|
893
956
|
// --- Statusline ---
|
|
894
957
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
958
|
+
export type StatuslineTarget = "claude" | "cursor";
|
|
959
|
+
|
|
960
|
+
function statuslineAgentDir(target: StatuslineTarget): string {
|
|
961
|
+
return target === "claude" ? platform.claudeDir() : platform.cursorDir();
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function statuslineCommand(target: StatuslineTarget): string {
|
|
965
|
+
const isPlatformWin32 = process.platform === "win32";
|
|
966
|
+
if (target === "cursor") {
|
|
967
|
+
return isPlatformWin32
|
|
968
|
+
? "powershell -NoProfile -File ~/.cursor/statusline.ps1"
|
|
969
|
+
: "~/.cursor/statusline.sh";
|
|
970
|
+
}
|
|
971
|
+
return isPlatformWin32
|
|
972
|
+
? "powershell -NoProfile -File ~/.claude/statusline.ps1"
|
|
973
|
+
: "~/.claude/statusline.sh";
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function isPalStatuslineCommand(cmd: string, target: StatuslineTarget): boolean {
|
|
977
|
+
return target === "claude"
|
|
978
|
+
? cmd.includes(".claude/statusline")
|
|
979
|
+
: cmd.includes(".cursor/statusline");
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/** Copy statusline script to ~/.claude/ or ~/.cursor/ for the current platform */
|
|
983
|
+
export function copyStatusline(target: StatuslineTarget = "claude"): boolean {
|
|
984
|
+
const agentDir = statuslineAgentDir(target);
|
|
985
|
+
mkdirSync(agentDir, { recursive: true });
|
|
899
986
|
|
|
900
987
|
const isPlatformWin32 = process.platform === "win32";
|
|
901
988
|
const sourcePath = isPlatformWin32
|
|
902
989
|
? assets.statuslineScriptPs1()
|
|
903
990
|
: assets.statuslineScriptBash();
|
|
904
991
|
const scriptName = isPlatformWin32 ? "statusline.ps1" : "statusline.sh";
|
|
905
|
-
const destPath = resolve(
|
|
992
|
+
const destPath = resolve(agentDir, scriptName);
|
|
906
993
|
|
|
907
994
|
if (!existsSync(sourcePath)) {
|
|
908
995
|
log.warn(`Statusline script not found at ${sourcePath}`);
|
|
@@ -912,7 +999,6 @@ export function copyStatusline(): boolean {
|
|
|
912
999
|
try {
|
|
913
1000
|
copyFileSync(sourcePath, destPath);
|
|
914
1001
|
|
|
915
|
-
// Make executable on Unix
|
|
916
1002
|
if (process.platform !== "win32") {
|
|
917
1003
|
chmodSync(destPath, 0o755);
|
|
918
1004
|
}
|
|
@@ -925,12 +1011,11 @@ export function copyStatusline(): boolean {
|
|
|
925
1011
|
}
|
|
926
1012
|
}
|
|
927
1013
|
|
|
928
|
-
/** Remove statusline script from ~/.claude/ */
|
|
929
|
-
export function removeStatusline(): boolean {
|
|
930
|
-
const claudeDir = platform.claudeDir();
|
|
1014
|
+
/** Remove statusline script from ~/.claude/ or ~/.cursor/ */
|
|
1015
|
+
export function removeStatusline(target: StatuslineTarget = "claude"): boolean {
|
|
931
1016
|
const isPlatformWin32 = process.platform === "win32";
|
|
932
1017
|
const scriptName = isPlatformWin32 ? "statusline.ps1" : "statusline.sh";
|
|
933
|
-
const scriptPath = resolve(
|
|
1018
|
+
const scriptPath = resolve(statuslineAgentDir(target), scriptName);
|
|
934
1019
|
|
|
935
1020
|
if (!existsSync(scriptPath)) {
|
|
936
1021
|
log.info("Statusline script not found, nothing to remove");
|
|
@@ -947,40 +1032,56 @@ export function removeStatusline(): boolean {
|
|
|
947
1032
|
}
|
|
948
1033
|
}
|
|
949
1034
|
|
|
950
|
-
/** Add statusLine config
|
|
1035
|
+
/** Add statusLine config if not already present or if using an old broken command */
|
|
951
1036
|
export function addStatuslineConfig(
|
|
952
|
-
settings: Record<string, unknown
|
|
1037
|
+
settings: Record<string, unknown>,
|
|
1038
|
+
target: StatuslineTarget = "claude"
|
|
953
1039
|
): Record<string, unknown> {
|
|
954
1040
|
const statusLine = settings.statusLine as Record<string, unknown> | undefined;
|
|
955
1041
|
|
|
956
|
-
// Check if already configured with a valid command (not the old broken one)
|
|
957
1042
|
if (statusLine && typeof statusLine === "object" && statusLine.command) {
|
|
958
1043
|
const cmd = statusLine.command as string;
|
|
959
|
-
|
|
960
|
-
|
|
1044
|
+
if (target === "claude") {
|
|
1045
|
+
// Keep existing config unless it's the old broken Get-Content pattern
|
|
1046
|
+
if (!cmd.includes("Get-Content -Raw")) {
|
|
1047
|
+
return settings;
|
|
1048
|
+
}
|
|
1049
|
+
} else if (!isPalStatuslineCommand(cmd, "cursor")) {
|
|
1050
|
+
// Cursor: preserve user-defined statusLine commands
|
|
961
1051
|
return settings;
|
|
962
1052
|
}
|
|
963
1053
|
}
|
|
964
1054
|
|
|
965
|
-
const
|
|
966
|
-
const command = isPlatformWin32
|
|
967
|
-
? "powershell -NoProfile -File ~/.claude/statusline.ps1"
|
|
968
|
-
: "~/.claude/statusline.sh";
|
|
969
|
-
|
|
1055
|
+
const command = statuslineCommand(target);
|
|
970
1056
|
settings.statusLine = {
|
|
971
1057
|
type: "command",
|
|
972
1058
|
command,
|
|
973
1059
|
padding: 2,
|
|
1060
|
+
...(target === "cursor" ? { updateIntervalMs: 300, timeoutMs: 2000 } : {}),
|
|
974
1061
|
};
|
|
975
1062
|
|
|
976
1063
|
return settings;
|
|
977
1064
|
}
|
|
978
1065
|
|
|
979
|
-
/** Remove statusLine config from settings */
|
|
1066
|
+
/** Remove statusLine config from settings (PAL-owned only for Cursor) */
|
|
980
1067
|
export function removeStatuslineConfig(
|
|
981
|
-
settings: Record<string, unknown
|
|
1068
|
+
settings: Record<string, unknown>,
|
|
1069
|
+
target: StatuslineTarget = "claude"
|
|
982
1070
|
): Record<string, unknown> {
|
|
983
|
-
|
|
1071
|
+
const statusLine = settings.statusLine as Record<string, unknown> | undefined;
|
|
1072
|
+
if (!statusLine || typeof statusLine !== "object") {
|
|
1073
|
+
return settings;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (target === "claude") {
|
|
1077
|
+
delete settings.statusLine;
|
|
1078
|
+
return settings;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const cmd = statusLine.command as string | undefined;
|
|
1082
|
+
if (cmd && isPalStatuslineCommand(cmd, "cursor")) {
|
|
1083
|
+
delete settings.statusLine;
|
|
1084
|
+
}
|
|
984
1085
|
return settings;
|
|
985
1086
|
}
|
|
986
1087
|
|