@tekmidian/pai 0.49.0 → 0.51.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/dist/{aibroker-client-D5NT0S1N.mjs → aibroker-client-Bu72yPKj.mjs} +1 -1
- package/dist/{aibroker-client-Bn9SnGMa.mjs → aibroker-client-CDIXmq-e.mjs} +1 -1
- package/dist/{aibroker-client-Bn9SnGMa.mjs.map → aibroker-client-CDIXmq-e.mjs.map} +1 -1
- package/dist/{chain-CNSa0A3G.mjs → chain-BBbVbWVv.mjs} +4 -4
- package/dist/{chain-CNSa0A3G.mjs.map → chain-BBbVbWVv.mjs.map} +1 -1
- package/dist/cli/index.mjs +14 -14
- package/dist/cli/program.mjs +14 -14
- package/dist/config-Bn9jpblO.mjs +653 -0
- package/dist/config-Bn9jpblO.mjs.map +1 -0
- package/dist/{config-DxMZjpvY.mjs → config-DYXARgk8.mjs} +180 -151
- package/dist/config-DYXARgk8.mjs.map +1 -0
- package/dist/config-RjeVGJ7K.mjs +3 -0
- package/dist/{context-handover-cache-B9NKSjwR.mjs → context-handover-cache-yOuK9RIk.mjs} +5 -5
- package/dist/{context-handover-cache-B9NKSjwR.mjs.map → context-handover-cache-yOuK9RIk.mjs.map} +1 -1
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/{daemon-DZ4Q0jrs.mjs → daemon-CBTQYtHF.mjs} +49 -30
- package/dist/daemon-CBTQYtHF.mjs.map +1 -0
- package/dist/daemon-Vf7pHnjR.mjs +25 -0
- package/dist/daemon-mcp/index.mjs +186 -23
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/factory-Ba8j9K7q.mjs +3 -0
- package/dist/{factory-DNYAqjH0.mjs → factory-Bt7TWQje.mjs} +3 -3
- package/dist/{factory-DNYAqjH0.mjs.map → factory-Bt7TWQje.mjs.map} +1 -1
- package/dist/{fallback-IeR2FrFn.mjs → fallback-CIGspCSi.mjs} +260 -10
- package/dist/fallback-CIGspCSi.mjs.map +1 -0
- package/dist/hooks/capture-all-events.mjs +80 -8
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/context-compression-hook.mjs +54 -39
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/post-compact-inject.mjs +54 -39
- package/dist/hooks/post-compact-inject.mjs.map +4 -4
- package/dist/hooks/route-agents-to-worker.mjs +204 -109
- package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
- package/dist/hooks/whisper-rules.mjs +7613 -9
- package/dist/hooks/whisper-rules.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +204 -109
- package/dist/hooks/worker-proxy.mjs.map +4 -4
- package/dist/hooks/worker-status-line.mjs +211 -116
- package/dist/hooks/worker-status-line.mjs.map +4 -4
- package/dist/hooks/worker-supervision.mjs +208 -113
- package/dist/hooks/worker-supervision.mjs.map +4 -4
- package/dist/{kg-entity-BSjM5xZh.mjs → kg-entity-BSW7ra8i.mjs} +1 -1
- package/dist/{kg-entity-BSjM5xZh.mjs.map → kg-entity-BSW7ra8i.mjs.map} +1 -1
- package/dist/main-resolver-BTastpp4.mjs +7 -0
- package/dist/{main-resolver-DvYu9jbl.mjs → main-resolver-BmZPCmgj.mjs} +3 -3
- package/dist/{main-resolver-DvYu9jbl.mjs.map → main-resolver-BmZPCmgj.mjs.map} +1 -1
- package/dist/{planner-Cn8HmT7l.mjs → planner-BiH9msqq.mjs} +6 -6
- package/dist/{planner-Cn8HmT7l.mjs.map → planner-BiH9msqq.mjs.map} +1 -1
- package/dist/{program-D9-wfmkZ.mjs → program-srUeFUfW.mjs} +5806 -5615
- package/dist/program-srUeFUfW.mjs.map +1 -0
- package/dist/query-feedback-Bm6XvCmg.mjs +3 -0
- package/dist/{query-feedback-CYyOGi3y.mjs → query-feedback-CTs_jx6i.mjs} +1 -1
- package/dist/{query-feedback-CYyOGi3y.mjs.map → query-feedback-CTs_jx6i.mjs.map} +1 -1
- package/dist/{run-Cls0YGgV.mjs → run-TChOnMO_.mjs} +1388 -1183
- package/dist/run-TChOnMO_.mjs.map +1 -0
- package/dist/{server-Ch99xt5K.mjs → server-R9UkCn3v.mjs} +42 -3
- package/dist/server-R9UkCn3v.mjs.map +1 -0
- package/dist/session-keepalive-BuG3wvik.mjs +579 -0
- package/dist/session-keepalive-BuG3wvik.mjs.map +1 -0
- package/dist/skills/VaultContext/SKILL.md +1 -1
- package/dist/{state-DnYGRmS7.mjs → state-Ca0ltLgI.mjs} +1 -1
- package/dist/{state-PJrgfDLW.mjs → state-DwrsOoMi.mjs} +1 -1
- package/dist/{state-PJrgfDLW.mjs.map → state-DwrsOoMi.mjs.map} +1 -1
- package/dist/{tools-B77WHeVZ.mjs → tools-BEqiJt_j.mjs} +13 -13
- package/dist/{tools-B77WHeVZ.mjs.map → tools-BEqiJt_j.mjs.map} +1 -1
- package/dist/{tools-NnR_n-Ja.mjs → tools-BwsOTmjh.mjs} +2 -2
- package/dist/{wakeup-UrhiW-KU.mjs → wakeup-Yk5lQ0jb.mjs} +1 -1
- package/dist/{wakeup-UrhiW-KU.mjs.map → wakeup-Yk5lQ0jb.mjs.map} +1 -1
- package/dist/{work-queue-worker-BA5IDBwQ.mjs → work-queue-worker-B9zVWImP.mjs} +3 -3
- package/dist/{work-queue-worker-BA5IDBwQ.mjs.map → work-queue-worker-B9zVWImP.mjs.map} +1 -1
- package/dist/work-queue-worker-D-DnQ7oh.mjs +14 -0
- package/dist/{zettelkasten-BBjA7GcP.mjs → zettelkasten-DezbXghl.mjs} +2 -2
- package/dist/{zettelkasten-BBjA7GcP.mjs.map → zettelkasten-DezbXghl.mjs.map} +1 -1
- package/docs/cache-keepalive.md +138 -0
- package/docs/commands/README.md +7 -0
- package/docs/commands/audit.md +1 -1
- package/docs/commands/config.md +84 -0
- package/docs/commands/daemon.md +6 -0
- package/docs/commands/worker.md +37 -2
- package/docs/config.md +171 -0
- package/docs/model-catalogue.md +502 -0
- package/docs/provider-independence-details.md +1 -1
- package/docs/task-bus.md +1 -1
- package/docs/worker.md +33 -5
- package/docs/workers-config.md +105 -12
- package/package.json +1 -1
- package/src/hooks/ts/capture-all-events.test.ts +71 -0
- package/src/hooks/ts/capture-all-events.ts +10 -3
- package/src/hooks/ts/lib/context-fill.test.ts +124 -46
- package/src/hooks/ts/lib/context-fill.ts +57 -14
- package/src/hooks/ts/user-prompt/whisper-rules.test.ts +56 -1
- package/src/hooks/ts/user-prompt/whisper-rules.ts +24 -0
- package/statusline-command.sh +103 -16
- package/dist/config-DCu8THKi.mjs +0 -228
- package/dist/config-DCu8THKi.mjs.map +0 -1
- package/dist/config-DeqFqa1N.mjs +0 -3
- package/dist/config-DxMZjpvY.mjs.map +0 -1
- package/dist/daemon-CYpVy_Dw.mjs +0 -24
- package/dist/daemon-DZ4Q0jrs.mjs.map +0 -1
- package/dist/factory-47Qf_df7.mjs +0 -3
- package/dist/fallback-IeR2FrFn.mjs.map +0 -1
- package/dist/indexer-C5Le7XE7.mjs +0 -1
- package/dist/main-resolver-CnfnWa8C.mjs +0 -7
- package/dist/program-D9-wfmkZ.mjs.map +0 -1
- package/dist/query-feedback-C7rSfiXU.mjs +0 -3
- package/dist/run-Cls0YGgV.mjs.map +0 -1
- package/dist/server-Ch99xt5K.mjs.map +0 -1
- package/dist/work-queue-worker-DLM2wlqM.mjs +0 -14
|
@@ -294,7 +294,8 @@ export function formatContextFill(reading: ContextFillReading): FillDisplay {
|
|
|
294
294
|
// The configured chain — env override, then a default — is the fallback,
|
|
295
295
|
// used ONLY when a project has no compaction history yet:
|
|
296
296
|
//
|
|
297
|
-
// effectiveTrigger = measured ?? (windowSize
|
|
297
|
+
// effectiveTrigger = measured ?? configuredTrigger(windowSize, override ?? 80)
|
|
298
|
+
// configuredTrigger(windowSize, pct) = pct/100 * (windowSize - 20,000)
|
|
298
299
|
//
|
|
299
300
|
// DEFAULT IS 80, NOT 100, WHEN NOTHING IS KNOWN. 100 was considered — it
|
|
300
301
|
// matches the FIRST regime above — and rejected: the costs are asymmetric.
|
|
@@ -313,6 +314,17 @@ export function formatContextFill(reading: ContextFillReading): FillDisplay {
|
|
|
313
314
|
// waiting for one.
|
|
314
315
|
export const DEFAULT_AUTOCOMPACT_PCT = 80;
|
|
315
316
|
|
|
317
|
+
/**
|
|
318
|
+
* The platform's actual compaction trigger for a window/pct pair. NOT
|
|
319
|
+
* windowSize * pct/100 — that formula gives 800,000 on 1M/80 and 160,000 on
|
|
320
|
+
* 200k/80, but the platform fires at pct/100 * (windowSize - 20,000):
|
|
321
|
+
* 784,000 and 144,000, measured on every compact_boundary on this machine,
|
|
322
|
+
* 2026-09-20 (see the module comment above).
|
|
323
|
+
*/
|
|
324
|
+
export function configuredTrigger(windowSize: number, pct: number): number {
|
|
325
|
+
return Math.round((pct / 100) * (windowSize - 20_000));
|
|
326
|
+
}
|
|
327
|
+
|
|
316
328
|
/** How many of the most recent compact_boundary events to consider, and to
|
|
317
329
|
* take the minimum of. */
|
|
318
330
|
const MEASURED_TRIGGER_SAMPLE_SIZE = 3;
|
|
@@ -575,26 +587,57 @@ export function selectedCompactionSamples(
|
|
|
575
587
|
|
|
576
588
|
/**
|
|
577
589
|
* The measured compaction trigger for a project, or null when it has no
|
|
578
|
-
* compaction history yet (a brand-new project,
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
590
|
+
* compaction history yet (a brand-new project, one whose transcripts this
|
|
591
|
+
* process cannot read, or one whose only recent samples belong to a
|
|
592
|
+
* different override regime — see the admissibility filter below).
|
|
593
|
+
* Minimum of the most recent MEASURED_TRIGGER_SAMPLE_SIZE DISTINCT
|
|
594
|
+
* compact_boundary events, ordered by the events' own timestamps across
|
|
595
|
+
* every transcript the project has (live and archived) — see the module
|
|
596
|
+
* comment above for why minimum, not mean, and readCompactBoundarySamples
|
|
597
|
+
* for why "distinct" and "own timestamp" both matter (a file-mtime-ordered,
|
|
598
|
+
* non-deduplicated version of this returned a stale pre-regime-change
|
|
599
|
+
* trigger on real project data).
|
|
600
|
+
*
|
|
601
|
+
* When `configuredTriggerTokens` is given, a sample is admissible only when
|
|
602
|
+
* preTokens >= 0.5 * configuredTriggerTokens — a compaction that fired far
|
|
603
|
+
* below the currently configured trigger came from a lower override or a
|
|
604
|
+
* smaller window, a different regime than the one in force now, and must
|
|
605
|
+
* not drag the trigger down with it. Real case, 2026-09-20:
|
|
606
|
+
* CLAUDE_AUTOCOMPACT_PCT_OVERRIDE was 20 for seven hours, this project
|
|
607
|
+
* compacted three times at ~195k-198k under it, then the override went
|
|
608
|
+
* back to 80 (configured ~784k) — those three samples are inadmissible and
|
|
609
|
+
* must be discarded, not averaged into the new regime's trigger, or the
|
|
610
|
+
* handover would fire at ~155k for days until three fresh ~784k
|
|
611
|
+
* compactions pushed them out on their own.
|
|
586
612
|
*/
|
|
587
613
|
export function measureCompactionTrigger(
|
|
588
614
|
cwd: string,
|
|
589
615
|
projectsDir: string = CLAUDE_PROJECTS_DIR,
|
|
590
|
-
configPath?: string
|
|
616
|
+
configPath?: string,
|
|
617
|
+
configuredTriggerTokens?: number
|
|
591
618
|
): number | null {
|
|
592
619
|
const samples = selectedCompactionSamples(cwd, projectsDir, configPath);
|
|
593
620
|
if (samples.length === 0) return null;
|
|
594
|
-
|
|
621
|
+
|
|
622
|
+
let admissible = samples;
|
|
623
|
+
if (configuredTriggerTokens !== undefined) {
|
|
624
|
+
const floor = 0.5 * configuredTriggerTokens;
|
|
625
|
+
admissible = samples.filter((s) => s.preTokens >= floor);
|
|
626
|
+
const discarded = samples.length - admissible.length;
|
|
627
|
+
if (discarded > 0) {
|
|
628
|
+
console.error(
|
|
629
|
+
`[context-fill] discarded ${discarded} compaction sample(s) as other-regime for ${cwd} ` +
|
|
630
|
+
`(preTokens below ${Math.round(floor)}, half of configured=${configuredTriggerTokens}): ` +
|
|
631
|
+
samples.filter((s) => s.preTokens < floor).map((s) => `${s.preTokens}@${s.timestamp}`).join(", ")
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
if (admissible.length === 0) return null;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const trigger = Math.min(...admissible.map((s) => s.preTokens));
|
|
595
638
|
console.error(
|
|
596
639
|
`[context-fill] measured trigger for ${cwd}: ${trigger} (minimum of ` +
|
|
597
|
-
|
|
640
|
+
admissible.map((s) => `${s.preTokens}@${s.timestamp}`).join(", ") + ")"
|
|
598
641
|
);
|
|
599
642
|
return trigger;
|
|
600
643
|
}
|
|
@@ -717,12 +760,12 @@ export function contextFillThresholds(
|
|
|
717
760
|
): ContextFillThresholds {
|
|
718
761
|
const windowConfirmed = reading.source === "statusline";
|
|
719
762
|
const autocompactPct = resolveAutocompactPct(env);
|
|
720
|
-
const configuredTriggerTokens =
|
|
763
|
+
const configuredTriggerTokens = configuredTrigger(reading.windowSize, autocompactPct);
|
|
721
764
|
|
|
722
765
|
const measuredTriggerTokens = opts.measuredTrigger !== undefined
|
|
723
766
|
? opts.measuredTrigger
|
|
724
767
|
: opts.cwd
|
|
725
|
-
? measureCompactionTrigger(opts.cwd)
|
|
768
|
+
? measureCompactionTrigger(opts.cwd, CLAUDE_PROJECTS_DIR, undefined, configuredTriggerTokens)
|
|
726
769
|
: null;
|
|
727
770
|
|
|
728
771
|
let effectiveTriggerTokens: number;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
3
6
|
|
|
4
7
|
// whisper-rules.ts calls main() unconditionally at module scope (it's a hook
|
|
5
8
|
// entrypoint, always run as a subprocess) — including a blocking read of fd 0.
|
|
@@ -46,4 +49,56 @@ describe("user-prompt/whisper-rules entrypoint", () => {
|
|
|
46
49
|
expect(stdout).toContain("<system-reminder>");
|
|
47
50
|
expect(stdout).toContain("CURRENT LOCAL TIME");
|
|
48
51
|
});
|
|
52
|
+
|
|
53
|
+
describe("cache-keepalive beat prompt", () => {
|
|
54
|
+
const dirs: string[] = [];
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function configuredEnv(config: Record<string, unknown>): NodeJS.ProcessEnv {
|
|
61
|
+
const dir = mkdtempSync(join(tmpdir(), "pai-whisper-keepalive-"));
|
|
62
|
+
dirs.push(dir);
|
|
63
|
+
const file = join(dir, "config.json");
|
|
64
|
+
writeFileSync(file, JSON.stringify(config), "utf8");
|
|
65
|
+
return { ...process.env, PAI_CONFIG_FILE: file };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
it("replies with only the minimal tool-free instruction, bypassing rules/advisor", () => {
|
|
69
|
+
const env = configuredEnv({ sessions: { cacheKeepalive: { enabled: true, prompt: "keepalive" } } });
|
|
70
|
+
const stdout = execFileSync("bun", [ENTRYPOINT], {
|
|
71
|
+
input: JSON.stringify({ prompt: "keepalive" }),
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
timeout: 15_000,
|
|
74
|
+
env,
|
|
75
|
+
});
|
|
76
|
+
expect(stdout).toBe(
|
|
77
|
+
"<system-reminder>\nReply with a single period and nothing else. Do not call tools.\n</system-reminder>\n"
|
|
78
|
+
);
|
|
79
|
+
expect(stdout).not.toContain("CURRENT LOCAL TIME");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("falls through to the normal rule block when the feature is disabled", () => {
|
|
83
|
+
const env = configuredEnv({ sessions: { cacheKeepalive: { enabled: false, prompt: "keepalive" } } });
|
|
84
|
+
const stdout = execFileSync("bun", [ENTRYPOINT], {
|
|
85
|
+
input: JSON.stringify({ prompt: "keepalive" }),
|
|
86
|
+
encoding: "utf8",
|
|
87
|
+
timeout: 15_000,
|
|
88
|
+
env,
|
|
89
|
+
});
|
|
90
|
+
expect(stdout).toContain("CURRENT LOCAL TIME");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("falls through to the normal rule block for a prompt that doesn't match the configured word", () => {
|
|
94
|
+
const env = configuredEnv({ sessions: { cacheKeepalive: { enabled: true, prompt: "keepalive" } } });
|
|
95
|
+
const stdout = execFileSync("bun", [ENTRYPOINT], {
|
|
96
|
+
input: JSON.stringify({ prompt: "please fix the login bug" }),
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
timeout: 15_000,
|
|
99
|
+
env,
|
|
100
|
+
});
|
|
101
|
+
expect(stdout).toContain("CURRENT LOCAL TIME");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
49
104
|
});
|
|
@@ -24,6 +24,7 @@ import { join } from "node:path";
|
|
|
24
24
|
import { tmpdir } from "node:os";
|
|
25
25
|
import { resolveAdvisorMode, type AdvisorConfig } from "../lib/advisor-budget.js";
|
|
26
26
|
import { whisperRulesPath, advisorModePath } from "../../../config/pai-files.js";
|
|
27
|
+
import { loadConfig } from "../../../daemon/config.js";
|
|
27
28
|
|
|
28
29
|
const WHISPER_FILE = whisperRulesPath();
|
|
29
30
|
const ADVISOR_FILE = advisorModePath();
|
|
@@ -181,6 +182,24 @@ function resetReinjectCounter(sessionId: string): void {
|
|
|
181
182
|
* several times per turn during worker-heavy work, so injecting the ~5KB rule
|
|
182
183
|
* block on each one was landing it 3-5 times in a single turn.
|
|
183
184
|
*/
|
|
185
|
+
/**
|
|
186
|
+
* True when this prompt is the daemon's own idle-triggered cache-keepalive
|
|
187
|
+
* beat (sessions.cacheKeepalive, src/daemon/config.ts): the daemon typed the
|
|
188
|
+
* configured word into this session purely to refresh its prompt-cache TTL,
|
|
189
|
+
* not to ask anything. The whole point is a beat that costs as little output
|
|
190
|
+
* as possible, so this bypasses every other rule/advisor injection below —
|
|
191
|
+
* adding ~5KB of context to answer nothing would erase the saving the beat
|
|
192
|
+
* exists to capture.
|
|
193
|
+
*/
|
|
194
|
+
export function isCacheKeepaliveBeat(prompt: string): boolean {
|
|
195
|
+
try {
|
|
196
|
+
const { enabled, prompt: word } = loadConfig().sessions.cacheKeepalive;
|
|
197
|
+
return enabled && prompt.trim() === word;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
184
203
|
export function isRelayedPrompt(prompt: string): boolean {
|
|
185
204
|
return (
|
|
186
205
|
/^\s*\[Session:[^\]]+\]/.test(prompt) ||
|
|
@@ -204,6 +223,11 @@ function main() {
|
|
|
204
223
|
|
|
205
224
|
resetReinjectCounter(sessionId);
|
|
206
225
|
|
|
226
|
+
if (isCacheKeepaliveBeat(prompt)) {
|
|
227
|
+
console.log("<system-reminder>\nReply with a single period and nothing else. Do not call tools.\n</system-reminder>");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
207
231
|
if (isRelayedPrompt(prompt)) return;
|
|
208
232
|
|
|
209
233
|
const isWorker = process.env.PAI_WORKER === "1";
|
package/statusline-command.sh
CHANGED
|
@@ -114,15 +114,90 @@ if [ ! -f "$pai_config" ]; then
|
|
|
114
114
|
pai_config="${XDG_CONFIG_HOME:-$HOME/.config}/pai/config.json"
|
|
115
115
|
fi
|
|
116
116
|
fi
|
|
117
|
+
|
|
118
|
+
# Providers moved to $pai_home_dir/workers.yaml (top-level `providers:`; see
|
|
119
|
+
# src/workers/config.ts readWorkersSection) on 2026-09-19 — jq never sees the
|
|
120
|
+
# YAML directly (no yq on this machine), so a small node script converts it
|
|
121
|
+
# via the `yaml` package sitting next to this script's own real location
|
|
122
|
+
# (readlink -f "$0": this file is usually reached through a symlink at
|
|
123
|
+
# ~/.claude/statusline-command.sh). The conversion is cached under
|
|
124
|
+
# pai_cache_dir, the cache kept only while it is newer than workers.yaml's own
|
|
125
|
+
# mtime, so node is spawned only when the YAML actually changed. Resolved once
|
|
126
|
+
# into $providers_file (holding just the providers object, unwrapped) so both
|
|
127
|
+
# this lookup and the usage block below read the same data — never re-derived.
|
|
128
|
+
# Any failure (no workers.yaml, no node, parse error) falls back silently to
|
|
129
|
+
# the legacy `.workers.providers` of $pai_config, then to anthropic.
|
|
130
|
+
providers_file=""
|
|
131
|
+
_workers_yaml="${pai_home_dir}/workers.yaml"
|
|
132
|
+
if [ -f "$_workers_yaml" ] && command -v node >/dev/null 2>&1; then
|
|
133
|
+
_sl_script="$(readlink -f "$0" 2>/dev/null || echo "$0")"
|
|
134
|
+
_sl_script_dir="$(dirname "$_sl_script")"
|
|
135
|
+
_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)
|
|
138
|
+
if [ ! -s "$_providers_cache" ] || [ "$_cache_mtime" -lt "$_yaml_mtime" ] 2>/dev/null; then
|
|
139
|
+
mkdir -p "$pai_cache_dir" 2>/dev/null
|
|
140
|
+
# umask 077 so the temp file is born 0600 — it must never exist at a
|
|
141
|
+
# world-readable mode even transiently, since providers carry inline
|
|
142
|
+
# `key:` secrets and only the allow-listed fields below leave this
|
|
143
|
+
# subshell.
|
|
144
|
+
(
|
|
145
|
+
umask 077
|
|
146
|
+
NODE_PATH="${_sl_script_dir}/node_modules" node - "$_workers_yaml" \
|
|
147
|
+
> "${_providers_cache}.tmp" 2>/dev/null <<'PAI_YAML_PROVIDERS'
|
|
148
|
+
const fs = require("fs");
|
|
149
|
+
try {
|
|
150
|
+
const YAML = require("yaml");
|
|
151
|
+
const doc = YAML.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
152
|
+
const providers = (doc && doc.providers) || {};
|
|
153
|
+
// Allow-list: only what the status line ever reads from a provider.
|
|
154
|
+
// workers.yaml is 0600 because providers carry inline `key:` secrets —
|
|
155
|
+
// this cache must never repeat them (or `env`, `url`, `headers`, …).
|
|
156
|
+
const safe = {};
|
|
157
|
+
for (const [name, p] of Object.entries(providers || {})) {
|
|
158
|
+
safe[name] = {
|
|
159
|
+
models: (p && p.models) || {},
|
|
160
|
+
usage: (p && p.usage) || undefined,
|
|
161
|
+
keyFile: (p && p.keyFile) || undefined,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
process.stdout.write(JSON.stringify(safe));
|
|
165
|
+
} catch {
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
PAI_YAML_PROVIDERS
|
|
169
|
+
)
|
|
170
|
+
if [ -s "${_providers_cache}.tmp" ]; then
|
|
171
|
+
mv "${_providers_cache}.tmp" "$_providers_cache"
|
|
172
|
+
else
|
|
173
|
+
rm -f "${_providers_cache}.tmp"
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
[ -s "$_providers_cache" ] && providers_file="$_providers_cache"
|
|
177
|
+
fi
|
|
178
|
+
if [ -z "$providers_file" ] && [ -f "$pai_config" ] && command -v jq >/dev/null 2>&1; then
|
|
179
|
+
_providers_legacy="${pai_cache_dir}/statusline-providers-legacy.json"
|
|
180
|
+
mkdir -p "$pai_cache_dir" 2>/dev/null
|
|
181
|
+
# Same allow-list and 0600-from-birth handling as the YAML path above:
|
|
182
|
+
# this legacy config can carry the same inline `key:` secrets.
|
|
183
|
+
if (umask 077; jq -c '(.workers.providers // {}) | with_entries(.value |= {models, usage, keyFile})' "$pai_config" > "${_providers_legacy}.tmp" 2>/dev/null) \
|
|
184
|
+
&& [ -s "${_providers_legacy}.tmp" ]; then
|
|
185
|
+
mv "${_providers_legacy}.tmp" "$_providers_legacy"
|
|
186
|
+
providers_file="$_providers_legacy"
|
|
187
|
+
else
|
|
188
|
+
rm -f "${_providers_legacy}.tmp"
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
|
|
117
192
|
model_base="${model_id%%\[*}"
|
|
118
193
|
session_provider=anthropic
|
|
119
|
-
if [ -n "$model_base" ] && [ -
|
|
194
|
+
if [ -n "$model_base" ] && [ -n "$providers_file" ] && command -v jq >/dev/null 2>&1; then
|
|
120
195
|
_matched_provider=$(jq -r --arg mb "$model_base" '
|
|
121
|
-
(.
|
|
196
|
+
(. // {}) | to_entries[] as $p
|
|
122
197
|
| (($p.value.models // {}) | to_entries[]) as $m
|
|
123
198
|
| ($m.value | tostring) as $v
|
|
124
199
|
| select($v == $mb or ($v | split("[")[0]) == $mb)
|
|
125
|
-
| $p.key' "$
|
|
200
|
+
| $p.key' "$providers_file" 2>/dev/null | head -1)
|
|
126
201
|
[ -n "$_matched_provider" ] && session_provider="$_matched_provider"
|
|
127
202
|
fi
|
|
128
203
|
|
|
@@ -624,8 +699,12 @@ if [ "$session_provider" = "anthropic" ]; then
|
|
|
624
699
|
remaining_secs=$((seven_reset_epoch - now_epoch))
|
|
625
700
|
[ "$remaining_secs" -lt 0 ] && remaining_secs=0
|
|
626
701
|
elapsed_secs=$((window_secs - remaining_secs))
|
|
627
|
-
# Daily pace: actual spend/day vs
|
|
628
|
-
# Budget = remaining capacity / remaining days
|
|
702
|
+
# Daily pace: actual spend/day vs the flat weekly share.
|
|
703
|
+
# Budget = static 100/7 (not remaining capacity / remaining days): the
|
|
704
|
+
# user reads "1d" as "what I may spend today", a fixed share of the
|
|
705
|
+
# week, not a figure that shrinks as the week is used — dividing the
|
|
706
|
+
# shrinking remainder by the shrinking remaining days double-counted
|
|
707
|
+
# overspend and drove the pace red for no new reason.
|
|
629
708
|
elapsed_days_x10=$((elapsed_secs * 10 / 86400))
|
|
630
709
|
# A per-day rate divided by a fraction of a day is not a rate, it is an
|
|
631
710
|
# extrapolation from noise: an hour into a fresh window the old clamp to
|
|
@@ -633,10 +712,7 @@ if [ "$session_provider" = "anthropic" ]; then
|
|
|
633
712
|
# Below half a day there is nothing to pace against yet — say nothing.
|
|
634
713
|
if [ "$elapsed_days_x10" -ge 5 ]; then
|
|
635
714
|
spend_per_day=$((seven_day_int * 10 / elapsed_days_x10))
|
|
636
|
-
|
|
637
|
-
[ "$remaining_days_x10" -lt 1 ] && remaining_days_x10=1
|
|
638
|
-
remaining_budget=$((100 - seven_day_int))
|
|
639
|
-
budget_per_day=$((remaining_budget * 10 / remaining_days_x10))
|
|
715
|
+
budget_per_day=$((100 / 7))
|
|
640
716
|
# Color: green = under budget, orange = near budget, red = over budget
|
|
641
717
|
overspend=$((spend_per_day - budget_per_day))
|
|
642
718
|
if [ "$overspend" -le -3 ] 2>/dev/null; then
|
|
@@ -738,9 +814,9 @@ _fetch_provider_usage() {
|
|
|
738
814
|
if [ "$session_provider" != "anthropic" ]; then
|
|
739
815
|
provider_usage=""
|
|
740
816
|
provider_keyfile=""
|
|
741
|
-
if [ -
|
|
742
|
-
provider_usage=$(jq -c --arg p "$session_provider" '.
|
|
743
|
-
provider_keyfile=$(jq -r --arg p "$session_provider" '.
|
|
817
|
+
if [ -n "$providers_file" ] && command -v jq >/dev/null 2>&1; then
|
|
818
|
+
provider_usage=$(jq -c --arg p "$session_provider" '.[$p].usage // empty' "$providers_file" 2>/dev/null)
|
|
819
|
+
provider_keyfile=$(jq -r --arg p "$session_provider" '.[$p].keyFile // empty' "$providers_file" 2>/dev/null)
|
|
744
820
|
case "$provider_keyfile" in "~/"*) provider_keyfile="$HOME/${provider_keyfile#\~/}" ;; esac
|
|
745
821
|
fi
|
|
746
822
|
|
|
@@ -828,10 +904,21 @@ if [ "$session_provider" != "anthropic" ]; then
|
|
|
828
904
|
fi
|
|
829
905
|
|
|
830
906
|
# LINE 3 - Context meter + usage limits
|
|
831
|
-
# Auto-compact remaining: how much context left until compaction triggers
|
|
907
|
+
# Auto-compact remaining: how much context left until compaction triggers.
|
|
908
|
+
# Claude Code fires the auto-compact hook at pct/100 * (window - 20000)
|
|
909
|
+
# tokens, not at pct% of the raw window — the first 20k tokens of any window
|
|
910
|
+
# are reserved and never count toward the trigger. Computing "% left" against
|
|
911
|
+
# the raw window (as a plain threshold-context_pct subtraction did) reads as
|
|
912
|
+
# "the window is full" on a 1M-token session that still has 80% of its real
|
|
913
|
+
# capacity free. Work in tokens, then re-express as a percent of the real
|
|
914
|
+
# window so the figure is comparable across window sizes.
|
|
832
915
|
ac_threshold="${CLAUDE_AUTOCOMPACT_PCT_OVERRIDE:-80}"
|
|
833
|
-
|
|
834
|
-
|
|
916
|
+
ac_trigger_tokens=$((ac_threshold * (context_size - 20000) / 100))
|
|
917
|
+
ac_used_tokens=$((context_pct * context_size / 100))
|
|
918
|
+
ac_left_tokens=$((ac_trigger_tokens - ac_used_tokens))
|
|
919
|
+
[ "$ac_left_tokens" -lt 0 ] && ac_left_tokens=0
|
|
920
|
+
ac_remaining=$((ac_left_tokens * 100 / context_size))
|
|
921
|
+
ac_left_k=$((ac_left_tokens / 1000))
|
|
835
922
|
# Color the remaining %: red ≤5, yellow ≤15, green otherwise
|
|
836
923
|
if [ "$ac_remaining" -le 5 ] 2>/dev/null; then
|
|
837
924
|
ac_color="$BRIGHT_RED"
|
|
@@ -840,7 +927,7 @@ elif [ "$ac_remaining" -le 15 ] 2>/dev/null; then
|
|
|
840
927
|
else
|
|
841
928
|
ac_color="$BRIGHT_GREEN"
|
|
842
929
|
fi
|
|
843
|
-
ac_suffix=" ${ac_color}(${ac_remaining}%% left)${RESET}"
|
|
930
|
+
ac_suffix=" ${ac_color}(${ac_remaining}%% left, ${ac_left_k}K)${RESET}"
|
|
844
931
|
|
|
845
932
|
if [ "$context_pct" -gt 0 ] 2>/dev/null; then
|
|
846
933
|
# Color based on usage: green < 50%, yellow 50-75%, red > 75%
|
package/dist/config-DCu8THKi.mjs
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { a as paiHomePath, o as resolvePaiFile, r as migratePaiFile } from "./pai-home-ChAOupZc.mjs";
|
|
2
|
-
import { i as paiSocketPath } from "./runtime-paths-B0P1TvUr.mjs";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { homedir, userInfo } from "node:os";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
|
-
|
|
7
|
-
//#region src/notifications/types.ts
|
|
8
|
-
const DEFAULT_ROUTING = {
|
|
9
|
-
error: [
|
|
10
|
-
"whatsapp",
|
|
11
|
-
"macos",
|
|
12
|
-
"ntfy",
|
|
13
|
-
"cli"
|
|
14
|
-
],
|
|
15
|
-
completion: [
|
|
16
|
-
"whatsapp",
|
|
17
|
-
"macos",
|
|
18
|
-
"ntfy",
|
|
19
|
-
"cli"
|
|
20
|
-
],
|
|
21
|
-
info: ["cli"],
|
|
22
|
-
progress: ["cli"],
|
|
23
|
-
debug: []
|
|
24
|
-
};
|
|
25
|
-
const DEFAULT_CHANNELS = {
|
|
26
|
-
ntfy: {
|
|
27
|
-
enabled: false,
|
|
28
|
-
url: void 0,
|
|
29
|
-
priority: "default"
|
|
30
|
-
},
|
|
31
|
-
whatsapp: {
|
|
32
|
-
enabled: true,
|
|
33
|
-
recipient: void 0
|
|
34
|
-
},
|
|
35
|
-
macos: { enabled: true },
|
|
36
|
-
voice: {
|
|
37
|
-
enabled: false,
|
|
38
|
-
voiceName: "bm_george"
|
|
39
|
-
},
|
|
40
|
-
cli: { enabled: true }
|
|
41
|
-
};
|
|
42
|
-
const DEFAULT_NOTIFICATION_CONFIG = {
|
|
43
|
-
mode: "auto",
|
|
44
|
-
channels: DEFAULT_CHANNELS,
|
|
45
|
-
routing: DEFAULT_ROUTING
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region src/tasks/types.ts
|
|
50
|
-
/**
|
|
51
|
-
* Prefix marking a tracker label as a PAI ownership assertion.
|
|
52
|
-
* A task labelled `pai:acme-api` is owned by the `acme-api` project.
|
|
53
|
-
*/
|
|
54
|
-
const OWNER_LABEL_PREFIX = "pai:";
|
|
55
|
-
/** An unresolved owner. UNROUTED is a normal state, not an error. */
|
|
56
|
-
const UNROUTED = {
|
|
57
|
-
project: null,
|
|
58
|
-
rootPath: null,
|
|
59
|
-
source: "none"
|
|
60
|
-
};
|
|
61
|
-
const DEFAULT_TASK_CONFIG = {
|
|
62
|
-
enabled: false,
|
|
63
|
-
providers: { todoist: { enabled: false } },
|
|
64
|
-
autoDispatch: false
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
//#endregion
|
|
68
|
-
//#region src/daemon/config.ts
|
|
69
|
-
/**
|
|
70
|
-
* config.ts — Configuration loader for PAI Daemon
|
|
71
|
-
*
|
|
72
|
-
* Loads config from ~/.claude/pai/config.json (the pre-2026-09-19 location
|
|
73
|
-
* was ~/.config/pai/config.json, briefly ~/.claude/pai.json in between —
|
|
74
|
-
* see paiConfigFilePath/migrateConfigFile).
|
|
75
|
-
* Deep-merges with defaults so partial configs work fine.
|
|
76
|
-
* Expands ~ in path values at runtime.
|
|
77
|
-
*/
|
|
78
|
-
/** Derive a per-user Postgres database name: pai_<username> */
|
|
79
|
-
function perUserDbName() {
|
|
80
|
-
return `pai_${userInfo().username.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase()}`;
|
|
81
|
-
}
|
|
82
|
-
/** Derive the per-user connection string */
|
|
83
|
-
function perUserConnectionString() {
|
|
84
|
-
return `postgresql://pai:pai@localhost:5432/${perUserDbName()}`;
|
|
85
|
-
}
|
|
86
|
-
const DEFAULTS = {
|
|
87
|
-
socketPath: paiSocketPath(),
|
|
88
|
-
indexIntervalSecs: 300,
|
|
89
|
-
embedIntervalSecs: 600,
|
|
90
|
-
embedOnStartup: false,
|
|
91
|
-
storageBackend: "sqlite",
|
|
92
|
-
postgres: {
|
|
93
|
-
connectionString: perUserConnectionString(),
|
|
94
|
-
maxConnections: 5,
|
|
95
|
-
connectionTimeoutMs: 5e3
|
|
96
|
-
},
|
|
97
|
-
embeddingModel: "Snowflake/snowflake-arctic-embed-m-v1.5",
|
|
98
|
-
logLevel: "info",
|
|
99
|
-
notifications: DEFAULT_NOTIFICATION_CONFIG,
|
|
100
|
-
tasks: DEFAULT_TASK_CONFIG,
|
|
101
|
-
identity: { selfEmails: [] },
|
|
102
|
-
search: {
|
|
103
|
-
mode: "keyword",
|
|
104
|
-
rerank: true,
|
|
105
|
-
recencyBoostDays: 90,
|
|
106
|
-
defaultLimit: 10,
|
|
107
|
-
snippetLength: 200
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
/** Config template — generated at runtime so the DB name is per-user */
|
|
111
|
-
function configTemplate() {
|
|
112
|
-
return `{
|
|
113
|
-
"socketPath": "/tmp/pai.sock",
|
|
114
|
-
"indexIntervalSecs": 300,
|
|
115
|
-
"embedIntervalSecs": 600,
|
|
116
|
-
"storageBackend": "sqlite",
|
|
117
|
-
"postgres": {
|
|
118
|
-
"connectionString": "${perUserConnectionString()}",
|
|
119
|
-
"maxConnections": 5,
|
|
120
|
-
"connectionTimeoutMs": 5000
|
|
121
|
-
},
|
|
122
|
-
"embeddingModel": "Snowflake/snowflake-arctic-embed-m-v1.5",
|
|
123
|
-
"logLevel": "info",
|
|
124
|
-
"vaultPath": "",
|
|
125
|
-
"vaultProjectId": 0,
|
|
126
|
-
"search": {
|
|
127
|
-
"mode": "keyword",
|
|
128
|
-
"rerank": true,
|
|
129
|
-
"recencyBoostDays": 90,
|
|
130
|
-
"defaultLimit": 10,
|
|
131
|
-
"snippetLength": 200
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
`;
|
|
135
|
-
}
|
|
136
|
-
/** Expand a leading ~ to the real home directory */
|
|
137
|
-
function expandHome(p) {
|
|
138
|
-
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) return join(homedir(), p.slice(1));
|
|
139
|
-
return p;
|
|
140
|
-
}
|
|
141
|
-
/** Canonical location since 2026-09-19: under the PAI_HOME namespace dir,
|
|
142
|
-
* so nothing PAI writes can collide with a file Claude Code itself owns. */
|
|
143
|
-
const NEW_CONFIG_FILE = paiHomePath("config.json");
|
|
144
|
-
/** Briefly the canonical location between 2026-09-19's two migrations —
|
|
145
|
-
* read during the transition, never written to again. */
|
|
146
|
-
const OLD_CONFIG_FILE = join(homedir(), ".claude", "pai.json");
|
|
147
|
-
/** Where the config lived before 2026-09-19 — read during the transition,
|
|
148
|
-
* never written to once NEW_CONFIG_FILE exists (see `pai config migrate`). */
|
|
149
|
-
const LEGACY_CONFIG_FILE = join(homedir(), ".config", "pai", "config.json");
|
|
150
|
-
/**
|
|
151
|
-
* The path any read/write of the PAI config actually uses: PAI_CONFIG_FILE
|
|
152
|
-
* (tests, power users) first, else the new PAI_HOME location if it exists,
|
|
153
|
-
* else the most recent old location that is actually on disk (printing a
|
|
154
|
-
* one-time notice), else the new location (the target a first write creates).
|
|
155
|
-
*/
|
|
156
|
-
function paiConfigFilePath() {
|
|
157
|
-
const override = process.env.PAI_CONFIG_FILE;
|
|
158
|
-
if (override) return override;
|
|
159
|
-
return resolvePaiFile(NEW_CONFIG_FILE, [OLD_CONFIG_FILE, LEGACY_CONFIG_FILE], "pai config migrate");
|
|
160
|
-
}
|
|
161
|
-
const CONFIG_FILE = paiConfigFilePath();
|
|
162
|
-
const CONFIG_DIR = dirname(CONFIG_FILE);
|
|
163
|
-
/**
|
|
164
|
-
* `pai config migrate`: move config.json (from ~/.claude/pai.json or
|
|
165
|
-
* ~/.config/pai/config.json, whichever is found) to ~/.claude/pai/config.json
|
|
166
|
-
* byte-for-byte, verify the copy, then rename the old file aside as
|
|
167
|
-
* config.json.migrated-<YYYYMMDD> (never deleted).
|
|
168
|
-
*/
|
|
169
|
-
function migrateConfigFile(opts = {}) {
|
|
170
|
-
return migratePaiFile(NEW_CONFIG_FILE, [OLD_CONFIG_FILE, LEGACY_CONFIG_FILE], opts);
|
|
171
|
-
}
|
|
172
|
-
function deepMerge(target, source) {
|
|
173
|
-
const result = { ...target };
|
|
174
|
-
for (const key of Object.keys(source)) {
|
|
175
|
-
const srcVal = source[key];
|
|
176
|
-
if (srcVal === void 0 || srcVal === null) continue;
|
|
177
|
-
const tgtVal = target[key];
|
|
178
|
-
if (typeof srcVal === "object" && !Array.isArray(srcVal) && typeof tgtVal === "object" && tgtVal !== null && !Array.isArray(tgtVal)) result[key] = deepMerge(tgtVal, srcVal);
|
|
179
|
-
else result[key] = srcVal;
|
|
180
|
-
}
|
|
181
|
-
return result;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Load configuration from CONFIG_FILE (see paiConfigFilePath).
|
|
185
|
-
* Returns defaults merged with any values found in the file.
|
|
186
|
-
*/
|
|
187
|
-
function loadConfig() {
|
|
188
|
-
if (!existsSync(CONFIG_FILE)) return { ...DEFAULTS };
|
|
189
|
-
let raw;
|
|
190
|
-
try {
|
|
191
|
-
raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
192
|
-
} catch (e) {
|
|
193
|
-
process.stderr.write(`[pai-daemon] Could not read config file at ${CONFIG_FILE}: ${e}\n`);
|
|
194
|
-
return { ...DEFAULTS };
|
|
195
|
-
}
|
|
196
|
-
let parsed;
|
|
197
|
-
try {
|
|
198
|
-
parsed = JSON.parse(raw);
|
|
199
|
-
} catch (e) {
|
|
200
|
-
process.stderr.write(`[pai-daemon] Config file is not valid JSON: ${e}\n`);
|
|
201
|
-
return { ...DEFAULTS };
|
|
202
|
-
}
|
|
203
|
-
if (parsed.obsidianVaultPath && !parsed.vaultPath) {
|
|
204
|
-
parsed.vaultPath = parsed.obsidianVaultPath;
|
|
205
|
-
process.stderr.write(`[pai-daemon] Config: mapped obsidianVaultPath → vaultPath (${parsed.vaultPath})\n`);
|
|
206
|
-
}
|
|
207
|
-
return deepMerge(DEFAULTS, parsed);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Ensure CONFIG_DIR exists and write a default config template to CONFIG_FILE
|
|
211
|
-
* if none exists yet. Call this only from the `serve` command.
|
|
212
|
-
*/
|
|
213
|
-
function ensureConfigDir() {
|
|
214
|
-
if (!existsSync(CONFIG_DIR)) {
|
|
215
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
216
|
-
process.stderr.write(`[pai-daemon] Created config directory: ${CONFIG_DIR}\n`);
|
|
217
|
-
}
|
|
218
|
-
if (!existsSync(CONFIG_FILE)) try {
|
|
219
|
-
writeFileSync(CONFIG_FILE, configTemplate(), "utf-8");
|
|
220
|
-
process.stderr.write(`[pai-daemon] Wrote default config to: ${CONFIG_FILE}\n`);
|
|
221
|
-
} catch (e) {
|
|
222
|
-
process.stderr.write(`[pai-daemon] Could not write default config: ${e}\n`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
//#endregion
|
|
227
|
-
export { expandHome as a, paiConfigFilePath as c, DEFAULT_NOTIFICATION_CONFIG as d, ensureConfigDir as i, OWNER_LABEL_PREFIX as l, CONFIG_FILE as n, loadConfig as o, DEFAULTS as r, migrateConfigFile as s, CONFIG_DIR as t, UNROUTED as u };
|
|
228
|
-
//# sourceMappingURL=config-DCu8THKi.mjs.map
|