@tekmidian/pai 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.
Files changed (106) hide show
  1. package/dist/{aibroker-client-D5NT0S1N.mjs → aibroker-client-Bu72yPKj.mjs} +1 -1
  2. package/dist/{aibroker-client-Bn9SnGMa.mjs → aibroker-client-CDIXmq-e.mjs} +1 -1
  3. package/dist/{aibroker-client-Bn9SnGMa.mjs.map → aibroker-client-CDIXmq-e.mjs.map} +1 -1
  4. package/dist/{chain-CNSa0A3G.mjs → chain-BBbVbWVv.mjs} +4 -4
  5. package/dist/{chain-CNSa0A3G.mjs.map → chain-BBbVbWVv.mjs.map} +1 -1
  6. package/dist/cli/index.mjs +14 -14
  7. package/dist/cli/program.mjs +14 -14
  8. package/dist/config-Bn9jpblO.mjs +653 -0
  9. package/dist/config-Bn9jpblO.mjs.map +1 -0
  10. package/dist/{config-DxMZjpvY.mjs → config-DYXARgk8.mjs} +180 -151
  11. package/dist/config-DYXARgk8.mjs.map +1 -0
  12. package/dist/config-RjeVGJ7K.mjs +3 -0
  13. package/dist/{context-handover-cache-B9NKSjwR.mjs → context-handover-cache-yOuK9RIk.mjs} +5 -5
  14. package/dist/{context-handover-cache-B9NKSjwR.mjs.map → context-handover-cache-yOuK9RIk.mjs.map} +1 -1
  15. package/dist/daemon/index.mjs +13 -12
  16. package/dist/daemon/index.mjs.map +1 -1
  17. package/dist/{daemon-DZ4Q0jrs.mjs → daemon-CBTQYtHF.mjs} +49 -30
  18. package/dist/daemon-CBTQYtHF.mjs.map +1 -0
  19. package/dist/daemon-Vf7pHnjR.mjs +25 -0
  20. package/dist/daemon-mcp/index.mjs +186 -23
  21. package/dist/daemon-mcp/index.mjs.map +1 -1
  22. package/dist/factory-Ba8j9K7q.mjs +3 -0
  23. package/dist/{factory-DNYAqjH0.mjs → factory-Bt7TWQje.mjs} +3 -3
  24. package/dist/{factory-DNYAqjH0.mjs.map → factory-Bt7TWQje.mjs.map} +1 -1
  25. package/dist/{fallback-IeR2FrFn.mjs → fallback-CIGspCSi.mjs} +260 -10
  26. package/dist/fallback-CIGspCSi.mjs.map +1 -0
  27. package/dist/hooks/capture-all-events.mjs +80 -8
  28. package/dist/hooks/capture-all-events.mjs.map +4 -4
  29. package/dist/hooks/context-compression-hook.mjs +54 -39
  30. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  31. package/dist/hooks/post-compact-inject.mjs +54 -39
  32. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  33. package/dist/hooks/route-agents-to-worker.mjs +204 -109
  34. package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
  35. package/dist/hooks/whisper-rules.mjs +7613 -9
  36. package/dist/hooks/whisper-rules.mjs.map +4 -4
  37. package/dist/hooks/worker-proxy.mjs +204 -109
  38. package/dist/hooks/worker-proxy.mjs.map +4 -4
  39. package/dist/hooks/worker-status-line.mjs +211 -116
  40. package/dist/hooks/worker-status-line.mjs.map +4 -4
  41. package/dist/hooks/worker-supervision.mjs +208 -113
  42. package/dist/hooks/worker-supervision.mjs.map +4 -4
  43. package/dist/{kg-entity-BSjM5xZh.mjs → kg-entity-BSW7ra8i.mjs} +1 -1
  44. package/dist/{kg-entity-BSjM5xZh.mjs.map → kg-entity-BSW7ra8i.mjs.map} +1 -1
  45. package/dist/main-resolver-BTastpp4.mjs +7 -0
  46. package/dist/{main-resolver-DvYu9jbl.mjs → main-resolver-BmZPCmgj.mjs} +3 -3
  47. package/dist/{main-resolver-DvYu9jbl.mjs.map → main-resolver-BmZPCmgj.mjs.map} +1 -1
  48. package/dist/{planner-Cn8HmT7l.mjs → planner-BiH9msqq.mjs} +6 -6
  49. package/dist/{planner-Cn8HmT7l.mjs.map → planner-BiH9msqq.mjs.map} +1 -1
  50. package/dist/{program-D9-wfmkZ.mjs → program-BX67-4I2.mjs} +233 -277
  51. package/dist/program-BX67-4I2.mjs.map +1 -0
  52. package/dist/query-feedback-Bm6XvCmg.mjs +3 -0
  53. package/dist/{query-feedback-CYyOGi3y.mjs → query-feedback-CTs_jx6i.mjs} +1 -1
  54. package/dist/{query-feedback-CYyOGi3y.mjs.map → query-feedback-CTs_jx6i.mjs.map} +1 -1
  55. package/dist/{run-Cls0YGgV.mjs → run-TChOnMO_.mjs} +1388 -1183
  56. package/dist/run-TChOnMO_.mjs.map +1 -0
  57. package/dist/{server-Ch99xt5K.mjs → server-R9UkCn3v.mjs} +42 -3
  58. package/dist/server-R9UkCn3v.mjs.map +1 -0
  59. package/dist/session-keepalive-BuG3wvik.mjs +579 -0
  60. package/dist/session-keepalive-BuG3wvik.mjs.map +1 -0
  61. package/dist/skills/VaultContext/SKILL.md +1 -1
  62. package/dist/{state-DnYGRmS7.mjs → state-Ca0ltLgI.mjs} +1 -1
  63. package/dist/{state-PJrgfDLW.mjs → state-DwrsOoMi.mjs} +1 -1
  64. package/dist/{state-PJrgfDLW.mjs.map → state-DwrsOoMi.mjs.map} +1 -1
  65. package/dist/{tools-B77WHeVZ.mjs → tools-BEqiJt_j.mjs} +13 -13
  66. package/dist/{tools-B77WHeVZ.mjs.map → tools-BEqiJt_j.mjs.map} +1 -1
  67. package/dist/{tools-NnR_n-Ja.mjs → tools-BwsOTmjh.mjs} +2 -2
  68. package/dist/{wakeup-UrhiW-KU.mjs → wakeup-Yk5lQ0jb.mjs} +1 -1
  69. package/dist/{wakeup-UrhiW-KU.mjs.map → wakeup-Yk5lQ0jb.mjs.map} +1 -1
  70. package/dist/{work-queue-worker-BA5IDBwQ.mjs → work-queue-worker-B9zVWImP.mjs} +3 -3
  71. package/dist/{work-queue-worker-BA5IDBwQ.mjs.map → work-queue-worker-B9zVWImP.mjs.map} +1 -1
  72. package/dist/work-queue-worker-D-DnQ7oh.mjs +14 -0
  73. package/dist/{zettelkasten-BBjA7GcP.mjs → zettelkasten-DezbXghl.mjs} +2 -2
  74. package/dist/{zettelkasten-BBjA7GcP.mjs.map → zettelkasten-DezbXghl.mjs.map} +1 -1
  75. package/docs/cache-keepalive.md +138 -0
  76. package/docs/commands/README.md +7 -0
  77. package/docs/commands/config.md +84 -0
  78. package/docs/commands/daemon.md +6 -0
  79. package/docs/commands/worker.md +37 -2
  80. package/docs/config.md +171 -0
  81. package/docs/model-catalogue.md +502 -0
  82. package/docs/provider-independence-details.md +1 -1
  83. package/docs/task-bus.md +1 -1
  84. package/docs/worker.md +33 -5
  85. package/docs/workers-config.md +105 -12
  86. package/package.json +1 -1
  87. package/src/hooks/ts/capture-all-events.test.ts +71 -0
  88. package/src/hooks/ts/capture-all-events.ts +10 -3
  89. package/src/hooks/ts/user-prompt/whisper-rules.test.ts +56 -1
  90. package/src/hooks/ts/user-prompt/whisper-rules.ts +24 -0
  91. package/statusline-command.sh +81 -6
  92. package/dist/config-DCu8THKi.mjs +0 -228
  93. package/dist/config-DCu8THKi.mjs.map +0 -1
  94. package/dist/config-DeqFqa1N.mjs +0 -3
  95. package/dist/config-DxMZjpvY.mjs.map +0 -1
  96. package/dist/daemon-CYpVy_Dw.mjs +0 -24
  97. package/dist/daemon-DZ4Q0jrs.mjs.map +0 -1
  98. package/dist/factory-47Qf_df7.mjs +0 -3
  99. package/dist/fallback-IeR2FrFn.mjs.map +0 -1
  100. package/dist/indexer-C5Le7XE7.mjs +0 -1
  101. package/dist/main-resolver-CnfnWa8C.mjs +0 -7
  102. package/dist/program-D9-wfmkZ.mjs.map +0 -1
  103. package/dist/query-feedback-C7rSfiXU.mjs +0 -3
  104. package/dist/run-Cls0YGgV.mjs.map +0 -1
  105. package/dist/server-Ch99xt5K.mjs.map +0 -1
  106. package/dist/work-queue-worker-DLM2wlqM.mjs +0 -14
