@letrquan/book 0.2.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 (103) hide show
  1. package/CHANGELOG.md +2504 -0
  2. package/LICENSE +123 -0
  3. package/README.md +1383 -0
  4. package/dist/app-MOXXWEOY.js +18945 -0
  5. package/dist/app-MOXXWEOY.js.map +1 -0
  6. package/dist/chunk-24AXE6SP.js +12 -0
  7. package/dist/chunk-24AXE6SP.js.map +1 -0
  8. package/dist/chunk-3U5IBM24.js +213 -0
  9. package/dist/chunk-3U5IBM24.js.map +1 -0
  10. package/dist/chunk-574C6MQR.js +743 -0
  11. package/dist/chunk-574C6MQR.js.map +1 -0
  12. package/dist/chunk-5GDZ22YP.js +449 -0
  13. package/dist/chunk-5GDZ22YP.js.map +1 -0
  14. package/dist/chunk-5KLVH3PY.js +167 -0
  15. package/dist/chunk-5KLVH3PY.js.map +1 -0
  16. package/dist/chunk-5RDRAO4B.js +92 -0
  17. package/dist/chunk-5RDRAO4B.js.map +1 -0
  18. package/dist/chunk-7N4J557C.js +10 -0
  19. package/dist/chunk-7N4J557C.js.map +1 -0
  20. package/dist/chunk-A7JLBK2Y.js +3506 -0
  21. package/dist/chunk-A7JLBK2Y.js.map +1 -0
  22. package/dist/chunk-ANSJPYGI.js +84 -0
  23. package/dist/chunk-ANSJPYGI.js.map +1 -0
  24. package/dist/chunk-BTZZUCLI.js +264 -0
  25. package/dist/chunk-BTZZUCLI.js.map +1 -0
  26. package/dist/chunk-C7D4NJ2D.js +217 -0
  27. package/dist/chunk-C7D4NJ2D.js.map +1 -0
  28. package/dist/chunk-CAGOB2N4.js +208 -0
  29. package/dist/chunk-CAGOB2N4.js.map +1 -0
  30. package/dist/chunk-DGMQEI6Y.js +98 -0
  31. package/dist/chunk-DGMQEI6Y.js.map +1 -0
  32. package/dist/chunk-E75IPST5.js +289 -0
  33. package/dist/chunk-E75IPST5.js.map +1 -0
  34. package/dist/chunk-EYGD6X2T.js +180 -0
  35. package/dist/chunk-EYGD6X2T.js.map +1 -0
  36. package/dist/chunk-GTVB4HEM.js +765 -0
  37. package/dist/chunk-GTVB4HEM.js.map +1 -0
  38. package/dist/chunk-J7MIZ5CA.js +1907 -0
  39. package/dist/chunk-J7MIZ5CA.js.map +1 -0
  40. package/dist/chunk-KWHQ2XXL.js +626 -0
  41. package/dist/chunk-KWHQ2XXL.js.map +1 -0
  42. package/dist/chunk-L6LK2QIQ.js +657 -0
  43. package/dist/chunk-L6LK2QIQ.js.map +1 -0
  44. package/dist/chunk-MAQXCUR4.js +1007 -0
  45. package/dist/chunk-MAQXCUR4.js.map +1 -0
  46. package/dist/chunk-S4XL7HOM.js +402 -0
  47. package/dist/chunk-S4XL7HOM.js.map +1 -0
  48. package/dist/chunk-URLQAXTR.js +60 -0
  49. package/dist/chunk-URLQAXTR.js.map +1 -0
  50. package/dist/chunk-V4BOYR52.js +3504 -0
  51. package/dist/chunk-V4BOYR52.js.map +1 -0
  52. package/dist/chunk-VZQXX3WW.js +210 -0
  53. package/dist/chunk-VZQXX3WW.js.map +1 -0
  54. package/dist/chunk-WKBO5R4O.js +34 -0
  55. package/dist/chunk-WKBO5R4O.js.map +1 -0
  56. package/dist/chunk-XIFFTATU.js +15753 -0
  57. package/dist/chunk-XIFFTATU.js.map +1 -0
  58. package/dist/command-approvals-6Y57VCWZ.js +28 -0
  59. package/dist/command-approvals-6Y57VCWZ.js.map +1 -0
  60. package/dist/context-ZC5IFEAX.js +25 -0
  61. package/dist/context-ZC5IFEAX.js.map +1 -0
  62. package/dist/hook-approvals-CTUFKEJT.js +22 -0
  63. package/dist/hook-approvals-CTUFKEJT.js.map +1 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +2582 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/interactive-assets-CCAAWVKH.js +31 -0
  68. package/dist/interactive-assets-CCAAWVKH.js.map +1 -0
  69. package/dist/job-runner.d.ts +2 -0
  70. package/dist/job-runner.js +235 -0
  71. package/dist/job-runner.js.map +1 -0
  72. package/dist/loader-4IDS2RIE.js +15 -0
  73. package/dist/loader-4IDS2RIE.js.map +1 -0
  74. package/dist/mcp-approvals-WNPH65BK.js +18 -0
  75. package/dist/mcp-approvals-WNPH65BK.js.map +1 -0
  76. package/dist/mcp-config-YIJIES2C.js +16 -0
  77. package/dist/mcp-config-YIJIES2C.js.map +1 -0
  78. package/dist/model-window-store-YFVNXCIP.js +27 -0
  79. package/dist/model-window-store-YFVNXCIP.js.map +1 -0
  80. package/dist/openai-compatible-RHZUBHV6.js +10 -0
  81. package/dist/openai-compatible-RHZUBHV6.js.map +1 -0
  82. package/dist/permission-approvals-76PVREFD.js +14 -0
  83. package/dist/permission-approvals-76PVREFD.js.map +1 -0
  84. package/dist/permissions-CHCKTFQQ.js +28 -0
  85. package/dist/permissions-CHCKTFQQ.js.map +1 -0
  86. package/dist/sandbox-CNVAQDA7.js +23 -0
  87. package/dist/sandbox-CNVAQDA7.js.map +1 -0
  88. package/dist/scrollback-LUKLIBVI.js +136 -0
  89. package/dist/scrollback-LUKLIBVI.js.map +1 -0
  90. package/dist/sdk.d.ts +4574 -0
  91. package/dist/sdk.js +665 -0
  92. package/dist/sdk.js.map +1 -0
  93. package/dist/settings-loader-M2QP5TX2.js +21 -0
  94. package/dist/settings-loader-M2QP5TX2.js.map +1 -0
  95. package/dist/settings-removed-JWWPT4WA.js +19 -0
  96. package/dist/settings-removed-JWWPT4WA.js.map +1 -0
  97. package/dist/shell-selection-V6QAYXPX.js +17 -0
  98. package/dist/shell-selection-V6QAYXPX.js.map +1 -0
  99. package/package.json +143 -0
  100. package/patches/ink+6.8.0.patch +13 -0
  101. package/scripts/apply-ink-patch.mjs +29 -0
  102. package/scripts/ink-patch.mjs +59 -0
  103. package/scripts/verify-ink-patch.mjs +11 -0
