@tekmidian/pai 0.59.0 → 0.60.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.
Files changed (129) hide show
  1. package/.claude-plugin/plugin.json +8 -33
  2. package/PLUGIN-ARCHITECTURE.md +4 -6
  3. package/dist/{aibroker-client-B8Wtasez.mjs → aibroker-client-BYG-GtAg.mjs} +1 -1
  4. package/dist/{chain-Cltx0NjY.mjs → chain-CGyAIHo4.mjs} +4 -4
  5. package/dist/{chain-Cltx0NjY.mjs.map → chain-CGyAIHo4.mjs.map} +1 -1
  6. package/dist/cli/index.mjs +12 -12
  7. package/dist/cli/program.mjs +12 -12
  8. package/dist/{config-D1omG_UU.mjs → config-CQCmH_gV.mjs} +17 -4
  9. package/dist/{config-D1omG_UU.mjs.map → config-CQCmH_gV.mjs.map} +1 -1
  10. package/dist/{config-DCYWFaEi.mjs → config-DjXq_W42.mjs} +1 -1
  11. package/dist/{context-handover-cache-Dt4HW1Ro.mjs → context-handover-cache-BzM7Qudn.mjs} +4 -4
  12. package/dist/{context-handover-cache-Dt4HW1Ro.mjs.map → context-handover-cache-BzM7Qudn.mjs.map} +1 -1
  13. package/dist/daemon/index.mjs +12 -12
  14. package/dist/{daemon-CmqJfeb9.mjs → daemon-B_TodsIS.mjs} +97 -23
  15. package/dist/daemon-B_TodsIS.mjs.map +1 -0
  16. package/dist/daemon-jviL3VVJ.mjs +19 -0
  17. package/dist/daemon-mcp/index.mjs +9 -9
  18. package/dist/daemon-mcp/index.mjs.map +1 -1
  19. package/dist/{factory-C9wGUe30.mjs → factory-DW7BCJJT.mjs} +1 -1
  20. package/dist/{factory-DiuntvwG.mjs → factory-EUUhSSoq.mjs} +7 -8
  21. package/dist/factory-EUUhSSoq.mjs.map +1 -0
  22. package/dist/{fallback-B_2ozQHw.mjs → fallback-CjdakQVu.mjs} +80 -9
  23. package/dist/fallback-CjdakQVu.mjs.map +1 -0
  24. package/dist/hooks/block-sleep-poll.mjs +7590 -0
  25. package/dist/hooks/block-sleep-poll.mjs.map +7 -0
  26. package/dist/hooks/capture-all-events.mjs +8 -4
  27. package/dist/hooks/capture-all-events.mjs.map +2 -2
  28. package/dist/hooks/capture-session-summary.mjs.map +1 -1
  29. package/dist/hooks/capture-tool-output.mjs.map +1 -1
  30. package/dist/hooks/cleanup-session-files.mjs.map +1 -1
  31. package/dist/hooks/context-compression-hook.mjs +6 -6
  32. package/dist/hooks/context-compression-hook.mjs.map +3 -3
  33. package/dist/hooks/initialize-session.mjs.map +1 -1
  34. package/dist/hooks/load-core-context.mjs.map +1 -1
  35. package/dist/hooks/load-project-context.mjs.map +2 -2
  36. package/dist/hooks/post-compact-inject.mjs +1 -1
  37. package/dist/hooks/post-compact-inject.mjs.map +2 -2
  38. package/dist/hooks/route-agents-to-worker.mjs +5 -4
  39. package/dist/hooks/route-agents-to-worker.mjs.map +3 -3
  40. package/dist/hooks/security-validator.mjs +5 -4
  41. package/dist/hooks/security-validator.mjs.map +3 -3
  42. package/dist/hooks/stop-hook.mjs.map +1 -1
  43. package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
  44. package/dist/hooks/whisper-rules.mjs +5 -4
  45. package/dist/hooks/whisper-rules.mjs.map +3 -3
  46. package/dist/hooks/worker-proxy.mjs +5 -4
  47. package/dist/hooks/worker-proxy.mjs.map +3 -3
  48. package/dist/hooks/worker-status-line.mjs +10 -7
  49. package/dist/hooks/worker-status-line.mjs.map +3 -3
  50. package/dist/hooks/worker-supervision.mjs +5 -4
  51. package/dist/hooks/worker-supervision.mjs.map +3 -3
  52. package/dist/{main-resolver-DYNxxNwJ.mjs → main-resolver-BFQfU5xo.mjs} +100 -21
  53. package/dist/main-resolver-BFQfU5xo.mjs.map +1 -0
  54. package/dist/main-resolver-VMBENifT.mjs +7 -0
  55. package/dist/{planner-DsZZEZCA.mjs → planner-DN967Lvm.mjs} +83 -30
  56. package/dist/planner-DN967Lvm.mjs.map +1 -0
  57. package/dist/{program-B5miXqQa.mjs → program-BnsjB57Q.mjs} +278 -92
  58. package/dist/program-BnsjB57Q.mjs.map +1 -0
  59. package/dist/router-C5hPk9YP.mjs +3 -0
  60. package/dist/{router-BdxdsAvs.mjs → router-DjFZIoDl.mjs} +2 -1
  61. package/dist/router-DjFZIoDl.mjs.map +1 -0
  62. package/dist/{run-XaA-UPdW.mjs → run-FeM1_UVa.mjs} +102 -18
  63. package/dist/run-FeM1_UVa.mjs.map +1 -0
  64. package/dist/{run-env-DyTWgZC9.mjs → run-env-CEHTeIUM.mjs} +2 -2
  65. package/dist/{run-env-DyTWgZC9.mjs.map → run-env-CEHTeIUM.mjs.map} +1 -1
  66. package/dist/{server-CzqHGbKt.mjs → server-CekGOJcp.mjs} +2 -2
  67. package/dist/{server-CzqHGbKt.mjs.map → server-CekGOJcp.mjs.map} +1 -1
  68. package/dist/{session-keepalive-DDsEo81Q.mjs → session-keepalive-Bc9aT678.mjs} +5 -5
  69. package/dist/{session-keepalive-DDsEo81Q.mjs.map → session-keepalive-Bc9aT678.mjs.map} +1 -1
  70. package/dist/{tools-Diro2s8J.mjs → tools-qXyHFwNj.mjs} +2 -2
  71. package/dist/{tools-Diro2s8J.mjs.map → tools-qXyHFwNj.mjs.map} +1 -1
  72. package/dist/{tools-KgL1rkPD.mjs → tools-r-7ZG8B5.mjs} +3 -3
  73. package/dist/{wakeup-D0uvqLsE.mjs → wakeup-C48hcN6O.mjs} +2 -2
  74. package/dist/wakeup-C48hcN6O.mjs.map +1 -0
  75. package/dist/{work-queue-worker-DL9lu8sN.mjs → work-queue-worker-BL4dq5P7.mjs} +2 -2
  76. package/dist/{work-queue-worker-DL9lu8sN.mjs.map → work-queue-worker-BL4dq5P7.mjs.map} +1 -1
  77. package/dist/work-queue-worker-DVOVcEnO.mjs +11 -0
  78. package/docker/init.sql +1 -1
  79. package/docs/commands/README.md +4 -4
  80. package/docs/commands/daemon.md +6 -6
  81. package/docs/commands/session.md +2 -2
  82. package/docs/commands/worker.md +5 -2
  83. package/docs/workers-config.md +5 -1
  84. package/package.json +1 -1
  85. package/plugins/context-preservation/hooks/hooks.json +38 -31
  86. package/plugins/context-preservation/plugin.json +1 -4
  87. package/plugins/core/hooks/hooks.json +51 -46
  88. package/plugins/core/plugin.json +1 -4
  89. package/plugins/creative/plugin.json +1 -5
  90. package/plugins/observability/hooks/hooks.json +87 -72
  91. package/plugins/observability/plugin.json +1 -5
  92. package/plugins/productivity/hooks/hooks.json +22 -19
  93. package/plugins/productivity/plugin.json +2 -5
  94. package/plugins/semantic-search/plugin.json +1 -16
  95. package/plugins/ui/hooks/hooks.json +17 -14
  96. package/plugins/ui/plugin.json +1 -5
  97. package/plugins/zettelkasten/plugin.json +1 -14
  98. package/src/hooks/session-autosave.sh +4 -2
  99. package/src/hooks/session-stop.sh +2 -2
  100. package/src/hooks/ts/capture-all-events.ts +3 -3
  101. package/src/hooks/ts/lib/metadata-extraction.ts +3 -2
  102. package/src/hooks/ts/lib/pai-paths.test.ts +1 -1
  103. package/src/hooks/ts/lib/sleep-poll-gate.test.ts +62 -0
  104. package/src/hooks/ts/lib/sleep-poll-gate.ts +77 -0
  105. package/src/hooks/ts/post-tool-use/capture-tool-output.ts +1 -1
  106. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +1 -1
  107. package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
  108. package/src/hooks/ts/pre-tool-use/block-sleep-poll.test.ts +44 -0
  109. package/src/hooks/ts/pre-tool-use/block-sleep-poll.ts +50 -0
  110. package/src/hooks/ts/session-end/capture-session-summary.ts +1 -1
  111. package/src/hooks/ts/session-start/initialize-session.ts +2 -2
  112. package/src/hooks/ts/session-start/load-core-context.ts +2 -2
  113. package/src/hooks/ts/session-start/load-project-context.ts +1 -1
  114. package/src/hooks/ts/stop/stop-hook.ts +1 -1
  115. package/src/hooks/ts/user-prompt/cleanup-session-files.ts +1 -1
  116. package/statusline-command.sh +26 -11
  117. package/dist/daemon-B6hx1Ysz.mjs +0 -19
  118. package/dist/daemon-CmqJfeb9.mjs.map +0 -1
  119. package/dist/factory-DiuntvwG.mjs.map +0 -1
  120. package/dist/fallback-B_2ozQHw.mjs.map +0 -1
  121. package/dist/main-resolver-DYNxxNwJ.mjs.map +0 -1
  122. package/dist/main-resolver-Df65bPAi.mjs +0 -7
  123. package/dist/planner-DsZZEZCA.mjs.map +0 -1
  124. package/dist/program-B5miXqQa.mjs.map +0 -1
  125. package/dist/router-BXOf_e9e.mjs +0 -3
  126. package/dist/router-BdxdsAvs.mjs.map +0 -1
  127. package/dist/run-XaA-UPdW.mjs.map +0 -1
  128. package/dist/wakeup-D0uvqLsE.mjs.map +0 -1
  129. package/dist/work-queue-worker-BY7xn4pw.mjs +0 -11