@@ -15,6 +15,11 @@ pai config <subcommand> [options]
15
15
  | Command | Description |
16
16
  |---------|-------------|
17
17
  | [`pai config path`](#pai-config-path) | Print the PAI_HOME namespace dir and each resolved per-user file |
18
+ | [`pai config yaml`](#pai-config-yaml) | Convert config.json → config.yaml (and voices.json → voices.yaml), with a short |
19
+ | [`pai config list`](#pai-config-list) | Print the main PAI config as YAML (secrets masked). Defaults to what the file explicitly sets. |
20
+ | [`pai config get <path>`](#pai-config-get-path) | Print one config value (dotted path, e.g. search.recencyBoostDays), masked if it looks like a secret. An object/array subtree prints as YAML; pass --json for JSON. |
21
+ | [`pai config set <path> <value>`](#pai-config-set-path-value) | Set one config value (dotted path). Value parsing: true/false, null, numbers, |
22
+ | [`pai config unset <path>`](#pai-config-unset-path) | Remove one config value (dotted path), reverting it to the built-in default |
18
23
  | [`pai config migrate`](#pai-config-migrate) | Move config.json, workers.yaml, whisper-rules.md, advisor-mode.json, session-state/, |
19
24
 
20
25
  ### pai config path
@@ -22,6 +27,85 @@ pai config <subcommand> [options]
22
27
  Print the PAI_HOME namespace dir and each resolved per-user file
23
28
 
24
29
 
30
+ ### pai config yaml
31
+
32
+ Convert config.json → config.yaml (and voices.json → voices.yaml), with a short
33
+
34
+ # comment above every top-level section and any "_comment"/"_...Note" JSON-
35
+ workaround key re-attached as a real comment. Verifies the generated YAML
36
+ re-parses to the exact same data before writing anything; the JSON is then
37
+ renamed to <name>.migrated-<YYYY-MM-DD> (never deleted). Every writer keeps
38
+ working unmodified afterwards — see docs/config.md.
39
+
40
+ **Options**
41
+
42
+ | Option | Description | Default |
43
+ |--------|-------------|---------|
44
+ | `--dry-run` | Print the plan without writing anything | |
45
+ | `--force` | Regenerate config.yaml (or voices.yaml) even if it already exists | |
46
+
47
+
48
+ ### pai config list
49
+
50
+ Print the main PAI config as YAML (secrets masked). Defaults to what the file explicitly sets.
51
+
52
+ **Options**
53
+
54
+ | Option | Description | Default |
55
+ |--------|-------------|---------|
56
+ | `--all` | Include every default value, not just what the file sets | |
57
+ | `--json` | Print JSON instead of YAML | |
58
+
59
+
60
+ ### pai config get <path>
61
+
62
+ Print one config value (dotted path, e.g. search.recencyBoostDays), masked if it looks like a secret. An object/array subtree prints as YAML; pass --json for JSON.
63
+
64
+ **Arguments**
65
+
66
+ | Argument | Kind |
67
+ |----------|------|
68
+ | `<path>` | required |
69
+
70
+ **Options**
71
+
72
+ | Option | Description | Default |
73
+ |--------|-------------|---------|
74
+ | `--json` | Print an object/array value as JSON instead of YAML | |
75
+
76
+
77
+ ### pai config set <path> <value>
78
+
79
+ Set one config value (dotted path). Value parsing: true/false, null, numbers,
80
+
81
+ [...] / {...} as JSON, else a string. Creates config.yaml (from config.json,
82
+ if any) on first use, then writes comment-preserving.
83
+
84
+ **Arguments**
85
+
86
+ | Argument | Kind |
87
+ |----------|------|
88
+ | `<path>` | required |
89
+ | `<value>` | required |
90
+
91
+ **Options**
92
+
93
+ | Option | Description | Default |
94
+ |--------|-------------|---------|
95
+ | `--force` | Set an unknown top-level key, or a value of a different type than the default | |
96
+
97
+
98
+ ### pai config unset <path>
99
+
100
+ Remove one config value (dotted path), reverting it to the built-in default
101
+
102
+ **Arguments**
103
+
104
+ | Argument | Kind |
105
+ |----------|------|
106
+ | `<path>` | required |
107
+
108
+
25
109
  ### pai config migrate
26
110
 
27
111
  Move config.json, workers.yaml, whisper-rules.md, advisor-mode.json, session-state/,
@@ -20,6 +20,7 @@ pai daemon <subcommand> [options]
20
20
  | [`pai daemon restart`](#pai-daemon-restart) | Send SIGTERM to the running daemon (launchd will restart it) |
21
21
  | [`pai daemon install`](#pai-daemon-install) | Install daemon as a launchd service and update ~/.claude.json to use the shim |
22
22
  | [`pai daemon uninstall`](#pai-daemon-uninstall) | Remove the launchd service and revert to direct MCP |
23
+ | [`pai daemon keepalive`](#pai-daemon-keepalive) | Show interactive-session cache keepalive config, per-session beat counters, and recent ledger lines |
23
24
  | [`pai daemon logs`](#pai-daemon-logs) | Tail the daemon log (/tmp/pai-daemon.log) |
24
25
 
25
26
  ### pai daemon serve
@@ -58,6 +59,11 @@ Install daemon as a launchd service and update ~/.claude.json to use the shim
58
59
  Remove the launchd service and revert to direct MCP
59
60
 
60
61
 
62
+ ### pai daemon keepalive
63
+
64
+ Show interactive-session cache keepalive config, per-session beat counters, and recent ledger lines
65
+
66
+
61
67
  ### pai daemon logs
62
68
 
63
69
  Tail the daemon log (/tmp/pai-daemon.log)
@@ -44,6 +44,7 @@ pai worker <subcommand> [options]
44
44
  | [`pai worker providers`](#pai-worker-providers) | Providers: list (default), add, remove, use, enable, disable, test |
45
45
  | [`pai worker classes`](#pai-worker-classes) | Classes: which provider serves draft / implement / review / … |
46
46
  | [`pai worker model [what] [model]`](#pai-worker-model-what-model) | Model ids per provider: no args lists them, |
47
+ | [`pai worker capability [name] [providers]`](#pai-worker-capability-name-providers) | Which provider(s) serve a capability (e.g. image), independent of any one |
47
48
  | [`pai worker config`](#pai-worker-config) | workers.yaml itself: path, init, migrate, check, inline-keys |
48
49
 
49
50
  ### pai worker run [args...]
@@ -75,6 +76,11 @@ Grant MCP tools by naming mcp__server__tool in --allowedTools (the server loads
75
76
  | `--provider <name>` | Provider to run on (default: active, else routing order) | |
76
77
  | `--class <name>` | Use the provider of this class (draft, implement, review, research, spotcheck, simple, complex, image) | |
77
78
  | `--role <name>` | Alias of --class (roles were renamed to classes) | |
79
+ | `--capability <name>` | Run on whichever configured provider serves this capability (see `pai worker capability`), not the active provider or --class's own one — e.g. --capability image runs an `engine: image` provider directly, POSTing a prompt and writing the PNG instead of spawning claude | |
80
+ | `--for <name>` | Alias of --capability | |
81
+ | `--out <file>` | Where an image-capability run writes its PNG (default: <logDir>/<worker-id>.png) | |
82
+ | `--size <WxH>` | Image size for an image-capability run, e.g. 1024x1024 (default: 1024x1024) | |
83
+ | `--timeout-ms <n>` | Timeout for an image-capability run's HTTP request (default: 120000) | |
78
84
  | `--chain <stages>` | Comma-separated stage classes, e.g. draft,implement or draft,implement,review | |
79
85
  | `--agent <name>` | Run the agent definition ~/.claude/agents/<name>.md on a worker | |
80
86
  | `--model <model>` | Override the model for this run. Headless (-p) workers default to the --class model; an interactive launch (no -p) with no --model uses the harness default model from settings.json. | |
@@ -391,6 +397,8 @@ Example: pai worker providers add glm --url https://…/anthropic \
391
397
  --key-file writes only a path to a 0600 file holding it — use one or the other.
392
398
  OpenAI-protocol: --protocol openai --upstream-url https://…/v1 (runs via the PAI proxy).
393
399
  Codex (ChatGPT plan): --engine codex — runs through the Codex CLI.
400
+ Image generation: --engine image — `pai worker run --capability image` then POSTs
401
+ straight to {url}/images/generations instead of spawning claude.
394
402
 
395
403
  **Arguments**
396
404
 
@@ -412,7 +420,7 @@ Codex (ChatGPT plan): --engine codex — runs through the Codex CLI.
412
420
  | `--note <text>` | Human note shown in `providers list` | |
413
421
  | `--protocol <proto>` | anthropic (default) or openai — openai runs through the local PAI proxy | |
414
422
  | `--upstream-url <url>` | Chat Completions base URL (required for --protocol openai) | |
415
- | `--engine <engine>` | claude (default) or codex — codex runs `codex exec --json` | |
423
+ | `--engine <engine>` | claude (default), codex, or image — codex runs `codex exec --json`, image POSTs {url}/images/generations | |
416
424
  | `--context-window <tokens>` | Context window for the meter (default 200000; init event overrides) | |
417
425
  | `--quota-probe <url>` | URL whose JSON first number is the quota percent (0-100) | |
418
426
  | `--cost-tier <1-5>` | Cost tier 1 (cheapest) … 5 (most expensive; default 3) | |
@@ -543,7 +551,9 @@ Model ids per provider: no args lists them,
543
551
 
544
552
  `model <model-id>` sets the active provider's default model (back-compat),
545
553
  `model <capability>` shows one capability, `model <capability> <model-id>` sets it.
546
- Capabilities: default, fast, image. --provider targets another provider.
554
+ Capability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);
555
+ `pai worker capability` picks which provider serves one across the whole config.
556
+ --provider targets another provider.
547
557
 
548
558
  **Arguments**
549
559
 
@@ -559,6 +569,31 @@ Capabilities: default, fast, image. --provider targets another provider.
559
569
  | `--provider <name>` | Provider to read or change (default: the active one) | |
560
570
 
561
571
 
572
+ ### pai worker capability [name] [providers]
573
+
574
+ Which provider(s) serve a capability (e.g. image), independent of any one
575
+
576
+ provider's own model table: no args lists every preference and what it
577
+ resolves to; `capability <name> <provider>[,<provider>…]` sets the
578
+ preference list (first usable one wins); `capability <name> --unset`
579
+ removes it. An `engine: image` provider preferred for "image" runs
580
+ `pai worker run --capability image` directly against its images API.
581
+
582
+ **Arguments**
583
+
584
+ | Argument | Kind |
585
+ |----------|------|
586
+ | `[name]` | optional |
587
+ | `[providers]` | optional |
588
+
589
+ **Options**
590
+
591
+ | Option | Description | Default |
592
+ |--------|-------------|---------|
593
+ | `--unset` | Remove the preference for <name> | |
594
+ | `--json` | Machine-readable output | |
595
+
596
+
562
597
  ### pai worker config
563
598
 
564
599
  workers.yaml itself: path, init, migrate, check, inline-keys
package/docs/config.md ADDED
@@ -0,0 +1,171 @@
1
+ # config.yaml
2
+
3
+ The main PAI config — everything `src/daemon/config.ts`'s `loadConfig()`
4
+ returns (search defaults, storage backend, notifications, task bus,
5
+ identity, the non-provider `workers` settings) plus whatever else a writer
6
+ has stashed in the file. Before 2026-09-20 this was JSON only, undocumented
7
+ in place, with no CLI or MCP surface to read or change a single value
8
+ without opening the file. `pai config yaml` converts it to YAML with
9
+ comments; `pai config list/get/set/unset` and the matching `config_*` MCP
10
+ tools work whether the file is YAML or JSON.
11
+
12
+ ## File map
13
+
14
+ | File | Format | Read/written by |
15
+ |---|---|---|
16
+ | `config.yaml` | YAML, comment-preserving | preferred whenever it exists — `readMainConfigRaw`/`writeMainConfigRaw` (`src/daemon/config.ts`) |
17
+ | `config.json` | JSON | fallback when no `config.yaml` exists; unchanged pre-2026-09-20 behaviour |
18
+ | `workers.yaml` | YAML, comment-preserving | `providers`/`classes`/`mcp_sets`/`active` only — see [docs/workers-config.md](workers-config.md) |
19
+ | `voices.yaml` | YAML, comment-preserving | preferred whenever it exists — `loadVoicesConfig`/`migrateVoicesToYaml` (`src/config/voices-config.ts`) |
20
+ | `voices.json` | JSON | fallback; currently an orphan (nothing in `src/` reads it) |
21
+
22
+ All five live under the PAI_HOME namespace dir (`~/.claude/pai` by default,
23
+ `PAI_HOME=<dir>` to move it; `pai config path` prints the resolved paths).
24
+
25
+ ### Known external readers
26
+
27
+ `~/.claude/statusline-command.sh` (outside the repo) reads `workers.providers` from the config file using `jq` and therefore sees only JSON. Since `providers` moved into `workers.yaml` on 2026-09-19, that lookup now falls back to `anthropic` (a pre-existing default), which is fine — the file is for display and already treats missing providers gracefully.
28
+
29
+ **State files stay JSON** — they are written many times a second by the
30
+ daemon or hooks, never hand-edited, and gain nothing from comments:
31
+ `advisor-mode.json`, `scheduler-state.json`, `work-queue.json`,
32
+ `session-routing.json`, `summary-cooldowns.json`, `kg-backfill-state.json`,
33
+ `registry-scan.json`, `session-scan-cache.json`. Only the two human-facing
34
+ config files above became YAML.
35
+
36
+ ## Migrating
37
+
38
+ ```
39
+ pai config yaml --dry-run # print the plan, write nothing
40
+ pai config yaml # convert config.json → config.yaml (and
41
+ # voices.json → voices.yaml), each JSON file
42
+ # renamed to <name>.json.migrated-<YYYY-MM-DD>
43
+ ```
44
+
45
+ What changes on disk:
46
+ - A `#` comment is added above every top-level section, drawn from
47
+ `PaiDaemonConfig`'s doc comments (`MAIN_CONFIG_SECTION_COMMENTS` in
48
+ `src/config/main-config.ts`).
49
+ - Any `_comment` / `_...Note` JSON-comment-workaround key is folded into a
50
+ real `#` comment above the key it was documenting, then dropped from the
51
+ data.
52
+ - The generated YAML is re-parsed and checked byte-for-byte equal (as JSON)
53
+ to the original before anything is written — a mismatch aborts with
54
+ nothing touched.
55
+ - The JSON file is renamed to `config.json.migrated-<YYYY-MM-DD>` (never
56
+ deleted) only after that check passes.
57
+ - Idempotent: re-running `pai config yaml` once `config.yaml` exists is a
58
+ no-op unless you pass `--force` (which regenerates it from the current
59
+ JSON, if any).
60
+
61
+ Every existing writer (identity, notifications, obsidian vault path,
62
+ setup wizard, memory search settings, the workers section) keeps working
63
+ unmodified — they all go through `readMainConfigRaw`/`writeMainConfigRaw`,
64
+ which pick YAML over JSON automatically.
65
+
66
+ ## Example `config.yaml`
67
+
68
+ ```yaml
69
+ # Unix Domain Socket path for daemon IPC.
70
+ socketPath: /tmp/pai.sock
71
+
72
+ # How often the daemon re-indexes changed files, in seconds.
73
+ indexIntervalSecs: 300
74
+
75
+ # How often the daemon runs the embedding pass, in seconds.
76
+ embedIntervalSecs: 600
77
+
78
+ # Storage backend: "sqlite" (default) or "postgres".
79
+ storageBackend: sqlite
80
+
81
+ # PostgreSQL connection settings, used when storageBackend is "postgres".
82
+ postgres:
83
+ connectionString: postgresql://pai:pai@localhost:5432/pai_<username>
84
+ maxConnections: 5
85
+ connectionTimeoutMs: 5000
86
+
87
+ # Embedding model name, used for semantic/hybrid search.
88
+ embeddingModel: Snowflake/snowflake-arctic-embed-m-v1.5
89
+
90
+ # Daemon log level: debug, info, warn, or error.
91
+ logLevel: info
92
+
93
+ # Search defaults, applied when an MCP tool or CLI call doesn't specify one.
94
+ search:
95
+ mode: keyword
96
+ rerank: true
97
+ recencyBoostDays: 90
98
+ defaultLimit: 10
99
+ snippetLength: 200
100
+
101
+ # Who "me" is — addresses that count as the user's own. Empty by default
102
+ # and never guessed: nothing is self-addressed until this is set.
103
+ identity:
104
+ selfEmails:
105
+ - you@example.com
106
+ deliverTo: you@example.com
107
+ sendingAccount: you@example.com
108
+
109
+ # Non-provider worker settings (pane, routing, tree, cache keepalive,
110
+ # fallback). Providers/classes/mcp_sets live in workers.yaml, not here.
111
+ workers:
112
+ enabled: true
113
+ cacheKeepaliveSecs: 15
114
+ ```
115
+
116
+ `postgres.connectionString` and anything under a `key`/`token`/`secret`/
117
+ `password` field is a secret: `pai config list`/`get` and `config_list`/
118
+ `config_get` always print it masked (`****<last4>`), and the file itself is
119
+ written mode `0600`.
120
+
121
+ ## CLI
122
+
123
+ ```
124
+ pai config path # resolved paths for every PAI_HOME file
125
+ pai config yaml [--dry-run] [--force] # convert config.json/voices.json → YAML
126
+ pai config list [--all] [--json] # file-set values as YAML (--all: defaults merged in too)
127
+ pai config get search.recencyBoostDays # one value, defaults-merged, masked if secret
128
+ pai config set search.recencyBoostDays 45 # writes config.yaml, comment-preserving
129
+ pai config set some.newThing 1 --force # allow an unknown top-level key / type mismatch
130
+ pai config unset search.recencyBoostDays # revert to the built-in default
131
+ ```
132
+
133
+ `pai config set` value parsing: `true`/`false`, `null`, bare numbers,
134
+ `[...]`/`{...}` parsed as JSON, everything else a plain string. `set`
135
+ refuses an unknown top-level key, or a value whose type disagrees with the
136
+ built-in default in `DEFAULTS`, unless `--force`.
137
+
138
+ Example session:
139
+
140
+ ```
141
+ $ pai config set search.recencyBoostDays 45
142
+ Set search.recencyBoostDays = 45
143
+
144
+ $ pai config get search.recencyBoostDays
145
+ 45
146
+
147
+ $ pai config list
148
+ search:
149
+ mode: keyword
150
+ rerank: true
151
+ recencyBoostDays: 45
152
+ defaultLimit: 10
153
+ snippetLength: 200
154
+ ```
155
+
156
+ ## MCP tools
157
+
158
+ On the `pai` MCP server (`src/daemon-mcp/index.ts`), backed by the same
159
+ `src/config/main-config-ops.ts` functions the CLI calls — masking and
160
+ validation cannot drift between the two surfaces.
161
+
162
+ - **`config_list`** — `{ "all": false, "json": false }` → the file-set config
163
+ as YAML (or JSON), secrets masked.
164
+ - **`config_get`** — `{ "path": "search.recencyBoostDays" }` → the
165
+ defaults-merged value, masked if the path looks like a secret.
166
+ - **`config_set`** — `{ "path": "search.recencyBoostDays", "value": "45" }`
167
+ → **writes config.yaml** (creating it from config.json first if neither
168
+ exists yet), comment-preserving. Add `"force": true` to bypass the
169
+ unknown-key/type checks.
170
+ - **`config_unset`** — `{ "path": "search.recencyBoostDays" }` → **writes
171
+ the file**, removing the key so it reverts to the built-in default.