@@ -0,0 +1,743 @@
1
+ // src/settings.ts
2
+ import { z } from "zod";
3
+ var permissionRuleSchema = z.string().min(1);
4
+ var sandboxSchema = z.object({
5
+ enabled: z.boolean().default(false),
6
+ failIfUnavailable: z.boolean().default(false),
7
+ autoAllowBashIfSandboxed: z.boolean().default(true),
8
+ excludedCommands: z.array(z.string()).default([]),
9
+ allowUnsandboxedCommands: z.boolean().default(true),
10
+ filesystem: z.object({
11
+ allowWrite: z.array(z.string()).default([]),
12
+ denyWrite: z.array(z.string()).default([]),
13
+ denyRead: z.array(z.string()).default([])
14
+ }).default({}),
15
+ network: z.object({
16
+ allowedDomains: z.array(z.string()).default([]),
17
+ deniedDomains: z.array(z.string()).default([])
18
+ }).default({})
19
+ });
20
+ var projectAllowRuleChoiceSchema = z.enum(["approved", "rejected"]);
21
+ var permissionsSchema = z.object({
22
+ allow: z.array(permissionRuleSchema).default([]),
23
+ ask: z.array(permissionRuleSchema).default([]),
24
+ deny: z.array(permissionRuleSchema).default([]),
25
+ /**
26
+ * Decisions about `allow` rules the project layer declared. A project rule
27
+ * with no `approved` entry here never reaches the resolved allow list.
28
+ */
29
+ projectAllowRules: z.record(projectAllowRuleChoiceSchema).default({})
30
+ });
31
+ var hookEntrySchema = z.object({
32
+ /** Tool(specifier) pattern to filter which events trigger this hook. */
33
+ matcher: z.string().optional(),
34
+ /** Shell command to run (passed through system shell). */
35
+ command: z.string().min(1),
36
+ /** Extra environment variables for this hook. */
37
+ env: z.record(z.string()).default({})
38
+ });
39
+ var projectHookChoiceSchema = z.enum(["approved", "rejected"]);
40
+ var HOOK_EVENTS = [
41
+ "SessionStart",
42
+ "SessionEnd",
43
+ "UserPromptSubmit",
44
+ "PreToolUse",
45
+ "PostToolUse",
46
+ "Stop",
47
+ "PreCompact",
48
+ "PostCompact",
49
+ "SubagentStart",
50
+ "SubagentStop",
51
+ /**
52
+ * Something a human should know about while nobody is watching: a run parked on
53
+ * a rejected credential, a budget crossing its ceiling, a refused spawn because
54
+ * the disk is nearly full. Wire ntfy/Slack/SMS here as an ordinary shell hook.
55
+ *
56
+ * Only `severity: 'alarm'` is meant to wake anyone; everything else is a line to
57
+ * tail. `hooksSchema` is generated from this array, so the schema cost is one
58
+ * entry.
59
+ */
60
+ "Notification"
61
+ ];
62
+ var hooksSchema = z.object({
63
+ ...Object.fromEntries(HOOK_EVENTS.map((e) => [e, z.array(hookEntrySchema).default([])])),
64
+ /**
65
+ * Decisions about hook entries the checked-in project layer declared. An
66
+ * entry whose fingerprint has no `approved` record here never reaches the
67
+ * resolved hooks.
68
+ */
69
+ projectEntries: z.record(projectHookChoiceSchema).default({})
70
+ });
71
+ var retrySettingsSchema = z.object({
72
+ maxAttempts: z.number().int().min(0).max(15).default(10),
73
+ baseDelayMs: z.number().int().min(100).max(6e4).default(1e3),
74
+ maxDelayMs: z.number().int().min(100).max(3e5).default(3e4),
75
+ totalBudgetMs: z.number().int().min(0).max(6e5).default(0),
76
+ requestTimeoutMs: z.number().int().min(5e3).max(6e5).default(6e5),
77
+ streamStallTimeoutMs: z.number().int().min(5e3).max(12e4).default(2e4),
78
+ /**
79
+ * Stall ceiling while the model is thinking, which is a different regime.
80
+ *
81
+ * `streamStallTimeoutMs` is tuned for a chat: 20s of silence means something
82
+ * broke. But with adaptive thinking on — the default for every Opus and Sonnet
83
+ * model here, at `high` effort unless told otherwise — a long quiet stretch
84
+ * before the first token is the model working, not a fault. Applying the chat
85
+ * timeout to it cancels a healthy request and reports `stream_stall`.
86
+ */
87
+ thinkingStallTimeoutMs: z.number().int().min(1e4).max(18e5).default(9e5),
88
+ toolRetries: z.number().int().min(0).max(3).default(1),
89
+ watchdog: z.boolean().default(false),
90
+ /**
91
+ * How many times a turn may be re-sent after a transport fault that left the
92
+ * work intact — a stalled stream, a dropped socket.
93
+ *
94
+ * `maxAttempts` covers connection setup only; once a 200 response is streaming
95
+ * it is out of scope, so without this a single 20-second provider silence ended
96
+ * the run. Set to 0 to restore that behavior exactly.
97
+ */
98
+ streamReissueAttempts: z.number().int().min(0).max(10).default(3),
99
+ /**
100
+ * Separate allowance for continuing after the provider's output cap. A large
101
+ * generated file legitimately hits the cap on consecutive turns, and sharing
102
+ * the transport budget would leave nothing for a real socket drop afterwards.
103
+ */
104
+ outputCapContinuations: z.number().int().min(0).max(50).default(10)
105
+ });
106
+ var effortLevelSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
107
+ var providerModelSchema = z.object({
108
+ label: z.string().optional(),
109
+ contextWindow: z.number().int().positive().optional(),
110
+ maxOutputTokens: z.number().int().positive().optional(),
111
+ /** Whether this model accepts image input. Unknown models remain optimistic. */
112
+ vision: z.boolean().optional(),
113
+ /**
114
+ * Entered by hand rather than returned by the endpoint's model-list API.
115
+ * Refreshing the catalog keeps these, since they exist precisely because
116
+ * discovery does not list them.
117
+ */
118
+ manual: z.boolean().optional(),
119
+ /** Mutation-tool preference for this model; defaults to a family-level prior. */
120
+ editFormat: z.enum(["patch", "replace", "whole"]).optional(),
121
+ effort: z.union([
122
+ z.literal(false),
123
+ z.object({
124
+ default: effortLevelSchema.optional(),
125
+ levels: z.array(effortLevelSchema).optional()
126
+ })
127
+ ]).optional()
128
+ });
129
+ var providerConfigSchema = z.object({
130
+ type: z.enum(["openai", "anthropic"]).default("openai"),
131
+ baseURL: z.string().min(1).optional(),
132
+ baseUrl: z.string().min(1).optional(),
133
+ apiKey: z.string().min(1).optional(),
134
+ models: z.record(providerModelSchema).default({})
135
+ });
136
+ var continuationSettingsSchema = z.object({
137
+ enabled: z.boolean().default(false),
138
+ /** Consecutive host-authored continuations before the run stops regardless. */
139
+ maxConsecutive: z.number().int().min(1).max(1e3).default(50),
140
+ /**
141
+ * Identical progress witnesses in a row before the run stops as no-progress.
142
+ * Minimum 1: a 0 here would fire on the first boundary, before any witness
143
+ * exists, and label a run that never got a chance to spin as spinning.
144
+ */
145
+ noProgressLimit: z.number().int().min(1).max(20).default(3),
146
+ /**
147
+ * Consecutive turns in which EVERY tool call was refused before the run stops.
148
+ *
149
+ * Enforced even when `enabled` is false, deliberately. The spin it catches
150
+ * predates continuation and needs no continuation to happen: a headless run in
151
+ * the default permission mode answers every prompt `deny`, so the model can
152
+ * re-issue a refused call forever while `toolCalls.length > 0` keeps the
153
+ * turn-end gate from ever firing. None of the existing anti-loop signals see it
154
+ * either — the repeated-failure breaker ignores anything that is not `error`,
155
+ * and `toolCallStats.failures` excludes `blocked` by construction.
156
+ *
157
+ * 0 disables the guard.
158
+ */
159
+ blockedToolTurnLimit: z.number().int().min(0).max(100).default(3),
160
+ /** Turns between host-authored work-state messages; 0 disables them. */
161
+ planRefreshTurns: z.number().int().min(0).max(500).default(25),
162
+ /** Wall-clock ceiling for one continued run; 0 means no ceiling. */
163
+ maxWallClockMs: z.number().int().min(0).default(0)
164
+ });
165
+ var memorySettingsSchema = z.object({
166
+ enabled: z.boolean().default(true),
167
+ autoSave: z.boolean().default(true),
168
+ requireApproval: z.boolean().default(true)
169
+ });
170
+ var agentSettingsSchema = z.object({
171
+ mode: z.enum(["adaptive", "manual", "off"]).default("adaptive"),
172
+ maxConcurrent: z.number().int().min(1).max(16).default(3),
173
+ maxSpawned: z.number().int().min(1).max(64).default(8),
174
+ maxDepth: z.literal(1).default(1),
175
+ persist: z.boolean().default(true),
176
+ includeUntrackedInSnapshot: z.boolean().default(true),
177
+ telemetry: z.boolean().default(true),
178
+ retentionDays: z.number().int().min(1).max(3650).default(30),
179
+ checks: z.record(z.union([z.string().min(1), z.array(z.string().min(1)).min(1)])).default({}),
180
+ /**
181
+ * Wall-clock ceiling for one `Check` run. The 120s default suits a focused
182
+ * suite; a full `npm test` on a large repository routinely exceeds it, and a
183
+ * check that always times out is worse than no check because the timeout was
184
+ * reported as a failing suite. Raise it per project rather than per invocation.
185
+ */
186
+ checkTimeoutMs: z.number().int().min(1e3).max(72e5).default(12e4),
187
+ /**
188
+ * Simultaneous agent worktrees per repository; 0 disables the check.
189
+ *
190
+ * Nothing reclaims a worktree automatically outside the TUI, and the store's
191
+ * retention sweep runs once at startup with a 30-day default — so on a long
192
+ * unattended run the count only grows. Sized above `maxConcurrent` so ordinary
193
+ * fan-out is unaffected and only accumulation trips it.
194
+ */
195
+ maxWorktrees: z.number().int().min(0).max(256).default(24),
196
+ /**
197
+ * Re-drive agents that were interrupted by process death on the next start.
198
+ *
199
+ * A restart otherwise converts the whole pending backlog into terminal records
200
+ * nothing picks up, silently losing hours of child work on a wide fan-out. The
201
+ * re-drive is contained: explorers are read-only and patchers/validators run in
202
+ * their own worktree, so nothing reaches the parent workspace without the usual
203
+ * evidence gate. Only `process_exit` interruptions qualify — a user stop stays
204
+ * stopped.
205
+ */
206
+ resumeInterrupted: z.boolean().default(true),
207
+ /**
208
+ * Refuse a new worktree when free disk would fall below this; 0 disables.
209
+ * Worktrees share the filesystem with the workspace, so exhausting it breaks
210
+ * the root agent's own Edit and Bash, not just the child's.
211
+ */
212
+ minFreeDiskBytes: z.number().int().min(0).default(2 * 1024 * 1024 * 1024),
213
+ profiles: z.record(
214
+ z.object({
215
+ model: z.string().min(1).optional(),
216
+ effort: effortLevelSchema.optional(),
217
+ maxTurns: z.number().int().min(1).optional(),
218
+ color: z.string().optional()
219
+ })
220
+ ).default({}),
221
+ ui: z.object({ enabled: z.boolean().default(true) }).default({}),
222
+ routing: z.object({
223
+ inlineSearchBudget: z.number().int().min(1).max(20).default(3),
224
+ exploreReminder: z.boolean().default(true)
225
+ }).default({}),
226
+ forwardTextEvents: z.boolean().default(false)
227
+ });
228
+ var toolDiscoverySettingsSchema = z.object({
229
+ mode: z.enum(["auto", "eager", "deferred"]).default("auto"),
230
+ eagerToolCount: z.number().int().min(1).max(100).default(10),
231
+ schemaTokenBudget: z.number().int().min(1e3).max(1e5).default(8e3),
232
+ maxLoadedTools: z.number().int().min(1).max(100).default(15),
233
+ searchLimit: z.number().int().min(1).max(5).default(5)
234
+ });
235
+ var toolExecutionSettingsSchema = z.object({
236
+ /** Shared across the root loop and managed children for parallel-safe tools. */
237
+ maxConcurrent: z.number().int().min(1).max(8).default(4)
238
+ });
239
+ var observabilitySettingsSchema = z.object({
240
+ /** Persist a per-tool-call JSONL record for `book tool-stats`. */
241
+ toolTelemetry: z.boolean().default(true),
242
+ /**
243
+ * Default reporting window (in days) for `book tool-stats` and the target for
244
+ * `book tool-stats --prune`. Disk use is bounded by size-based log rotation;
245
+ * records are only deleted when they rotate out or are explicitly pruned.
246
+ */
247
+ toolTelemetryRetentionDays: z.number().int().min(1).max(3650).default(30)
248
+ });
249
+ var mcpProjectServerChoiceSchema = z.object({
250
+ /** Hash of the server's command/args/env at decision time; a mismatch re-prompts. */
251
+ fingerprint: z.string().min(1),
252
+ choice: z.enum(["approved", "rejected"])
253
+ });
254
+ var mcpSettingsSchema = z.object({
255
+ /**
256
+ * Per-server trust decisions for workspace `.mcp.json` declarations.
257
+ * User-global servers (<BOOK_HOME>/.book/mcp.json) never require approval.
258
+ */
259
+ projectServers: z.record(mcpProjectServerChoiceSchema).default({})
260
+ });
261
+ var projectCommandChoiceSchema = z.object({
262
+ /** Digest of the shell the body substitutes at decision time; a mismatch re-prompts. */
263
+ fingerprint: z.string().min(1),
264
+ choice: z.enum(["approved", "rejected"])
265
+ });
266
+ var commandSettingsSchema = z.object({
267
+ /**
268
+ * Per-command trust decisions for `<workspace>/.book/commands/*.md` files
269
+ * that substitute shell into their prompt. That substitution runs outside the
270
+ * permission system and outside the sandbox, so a repository-controlled file
271
+ * needs a decision before it runs. User-global commands
272
+ * (<BOOK_HOME>/commands) were written by the user and never require one.
273
+ */
274
+ projectCommands: z.record(projectCommandChoiceSchema).default({})
275
+ });
276
+ var uiSettingsSchema = z.object({
277
+ /** Show provider-native and embedded model reasoning in the interactive transcript. */
278
+ showThinking: z.boolean().default(true),
279
+ /** Play the full-screen fire sequence on a newly created interactive startup session. */
280
+ startupAnimation: z.boolean().default(true)
281
+ });
282
+ var skillActivationSchema = z.enum(["auto", "name-only", "manual", "off"]);
283
+ var skillExecutionSchema = z.enum(["inherit", "ask", "deny"]);
284
+ var skillSettingsSchema = z.object({
285
+ /** Emergency switch that removes skill prompt and runtime effects without deleting packages. */
286
+ enabled: z.boolean().default(true),
287
+ /** Per-skill visibility overrides keyed by the skill's declared name. */
288
+ overrides: z.record(skillActivationSchema).default({}),
289
+ /** Per-skill activation consent policy. Tool calls still use the normal permission system. */
290
+ execution: z.record(skillExecutionSchema).default({})
291
+ });
292
+ var compactStrategySchema = z.literal("summary");
293
+ var bookSettingsSchema = z.object({
294
+ model: z.string().optional(),
295
+ /** Default strategy used to reduce historical conversation context. */
296
+ compactStrategy: compactStrategySchema.optional(),
297
+ /** Optional model used only to generate historical conversation checkpoints. */
298
+ compactModel: z.string().min(1).optional(),
299
+ /** Max agent turns per user message. Omit for unlimited. */
300
+ maxTurns: z.number().int().min(1).optional(),
301
+ maxTokens: z.number().int().min(1e3).optional(),
302
+ effort: effortLevelSchema.optional(),
303
+ /** TUI color theme: apple (default), dark, light, auto, a bundled palette, or a custom theme filename. */
304
+ theme: z.string().min(1).optional(),
305
+ /**
306
+ * The shell the Bash tool spawns: `bash`, `pwsh`, `powershell`, `cmd`, `sh`,
307
+ * or an executable path. Honoured from the user-global layer, an explicit
308
+ * `--settings` document, or `BOOK_SHELL` only: a workspace file cannot pick
309
+ * the program every command is handed to.
310
+ */
311
+ shell: z.string().min(1).optional(),
312
+ ui: uiSettingsSchema.default({}),
313
+ skills: skillSettingsSchema.default({}),
314
+ autoCompactEnabled: z.boolean().optional(),
315
+ /** Permission mode used by each host when no invocation-specific mode is supplied. */
316
+ defaultMode: z.enum(["default", "acceptEdits", "plan", "auto", "dontAsk", "bypassPermissions"]).optional(),
317
+ disableBypassPermissionsMode: z.boolean().optional(),
318
+ additionalDirectories: z.array(z.string()).default([]),
319
+ env: z.record(z.string()).default({}),
320
+ provider: z.record(providerConfigSchema).default({}),
321
+ permissions: permissionsSchema.default({}),
322
+ sandbox: sandboxSchema.default({}),
323
+ hooks: hooksSchema.default({}),
324
+ retry: retrySettingsSchema.default({}),
325
+ memory: memorySettingsSchema.default({}),
326
+ continuation: continuationSettingsSchema.default({}),
327
+ agents: agentSettingsSchema.default({}),
328
+ toolDiscovery: toolDiscoverySettingsSchema.default({}),
329
+ toolExecution: toolExecutionSettingsSchema.default({}),
330
+ observability: observabilitySettingsSchema.default({}),
331
+ mcp: mcpSettingsSchema.default({}),
332
+ commands: commandSettingsSchema.default({})
333
+ });
334
+ var DEFAULT_SETTINGS = {
335
+ compactStrategy: "summary",
336
+ permissions: { allow: [], ask: [], deny: [], projectAllowRules: {} },
337
+ sandbox: {
338
+ enabled: false,
339
+ failIfUnavailable: false,
340
+ autoAllowBashIfSandboxed: true,
341
+ excludedCommands: [],
342
+ allowUnsandboxedCommands: true,
343
+ filesystem: { allowWrite: [], denyWrite: [], denyRead: [] },
344
+ network: { allowedDomains: [], deniedDomains: [] }
345
+ },
346
+ hooks: {
347
+ SessionStart: [],
348
+ SessionEnd: [],
349
+ UserPromptSubmit: [],
350
+ PreToolUse: [],
351
+ PostToolUse: [],
352
+ Stop: [],
353
+ PreCompact: [],
354
+ PostCompact: [],
355
+ SubagentStart: [],
356
+ SubagentStop: [],
357
+ Notification: [],
358
+ projectEntries: {}
359
+ },
360
+ additionalDirectories: [],
361
+ env: {},
362
+ provider: {},
363
+ ui: { showThinking: true, startupAnimation: true },
364
+ skills: { enabled: true, overrides: {}, execution: {} },
365
+ retry: {
366
+ maxAttempts: 10,
367
+ baseDelayMs: 1e3,
368
+ maxDelayMs: 3e4,
369
+ totalBudgetMs: 0,
370
+ requestTimeoutMs: 6e5,
371
+ streamStallTimeoutMs: 2e4,
372
+ thinkingStallTimeoutMs: 9e5,
373
+ toolRetries: 1,
374
+ watchdog: false,
375
+ streamReissueAttempts: 3,
376
+ outputCapContinuations: 10
377
+ },
378
+ continuation: {
379
+ enabled: false,
380
+ maxConsecutive: 50,
381
+ noProgressLimit: 3,
382
+ blockedToolTurnLimit: 3,
383
+ planRefreshTurns: 25,
384
+ maxWallClockMs: 0
385
+ },
386
+ memory: {
387
+ enabled: true,
388
+ autoSave: true,
389
+ requireApproval: true
390
+ },
391
+ agents: {
392
+ mode: "adaptive",
393
+ maxConcurrent: 3,
394
+ maxSpawned: 8,
395
+ maxDepth: 1,
396
+ persist: true,
397
+ includeUntrackedInSnapshot: true,
398
+ telemetry: true,
399
+ retentionDays: 30,
400
+ checks: {},
401
+ checkTimeoutMs: 12e4,
402
+ maxWorktrees: 24,
403
+ resumeInterrupted: true,
404
+ minFreeDiskBytes: 2 * 1024 * 1024 * 1024,
405
+ profiles: {},
406
+ ui: { enabled: true },
407
+ routing: { inlineSearchBudget: 3, exploreReminder: true },
408
+ forwardTextEvents: false
409
+ },
410
+ toolDiscovery: {
411
+ mode: "auto",
412
+ eagerToolCount: 10,
413
+ schemaTokenBudget: 8e3,
414
+ maxLoadedTools: 15,
415
+ searchLimit: 5
416
+ },
417
+ toolExecution: {
418
+ maxConcurrent: 4
419
+ },
420
+ observability: {
421
+ toolTelemetry: true,
422
+ toolTelemetryRetentionDays: 30
423
+ },
424
+ mcp: {
425
+ projectServers: {}
426
+ },
427
+ commands: {
428
+ projectCommands: {}
429
+ }
430
+ };
431
+
432
+ // src/settings-repository.ts
433
+ import {
434
+ closeSync,
435
+ existsSync,
436
+ fsyncSync,
437
+ mkdirSync,
438
+ openSync,
439
+ readFileSync,
440
+ renameSync,
441
+ statSync,
442
+ unlinkSync,
443
+ writeSync
444
+ } from "fs";
445
+ import { basename, dirname, join } from "path";
446
+
447
+ // src/cli/utils.ts
448
+ function getNestedValue(obj, path) {
449
+ const parts = path.split(".");
450
+ let current = obj;
451
+ for (const part of parts) {
452
+ if (current === null || current === void 0 || typeof current !== "object") return void 0;
453
+ current = current[part];
454
+ }
455
+ return current;
456
+ }
457
+ function setNestedValue(obj, path, value) {
458
+ const parts = path.split(".");
459
+ let current = obj;
460
+ for (let i = 0; i < parts.length - 1; i++) {
461
+ if (!(parts[i] in current) || typeof current[parts[i]] !== "object" || current[parts[i]] === null) {
462
+ current[parts[i]] = {};
463
+ }
464
+ current = current[parts[i]];
465
+ }
466
+ current[parts[parts.length - 1]] = value;
467
+ }
468
+ function deleteNestedValue(obj, path) {
469
+ const parts = typeof path === "string" ? path.split(".") : [...path];
470
+ const ancestors = [];
471
+ let current = obj;
472
+ for (let i = 0; i < parts.length - 1; i++) {
473
+ const key = parts[i];
474
+ if (!Object.prototype.hasOwnProperty.call(current, key)) return false;
475
+ const next = current[key];
476
+ if (typeof next !== "object" || next === null || Array.isArray(next)) return false;
477
+ ancestors.push({ parent: current, key });
478
+ current = next;
479
+ }
480
+ const leaf = parts[parts.length - 1];
481
+ if (!Object.prototype.hasOwnProperty.call(current, leaf)) return false;
482
+ delete current[leaf];
483
+ for (let i = ancestors.length - 1; i >= 0; i--) {
484
+ const { parent, key } = ancestors[i];
485
+ const child = parent[key];
486
+ if (typeof child === "object" && child !== null && !Array.isArray(child) && Object.keys(child).length === 0) {
487
+ delete parent[key];
488
+ } else {
489
+ break;
490
+ }
491
+ }
492
+ return true;
493
+ }
494
+ function parseNumericFlag(raw, flag, opts = {}) {
495
+ if (raw === void 0 || raw === null || raw === "") return void 0;
496
+ const text = String(raw).trim();
497
+ const value = opts.integer ? Number.parseInt(text, 10) : Number.parseFloat(text);
498
+ if (!Number.isFinite(value) || !/^[+-]?(\d+\.?\d*|\.\d+)$/.test(text)) {
499
+ throw new Error(`${flag} expects a number, but received "${text}".`);
500
+ }
501
+ if (opts.integer && text.includes(".")) {
502
+ throw new Error(`${flag} expects a whole number, but received "${text}".`);
503
+ }
504
+ const min = opts.min ?? 0;
505
+ if (value < min) {
506
+ throw new Error(`${flag} must be at least ${min}, but received "${text}".`);
507
+ }
508
+ return value;
509
+ }
510
+
511
+ // src/settings-redaction.ts
512
+ var REDACTED_SECRET = "*** (stored)";
513
+ function redactProviderRecord(value) {
514
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
515
+ const clone = structuredClone(value);
516
+ if ("apiKey" in clone) clone.apiKey = REDACTED_SECRET;
517
+ return clone;
518
+ }
519
+ function redactProviderMap(value) {
520
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
521
+ const clone = structuredClone(value);
522
+ for (const [id, provider] of Object.entries(clone)) clone[id] = redactProviderRecord(provider);
523
+ return clone;
524
+ }
525
+ var REMOVED_SECRET_BEARING_BLOCKS = ["auth"];
526
+ function redactSettingValue(path, value) {
527
+ const parts = path.split(".");
528
+ if (parts[0] && REMOVED_SECRET_BEARING_BLOCKS.includes(parts[0])) return REDACTED_SECRET;
529
+ if (parts[0] !== "provider") return value;
530
+ if (parts.length === 1) return redactProviderMap(value);
531
+ if (parts.length === 2) return redactProviderRecord(value);
532
+ return parts[parts.length - 1] === "apiKey" ? REDACTED_SECRET : value;
533
+ }
534
+ function redactSettingsForDisplay(value) {
535
+ if (!value || typeof value !== "object") return value;
536
+ const clone = structuredClone(value);
537
+ clone.provider = redactProviderMap(clone.provider);
538
+ for (const key of REMOVED_SECRET_BEARING_BLOCKS) {
539
+ if (clone[key] !== void 0) clone[key] = REDACTED_SECRET;
540
+ }
541
+ return clone;
542
+ }
543
+
544
+ // src/settings-repository.ts
545
+ var SETTINGS_TOP_LEVEL_KEYS = Object.freeze(Object.keys(bookSettingsSchema.shape).sort());
546
+ function formatSettingsKeyHelp(heading = "Supported top-level settings:") {
547
+ return `${heading}
548
+ ${SETTINGS_TOP_LEVEL_KEYS.map((key) => ` ${key}`).join("\n")}`;
549
+ }
550
+ function issueDiagnostic(path, issue) {
551
+ return {
552
+ path,
553
+ issuePath: issue.path.join("."),
554
+ message: issue.message
555
+ };
556
+ }
557
+ function safeMessage(error) {
558
+ return error instanceof Error ? error.message : String(error);
559
+ }
560
+ function readSettingsDocument(path) {
561
+ if (!existsSync(path)) return { status: "absent", path };
562
+ let parsed;
563
+ try {
564
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
565
+ } catch (error) {
566
+ return { status: "malformed", path, error: safeMessage(error) };
567
+ }
568
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
569
+ return { status: "non-object", path, error: "Settings must contain a JSON object." };
570
+ }
571
+ return { status: "valid", path, document: parsed };
572
+ }
573
+ function writeFileAtomic(path, contents) {
574
+ const directory = dirname(path);
575
+ mkdirSync(directory, { recursive: true });
576
+ const temporaryPath = join(
577
+ directory,
578
+ `.${basename(path)}.${process.pid}.${crypto.randomUUID()}.tmp`
579
+ );
580
+ let descriptor;
581
+ try {
582
+ descriptor = openSync(temporaryPath, "wx", 384);
583
+ writeSync(descriptor, contents, void 0, "utf-8");
584
+ fsyncSync(descriptor);
585
+ closeSync(descriptor);
586
+ descriptor = void 0;
587
+ renameSync(temporaryPath, path);
588
+ } catch (error) {
589
+ if (descriptor !== void 0) closeSync(descriptor);
590
+ try {
591
+ unlinkSync(temporaryPath);
592
+ } catch {
593
+ }
594
+ throw error;
595
+ }
596
+ }
597
+ function validateTopLevelKeys(path, document) {
598
+ const supported = new Set(SETTINGS_TOP_LEVEL_KEYS);
599
+ return Object.keys(document).filter((key) => !supported.has(key)).map((key) => ({
600
+ path,
601
+ issuePath: key,
602
+ message: `Unknown top-level setting. Supported keys: ${SETTINGS_TOP_LEVEL_KEYS.join(", ")}`
603
+ }));
604
+ }
605
+ var SettingsRepository = class {
606
+ constructor(path, options = {}) {
607
+ this.path = path;
608
+ this.writeAtomic = options.writeAtomic ?? writeFileAtomic;
609
+ }
610
+ path;
611
+ writeAtomic;
612
+ read() {
613
+ return readSettingsDocument(this.path);
614
+ }
615
+ set(values) {
616
+ return this.mutate((candidate) => {
617
+ for (const [key, value] of Object.entries(values)) setNestedValue(candidate, key, value);
618
+ }, values);
619
+ }
620
+ remove(paths) {
621
+ return this.mutate((candidate) => {
622
+ for (const path of paths) deleteNestedValue(candidate, path.split("."));
623
+ }, {});
624
+ }
625
+ update(mutation, displayValues = {}) {
626
+ return this.mutate(mutation, displayValues);
627
+ }
628
+ mutate(mutation, values) {
629
+ let lock;
630
+ try {
631
+ lock = this.acquireLock();
632
+ return this.mutateLocked(mutation, values);
633
+ } catch (error) {
634
+ return {
635
+ ok: false,
636
+ path: this.path,
637
+ diagnostics: [{ path: this.path, message: safeMessage(error) }]
638
+ };
639
+ } finally {
640
+ if (lock !== void 0) this.releaseLock(lock);
641
+ }
642
+ }
643
+ acquireLock() {
644
+ mkdirSync(dirname(this.path), { recursive: true });
645
+ const lockPath = `${this.path}.lock`;
646
+ for (let attempt = 0; attempt < 50; attempt++) {
647
+ try {
648
+ return openSync(lockPath, "wx", 384);
649
+ } catch (error) {
650
+ if (error.code !== "EEXIST") throw error;
651
+ try {
652
+ if (Date.now() - statSync(lockPath).mtimeMs > 1e4) unlinkSync(lockPath);
653
+ } catch {
654
+ }
655
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
656
+ }
657
+ }
658
+ throw new Error(`Timed out waiting for settings lock: ${lockPath}`);
659
+ }
660
+ releaseLock(descriptor) {
661
+ closeSync(descriptor);
662
+ try {
663
+ unlinkSync(`${this.path}.lock`);
664
+ } catch {
665
+ }
666
+ }
667
+ mutateLocked(mutation, values) {
668
+ const source = this.read();
669
+ if (source.status === "malformed" || source.status === "non-object") {
670
+ return {
671
+ ok: false,
672
+ path: this.path,
673
+ diagnostics: [{ path: this.path, message: source.error }]
674
+ };
675
+ }
676
+ const candidate = structuredClone(source.status === "valid" ? source.document : {});
677
+ try {
678
+ mutation(candidate);
679
+ } catch (error) {
680
+ return {
681
+ ok: false,
682
+ path: this.path,
683
+ diagnostics: [{ path: this.path, message: safeMessage(error) }]
684
+ };
685
+ }
686
+ const keyDiagnostics = validateTopLevelKeys(this.path, candidate);
687
+ const validation = bookSettingsSchema.safeParse(candidate);
688
+ const diagnostics = [
689
+ ...keyDiagnostics,
690
+ ...validation.success ? [] : validation.error.issues.map((issue) => issueDiagnostic(this.path, issue))
691
+ ];
692
+ if (diagnostics.length > 0) return { ok: false, path: this.path, diagnostics };
693
+ try {
694
+ const changed = source.status !== "valid" || JSON.stringify(source.document) !== JSON.stringify(candidate);
695
+ if (changed) this.writeAtomic(this.path, JSON.stringify(candidate, null, 2) + "\n");
696
+ return {
697
+ ok: true,
698
+ path: this.path,
699
+ document: candidate,
700
+ changed,
701
+ values: Object.fromEntries(
702
+ Object.entries(values).map(([key, value]) => [key, redactSettingValue(key, value)])
703
+ )
704
+ };
705
+ } catch (error) {
706
+ return {
707
+ ok: false,
708
+ path: this.path,
709
+ diagnostics: [{ path: this.path, message: safeMessage(error) }]
710
+ };
711
+ }
712
+ }
713
+ };
714
+ function formatSettingsDiagnostics(diagnostics) {
715
+ return diagnostics.map((diagnostic) => {
716
+ const location = diagnostic.issuePath ? `${diagnostic.path} (${diagnostic.issuePath})` : diagnostic.path;
717
+ return `${location}: ${diagnostic.message}`;
718
+ }).join("\n");
719
+ }
720
+
721
+ export {
722
+ projectAllowRuleChoiceSchema,
723
+ projectHookChoiceSchema,
724
+ HOOK_EVENTS,
725
+ effortLevelSchema,
726
+ providerConfigSchema,
727
+ mcpProjectServerChoiceSchema,
728
+ projectCommandChoiceSchema,
729
+ bookSettingsSchema,
730
+ DEFAULT_SETTINGS,
731
+ getNestedValue,
732
+ setNestedValue,
733
+ parseNumericFlag,
734
+ redactSettingValue,
735
+ redactSettingsForDisplay,
736
+ SETTINGS_TOP_LEVEL_KEYS,
737
+ formatSettingsKeyHelp,
738
+ readSettingsDocument,
739
+ writeFileAtomic,
740
+ SettingsRepository,
741
+ formatSettingsDiagnostics
742
+ };
743
+ //# sourceMappingURL=chunk-574C6MQR.js.map