@@ -0,0 +1,77 @@
1
+ /**
2
+ * sleep-poll-gate.ts — the decision behind the PreToolUse Bash sleep-poll gate.
3
+ *
4
+ * Pure: no stdin, no filesystem, no process.env read of its own — the hook
5
+ * entry (pre-tool-use/block-sleep-poll.ts) does the I/O, this module decides,
6
+ * same split as lib/agent-gate.ts and lib/edit-gate.ts.
7
+ *
8
+ * CLAUDE.md already says "never sleep-poll"; prose does not enforce it, a
9
+ * PreToolUse deny does. `run_in_background: true` is always allowed — a
10
+ * backgrounded sleep blocks nothing, it is the harness's own long-command
11
+ * pattern.
12
+ */
13
+
14
+ import { longestSleepSecs } from "../../../workers/status.js";
15
+
16
+ export interface SleepPollGateInput {
17
+ tool_name?: string;
18
+ tool_input?: { command?: string; run_in_background?: boolean };
19
+ }
20
+
21
+ export type GateDecision = { decision: "allow" } | { decision: "deny"; reason: string };
22
+
23
+ /**
24
+ * A headless worker has no owner to deliver a background-exit notification
25
+ * to — ending its turn ends the process, so the interactive session's advice
26
+ * ("background it, the harness notifies you") does not apply. Text mirrors
27
+ * the worker contract's own sleep-poll line (workerContractPrompt in
28
+ * src/workers/report.ts) so a denied worker sees the same instruction twice.
29
+ */
30
+ export function denyReason(secs: number, isWorker: boolean): string {
31
+ const lead = `sleep ${secs}s blocked: do not sleep-poll. `;
32
+ if (isWorker) {
33
+ return (
34
+ lead +
35
+ "In a worker, run the long command in the FOREGROUND with a Bash timeout up to 600000 ms. " +
36
+ "If it can run longer, start it in the background and wait with a foreground loop that ends " +
37
+ "when the job ends: while kill -0 <pid> 2>/dev/null; do sleep 15; done"
38
+ );
39
+ }
40
+ return (
41
+ lead +
42
+ "Run the long command itself with run_in_background: true (the harness notifies when it exits), " +
43
+ "or wait on a specific condition with a bounded loop under 60s per step."
44
+ );
45
+ }
46
+
47
+ export function decideSleepPollGate(
48
+ input: SleepPollGateInput,
49
+ env: Record<string, string | undefined>
50
+ ): GateDecision {
51
+ if (input.tool_name && input.tool_name !== "Bash") return { decision: "allow" };
52
+ if (input.tool_input?.run_in_background === true) return { decision: "allow" };
53
+
54
+ const command = input.tool_input?.command;
55
+ if (typeof command !== "string") return { decision: "allow" };
56
+
57
+ const secs = longestSleepSecs(command);
58
+ if (secs === null) return { decision: "allow" };
59
+
60
+ return { decision: "deny", reason: denyReason(secs, env.PAI_WORKER === "1") };
61
+ }
62
+
63
+ /** Render a decision as the JSON the harness reads on the hook's stdout. */
64
+ export function renderDecision(d: GateDecision): string {
65
+ if (d.decision === "allow") {
66
+ return JSON.stringify({
67
+ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow" },
68
+ });
69
+ }
70
+ return JSON.stringify({
71
+ hookSpecificOutput: {
72
+ hookEventName: "PreToolUse",
73
+ permissionDecision: "deny",
74
+ permissionDecisionReason: d.reason,
75
+ },
76
+ });
77
+ }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { appendFileSync, mkdirSync, existsSync } from 'fs';
11
11
  import { join } from 'path';
