@narumitw/pi-statusline 0.34.0 → 0.36.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/README.md CHANGED
@@ -8,12 +8,12 @@ without setup and keeps useful context visible as the terminal narrows.
8
8
  A representative uncolored layout:
9
9
 
10
10
  ```text
11
- ░▒▓ 🤖 sonnet-4 🧠 high 📁 pi-extensions 🌿 main ~2 🪟 ctx 42% 💸 $0.184
11
+ ░▒▓ 🤖 sonnet-4 🧠 high 📁 pi-extensions 🌿 main ~2 🪟 ctx 42% 🕒 16:42
12
12
  ```
13
13
 
14
14
  ## ✨ Features
15
15
 
16
- - **Zero-config default:** model, thinking, workspace, Git/PR state, context use, and cost.
16
+ - **Zero-config default:** model, thinking, workspace, Git/PR state, context use, and local time.
17
17
  - **Responsive:** removes lower-priority segments before important information gets clipped.
18
18
  - **Quiet when idle:** activity appears only while Pi is streaming or running tools.
19
19
  - **Easy choices:** three information levels and seven previewable color presets.
@@ -73,7 +73,7 @@ preserved.
73
73
  | Level | Included segments |
74
74
  | --- | --- |
75
75
  | **Minimal** | `model cwd branch context` |
76
- | **Balanced** (default) | `model thinking cwd branch tools context cost` |
76
+ | **Balanced** (default) | `model thinking cwd branch tools context time` |
77
77
  | **Detailed** | `provider model thinking cwd branch tools context tokens cost time` |
78
78
  | **Custom** | Any other segment order, including explicit line breaks |
79
79
 
@@ -125,12 +125,13 @@ The extension uses one user-level file:
125
125
  <getAgentDir()>/pi-statusline.json
