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.
@@ -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`:
@@ -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
- # Extract data with fallbacks
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
- COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
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; REMAINING=100
23
+ USED=0
24
+ REMAINING=100
20
25
  fi
21
26
 
22
- # Extract git branch — try multiple sources for robustness
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
- # Format directory — show repo name if available, else just folder name
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
- # Format cost — show as $X.XX or 'free' if < $0.01
43
- if (( $(echo "$COST < 0.01" | bc -l) )); then
44
- COST_STR="free"
45
- else
46
- COST_STR=$(printf '$%.2f' "$COST")
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 with usage percentage, cost, and rate limits
163
- echo -e "${CONTEXT_COLOR}${BAR}${RESET} ${USED}% │ ${COST_STR} ${REMAINING}% free${DIM}${RATE_STR}${RESET}"
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,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 {
@@ -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
- /** Copy statusline script to ~/.claude/ for the current platform */
896
- export function copyStatusline(): boolean {
897
- const claudeDir = platform.claudeDir();
898
- mkdirSync(claudeDir, { recursive: true });
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(claudeDir, scriptName);
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(claudeDir, scriptName);
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 to settings if not already present or if using old broken command */
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
- // Keep existing config unless it's the old broken Get-Content pattern
960
- if (!cmd.includes("Get-Content -Raw")) {
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 isPlatformWin32 = process.platform === "win32";
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
- delete settings.statusLine;
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