12
- import { historyDir } from '../lib/pai-paths';
12
+ import { historyDir } from '../lib/pai-paths.js';
13
13
 
14
14
  interface ToolUseData {
15
15
  tool_name: string;
@@ -19,7 +19,7 @@ import {
19
19
  getCurrentNotePath,
20
20
  addWorkToSessionNote,
21
21
  type WorkItem
22
- } from '../lib/project-utils';
22
+ } from '../lib/project-utils.js';
23
23
 
24
24
  interface TodoItem {
25
25
  content: string;
@@ -31,7 +31,7 @@ import {
31
31
  updateTodoContinue,
32
32
  isProbeSession,
33
33
  WorkItem,
34
- } from '../lib/project-utils';
34
+ } from '../lib/project-utils.js';
35
35
  import { getContextFill, formatContextFill } from '../lib/context-fill.js';
36
36
  import { contentToText, isNoiseFilePath, preferCwdFiles } from '../lib/transcript-text.js';
37
37
  import { readContextHandoverCache } from '../lib/context-handover-cache.js';
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { execFileSync } from "node:child_process";
3
+
4
+ // The entry does its own stdin read and calls main() unconditionally at
5
+ // module scope — run it as a real subprocess with its own piped stdin,
6
+ // exactly like route-edits-to-worker.test.ts.
7
+ describe("block-sleep-poll", () => {
8
+ const ENTRYPOINT = "src/hooks/ts/pre-tool-use/block-sleep-poll.ts";
9
+
10
+ function runHook(command: string, extra: Record<string, unknown> = {}, env: NodeJS.ProcessEnv = {}): string {
11
+ // This test may itself run as a worker (PAI_WORKER=1 in its own env),
12
+ // which must not leak into the child and mask the case under test.
13
+ const { PAI_WORKER: _drop, ...baseEnv } = process.env;
14
+ return execFileSync("bun", [ENTRYPOINT], {
15
+ input: JSON.stringify({ tool_name: "Bash", tool_input: { command, ...extra } }),
16
+ encoding: "utf8",
17
+ timeout: 15_000,
18
+ env: { ...baseEnv, ...env },
19
+ });
20
+ }
21
+
22
+ it("denies a long sleep (interactive session)", () => {
23
+ const out = runHook("sleep 590");
24
+ const parsed = JSON.parse(out).hookSpecificOutput;
25
+ expect(parsed.permissionDecision).toBe("deny");
26
+ expect(parsed.permissionDecisionReason).toContain("sleep 590s blocked");
27
+ expect(parsed.permissionDecisionReason).toContain("run_in_background: true");
28
+ });
29
+
30
+ it("denies a long sleep with the worker-specific reason when PAI_WORKER=1", () => {
31
+ const out = runHook("sleep 590", {}, { PAI_WORKER: "1" });
32
+ const parsed = JSON.parse(out).hookSpecificOutput;
33
+ expect(parsed.permissionDecision).toBe("deny");
34
+ expect(parsed.permissionDecisionReason).toContain("FOREGROUND with a Bash timeout up to 600000 ms");
35
+ });
36
+
37
+ it("allows a short sleep (prints nothing)", () => {
38
+ expect(runHook("sleep 30")).toBe("");
39
+ });
40
+
41
+ it("allows a long sleep in the background (prints nothing)", () => {
42
+ expect(runHook("sleep 590", { run_in_background: true })).toBe("");
43
+ });
44
+ });
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * block-sleep-poll.ts — PreToolUse (Bash)
5
+ *
6
+ * Denies a Bash command that contains a `sleep N` of SLEEP_FLOOR_SECS or more
7
+ * as its own command segment — anywhere in the command, not only leading it —
8
+ * unless the call is backgrounded (`run_in_background: true`), which blocks
9
+ * nothing. CLAUDE.md already says "never sleep-poll"; this hook is the
10
+ * enforcement, prose alone does not stop it (observed 2026-09-29: sessions and
11
+ * workers kept running `sleep 590` waits on a background job anyway).
12
+ *
13
+ * This file is only I/O: read stdin, ask lib/sleep-poll-gate for the
14
+ * decision, print it. The decision itself lives in lib/sleep-poll-gate.ts and
15
+ * is tested there.
16
+ */
17
+
18
+ import { decideSleepPollGate, renderDecision, type SleepPollGateInput } from "../lib/sleep-poll-gate.js";
19
+
20
+ function allow(): void {
21
+ process.exit(0);
22
+ }
23
+
24
+ function deny(reason: string): void {
25
+ process.stdout.write(renderDecision({ decision: "deny", reason }) + "\n");
26
+ process.exit(0);
27
+ }
28
+
29
+ async function main(): Promise<void> {
30
+ let text = "";
31
+ try {
32
+ for await (const chunk of process.stdin) text += chunk;
33
+ } catch {
34
+ return allow();
35
+ }
36
+ if (!text.trim()) return allow();
37
+
38
+ let input: SleepPollGateInput;
39
+ try {
40
+ input = JSON.parse(text) as SleepPollGateInput;
41
+ } catch {
42
+ return allow();
43
+ }
44
+
45
+ const decision = decideSleepPollGate(input, process.env);
46
+ if (decision.decision === "deny") return deny(decision.reason);
47
+ return allow();
48
+ }
49
+
50
+ main().catch(() => process.exit(0));
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { writeFileSync, mkdirSync, existsSync, readFileSync, readdirSync } from 'fs';
11
11
  import { join } from 'path';