126
126
  ```
127
127
 
128
- There are no project or environment overrides. On first session start, pi-statusline atomically
129
- creates a complete editable default. It never overwrites malformed or unreadable settings. Settings
130
- reload on startup, `/reload`, and session replacement.
128
+ There are no project or environment overrides. When the file is absent, pi-statusline uses its
129
+ built-in defaults without creating the file or parent directory. The first successful settings save
130
+ creates a complete editable document atomically. Malformed or unreadable settings are never
131
+ overwritten. Settings reload on startup, `/reload`, and session replacement.
131
132
 
132
- A valid legacy `pi-statusline-settings.json` is migrated without rewriting its contents. If both files
133
- exist, `pi-statusline.json` wins.
133
+ A valid legacy `pi-statusline-settings.json` remains readable with a warning and is never modified
134
+ automatically; rename it to `pi-statusline.json`. If both files exist, `pi-statusline.json` wins.
134
135
 
135
136
  ### Settings reference
136
137
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-statusline",
3
- "version": "0.34.0",
3
+ "version": "0.36.0",
4
4
  "description": "Pi extension that replaces the footer with an information-rich statusline.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/commands.ts CHANGED
@@ -24,6 +24,7 @@ import { segmentPaletteForPreset } from "./presets/index.js";
24
24
  import {
25
25
  DEFAULT_STATUSLINE_DOCUMENT,
26
26
  type LoadedStatuslineSettings,
27
+ removeStatuslineSettingsDocumentIfMatches,
27
28
  saveStatuslineSettingsDocument,
28
29
  } from "./settings.js";
29
30
  import {
@@ -193,21 +194,31 @@ async function choosePalettePreset(
193
194
  }
194
195
  if (selection === undefined) return;
195
196
 
197
+ let loaded: LoadedStatuslineSettings;
196
198
  try {
197
199
  const rawDocument = palettePresetDocument(current, selection);
198
- const loaded = (options.save ?? saveStatuslineSettingsDocument)(
199
- options.settingsPath,
200
- rawDocument,
201
- );
202
- options.apply(loaded, ctx);
203
- const message =
204
- loaded.config.palettePreset === "custom"
205
- ? "Custom palette applied. Edit colors via /statusline → Advanced → Edit settings JSON."
206
- : `Palette preset applied: ${loaded.config.palettePreset}.`;
207
- ctx.ui.notify(message, "info");
200
+ loaded = (options.save ?? saveStatuslineSettingsDocument)(options.settingsPath, rawDocument);
208
201
  } catch (error) {
209
202
  ctx.ui.notify(`Palette preset was not saved: ${formatError(error)}`, "error");
203
+ return;
210
204
  }
205
+ try {
206
+ options.apply(loaded, ctx);
207
+ } catch (error) {
208
+ const rollbackError = restoreStatuslineSettings(ctx, options, current, loaded);
209
+ ctx.ui.notify(
210
+ rollbackError
211
+ ? `Palette preset could not be applied: ${formatError(error)}; rollback failed: ${formatError(rollbackError)}`
212
+ : `Palette preset could not be applied: ${formatError(error)}; previous settings restored.`,
213
+ "error",
214
+ );
215
+ return;
216
+ }
217
+ const message =
218
+ loaded.config.palettePreset === "custom"
219
+ ? "Custom palette applied. Edit colors via /statusline → Advanced → Edit settings JSON."
220
+ : `Palette preset applied: ${loaded.config.palettePreset}.`;
221
+ ctx.ui.notify(message, "info");
211
222
  }
212
223
 
213
224
  async function showPalettePresetPicker(
@@ -587,17 +598,30 @@ async function editSettings(ctx: ExtensionCommandContext, options: StatuslineCom
587
598
  current.rawDocument ?? DEFAULT_STATUSLINE_DOCUMENT,
588
599
  );
589
600
  if (edited === undefined) return;
601
+ let loaded: LoadedStatuslineSettings;
590
602
  try {
591
- const loaded = (options.save ?? saveStatuslineSettingsDocument)(options.settingsPath, edited);
592
- options.apply(loaded, ctx);
593
- const suffix =
594
- loaded.diagnostics.length > 0
595
- ? ` (${loaded.diagnostics.length} warning${loaded.diagnostics.length === 1 ? "" : "s"})`
596
- : "";
597
- ctx.ui.notify(`pi-statusline settings saved and applied${suffix}.`, "info");
603
+ loaded = (options.save ?? saveStatuslineSettingsDocument)(options.settingsPath, edited);
598
604
  } catch (error) {
599
605
  ctx.ui.notify(`pi-statusline settings were not saved: ${formatError(error)}`, "error");
606
+ return;
607
+ }
608
+ try {
609
+ options.apply(loaded, ctx);
610
+ } catch (error) {
611
+ const rollbackError = restoreStatuslineSettings(ctx, options, current, loaded);
612
+ ctx.ui.notify(
613
+ rollbackError
614
+ ? `pi-statusline settings could not be applied: ${formatError(error)}; rollback failed: ${formatError(rollbackError)}`
615
+ : `pi-statusline settings could not be applied: ${formatError(error)}; previous settings restored.`,
616
+ "error",
617
+ );
618
+ return;
600
619
  }
620
+ const suffix =
621
+ loaded.diagnostics.length > 0
622
+ ? ` (${loaded.diagnostics.length} warning${loaded.diagnostics.length === 1 ? "" : "s"})`
623
+ : "";
624
+ ctx.ui.notify(`pi-statusline settings saved and applied${suffix}.`, "info");
601
625
  }
602
626
 
603
627
  function palettePresetDocument(
@@ -771,15 +795,14 @@ function applySegmentsDocumentChange(
771
795
  ctx: ExtensionCommandContext,
772
796
  options: StatuslineCommandOptions,
773
797
  ): LoadedStatuslineSettings {
798
+ const previous = options.getLoaded();
774
799
  const save = options.save ?? saveStatuslineSettingsDocument;
775
800
  const next = save(options.settingsPath, change.nextDocument);
776
801
  try {
777
802
  options.apply(next, ctx);
778
803
  } catch (applyError) {
779
- try {
780
- const restored = save(options.settingsPath, change.previousDocument);
781
- options.apply(restored, ctx);
782
- } catch (rollbackError) {
804
+ const rollbackError = restoreStatuslineSettings(ctx, options, previous, next);
805
+ if (rollbackError) {
783
806
  throw new Error(
784
807
  `runtime update failed: ${formatError(applyError)}; rollback failed: ${formatError(rollbackError)}`,
785
808
  );
@@ -789,6 +812,36 @@ function applySegmentsDocumentChange(
789
812
  return next;
790
813
  }
791
814
 
815
+ function restoreStatuslineSettings(
816
+ ctx: ExtensionCommandContext,
817
+ options: StatuslineCommandOptions,
818
+ previous: LoadedStatuslineSettings,
819
+ saved: LoadedStatuslineSettings,
820
+ ): unknown {
821
+ try {
822
+ if (saved.rawDocument === undefined || saved.fileIdentity === undefined) {
823
+ throw new Error("The saved settings document identity is unavailable");
824
+ }
825
+ removeStatuslineSettingsDocumentIfMatches(
826
+ options.settingsPath,
827
+ saved.rawDocument,
828
+ saved.fileIdentity,
829
+ );
830
+ if (previous.rawDocument === undefined || previous.settingsPath !== options.settingsPath) {
831
+ options.apply(previous, ctx);
832
+ return undefined;
833
+ }
834
+ const restored = (options.save ?? saveStatuslineSettingsDocument)(
835
+ options.settingsPath,
836
+ previous.rawDocument,
837
+ );
838
+ options.apply(restored, ctx);
839
+ return undefined;
840
+ } catch (error) {
841
+ return error;
842
+ }
843
+ }
844
+
792
845
  function normalizeLineBreaks(segments: readonly ConfigSegmentName[]): ConfigSegmentName[] {
793
846
  const normalized: ConfigSegmentName[] = [];
794
847
  for (const segment of segments) {
@@ -808,14 +861,17 @@ function editableSettings(
808
861
  current: LoadedStatuslineSettings,
809
862
  action: string,
810
863
  ): { parsed: Record<string, unknown>; rawDocument: string } {
811
- if (
812
- current.source !== "user" ||
813
- current.rawDocument === undefined ||
814
- current.diagnostics.some((item) => item.code !== "unknown")
815
- ) {
864
+ const rawDocument =
865
+ current.source === "user" && current.rawDocument !== undefined
866
+ ? current.rawDocument
867
+ : current.source === "built-in" &&
868
+ current.rawDocument === undefined &&
869
+ current.diagnostics.length === 0
870
+ ? DEFAULT_STATUSLINE_DOCUMENT
871
+ : undefined;
872
+ if (rawDocument === undefined || current.diagnostics.some((item) => item.code !== "unknown")) {
816
873
  throw new Error(`Fix pi-statusline.json before ${action}`);
817
874
  }
818
- const rawDocument = current.rawDocument;
819
875
  const parsed = JSON.parse(rawDocument) as unknown;
820
876
  if (!isRecord(parsed)) throw new Error("Settings must contain a JSON object");
821
877
  return { parsed, rawDocument };
@@ -831,7 +887,8 @@ function showStatus(ctx: ExtensionCommandContext, options: StatuslineCommandOpti
831
887
  ctx.ui.notify(
832
888
  [
833
889
  `pi-statusline source: ${loaded.source}`,
834
- `path: ${options.settingsPath}`,
890
+ `active path: ${loaded.settingsPath}`,
891
+ `save target: ${options.settingsPath}`,
835
892
  `palette preset: ${loaded.config.palettePreset}`,
836
893
  `density: ${loaded.config.density}`,
837
894
  `separator: ${loaded.config.separator}`,
@@ -8,7 +8,7 @@ export const INFORMATION_PROFILES: Readonly<
8
8
  Record<InformationProfileName, readonly SegmentName[]>
9
9
  > = {
10
10
  minimal: ["model", "cwd", "branch", "context"],
11
- balanced: ["model", "thinking", "cwd", "branch", "tools", "context", "cost"],
11
+ balanced: ["model", "thinking", "cwd", "branch", "tools", "context", "time"],
12
12
  detailed: [
13
13
  "provider",
14
14
  "model",
package/src/settings.ts CHANGED
@@ -1,13 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import {
3
- linkSync,
4
- lstatSync,
5
- mkdirSync,
6
- readFileSync,
7
- renameSync,
8
- rmSync,
9
- writeFileSync,
10
- } from "node:fs";
2
+ import { lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
3
  import { dirname, join } from "node:path";
12
4
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
13
5
  import { INFORMATION_PROFILES } from "./information-profiles.js";
@@ -109,16 +101,10 @@ export interface LoadedStatuslineSettings {
109
101
  source: "built-in" | "user";
110
102
  settingsPath: string;
111
103
  rawDocument?: string;
104
+ fileIdentity?: { dev: number; ino: number };
112
105
  diagnostics: StatuslineConfigDiagnostic[];
113
106
  }
114
107
 
115
- interface InitialFileSystem {
116
- mkdirSync: typeof mkdirSync;
117
- writeFileSync: typeof writeFileSync;
118
- linkSync: typeof linkSync;
119
- rmSync: typeof rmSync;
120
- }
121
-
122
108
  interface AtomicFileSystem {
123
109
  mkdirSync: typeof mkdirSync;
124
110
  writeFileSync: typeof writeFileSync;
@@ -295,7 +281,7 @@ export function loadStatuslineSettings(settingsPath: string): LoadedStatuslineSe
295
281
  try {
296
282
  rawDocument = readFileSync(settingsPath, "utf8");
297
283
  } catch (error) {
298
- if (!pathExists(settingsPath)) {
284
+ if ((error as NodeJS.ErrnoException).code === "ENOENT" && !pathExists(settingsPath)) {
299
285
  return builtInSettings(settingsPath);
300
286
  }
301
287
  return builtInSettings(settingsPath, [
@@ -324,56 +310,32 @@ export function loadStatuslineSettings(settingsPath: string): LoadedStatuslineSe
324
310
  };
325
311
  }
326
312
 
327
- export function loadOrCreateStatuslineSettings(
328
- agentDir = getAgentDir(),
329
- overrides: Partial<InitialFileSystem> = {},
330
- ): LoadedStatuslineSettings {
313
+ export function loadStatuslineSettingsForAgent(agentDir = getAgentDir()): LoadedStatuslineSettings {
331
314
  pendingSettingsNotice = undefined;
332
315
  const canonicalPath = settingsFilePath(agentDir);
333
316
  const legacyPath = join(agentDir, LEGACY_SETTINGS_FILE_NAME);
334
- if (pathExists(canonicalPath)) {
335
- if (pathExists(legacyPath)) {
317
+ const canonical = loadStatuslineSettings(canonicalPath);
318
+ if (!isMissingStatuslineSettings(canonical)) {
319
+ if (!isMissingStatuslineSettings(loadStatuslineSettings(legacyPath))) {
336
320
  pendingSettingsNotice = `${LEGACY_SETTINGS_FILE_NAME} ignored because ${SETTINGS_FILE_NAME} takes precedence.`;
337
321
  }
338
- return loadStatuslineSettings(canonicalPath);
339
- }
340
- if (pathExists(legacyPath)) return migrateLegacySettings(canonicalPath, legacyPath);
341
- return createInitialSettings(canonicalPath, overrides);
342
- }
343
-
344
- function createInitialSettings(
345
- canonicalPath: string,
346
- overrides: Partial<InitialFileSystem>,
347
- ): LoadedStatuslineSettings {
348
- const fs = { mkdirSync, writeFileSync, linkSync, rmSync, ...overrides };
349
- const temporaryPath = temporarySettingsPath(canonicalPath);
350
- try {
351
- fs.mkdirSync(dirname(canonicalPath), { recursive: true });
352
- fs.writeFileSync(temporaryPath, DEFAULT_STATUSLINE_DOCUMENT, {
353
- encoding: "utf8",
354
- flag: "wx",
355
- });
356
- try {
357
- fs.linkSync(temporaryPath, canonicalPath);
358
- } catch (error) {
359
- if (isAlreadyExistsError(error)) return loadStatuslineSettings(canonicalPath);
360
- throw error;
361
- }
362
- return loadStatuslineSettings(canonicalPath);
363
- } catch (error) {
364
- return builtInSettings(canonicalPath, [
365
- diagnostic("warning", "io", "", `Unable to create default settings: ${formatError(error)}`),
366
- ]);
367
- } finally {
368
- removeTemporaryFile(fs.rmSync, temporaryPath);
322
+ return canonical;
369
323
  }
324
+ const legacy = loadStatuslineSettings(legacyPath);
325
+ return isMissingStatuslineSettings(legacy)
326
+ ? canonical
327
+ : migrateLegacySettings(canonicalPath, legacy);
370
328
  }
371
329
 
372
330
  function migrateLegacySettings(
373
331
  canonicalPath: string,
374
- legacyPath: string,
332
+ legacy: LoadedStatuslineSettings,
375
333
  ): LoadedStatuslineSettings {
376
- const legacy = loadStatuslineSettings(legacyPath);
334
+ const racedCanonical = loadStatuslineSettings(canonicalPath);
335
+ if (!isMissingStatuslineSettings(racedCanonical)) {
336
+ pendingSettingsNotice = `${LEGACY_SETTINGS_FILE_NAME} ignored because ${SETTINGS_FILE_NAME} was created concurrently.`;
337
+ return racedCanonical;
338
+ }
377
339
  if (
378
340
  legacy.source !== "user" ||
379
341
  legacy.rawDocument === undefined ||
@@ -382,30 +344,16 @@ function migrateLegacySettings(
382
344
  pendingSettingsNotice = `${LEGACY_SETTINGS_FILE_NAME} is invalid and was ignored.`;
383
345
  return legacy;
384
346
  }
385
- let identity: FileIdentity;
386
- try {
387
- identity = installFileExclusively(canonicalPath, legacy.rawDocument);
388
- } catch (error) {
389
- if (pathExists(canonicalPath)) {
390
- pendingSettingsNotice = `${LEGACY_SETTINGS_FILE_NAME} ignored because ${SETTINGS_FILE_NAME} was created concurrently.`;
391
- return loadStatuslineSettings(canonicalPath);
392
- }
393
- pendingSettingsNotice = `Statusline settings migration failed: ${formatError(error)}. The legacy file was used for this session.`;
394
- return legacy;
395
- }
396
- if (!fileContentsEqual(legacyPath, legacy.rawDocument)) {
397
- pendingSettingsNotice = removeFileIfIdentityMatches(canonicalPath, identity, legacy.rawDocument)
398
- ? `${LEGACY_SETTINGS_FILE_NAME} changed during migration; the stale ${SETTINGS_FILE_NAME} snapshot was removed.`
399
- : `${LEGACY_SETTINGS_FILE_NAME} changed during migration, but ${SETTINGS_FILE_NAME} was replaced concurrently and takes precedence on the next load.`;
400
- return legacy;
401
- }
402
- try {
403
- rmSync(legacyPath);
404
- pendingSettingsNotice = `Statusline settings migrated from ${LEGACY_SETTINGS_FILE_NAME} to ${SETTINGS_FILE_NAME}.`;
405
- } catch (error) {
406
- pendingSettingsNotice = `Statusline settings migrated to ${SETTINGS_FILE_NAME}, but ${LEGACY_SETTINGS_FILE_NAME} could not be removed: ${formatError(error)}.`;
407
- }
408
- return loadStatuslineSettings(canonicalPath);
347
+ pendingSettingsNotice = `Using legacy ${LEGACY_SETTINGS_FILE_NAME}; rename it to ${SETTINGS_FILE_NAME}. Future saves write ${SETTINGS_FILE_NAME} without modifying the legacy file.`;
348
+ return legacy;
349
+ }
350
+
351
+ function isMissingStatuslineSettings(settings: LoadedStatuslineSettings): boolean {
352
+ return (
353
+ settings.source === "built-in" &&
354
+ settings.rawDocument === undefined &&
355
+ settings.diagnostics.length === 0
356
+ );
409
357
  }
410
358
 
411
359
  export function saveStatuslineSettingsDocument(
@@ -426,10 +374,17 @@ export function saveStatuslineSettingsDocument(
426
374
  }
427
375
 
428
376
  const fs = { mkdirSync, writeFileSync, renameSync, rmSync, ...overrides };
377
+ const replaceExisting = pathEntryExists(settingsPath);
429
378
  const temporaryPath = temporarySettingsPath(settingsPath);
379
+ let fileIdentity: { dev: number; ino: number } | undefined;
430
380
  try {
431
381
  fs.mkdirSync(dirname(settingsPath), { recursive: true });
432
382
  fs.writeFileSync(temporaryPath, rawDocument, { encoding: "utf8", flag: "wx" });
383
+ const info = lstatSync(temporaryPath);
384
+ fileIdentity = { dev: info.dev, ino: info.ino };
385
+ if (!replaceExisting && pathEntryExists(settingsPath)) {
386
+ throw new Error(`${SETTINGS_FILE_NAME} was created concurrently; reopen settings and retry.`);
387
+ }
433
388
  fs.renameSync(temporaryPath, settingsPath);
434
389
  } finally {
435
390
  removeTemporaryFile(fs.rmSync, temporaryPath);
@@ -439,10 +394,45 @@ export function saveStatuslineSettingsDocument(
439
394
  source: "user",
440
395
  settingsPath,
441
396
  rawDocument,
397
+ ...(fileIdentity ? { fileIdentity } : {}),
442
398
  diagnostics: normalized.diagnostics,
443
399
  };
444
400
  }
445
401
 
402
+ export function removeStatuslineSettingsDocumentIfMatches(
403
+ settingsPath: string,
404
+ expectedRawDocument: string,
405
+ expectedIdentity: { dev: number; ino: number },
406
+ ): void {
407
+ const quarantinePath = join(
408
+ dirname(settingsPath),
409
+ `.${SETTINGS_FILE_NAME}.${randomUUID()}.rollback`,
410
+ );
411
+ const before = lstatSync(settingsPath);
412
+ if (before.dev !== expectedIdentity.dev || before.ino !== expectedIdentity.ino) {
413
+ throw new Error("Statusline settings changed concurrently; the newer file was preserved");
414
+ }
415
+ renameSync(settingsPath, quarantinePath);
416
+ const quarantined = lstatSync(quarantinePath);
417
+ const quarantinedSavedFile =
418
+ quarantined.isFile() &&
419
+ !quarantined.isSymbolicLink() &&
420
+ quarantined.dev === expectedIdentity.dev &&
421
+ quarantined.ino === expectedIdentity.ino;
422
+ if (quarantinedSavedFile && readFileSync(quarantinePath, "utf8") === expectedRawDocument) {
423
+ rmSync(quarantinePath);
424
+ return;
425
+ }
426
+ if (quarantinedSavedFile && !pathEntryExists(settingsPath)) {
427
+ try {
428
+ renameSync(quarantinePath, settingsPath);
429
+ } catch {
430
+ // Keep the quarantine for recovery when its atomic restoration fails.
431
+ }
432
+ }
433
+ throw new Error("Statusline settings changed concurrently; the newer file was preserved");
434
+ }
435
+
446
436
  export function consumeStatuslineSettingsNotice(): string | undefined {
447
437
  const notice = pendingSettingsNotice;
448
438
  pendingSettingsNotice = undefined;
@@ -452,7 +442,7 @@ export function consumeStatuslineSettingsNotice(): string | undefined {
452
442
  export function readStatuslineSettings(settingsPath?: string): StatuslineConfig {
453
443
  return settingsPath
454
444
  ? loadStatuslineSettings(settingsPath).config
455
- : loadOrCreateStatuslineSettings().config;
445
+ : loadStatuslineSettingsForAgent().config;
456
446
  }
457
447
 
458
448
  export function normalizeStatuslineSettings(value: unknown): StatuslineConfig {
@@ -628,58 +618,25 @@ function removeTemporaryFile(remove: typeof rmSync, temporaryPath: string) {
628
618
  }
629
619
  }
630
620
 
631
- type FileIdentity = { dev: number; ino: number };
632
-
633
- function installFileExclusively(filePath: string, contents: string): FileIdentity {
634
- mkdirSync(dirname(filePath), { recursive: true });
635
- const temporaryPath = temporarySettingsPath(filePath);
636
- try {
637
- writeFileSync(temporaryPath, contents, { encoding: "utf8", flag: "wx" });
638
- const identity = lstatSync(temporaryPath);
639
- linkSync(temporaryPath, filePath);
640
- return { dev: identity.dev, ino: identity.ino };
641
- } finally {
642
- removeTemporaryFile(rmSync, temporaryPath);
643
- }
644
- }
645
-
646
- function removeFileIfIdentityMatches(
647
- filePath: string,
648
- expected: FileIdentity,
649
- expectedContents: string,
650
- ): boolean {
621
+ function pathExists(path: string): boolean {
651
622
  try {
652
- const current = lstatSync(filePath);
653
- if (current.dev !== expected.dev || current.ino !== expected.ino) return false;
654
- if (readFileSync(filePath, "utf8") !== expectedContents) return false;
655
- rmSync(filePath);
623
+ lstatSync(path);
656
624
  return true;
657
625
  } catch {
658
626
  return false;
659
627
  }
660
628
  }
661
629
 
662
- function fileContentsEqual(path: string, expected: string): boolean {
663
- try {
664
- return readFileSync(path, "utf8") === expected;
665
- } catch {
666
- return false;
667
- }
668
- }
669
-
670
- function pathExists(path: string): boolean {
630
+ function pathEntryExists(path: string): boolean {
671
631
  try {
672
632
  lstatSync(path);
673
633
  return true;
674
- } catch {
675
- return false;
634
+ } catch (error) {
635
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return false;
636
+ throw error;
676
637
  }
677
638
  }
678
639
 
679
- function isAlreadyExistsError(error: unknown): boolean {
680
- return error instanceof Error && "code" in error && error.code === "EEXIST";
681
- }
682
-
683
640
  function formatError(error: unknown): string {
684
641
  return error instanceof Error ? error.message : String(error);
685
642
  }
package/src/statusline.ts CHANGED
@@ -15,8 +15,8 @@ import { type RuntimeState, renderExtensionStatusline, renderStatusline } from "
15
15
  import {
16
16
  consumeStatuslineSettingsNotice,
17
17
  type LoadedStatuslineSettings,
18
- loadOrCreateStatuslineSettings,
19
18
  loadStatuslineSettings,
19
+ loadStatuslineSettingsForAgent,
20
20
  settingsFilePath,
21
21
  } from "./settings.js";
22
22
  import type { PalettePreset } from "./types.js";
@@ -197,7 +197,7 @@ export default function statusline(pi: ExtensionAPI) {
197
197
  runtime.turnCount = 0;
198
198
  runtime.activeTools.clear();
199
199
  runtime.isStreaming = false;
200
- loaded = loadOrCreateStatuslineSettings(agentDir);
200
+ loaded = loadStatuslineSettingsForAgent(agentDir);
201
201
  const settingsNotice = consumeStatuslineSettingsNotice();
202
202
  if (settingsNotice) ctx.ui.notify(settingsNotice, "warning");
203
203
  if (loaded.diagnostics.length > 0) {