12
- import { historyDir } from '../lib/pai-paths';
12
+ import { historyDir } from '../lib/pai-paths.js';
13
13
 
14
14
  interface SessionData {
15
15
  conversation_id: string;
@@ -24,8 +24,8 @@ import { isWorkerSession } from "../lib/worker-session.js";
24
24
  import { existsSync, statSync, readFileSync, writeFileSync } from 'fs';
25
25
  import { join } from 'path';
26
26
  import { tmpdir } from 'os';
27
- import { ADAPTER_DIR } from '../lib/pai-paths';
28
- import { sendNtfyNotification, isWhatsAppEnabled, isProbeSession } from '../lib/project-utils';
27
+ import { ADAPTER_DIR } from '../lib/pai-paths.js';
28
+ import { sendNtfyNotification, isWhatsAppEnabled, isProbeSession } from '../lib/project-utils.js';
29
29
 
30
30
  // Debounce duration in milliseconds (prevents duplicate SessionStart events)
31
31
  const DEBOUNCE_MS = 2000;
@@ -28,8 +28,8 @@
28
28
  import { isWorkerSession } from "../lib/worker-session.js";
29
29
  import { readFileSync, existsSync } from 'fs';
30
30
  import { join } from 'path';
31
- import { SKILLS_DIR } from '../lib/pai-paths';
32
- import { isProbeSession } from '../lib/project-utils';
31
+ import { SKILLS_DIR } from '../lib/pai-paths.js';
32
+ import { isProbeSession } from '../lib/project-utils.js';
33
33
  import { stripFrontmatter } from '../lib/frontmatter.js';
34
34
 
35
35
  async function main() {
@@ -35,7 +35,7 @@ import {
35
35
  sendNtfyNotification,
36
36
  isProbeSession,
37
37
  archiveSessionFilesToSessionsDir
38
- } from '../lib/project-utils';
38
+ } from '../lib/project-utils.js';
39
39
 
40
40
  /**
41
41
  * Find the pai CLI binary path dynamically.
@@ -15,7 +15,7 @@ import {
15
15
  isProbeSession,
16
16
  updateTodoContinue, sessionIdFromTranscript,
17
17
  WorkItem
18
- } from '../lib/project-utils';
18
+ } from '../lib/project-utils.js';
19
19
  import { paiHomePath, sessionStateDir } from '../../../config/pai-files.js';
20
20
 
21
21
  // ---------------------------------------------------------------------------
@@ -9,7 +9,7 @@
9
9
  */
10
10
 
11
11
  import { dirname, basename } from 'path';
12
- import { archiveSessionFilesToSessionsDir } from '../lib/project-utils';
12
+ import { archiveSessionFilesToSessionsDir } from '../lib/project-utils.js';
13
13
 
14
14
  interface HookInput {
15
15
  session_id: string;
@@ -28,6 +28,21 @@ claude_env="${PAI_DIR:-$HOME/.claude}/.env"
28
28
  # Read JSON input from stdin
29
29
  input=$(cat)
30
30
 
31
+ # GNU coreutils first, BSD/macOS fallback: `stat -f %m` on GNU is filesystem
32
+ # status (not "file", -f means "filesystem"), so it silently parses the path
33
+ # as a file operand and prints filesystem info instead of failing — a cache
34
+ # never reads as fresh. `stat -c` errors cleanly on BSD, so GNU-first is safe
35
+ # on both.
36
+ file_mtime() {
37
+ stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || printf '%s\n' "${2:-0}"
38
+ }
39
+
40
+ # Same GNU-first/BSD-fallback rule for formatting an epoch: `date -d @epoch`
41
+ # on GNU, `date -r epoch` on BSD/macOS.
42
+ fmt_epoch() {
43
+ date -d "@$1" "+$2" 2>/dev/null || date -r "$1" "+$2" 2>/dev/null || printf ''
44
+ }
45
+
31
46
  # Get Digital Assistant configuration from environment
32
47
  DA_NAME="${DA:-Assistant}" # Assistant name
33
48
  DA_COLOR="${DA_COLOR:-purple}" # Color for the assistant name
@@ -81,7 +96,7 @@ model_id=$(echo "$input" | jq -r '.model.id // .model.display_name // empty' 2>/
81
96
  cc_version=$(echo "$input" | jq -r '.version // empty' 2>/dev/null)
82
97
  if [ -z "$cc_version" ]; then
83
98
  _ccv_cache="${pai_cache_dir}/cc-version"
84
- _ccv_age=$(( $(date +%s) - $(stat -f %m "$_ccv_cache" 2>/dev/null || echo 0) ))
99
+ _ccv_age=$(( $(date +%s) - $(file_mtime "$_ccv_cache" 0) ))
85
100
  if [ -s "$_ccv_cache" ] && [ "$_ccv_age" -lt 86400 ] 2>/dev/null; then
86
101
  read -r cc_version < "$_ccv_cache"
87
102
  elif command -v claude >/dev/null 2>&1; then
@@ -133,8 +148,8 @@ if [ -f "$_workers_yaml" ] && command -v node >/dev/null 2>&1; then
133
148
  _sl_script="$(readlink -f "$0" 2>/dev/null || echo "$0")"
134
149
  _sl_script_dir="$(dirname "$_sl_script")"
135
150
  _providers_cache="${pai_cache_dir}/statusline-providers.json"
136
- _yaml_mtime=$(stat -f %m "$_workers_yaml" 2>/dev/null || echo 0)
137
- _cache_mtime=$(stat -f %m "$_providers_cache" 2>/dev/null || echo -1)
151
+ _yaml_mtime=$(file_mtime "$_workers_yaml" 0)
152
+ _cache_mtime=$(file_mtime "$_providers_cache" -1)
138
153
  if [ ! -s "$_providers_cache" ] || [ "$_cache_mtime" -lt "$_yaml_mtime" ] 2>/dev/null; then
139
154
  mkdir -p "$pai_cache_dir" 2>/dev/null
140
155
  # umask 077 so the temp file is born 0600 — it must never exist at a
@@ -209,7 +224,7 @@ dir_name=$(basename "$current_dir" 2>/dev/null)
209
224
 
210
225
  # Read Whazaa session name from iTerm2 user variable
211
226
  pai_session_name=""
212
- if [ -n "$ITERM_SESSION_ID" ]; then
227
+ if [ -n "$ITERM_SESSION_ID" ] && command -v osascript >/dev/null 2>&1; then
213
228
  ITERM_UUID="${ITERM_SESSION_ID##*:}"
214
229
  pai_session_name=$(osascript << APPLESCRIPT 2>/dev/null
215
230
  tell application "iTerm2"
@@ -634,7 +649,7 @@ fi
634
649
  # buys nothing when the live numbers arrived on stdin.
635
650
  if [ "$session_provider" = "anthropic" ] && [ -z "$usage_source" ]; then
636
651
  if [ -f "$usage_cache" ]; then
637
- cache_age=$(( $(date +%s) - $(stat -f %m "$usage_cache" 2>/dev/null || echo 0) ))
652
+ cache_age=$(( $(date +%s) - $(file_mtime "$usage_cache" 0) ))
638
653
  [ "$cache_age" -gt "$usage_cache_ttl" ] && _fetch_usage &
639
654
  else
640
655
  _fetch_usage &
@@ -643,7 +658,7 @@ fi
643
658
 
644
659
  # Source 2 (fallback): the OAuth endpoint cache, and only while it is fresh.
645
660
  if [ "$session_provider" = "anthropic" ] && [ -z "$usage_source" ] && [ -f "$usage_cache" ]; then
646
- _usage_cache_age=$(( $(date +%s) - $(stat -f %m "$usage_cache" 2>/dev/null || echo 0) ))
661
+ _usage_cache_age=$(( $(date +%s) - $(file_mtime "$usage_cache" 0) ))
647
662
  if [ "$_usage_cache_age" -le "$usage_cache_max_age" ] 2>/dev/null; then
648
663
  usage_source="oauth"
649
664
  five_hour=$(jq -r '.five_hour.utilization // empty' "$usage_cache" 2>/dev/null)
@@ -686,8 +701,8 @@ if [ "$session_provider" = "anthropic" ]; then
686
701
  # Format reset times in local time
687
702
  five_reset_fmt=""
688
703
  seven_reset_fmt=""
689
- [ "$five_reset_epoch" -gt 0 ] 2>/dev/null && five_reset_fmt=$(date -r "$five_reset_epoch" "+%H:%M" 2>/dev/null || echo "")
690
- [ "$seven_reset_epoch" -gt 0 ] 2>/dev/null && seven_reset_fmt=$(date -r "$seven_reset_epoch" "+%a %H:%M" 2>/dev/null || echo "")
704
+ [ "$five_reset_epoch" -gt 0 ] 2>/dev/null && five_reset_fmt=$(fmt_epoch "$five_reset_epoch" "%H:%M")
705
+ [ "$seven_reset_epoch" -gt 0 ] 2>/dev/null && seven_reset_fmt=$(fmt_epoch "$seven_reset_epoch" "%a %H:%M")
691
706
 
692
707
  five_color=$(_usage_color "$five_hour_int")
693
708
  seven_color=$(_usage_color "$seven_day_int")
@@ -834,7 +849,7 @@ if [ "$session_provider" != "anthropic" ]; then
834
849
  # Use cache if fresh, otherwise refresh in the background
835
850
  if [ -n "$usage_url" ]; then
836
851
  if [ -f "$provider_cache" ]; then
837
- cache_age=$(( $(date +%s) - $(stat -f %m "$provider_cache" 2>/dev/null || echo 0) ))
852
+ cache_age=$(( $(date +%s) - $(file_mtime "$provider_cache" 0) ))
838
853
  if [ "$cache_age" -gt "$usage_ttl" ]; then
839
854
  _fetch_provider_usage "$usage_url" "$usage_auth" "$provider_keyfile" "$provider_cache" "$usage_probe" &
840
855
  fi
@@ -879,9 +894,9 @@ if [ "$session_provider" != "anthropic" ]; then
879
894
  # Within the next 24h the weekday carries no information;
880
895
  # beyond it, the bare time would be ambiguous.
881
896
  if [ $(( w_epoch - $(date +%s) )) -lt 86400 ]; then
882
- w_reset_fmt=$(date -r "$w_epoch" "+%H:%M" 2>/dev/null || echo "")
897
+ w_reset_fmt=$(fmt_epoch "$w_epoch" "%H:%M")
883
898
  else
884
- w_reset_fmt=$(date -r "$w_epoch" "+%a %H:%M" 2>/dev/null || echo "")
899
+ w_reset_fmt=$(fmt_epoch "$w_epoch" "%a %H:%M")
885
900
  fi
886
901
  fi
887
902
  fi
@@ -1,19 +0,0 @@
1
- import "./utils-C9HsYDpO.mjs";
2
- import "./migrate-BD7D8EEh.mjs";
3
- import "./factory-DiuntvwG.mjs";
4
- import "./wakeup-D0uvqLsE.mjs";
5
- import "./context-handover-cache-Dt4HW1Ro.mjs";
6
- import "./config-D1omG_UU.mjs";
7
- import "./run-env-DyTWgZC9.mjs";
8
- import "./run-XaA-UPdW.mjs";
9
- import "./server-CzqHGbKt.mjs";
10
- import "./state-n13uPK5B.mjs";
11
- import "./aibroker-client-BK1h--W_.mjs";
12
- import "./session-keepalive-DDsEo81Q.mjs";
13
- import "./embeddings-DfrYNIng.mjs";
14
- import { t as serve } from "./daemon-CmqJfeb9.mjs";
15
- import "./router-BdxdsAvs.mjs";
16
- import "./tools-Diro2s8J.mjs";
17
- import "./work-queue-worker-DL9lu8sN.mjs";
18
-
19
- export { serve };