@letrquan/book 0.2.0 → 0.4.0-beta.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 (177) hide show
  1. package/CHANGELOG.md +3771 -40
  2. package/README.md +135 -1311
  3. package/dist/additional-roots-VAWRQ625.js +32 -0
  4. package/dist/{app-MOXXWEOY.js → app-J7BVPSUH.js} +9648 -5509
  5. package/dist/app-J7BVPSUH.js.map +1 -0
  6. package/dist/chunk-2AAOF5B5.js +28 -0
  7. package/dist/chunk-2AAOF5B5.js.map +1 -0
  8. package/dist/chunk-32WBCUAN.js +660 -0
  9. package/dist/chunk-32WBCUAN.js.map +1 -0
  10. package/dist/chunk-4DKHLZRI.js +120 -0
  11. package/dist/chunk-4DKHLZRI.js.map +1 -0
  12. package/dist/{chunk-CAGOB2N4.js → chunk-62EEAUID.js} +6 -4
  13. package/dist/chunk-62EEAUID.js.map +1 -0
  14. package/dist/chunk-AHC7OU75.js +1124 -0
  15. package/dist/chunk-AHC7OU75.js.map +1 -0
  16. package/dist/{chunk-E75IPST5.js → chunk-AJZWLKLQ.js} +73 -70
  17. package/dist/chunk-AJZWLKLQ.js.map +1 -0
  18. package/dist/chunk-B5FC3MYZ.js +395 -0
  19. package/dist/chunk-B5FC3MYZ.js.map +1 -0
  20. package/dist/{chunk-KWHQ2XXL.js → chunk-DNFQTSMJ.js} +55 -13
  21. package/dist/chunk-DNFQTSMJ.js.map +1 -0
  22. package/dist/{chunk-J7MIZ5CA.js → chunk-E5WLQSKA.js} +719 -89
  23. package/dist/chunk-E5WLQSKA.js.map +1 -0
  24. package/dist/chunk-ECLC4CIJ.js +1181 -0
  25. package/dist/chunk-ECLC4CIJ.js.map +1 -0
  26. package/dist/chunk-EEX6QNXV.js +127 -0
  27. package/dist/chunk-EEX6QNXV.js.map +1 -0
  28. package/dist/chunk-EFXVSQ5J.js +931 -0
  29. package/dist/chunk-EFXVSQ5J.js.map +1 -0
  30. package/dist/{chunk-V4BOYR52.js → chunk-ENLB7NYV.js} +1304 -326
  31. package/dist/chunk-ENLB7NYV.js.map +1 -0
  32. package/dist/chunk-FPR7SKIE.js +6926 -0
  33. package/dist/chunk-FPR7SKIE.js.map +1 -0
  34. package/dist/{chunk-DGMQEI6Y.js → chunk-HFB74CLM.js} +30 -9
  35. package/dist/chunk-HFB74CLM.js.map +1 -0
  36. package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
  37. package/dist/chunk-HOZXKOE3.js.map +1 -0
  38. package/dist/chunk-IDYWN37T.js +36 -0
  39. package/dist/chunk-IDYWN37T.js.map +1 -0
  40. package/dist/chunk-J4F2QLM2.js +273 -0
  41. package/dist/chunk-J4F2QLM2.js.map +1 -0
  42. package/dist/chunk-JGF5HLSI.js +1065 -0
  43. package/dist/chunk-JGF5HLSI.js.map +1 -0
  44. package/dist/{chunk-XIFFTATU.js → chunk-KDNWMKXV.js} +7166 -4237
  45. package/dist/chunk-KDNWMKXV.js.map +1 -0
  46. package/dist/chunk-KXAYUIKP.js +90 -0
  47. package/dist/chunk-KXAYUIKP.js.map +1 -0
  48. package/dist/{chunk-3U5IBM24.js → chunk-MEOB2DZA.js} +12 -2
  49. package/dist/chunk-MEOB2DZA.js.map +1 -0
  50. package/dist/{chunk-WKBO5R4O.js → chunk-MV5PQNIA.js} +2 -2
  51. package/dist/chunk-MV5PQNIA.js.map +1 -0
  52. package/dist/chunk-N2G4Q7ME.js +2672 -0
  53. package/dist/chunk-N2G4Q7ME.js.map +1 -0
  54. package/dist/chunk-OJBMDM5H.js +299 -0
  55. package/dist/chunk-OJBMDM5H.js.map +1 -0
  56. package/dist/{chunk-URLQAXTR.js → chunk-PTDC7ZUP.js} +2 -2
  57. package/dist/chunk-PTDC7ZUP.js.map +1 -0
  58. package/dist/{chunk-574C6MQR.js → chunk-REASC2QG.js} +173 -64
  59. package/dist/chunk-REASC2QG.js.map +1 -0
  60. package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
  61. package/dist/chunk-RT5SATF7.js.map +1 -0
  62. package/dist/chunk-SBTU5BEC.js +114 -0
  63. package/dist/chunk-SBTU5BEC.js.map +1 -0
  64. package/dist/{chunk-5KLVH3PY.js → chunk-SWPDKLNM.js} +5 -3
  65. package/dist/chunk-SWPDKLNM.js.map +1 -0
  66. package/dist/chunk-UCFJ43OS.js +520 -0
  67. package/dist/chunk-UCFJ43OS.js.map +1 -0
  68. package/dist/{chunk-VZQXX3WW.js → chunk-W7SLYS3M.js} +80 -16
  69. package/dist/chunk-W7SLYS3M.js.map +1 -0
  70. package/dist/{chunk-ANSJPYGI.js → chunk-WV3O5PGX.js} +3 -3
  71. package/dist/chunk-WV3O5PGX.js.map +1 -0
  72. package/dist/chunk-XHAO3K7I.js +18 -0
  73. package/dist/chunk-XHAO3K7I.js.map +1 -0
  74. package/dist/chunk-XJRJVXJR.js +1050 -0
  75. package/dist/chunk-XJRJVXJR.js.map +1 -0
  76. package/dist/chunk-YIL6HTJX.js +663 -0
  77. package/dist/chunk-YIL6HTJX.js.map +1 -0
  78. package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
  79. package/dist/chunk-ZPSZZUWF.js.map +1 -0
  80. package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-ZRY66LCW.js} +7 -5
  81. package/dist/context-C3RAP6K5.js +41 -0
  82. package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-DPQD5H65.js} +6 -5
  83. package/dist/index.js +947 -203
  84. package/dist/index.js.map +1 -1
  85. package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-73AQO5WP.js} +18 -9
  86. package/dist/interactive-assets-73AQO5WP.js.map +1 -0
  87. package/dist/job-runner.js +166 -111
  88. package/dist/job-runner.js.map +1 -1
  89. package/dist/job-supervisor.d.ts +2 -0
  90. package/dist/job-supervisor.js +121 -0
  91. package/dist/job-supervisor.js.map +1 -0
  92. package/dist/loader-DQNPDKOX.js +27 -0
  93. package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-27LJYADQ.js} +6 -5
  94. package/dist/{mcp-config-YIJIES2C.js → mcp-config-GXSODNCM.js} +4 -3
  95. package/dist/memory-store-I6Z54VUN.js +51 -0
  96. package/dist/{model-window-store-YFVNXCIP.js → model-window-store-6VKXXTPJ.js} +9 -4
  97. package/dist/openai-compatible-IPUNBJ53.js +16 -0
  98. package/dist/permission-approvals-Q5IMFEZI.js +15 -0
  99. package/dist/permissions-7Y35X6HZ.js +62 -0
  100. package/dist/provider-approvals-5AGH3RVT.js +76 -0
  101. package/dist/sandbox-N2RY72GN.js +42 -0
  102. package/dist/{scrollback-LUKLIBVI.js → scrollback-GD76GNM4.js} +44 -26
  103. package/dist/scrollback-GD76GNM4.js.map +1 -0
  104. package/dist/sdk.d.ts +2739 -2069
  105. package/dist/sdk.js +77 -42
  106. package/dist/sdk.js.map +1 -1
  107. package/dist/settings-loader-KDBH45AQ.js +43 -0
  108. package/dist/settings-loader-KDBH45AQ.js.map +1 -0
  109. package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
  110. package/dist/settings-removed-3ATWJEF3.js.map +1 -0
  111. package/dist/shell-selection-Z5WORZU4.js +31 -0
  112. package/dist/shell-selection-Z5WORZU4.js.map +1 -0
  113. package/package.json +25 -24
  114. package/dist/app-MOXXWEOY.js.map +0 -1
  115. package/dist/chunk-24AXE6SP.js.map +0 -1
  116. package/dist/chunk-3U5IBM24.js.map +0 -1
  117. package/dist/chunk-574C6MQR.js.map +0 -1
  118. package/dist/chunk-5GDZ22YP.js +0 -449
  119. package/dist/chunk-5GDZ22YP.js.map +0 -1
  120. package/dist/chunk-5KLVH3PY.js.map +0 -1
  121. package/dist/chunk-5RDRAO4B.js.map +0 -1
  122. package/dist/chunk-7N4J557C.js.map +0 -1
  123. package/dist/chunk-A7JLBK2Y.js +0 -3506
  124. package/dist/chunk-A7JLBK2Y.js.map +0 -1
  125. package/dist/chunk-ANSJPYGI.js.map +0 -1
  126. package/dist/chunk-BTZZUCLI.js +0 -264
  127. package/dist/chunk-BTZZUCLI.js.map +0 -1
  128. package/dist/chunk-C7D4NJ2D.js +0 -217
  129. package/dist/chunk-C7D4NJ2D.js.map +0 -1
  130. package/dist/chunk-CAGOB2N4.js.map +0 -1
  131. package/dist/chunk-DGMQEI6Y.js.map +0 -1
  132. package/dist/chunk-E75IPST5.js.map +0 -1
  133. package/dist/chunk-EYGD6X2T.js +0 -180
  134. package/dist/chunk-EYGD6X2T.js.map +0 -1
  135. package/dist/chunk-GTVB4HEM.js +0 -765
  136. package/dist/chunk-GTVB4HEM.js.map +0 -1
  137. package/dist/chunk-J7MIZ5CA.js.map +0 -1
  138. package/dist/chunk-KWHQ2XXL.js.map +0 -1
  139. package/dist/chunk-L6LK2QIQ.js +0 -657
  140. package/dist/chunk-L6LK2QIQ.js.map +0 -1
  141. package/dist/chunk-MAQXCUR4.js +0 -1007
  142. package/dist/chunk-MAQXCUR4.js.map +0 -1
  143. package/dist/chunk-S4XL7HOM.js +0 -402
  144. package/dist/chunk-S4XL7HOM.js.map +0 -1
  145. package/dist/chunk-URLQAXTR.js.map +0 -1
  146. package/dist/chunk-V4BOYR52.js.map +0 -1
  147. package/dist/chunk-VZQXX3WW.js.map +0 -1
  148. package/dist/chunk-WKBO5R4O.js.map +0 -1
  149. package/dist/chunk-XIFFTATU.js.map +0 -1
  150. package/dist/context-ZC5IFEAX.js +0 -25
  151. package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
  152. package/dist/loader-4IDS2RIE.js +0 -15
  153. package/dist/openai-compatible-RHZUBHV6.js +0 -10
  154. package/dist/permission-approvals-76PVREFD.js +0 -14
  155. package/dist/permissions-CHCKTFQQ.js +0 -28
  156. package/dist/sandbox-CNVAQDA7.js +0 -23
  157. package/dist/scrollback-LUKLIBVI.js.map +0 -1
  158. package/dist/settings-loader-M2QP5TX2.js +0 -21
  159. package/dist/shell-selection-V6QAYXPX.js +0 -17
  160. package/patches/ink+6.8.0.patch +0 -13
  161. package/scripts/apply-ink-patch.mjs +0 -29
  162. package/scripts/ink-patch.mjs +0 -59
  163. package/scripts/verify-ink-patch.mjs +0 -11
  164. /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-VAWRQ625.js.map} +0 -0
  165. /package/dist/{context-ZC5IFEAX.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
  166. /package/dist/{hook-approvals-CTUFKEJT.js.map → context-C3RAP6K5.js.map} +0 -0
  167. /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
  168. /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-DQNPDKOX.js.map} +0 -0
  169. /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
  170. /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-GXSODNCM.js.map} +0 -0
  171. /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-I6Z54VUN.js.map} +0 -0
  172. /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
  173. /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
  174. /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
  175. /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
  176. /package/dist/{settings-removed-JWWPT4WA.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
  177. /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-N2RY72GN.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,98 +1,159 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ checkProviderKey,
4
+ hasInkTrailingNewlineFix,
5
+ inkBuildDir,
3
6
  installFrameCapture,
4
- installInkScrollRenderer
5
- } from "./chunk-C7D4NJ2D.js";
7
+ installInkFrameClip,
8
+ installInkOutputCache,
9
+ installInkRenderCull,
10
+ installInkScrollRenderer,
11
+ isCiEnvironment,
12
+ loadInkFlushSync,
13
+ providerKeyTarget,
14
+ resolveInkMaxFps,
15
+ resolveTuiRendererMode,
16
+ validateApiKey,
17
+ writeProviderKey
18
+ } from "./chunk-ECLC4CIJ.js";
6
19
  import {
7
20
  buildMcpToolDefinitions,
21
+ cleanupRunStatus,
22
+ collectSkippedProjectFileNotices,
8
23
  collectWithheldProjectNotices,
24
+ collectWithheldProviderNotices,
9
25
  connectMcpServers,
10
26
  disconnectMcpServers,
27
+ isReaderGoneError,
11
28
  readRunStatus,
12
- runHeadless
13
- } from "./chunk-J7MIZ5CA.js";
29
+ runHeadless,
30
+ runStatusPath
31
+ } from "./chunk-E5WLQSKA.js";
32
+ import {
33
+ evaluateMcpServerApproval,
34
+ mcpServerFingerprint,
35
+ mcpServersToRecord,
36
+ partitionMcpServersByApproval,
37
+ persistMcpProjectServerChoice
38
+ } from "./chunk-PTDC7ZUP.js";
14
39
  import {
15
40
  SessionStore,
16
- resolveSessionBootstrap
17
- } from "./chunk-V4BOYR52.js";
41
+ discardUntouchedSessions,
42
+ parseSessionDriver,
43
+ resolveSessionBootstrap,
44
+ resolveSessionDriver
45
+ } from "./chunk-ENLB7NYV.js";
18
46
  import {
19
47
  aggregateToolUse,
20
48
  createDefaultRegistry,
21
49
  formatToolStatsReport,
22
- freezeAgentConfig,
23
- loadConfig,
50
+ pathExists,
24
51
  pruneToolUseRecords,
25
52
  readToolUseRecords
26
- } from "./chunk-XIFFTATU.js";
53
+ } from "./chunk-KDNWMKXV.js";
27
54
  import {
28
- EFFORT_LEVELS,
29
55
  applySettingWrite,
30
56
  describeSettingShadow,
31
57
  discoverAgents,
32
58
  estimateUsageCost,
33
59
  getPackageVersion,
34
- isEffortLevel,
35
- parseEffortLevel,
36
60
  readScopeDocument,
37
61
  resolvePermissionMode,
38
62
  settingsScopeLabel,
39
63
  withBuiltInAgents
40
- } from "./chunk-A7JLBK2Y.js";
41
- import "./chunk-KWHQ2XXL.js";
42
- import "./chunk-VZQXX3WW.js";
64
+ } from "./chunk-FPR7SKIE.js";
65
+ import {
66
+ EFFORT_LEVELS,
67
+ freezeAgentConfig,
68
+ isEffortLevel,
69
+ loadConfig,
70
+ parseEffortLevel,
71
+ readLegacyBaseUrl
72
+ } from "./chunk-N2G4Q7ME.js";
73
+ import "./chunk-XJRJVXJR.js";
74
+ import "./chunk-AHC7OU75.js";
75
+ import "./chunk-DNFQTSMJ.js";
76
+ import {
77
+ applySettingsEnvOverrides,
78
+ isUserSettingsFile,
79
+ loadSettingsFile,
80
+ resolveSettings,
81
+ resolveSettingsWithProviderGate,
82
+ startupAnimationEnvNote
83
+ } from "./chunk-UCFJ43OS.js";
84
+ import "./chunk-HOZXKOE3.js";
85
+ import "./chunk-EFXVSQ5J.js";
86
+ import "./chunk-62EEAUID.js";
43
87
  import {
44
88
  discoverCommands
45
- } from "./chunk-E75IPST5.js";
89
+ } from "./chunk-AJZWLKLQ.js";
90
+ import "./chunk-KXAYUIKP.js";
46
91
  import {
47
92
  describeProjectCommandShell,
48
93
  displayableCommandName,
49
94
  partitionProjectCommands,
50
95
  persistProjectCommandChoice,
51
96
  projectCommandFingerprint
52
- } from "./chunk-5KLVH3PY.js";
97
+ } from "./chunk-SWPDKLNM.js";
98
+ import "./chunk-2AAOF5B5.js";
53
99
  import {
54
100
  formatMcpServerCommand,
55
101
  isValidMcpServerName,
56
102
  resolveMcpServerList
57
- } from "./chunk-3U5IBM24.js";
103
+ } from "./chunk-MEOB2DZA.js";
104
+ import "./chunk-W7SLYS3M.js";
105
+ import "./chunk-JGF5HLSI.js";
106
+ import "./chunk-32WBCUAN.js";
107
+ import "./chunk-RT5SATF7.js";
58
108
  import {
59
- evaluateMcpServerApproval,
60
- mcpServerFingerprint,
61
- mcpServersToRecord,
62
- partitionMcpServersByApproval,
63
- persistMcpProjectServerChoice
64
- } from "./chunk-URLQAXTR.js";
65
- import "./chunk-MAQXCUR4.js";
109
+ buildChildEnv
110
+ } from "./chunk-XHAO3K7I.js";
111
+ import "./chunk-SBTU5BEC.js";
66
112
  import {
67
- canonicalToolName
68
- } from "./chunk-5GDZ22YP.js";
69
- import "./chunk-EYGD6X2T.js";
113
+ collectDeclaredDirectories,
114
+ partitionProjectDirectories,
115
+ persistProjectDirectoryChoice,
116
+ realPathOfDirectory
117
+ } from "./chunk-4DKHLZRI.js";
70
118
  import {
71
119
  DEFAULT_LOCAL_DATA_RETENTION_DAYS,
72
120
  cleanupDebugLogs,
73
121
  createDebugLogger,
74
122
  getDebugLogPath
75
- } from "./chunk-GTVB4HEM.js";
76
- import "./chunk-S4XL7HOM.js";
77
- import "./chunk-7N4J557C.js";
123
+ } from "./chunk-OJBMDM5H.js";
78
124
  import {
79
- loadSettingsFile,
80
- resolveSettings
81
- } from "./chunk-BTZZUCLI.js";
125
+ canonicalToolName
126
+ } from "./chunk-YIL6HTJX.js";
127
+ import {
128
+ createUncGuard,
129
+ reachesUncPath
130
+ } from "./chunk-EEX6QNXV.js";
82
131
  import {
83
132
  partitionProjectAllowRules,
84
133
  persistProjectAllowRuleChoice
85
- } from "./chunk-WKBO5R4O.js";
134
+ } from "./chunk-MV5PQNIA.js";
135
+ import {
136
+ LEGACY_BASE_URL_ID,
137
+ describeLegacyBaseUrl,
138
+ describeReservedProvider,
139
+ describeWorkspaceProvider,
140
+ environmentModelSelections,
141
+ evaluateLegacyBaseUrl,
142
+ legacyBaseUrlFingerprint,
143
+ persistWorkspaceProviderChoice,
144
+ providerTrustCommand,
145
+ trustCommandText,
146
+ withEnvironmentSelections
147
+ } from "./chunk-B5FC3MYZ.js";
86
148
  import {
87
149
  collectDeclaredHooks,
88
150
  describeDeclaredHook,
89
151
  hookFingerprint,
90
152
  partitionProjectHooks,
91
- persistProjectHookChoice
92
- } from "./chunk-ANSJPYGI.js";
93
- import "./chunk-DGMQEI6Y.js";
94
- import "./chunk-5RDRAO4B.js";
95
- import "./chunk-CAGOB2N4.js";
153
+ persistProjectHookChoice,
154
+ renderUntrustedText
155
+ } from "./chunk-WV3O5PGX.js";
156
+ import "./chunk-HFB74CLM.js";
96
157
  import {
97
158
  DEFAULT_SETTINGS,
98
159
  SettingsRepository,
@@ -102,21 +163,27 @@ import {
102
163
  parseNumericFlag,
103
164
  redactSettingValue,
104
165
  redactSettingsForDisplay,
166
+ resolvePrintPrompt,
105
167
  writeFileAtomic
106
- } from "./chunk-574C6MQR.js";
168
+ } from "./chunk-REASC2QG.js";
169
+ import {
170
+ projectFileWriteRefusal
171
+ } from "./chunk-IDYWN37T.js";
107
172
  import {
108
173
  resolveBookHome
109
- } from "./chunk-24AXE6SP.js";
174
+ } from "./chunk-ZPSZZUWF.js";
110
175
 
111
176
  // src/runtime-env.ts
112
177
  if (!process.env.NODE_ENV) {
113
178
  process.env.NODE_ENV = "production";
179
+ process.env.BOOK_DEFAULTED_NODE_ENV = "1";
114
180
  }
115
181
 
116
182
  // src/index.ts
117
183
  import { InvalidArgumentError, program } from "commander";
118
184
 
119
185
  // src/cli/status-cmd.ts
186
+ import { existsSync } from "fs";
120
187
  import { join } from "path";
121
188
  function isProcessAlive(pid) {
122
189
  if (!Number.isInteger(pid) || pid <= 0) return false;
@@ -165,13 +232,17 @@ function buildSessionStatus(meta, loaded, runRecord) {
165
232
  let costUsd = models.length > 0 && usage ? 0 : null;
166
233
  if (usage) {
167
234
  for (const model of models) {
168
- const quote = estimateUsageCost(model, {
169
- promptTokens: usage.promptTokens,
170
- completionTokens: usage.completionTokens,
171
- totalTokens: usage.totalTokens,
172
- cacheReadInputTokens: usage.cacheReadInputTokens,
173
- cacheCreationInputTokens: usage.cacheCreationInputTokens
174
- });
235
+ const quote = estimateUsageCost(
236
+ model,
237
+ {
238
+ promptTokens: usage.promptTokens,
239
+ completionTokens: usage.completionTokens,
240
+ totalTokens: usage.totalTokens,
241
+ cacheReadInputTokens: usage.cacheReadInputTokens,
242
+ cacheCreationInputTokens: usage.cacheCreationInputTokens
243
+ },
244
+ "pool"
245
+ );
175
246
  if (quote.status !== "known") {
176
247
  costUsd = null;
177
248
  break;
@@ -314,7 +385,13 @@ function runStatusCommand(options) {
314
385
  const stdout = options.stdout ?? process.stdout;
315
386
  const home = options.home ?? resolveBookHome();
316
387
  const store = options.store ?? new SessionStore(join(home, "sessions"));
317
- const meta = options.session ? store.findById(options.session) ?? store.findByName(options.session) : store.mostRecentInCwd(options.workspace);
388
+ const meta = options.session ? store.findById(options.session) ?? store.findByName(options.session) : (
389
+ // A print run of host-performed commands records no message, only a run record (#428).
390
+ store.mostRecentInCwd(
391
+ options.workspace,
392
+ (candidate) => candidate.messageCount > 0 || existsSync(runStatusPath(candidate.id, home))
393
+ )
394
+ );
318
395
  if (!meta) {
319
396
  stdout.write(
320
397
  options.session ? `No session found matching "${options.session}".
@@ -344,9 +421,12 @@ function exit(code = 0) {
344
421
  function isExiting() {
345
422
  return exiting;
346
423
  }
347
-
348
- // src/cli/doctor.ts
349
- import { resolve } from "path";
424
+ var exitCodeFn = (code) => {
425
+ process.exitCode = code;
426
+ };
427
+ function setExitCode(code) {
428
+ exitCodeFn(code);
429
+ }
350
430
 
351
431
  // src/agents/diagnostics.ts
352
432
  var MUTATION_TOOLS = /* @__PURE__ */ new Set([
@@ -436,20 +516,14 @@ function collectAgentDiagnostics(config, profiles) {
436
516
  }
437
517
 
438
518
  // src/cli/doctor.ts
439
- var SHELL_SAFE_BARE = /^[A-Za-z0-9_@%+=:,./\\-]+$/;
440
- function shellArgument(value) {
441
- if (SHELL_SAFE_BARE.test(value)) return value;
442
- if (/["`$\n\r]/.test(value) || value.endsWith("\\")) return null;
443
- return `"${value}"`;
444
- }
445
- function trustCommandLine(workspace, rest) {
446
- if (resolve(workspace) === resolve(process.cwd())) return `book trust ${rest}`;
447
- const quoted = shellArgument(workspace);
448
- return quoted === null ? null : `book trust ${rest} --workspace ${quoted}`;
449
- }
519
+ var WORKSPACE_LAYER_TRUST = {
520
+ User: "trusted",
521
+ Project: "repository",
522
+ Local: "local"
523
+ };
450
524
  function approvalHint(label, workspace, rest) {
451
- const command = trustCommandLine(workspace, rest);
452
- return command === null ? `${label} book trust ${rest} (run it from ${workspace})` : `${label} ${command}`;
525
+ const { command, note } = trustCommandText(workspace, rest);
526
+ return `${label} ${command}${note}`;
453
527
  }
454
528
  function formatRelativeTime(timestamp, now = Date.now()) {
455
529
  const elapsedMs = Math.max(0, now - timestamp);
@@ -466,8 +540,8 @@ function describeCredentials(config) {
466
540
  }
467
541
  var ABSENT_LAYER = "\0absent-settings-layer";
468
542
  async function settingsLayers(workspace) {
469
- const { join: join8, resolve: resolve4 } = await import("path");
470
- const root = resolve4(workspace);
543
+ const { join: join8, resolve: resolve3 } = await import("path");
544
+ const root = resolve3(workspace);
471
545
  return [
472
546
  ["User", join8(resolveBookHome(), "settings.json")],
473
547
  ["Project", join8(root, ".book", "settings.json")],
@@ -494,15 +568,15 @@ async function attributeFailingLayer(workspace) {
494
568
  return void 0;
495
569
  }
496
570
  async function reportUnloadableConfig(workspace, error) {
497
- const { existsSync: existsSync4 } = await import("fs");
498
- const { resolve: resolve4 } = await import("path");
571
+ const { existsSync: existsSync5 } = await import("fs");
572
+ const { resolve: resolve3 } = await import("path");
499
573
  console.log("Book Doctor");
500
574
  console.log("===========");
501
575
  console.log();
502
576
  console.log("Version:", getPackageVersion());
503
577
  console.log("Node:", process.version);
504
578
  console.log("Platform:", process.platform, process.arch);
505
- console.log("Workspace:", resolve4(workspace));
579
+ console.log("Workspace:", resolve3(workspace));
506
580
  console.log();
507
581
  console.log("Configuration: FAILED TO LOAD");
508
582
  console.log(" " + (error instanceof Error ? error.message : String(error)));
@@ -511,7 +585,7 @@ async function reportUnloadableConfig(workspace, error) {
511
585
  const culprit = await attributeFailingLayer(workspace);
512
586
  console.log(" Settings layers, in the order they are applied:");
513
587
  for (const [label, path] of await settingsLayers(workspace)) {
514
- const marker = existsSync4(path) ? "[x]" : "[ ]";
588
+ const marker = existsSync5(path) ? "[x]" : "[ ]";
515
589
  const blame = label === culprit ? " <- the failure appears with this layer" : "";
516
590
  console.log(" " + marker + " " + label + ": " + path + blame);
517
591
  }
@@ -553,8 +627,8 @@ async function runDoctorCommand(workspace, options = {}) {
553
627
  collectRemovedEnvNotices,
554
628
  removedAuthStoreNotice,
555
629
  readSettingsDocumentForNotices
556
- } = await import("./settings-removed-JWWPT4WA.js");
557
- const { settingsLayerPaths } = await import("./settings-loader-M2QP5TX2.js");
630
+ } = await import("./settings-removed-3ATWJEF3.js");
631
+ const { settingsLayerPaths } = await import("./settings-loader-KDBH45AQ.js");
558
632
  const { join: joinPath } = await import("path");
559
633
  const { existsSync: fileExists } = await import("fs");
560
634
  const removedNotices = [
@@ -576,11 +650,13 @@ async function runDoctorCommand(workspace, options = {}) {
576
650
  }
577
651
  console.log();
578
652
  console.log("Settings layers:");
579
- const { existsSync: existsSync4 } = await import("fs");
653
+ const { existsSync: existsSync5 } = await import("fs");
580
654
  const { join: join8 } = await import("path");
655
+ const { isUserSettingsFile: isUserSettingsFile2 } = await import("./settings-loader-KDBH45AQ.js");
581
656
  for (const [label, path] of await settingsLayers(config.workspace)) {
582
- const marker = options.noSettings ? "[-]" : existsSync4(path) ? "[x]" : "[ ]";
583
- console.log(" " + marker + " " + label + ": " + path);
657
+ const marker = options.noSettings ? "[-]" : existsSync5(path) ? "[x]" : "[ ]";
658
+ const note = label === "Project" && isUserSettingsFile2(path) ? " (the User file; read once, as User)" : "";
659
+ console.log(" " + marker + " " + label + ": " + path + note);
584
660
  }
585
661
  if (options.noSettings) {
586
662
  console.log(" (--no-settings: every layer skipped, defaults reported below)");
@@ -591,9 +667,10 @@ async function runDoctorCommand(workspace, options = {}) {
591
667
  console.log(" Allow rules: " + perms.allow.length);
592
668
  console.log(" Ask rules: " + perms.ask.length);
593
669
  console.log(" Deny rules: " + perms.deny.length);
594
- const { loadSettingsFile: loadSettingsFile2 } = await import("./settings-loader-M2QP5TX2.js");
595
- const { partitionProjectAllowRules: partitionProjectAllowRules2 } = await import("./permission-approvals-76PVREFD.js");
596
- const projectSettings = loadSettingsFile2(join8(config.workspace, ".book", "settings.json"));
670
+ const { loadSettingsFile: loadSettingsFile2 } = await import("./settings-loader-KDBH45AQ.js");
671
+ const { partitionProjectAllowRules: partitionProjectAllowRules2 } = await import("./permission-approvals-Q5IMFEZI.js");
672
+ const projectPath = join8(config.workspace, ".book", "settings.json");
673
+ const projectSettings = isUserSettingsFile2(projectPath) ? null : loadSettingsFile2(projectPath);
597
674
  const projectAllow = partitionProjectAllowRules2(
598
675
  projectSettings?.permissions?.allow ?? [],
599
676
  perms.projectAllowRules
@@ -610,9 +687,33 @@ async function runDoctorCommand(workspace, options = {}) {
610
687
  );
611
688
  }
612
689
  }
690
+ const { collectDeclaredDirectories: collectDeclaredDirectories2, partitionProjectDirectories: partitionProjectDirectories2, resolveAdditionalRoots } = await import("./additional-roots-VAWRQ625.js");
691
+ const projectDirectories = partitionProjectDirectories2(
692
+ collectDeclaredDirectories2(config.workspace, projectSettings?.additionalDirectories ?? []),
693
+ settings.projectDirectories
694
+ );
695
+ const honoredAdditionalRoots = resolveAdditionalRoots(
696
+ config.workspace,
697
+ settings.additionalDirectories
698
+ );
699
+ if (honoredAdditionalRoots.length > 0 || projectDirectories.pending.length > 0) {
700
+ console.log("Additional directories (Read, Glob and Grep may reach these):");
701
+ for (const root of honoredAdditionalRoots) console.log(" [x] " + root);
702
+ for (const directory of projectDirectories.pending) {
703
+ console.log(
704
+ " [!] " + directory.realPath + " (not in effect; declared as " + directory.declared + ")"
705
+ );
706
+ }
707
+ if (projectDirectories.pending.length > 0) {
708
+ console.log(approvalHint(" Approve one:", config.workspace, "dir <path>"));
709
+ console.log(
710
+ approvalHint(" Approve all pending:", config.workspace, "dir --all-pending") + " (add --reject to refuse)"
711
+ );
712
+ }
713
+ }
613
714
  console.log();
614
- const { discoverCommands: discoverCommands2 } = await import("./loader-4IDS2RIE.js");
615
- const { partitionProjectCommands: partitionProjectCommands2, describeProjectCommandShell: describeProjectCommandShell2, displayableCommandName: displayableCommandName2 } = await import("./command-approvals-6Y57VCWZ.js");
715
+ const { discoverCommands: discoverCommands2 } = await import("./loader-DQNPDKOX.js");
716
+ const { partitionProjectCommands: partitionProjectCommands2, describeProjectCommandShell: describeProjectCommandShell2, displayableCommandName: displayableCommandName2 } = await import("./command-approvals-ZRY66LCW.js");
616
717
  const gatedCommands = partitionProjectCommands2(discoverCommands2(config.workspace), settings);
617
718
  if (gatedCommands.approved.length + gatedCommands.pending.length + gatedCommands.rejected.length > 0) {
618
719
  console.log("Project commands that run shell (require approval):");
@@ -632,6 +733,90 @@ async function runDoctorCommand(workspace, options = {}) {
632
733
  }
633
734
  console.log();
634
735
  }
736
+ const providerGate = config.workspaceProviderGate;
737
+ if (providerGate) {
738
+ const {
739
+ describeDroppedOverride,
740
+ describeLegacyBaseUrl: describeLegacyBaseUrl2,
741
+ describeReservedProvider: describeReservedProvider2,
742
+ describeUnaffectedSelection,
743
+ describeWithheldSelection,
744
+ describeWorkspaceProvider: describeWorkspaceProvider2
745
+ } = await import("./provider-approvals-5AGH3RVT.js");
746
+ const STATE_MARKS = {
747
+ approved: { mark: "[x]", suffix: "" },
748
+ rejected: { mark: "[-]", suffix: " (rejected)" },
749
+ pending: { mark: "[!]", suffix: " (not in effect)" }
750
+ };
751
+ const providerGroups = ["approved", "rejected", "pending"].map((state) => ({
752
+ ...STATE_MARKS[state],
753
+ state,
754
+ entries: providerGate.partition[state]
755
+ }));
756
+ const legacy = providerGate.legacyBaseUrl;
757
+ const anyPending = providerGate.partition.pending.length > 0 || legacy?.state === "pending";
758
+ if (providerGate.declared.length > 0 || legacy) {
759
+ console.log("Workspace-declared providers (require approval):");
760
+ for (const group of providerGroups) {
761
+ for (const entry of group.entries) {
762
+ const routed = group.state === "pending" ? providerGate.unaffectedSelections.filter((s) => s.providerId === entry.id) : [];
763
+ const [first, ...rest] = describeWorkspaceProvider2(entry, config.workspace, routed);
764
+ console.log(` ${group.mark} ${first}${group.suffix}`);
765
+ for (const line of rest) console.log(` ${line}`);
766
+ console.log(` declared in: ${entry.layers.join(", ")}`);
767
+ }
768
+ }
769
+ for (const entry of providerGate.partition.reserved) {
770
+ console.log(` [!] ${describeReservedProvider2(entry)}`);
771
+ }
772
+ if (legacy) {
773
+ const { mark, suffix } = STATE_MARKS[legacy.state];
774
+ const [first, ...rest] = describeLegacyBaseUrl2(legacy.baseUrl, {
775
+ providers: settings.provider,
776
+ workspace: config.workspace
777
+ });
778
+ console.log(` ${mark} ${first}${suffix}`);
779
+ for (const line of rest) console.log(` ${line}`);
780
+ }
781
+ for (const entry of providerGate.partition.pending) {
782
+ const { command, note } = providerTrustCommand(config.workspace, entry.id);
783
+ console.log(` Approve ${renderUntrustedText(entry.id, 64)}:`);
784
+ console.log(` ${command}${note}`);
785
+ }
786
+ if (legacy?.state === "pending") {
787
+ console.log(
788
+ approvalHint(" Approve the legacy baseUrl:", config.workspace, "provider .bookrc.json")
789
+ );
790
+ }
791
+ if (anyPending) {
792
+ console.log(
793
+ approvalHint(" Approve all pending:", config.workspace, "provider --all-pending") + " (add --reject to refuse)"
794
+ );
795
+ }
796
+ console.log();
797
+ }
798
+ if (providerGate.droppedOverrides.length > 0) {
799
+ console.log("Workspace overrides of your providers (dropped):");
800
+ for (const dropped of providerGate.droppedOverrides) {
801
+ console.log(` [!] ${describeDroppedOverride(dropped)}`);
802
+ }
803
+ console.log();
804
+ }
805
+ if (providerGate.withheldSelections.length > 0) {
806
+ console.log("Withheld model selections:");
807
+ for (const selection of providerGate.withheldSelections) {
808
+ console.log(` [!] ${describeWithheldSelection(selection)}`);
809
+ }
810
+ console.log();
811
+ }
812
+ if (providerGate.unaffectedSelections.length > 0) {
813
+ console.log("Your model selections that share a prefix with a withheld provider:");
814
+ for (const selection of providerGate.unaffectedSelections) {
815
+ console.log(` [x] ${describeUnaffectedSelection(selection)}`);
816
+ }
817
+ console.log();
818
+ }
819
+ }
635
820
  const hooks = settings.hooks;
636
821
  const hookLists = Object.entries(hooks).filter(
637
822
  (pair) => Array.isArray(pair[1])
@@ -643,7 +828,7 @@ async function runDoctorCommand(workspace, options = {}) {
643
828
  if (entries.length > 0) console.log(" " + event + ": " + entries.length);
644
829
  }
645
830
  if (hookTotal === 0) console.log(" (none)");
646
- const { collectDeclaredHooks: collectDeclaredHooks2, describeDeclaredHook: describeDeclaredHook2, partitionProjectHooks: partitionProjectHooks2 } = await import("./hook-approvals-CTUFKEJT.js");
831
+ const { collectDeclaredHooks: collectDeclaredHooks2, describeDeclaredHook: describeDeclaredHook2, partitionProjectHooks: partitionProjectHooks2 } = await import("./hook-approvals-DPQD5H65.js");
647
832
  const declaredProjectHooks = collectDeclaredHooks2(projectSettings);
648
833
  if (declaredProjectHooks.length > 0) {
649
834
  const hookPartition = partitionProjectHooks2(
@@ -671,8 +856,8 @@ async function runDoctorCommand(workspace, options = {}) {
671
856
  }
672
857
  }
673
858
  console.log();
674
- const { formatMcpServerCommand: formatMcpServerCommand2, resolveMcpServerList: resolveMcpServerList2 } = await import("./mcp-config-YIJIES2C.js");
675
- const { evaluateMcpServerApproval: evaluateMcpServerApproval2 } = await import("./mcp-approvals-WNPH65BK.js");
859
+ const { formatMcpServerCommand: formatMcpServerCommand2, resolveMcpServerList: resolveMcpServerList2 } = await import("./mcp-config-GXSODNCM.js");
860
+ const { evaluateMcpServerApproval: evaluateMcpServerApproval2 } = await import("./mcp-approvals-27LJYADQ.js");
676
861
  const mcpServers = resolveMcpServerList2(config.workspace);
677
862
  console.log("MCP Servers:");
678
863
  for (const server of mcpServers) {
@@ -681,8 +866,8 @@ async function runDoctorCommand(workspace, options = {}) {
681
866
  }
682
867
  if (mcpServers.length === 0) console.log(" (none)");
683
868
  console.log();
684
- const { createSandbox, sandboxPolicySummary } = await import("./sandbox-CNVAQDA7.js");
685
- const { hasAdjudicationPolicy } = await import("./permissions-CHCKTFQQ.js");
869
+ const { createSandbox, sandboxPolicySummary } = await import("./sandbox-N2RY72GN.js");
870
+ const { hasAdjudicationPolicy } = await import("./permissions-7Y35X6HZ.js");
686
871
  const sandbox = createSandbox(settings.sandbox);
687
872
  const policy = sandboxPolicySummary(settings.sandbox, {
688
873
  sandboxActive: sandbox !== null,
@@ -695,12 +880,49 @@ async function runDoctorCommand(workspace, options = {}) {
695
880
  console.log(" Excluded commands: " + settings.sandbox.excludedCommands.length);
696
881
  console.log(" Unsandboxed commands: " + policy.unsandboxedCommands);
697
882
  console.log(" Auto-allow Bash: " + policy.autoAllowBash);
883
+ console.log(" Git directories: " + policy.gitDirectories);
884
+ if (!options.noSettings) {
885
+ const {
886
+ formatIgnoredWorkspaceSandboxKey,
887
+ ignoredWorkspaceSandboxKeys,
888
+ loadSettingsFile: loadSettingsFile3,
889
+ workspaceLayerThatEnabledSandbox
890
+ } = await import("./settings-loader-KDBH45AQ.js");
891
+ const layers = (await settingsLayers(config.workspace)).map(([label, path]) => ({
892
+ label,
893
+ path,
894
+ document: label === "Project" && isUserSettingsFile2(path) ? null : loadSettingsFile3(path)
895
+ }));
896
+ const activation = workspaceLayerThatEnabledSandbox(
897
+ layers.map((layer) => ({
898
+ path: layer.path,
899
+ trust: WORKSPACE_LAYER_TRUST[layer.label] ?? "local",
900
+ document: layer.document
901
+ }))
902
+ );
903
+ for (const { label, path, document } of layers) {
904
+ if (label === "User") continue;
905
+ for (const entry of ignoredWorkspaceSandboxKeys(document ?? {})) {
906
+ console.log(
907
+ ` Ignored from ${path}: ${formatIgnoredWorkspaceSandboxKey(entry)} \u2014 workspace settings may only tighten the sandbox; set it in ~/.book/settings.json or pass --settings`
908
+ );
909
+ }
910
+ if (path === activation) {
911
+ console.log(
912
+ ` From ${path}: sandbox.enabled=true \u2014 a workspace file turned the sandbox on, so sandbox.autoAllowBashIfSandboxed=false with it; set autoAllowBashIfSandboxed in ~/.book/settings.json to have it back.`
913
+ );
914
+ }
915
+ }
916
+ }
698
917
  console.log();
699
- const { describeShell, resolveShell } = await import("./shell-selection-V6QAYXPX.js");
918
+ const { describeShell, resolveShell } = await import("./shell-selection-Z5WORZU4.js");
700
919
  const shell = config.shell ?? resolveShell({ requested: settings.shell });
701
920
  console.log("Shell:");
702
921
  console.log(" Bash tool runs: " + describeShell(shell));
703
922
  console.log(" Selected by: " + shell.source);
923
+ if (process.platform === "win32" && (shell.kind === "bash" || shell.kind === "sh")) {
924
+ console.log(" msys: " + (shell.msys ? "yes" : "no"));
925
+ }
704
926
  if (shell.warning) console.log(` [!] ${shell.warning}`);
705
927
  console.log();
706
928
  console.log("Managed agents:");
@@ -719,14 +941,14 @@ async function runDoctorCommand(workspace, options = {}) {
719
941
  join8(config.workspace, ".claude", "agents"),
720
942
  join8(config.workspace, ".agents", "agents")
721
943
  ]) {
722
- if (existsSync4(directory)) {
944
+ if (existsSync5(directory)) {
723
945
  console.log(
724
946
  ` [!] Third-party definitions found at ${directory}; preview them with /agents import ${directory}.`
725
947
  );
726
948
  }
727
949
  }
728
950
  console.log();
729
- const { createModelWindowStore } = await import("./model-window-store-YFVNXCIP.js");
951
+ const { createModelWindowStore } = await import("./model-window-store-6VKXXTPJ.js");
730
952
  const windowStore = createModelWindowStore();
731
953
  const allLearned = windowStore.all();
732
954
  const learnedModels = Object.keys(allLearned);
@@ -742,6 +964,41 @@ async function runDoctorCommand(workspace, options = {}) {
742
964
  }
743
965
  }
744
966
  console.log();
967
+ const {
968
+ DEFAULT_MAX_INDEX_LINES,
969
+ MAX_INDEX_BYTES,
970
+ findLegacyMemoryBackup,
971
+ getMemoryHealth,
972
+ legacyBackupNotice,
973
+ loadMemoryContext
974
+ } = await import("./memory-store-I6Z54VUN.js");
975
+ const memoryContext = loadMemoryContext(config.workspace);
976
+ const memoryHealth = getMemoryHealth(memoryContext);
977
+ const memoryBackup = findLegacyMemoryBackup(memoryContext.dir);
978
+ const lastWrite = memoryHealth.lastWrite ? memoryHealth.lastWrite.toISOString() : "never";
979
+ console.log("Memory:");
980
+ console.log(" Memories: " + memoryHealth.fileCount);
981
+ if (memoryHealth.legacySupersededCount > 0) {
982
+ console.log(
983
+ ` Legacy superseded: ${memoryHealth.legacySupersededCount} (retired by the old save tool; they load like any other)`
984
+ );
985
+ }
986
+ console.log(` Index lines: ${memoryHealth.indexLineCount} / ${DEFAULT_MAX_INDEX_LINES}`);
987
+ console.log(
988
+ ` Index bytes: ${memoryHealth.indexByteCount.toLocaleString("en-US")} / ${MAX_INDEX_BYTES.toLocaleString("en-US")}`
989
+ );
990
+ if (memoryHealth.wasLineTruncated || memoryHealth.wasByteTruncated) {
991
+ const limits = [
992
+ memoryHealth.wasLineTruncated ? `${DEFAULT_MAX_INDEX_LINES} lines` : "",
993
+ memoryHealth.wasByteTruncated ? `${MAX_INDEX_BYTES.toLocaleString("en-US")} bytes` : ""
994
+ ].filter(Boolean);
995
+ console.log(
996
+ ` Index cut: MEMORY.md is over the ${limits.join(" and ")} limit; a session loads only part of it. Consolidate it.`
997
+ );
998
+ }
999
+ console.log(" Last write: " + lastWrite);
1000
+ if (memoryBackup) console.log(" " + legacyBackupNotice(memoryBackup));
1001
+ console.log();
745
1002
  console.log("Environment:");
746
1003
  for (const key of ["BOOK_API_KEY", "BOOK_BASE_URL", "BOOK_MODEL", "BOOK_WORKSPACE"]) {
747
1004
  const val = process.env[key];
@@ -790,9 +1047,190 @@ async function runToolStatsCommand(options) {
790
1047
  }
791
1048
  }
792
1049
 
1050
+ // src/cli/provider-key-cmd.ts
1051
+ import { createInterface } from "readline";
1052
+ import { Writable } from "stream";
1053
+ var USAGE = "Usage: book config provider-key <provider-id> (the key is read from stdin)";
1054
+ var MAX_PIPED_BYTES = 64 * 1024;
1055
+ async function readPiped(input) {
1056
+ const chunks = [];
1057
+ let size = 0;
1058
+ for await (const chunk of input) {
1059
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1060
+ size += buffer.length;
1061
+ if (size > MAX_PIPED_BYTES) return void 0;
1062
+ chunks.push(buffer);
1063
+ }
1064
+ return Buffer.concat(chunks).toString("utf-8").replace(/\r?\n$/, "");
1065
+ }
1066
+ async function readTerminalLine(input, stderr, prompt, hidden) {
1067
+ stderr.write(prompt);
1068
+ const output = hidden ? new Writable({
1069
+ write(_chunk, _encoding, done) {
1070
+ done();
1071
+ }
1072
+ }) : stderr;
1073
+ const rl = createInterface({ input, output, terminal: true });
1074
+ try {
1075
+ return await new Promise((resolve3) => {
1076
+ rl.once("line", resolve3);
1077
+ rl.once("SIGINT", () => resolve3(void 0));
1078
+ rl.once("close", () => resolve3(void 0));
1079
+ });
1080
+ } finally {
1081
+ rl.close();
1082
+ if (hidden) stderr.write("\n");
1083
+ }
1084
+ }
1085
+ async function askAfterFailure(input, stderr) {
1086
+ const answer = await readTerminalLine(
1087
+ input,
1088
+ stderr,
1089
+ "[s] save anyway [r] retry [e] enter a different key [c] cancel: ",
1090
+ false
1091
+ );
1092
+ switch (answer?.trim().toLowerCase()) {
1093
+ case "s":
1094
+ return "save";
1095
+ case "r":
1096
+ return "retry";
1097
+ case "e":
1098
+ return "edit";
1099
+ default:
1100
+ return "cancel";
1101
+ }
1102
+ }
1103
+ async function runProviderKeyCommand(workspace, providerId, extra, options = {}) {
1104
+ const stdin = options.stdin ?? process.stdin;
1105
+ const stderr = options.stderr ?? process.stderr;
1106
+ if (extra !== void 0) {
1107
+ console.error(
1108
+ "provider-key reads the key from stdin, never from an argument; nothing was saved. A key typed on the command line is in your shell history: consider rotating it."
1109
+ );
1110
+ console.error(USAGE);
1111
+ return exit(1);
1112
+ }
1113
+ if (!providerId) {
1114
+ console.error(USAGE);
1115
+ return exit(1);
1116
+ }
1117
+ if (options.ignoresSettingsFlags) {
1118
+ console.error(
1119
+ "provider-key edits your user settings (<BOOK_HOME>/settings.json) whatever the settings layers in use; --settings and --no-settings do not apply to it."
1120
+ );
1121
+ return exit(1);
1122
+ }
1123
+ if (options.scope && options.scope !== "user") {
1124
+ console.error(
1125
+ `provider-key changes providers in your user settings only, not ${settingsScopeLabel(options.scope)} settings.`
1126
+ );
1127
+ return exit(1);
1128
+ }
1129
+ const target = providerKeyTarget(workspace, providerId);
1130
+ if (target.kind === "unreadable") {
1131
+ console.error(`Could not read user settings (${target.path}): ${target.error}`);
1132
+ return exit(1);
1133
+ }
1134
+ if (target.kind === "workspace-only") {
1135
+ console.error(
1136
+ `Provider "${providerId}" is declared by this repository (${target.declaredIn}), not your user settings (${target.path}). provider-key changes only providers you configured.`
1137
+ );
1138
+ return exit(1);
1139
+ }
1140
+ if (target.kind === "unknown") {
1141
+ console.error(
1142
+ // The id is not repeated: one that names no provider is as likely to be a key typed in
1143
+ // its place, and the command promises never to echo a key.
1144
+ `No provider with that id in user settings (${target.path}). ` + (target.configured.length > 0 ? `Configured: ${target.configured.join(", ")}.` : "No providers are configured there yet; add one with /providers in the TUI.")
1145
+ );
1146
+ return exit(1);
1147
+ }
1148
+ const interactive = stdin.isTTY === true;
1149
+ const readKey = async () => {
1150
+ const raw = interactive ? await readTerminalLine(stdin, stderr, `API key for ${providerId}: `, true) : await readPiped(stdin);
1151
+ if (raw === void 0) {
1152
+ console.error(
1153
+ interactive ? "Cancelled; nothing was saved." : `The input is longer than ${MAX_PIPED_BYTES} bytes, which no API key is; nothing was saved.`
1154
+ );
1155
+ return exit(1);
1156
+ }
1157
+ try {
1158
+ return validateApiKey(raw);
1159
+ } catch (caught) {
1160
+ console.error(caught instanceof Error ? caught.message : "Invalid API key.");
1161
+ return exit(1);
1162
+ }
1163
+ };
1164
+ let apiKey = await readKey();
1165
+ let tested = false;
1166
+ while (options.check !== false) {
1167
+ const result = await checkProviderKey({
1168
+ provider: target.provider,
1169
+ apiKey,
1170
+ workspace,
1171
+ retry: DEFAULT_SETTINGS.retry,
1172
+ discover: options.discover
1173
+ });
1174
+ if (result.ok) {
1175
+ tested = true;
1176
+ break;
1177
+ }
1178
+ console.error(`The key check failed: ${result.error}`);
1179
+ if (options.force) {
1180
+ console.warn("Saving anyway (--force).");
1181
+ break;
1182
+ }
1183
+ if (!interactive) {
1184
+ console.error(
1185
+ "Nothing was saved. Pass --force to save anyway, or --no-check to skip the check."
1186
+ );
1187
+ return exit(1);
1188
+ }
1189
+ const choice = await askAfterFailure(stdin, stderr);
1190
+ if (choice === "save") break;
1191
+ if (choice === "cancel") {
1192
+ console.error("Cancelled; nothing was saved.");
1193
+ return exit(1);
1194
+ }
1195
+ if (choice === "edit") apiKey = await readKey();
1196
+ }
1197
+ const written = writeProviderKey(workspace, providerId, apiKey);
1198
+ if (!written.ok) {
1199
+ console.error(written.error);
1200
+ return exit(1);
1201
+ }
1202
+ console.log(
1203
+ `Updated the API key of provider "${providerId}" in user settings (${written.path})` + (tested ? "; the endpoint accepted it." : ".")
1204
+ );
1205
+ if (written.localCleared) {
1206
+ console.log(
1207
+ `Removed the connection keys of the same-named entry in ${written.localCleared}, which would otherwise look like they override it.`
1208
+ );
1209
+ }
1210
+ if (written.localError) {
1211
+ console.warn(`\u26A0 Could not clean up the local entry: ${written.localError}`);
1212
+ }
1213
+ }
1214
+
793
1215
  // src/cli/config-cmd.ts
794
1216
  async function runConfigCommand(workspace, action, key, value, settingsOptions = {}) {
795
- const resolveMergedSettings = () => settingsOptions.noSettings ? structuredClone(DEFAULT_SETTINGS) : resolveSettings(workspace, settingsOptions.settingsOverridePath);
1217
+ const resolveMergedSettings = () => applySettingsEnvOverrides(
1218
+ settingsOptions.noSettings ? structuredClone(DEFAULT_SETTINGS) : resolveSettings(workspace, settingsOptions.settingsOverridePath)
1219
+ );
1220
+ if (action === "provider-key") {
1221
+ await runProviderKeyCommand(workspace, key, value, {
1222
+ ...settingsOptions.providerKey,
1223
+ scope: settingsOptions.scope,
1224
+ ignoresSettingsFlags: Boolean(
1225
+ settingsOptions.settingsOverridePath || settingsOptions.noSettings
1226
+ )
1227
+ });
1228
+ return;
1229
+ }
1230
+ if (settingsOptions.providerKey?.force || settingsOptions.providerKey?.check === false) {
1231
+ console.error("--force and --no-check apply to provider-key only.");
1232
+ exit(1);
1233
+ }
796
1234
  const scopeRead = settingsOptions.scope ? readScopeDocument(settingsOptions.scope, workspace) : void 0;
797
1235
  if (!action || action === "list") {
798
1236
  if (scopeRead) {
@@ -808,6 +1246,8 @@ async function runConfigCommand(workspace, action, key, value, settingsOptions =
808
1246
  }
809
1247
  console.log("Resolved settings:");
810
1248
  console.log(JSON.stringify(redactSettingsForDisplay(resolveMergedSettings()), null, 2));
1249
+ const listNote = startupAnimationEnvNote();
1250
+ if (listNote) console.log(`\u26A0 ${listNote}`);
811
1251
  return;
812
1252
  }
813
1253
  if (action === "get") {
@@ -829,6 +1269,10 @@ async function runConfigCommand(workspace, action, key, value, settingsOptions =
829
1269
  );
830
1270
  } else {
831
1271
  console.log(JSON.stringify(redactSettingValue(key, val), null, 2));
1272
+ if (!scopeRead && key === "ui.startupAnimation") {
1273
+ const note = startupAnimationEnvNote();
1274
+ if (note) console.log(`\u26A0 ${note}`);
1275
+ }
832
1276
  }
833
1277
  return;
834
1278
  }
@@ -871,7 +1315,7 @@ async function runConfigCommand(workspace, action, key, value, settingsOptions =
871
1315
  exit(1);
872
1316
  }
873
1317
  const scope = settingsOptions.scope ?? "user";
874
- const layer = readScopeDocument(scope, workspace);
1318
+ const layer = readScopeDocument(scope, workspace, { forWrite: true });
875
1319
  if (layer.status === "unreadable") {
876
1320
  console.error(
877
1321
  `Could not read ${settingsScopeLabel(scope)} settings (${layer.path}): ${layer.error ?? "unknown error"}`
@@ -893,7 +1337,7 @@ async function runConfigCommand(workspace, action, key, value, settingsOptions =
893
1337
  return;
894
1338
  }
895
1339
  console.error(
896
- "Unknown action: " + action + ". Use: get <key>, set <key> <value>, unset <key>, or list"
1340
+ "Unknown action: " + action + ". Use: get <key>, set <key> <value>, unset <key>, list, or provider-key <id>"
897
1341
  );
898
1342
  exit(1);
899
1343
  }
@@ -911,21 +1355,25 @@ async function runTrustCommand(kind, target, options) {
911
1355
  if (options.allPending && target !== void 0) {
912
1356
  fail(`book trust ${kind}: pass either a target or --all-pending, not both`);
913
1357
  }
914
- if (!options.allPending && target === void 0) {
1358
+ const listsProviders = kind === "provider" && !options.allPending && target === void 0;
1359
+ if (!options.allPending && target === void 0 && !listsProviders) {
915
1360
  fail(
916
1361
  `book trust ${kind}: name what to trust, or pass --all-pending. Run \`book doctor\` to list what is withheld.`
917
1362
  );
918
1363
  }
919
1364
  let settings;
1365
+ let providerGate;
920
1366
  try {
921
- settings = resolveSettings(workspace);
1367
+ ({ settings, providerGate } = resolveSettingsWithProviderGate(workspace));
922
1368
  } catch (error) {
923
1369
  return fail(
924
1370
  `book trust ${kind}: cannot read settings for ${workspace}: ${error instanceof Error ? error.message : String(error)}`
925
1371
  );
926
1372
  }
927
- const projectSettings = loadSettingsFile(join2(workspace, ".book", "settings.json"));
1373
+ const projectPath = join2(workspace, ".book", "settings.json");
1374
+ const projectSettings = isUserSettingsFile(projectPath) ? null : loadSettingsFile(projectPath);
928
1375
  const decisions = [];
1376
+ const directoryDecisions = [];
929
1377
  if (kind === "command") {
930
1378
  const partition = partitionProjectCommands(discoverCommands(workspace), settings);
931
1379
  const push = (command) => {
@@ -988,6 +1436,120 @@ ${known}` : "\nNothing is awaiting a decision.")
988
1436
  }
989
1437
  decisions.push({ key: target, label: describeDeclaredHook(hook).headline });
990
1438
  }
1439
+ } else if (kind === "dir") {
1440
+ const declared = collectDeclaredDirectories(
1441
+ workspace,
1442
+ projectSettings?.additionalDirectories ?? []
1443
+ );
1444
+ const partition = partitionProjectDirectories(declared, settings.projectDirectories);
1445
+ const targetRealPath = realPathOfDirectory(workspace, target ?? "");
1446
+ const entry = declared.find(
1447
+ (candidate) => candidate.realPath === targetRealPath || candidate.declared === target
1448
+ );
1449
+ if (options.allPending) {
1450
+ if (partition.pending.length === 0) {
1451
+ console.log(`No project-declared directories are awaiting a decision in ${workspace}.`);
1452
+ return;
1453
+ }
1454
+ for (const pending of partition.pending) {
1455
+ console.log(`${pending.declared} is really:`);
1456
+ console.log(` ${pending.realPath}`);
1457
+ directoryDecisions.push(pending);
1458
+ }
1459
+ } else if (!entry) {
1460
+ const known = partition.pending.map((pending) => ` ${pending.realPath}`).join("\n");
1461
+ return fail(
1462
+ `book trust dir: ${workspace} declares no project additionalDirectories entry at "${target}".` + (known ? `
1463
+ Awaiting a decision:
1464
+ ${known}` : "\nNothing is awaiting a decision.")
1465
+ );
1466
+ } else {
1467
+ if (!options.reject) {
1468
+ console.log(`${entry.declared} is really:`);
1469
+ console.log(` ${entry.realPath}`);
1470
+ }
1471
+ directoryDecisions.push(entry);
1472
+ }
1473
+ } else if (kind === "provider") {
1474
+ const unaffected = withEnvironmentSelections(
1475
+ providerGate.unaffectedSelections,
1476
+ environmentModelSelections(void 0),
1477
+ new Set(providerGate.withheld)
1478
+ );
1479
+ const candidates = ["approved", "pending", "rejected"].flatMap(
1480
+ (state) => providerGate.partition[state].map((entry) => ({
1481
+ id: entry.id,
1482
+ fingerprint: entry.fingerprint,
1483
+ state,
1484
+ lines: describeWorkspaceProvider(
1485
+ entry,
1486
+ workspace,
1487
+ unaffected.filter((selection) => selection.providerId === entry.id)
1488
+ )
1489
+ }))
1490
+ );
1491
+ const legacyBaseUrl = readLegacyBaseUrl(workspace);
1492
+ if (legacyBaseUrl !== void 0) {
1493
+ candidates.push({
1494
+ id: LEGACY_BASE_URL_ID,
1495
+ fingerprint: legacyBaseUrlFingerprint(legacyBaseUrl),
1496
+ state: evaluateLegacyBaseUrl(settings.workspaceProviders, legacyBaseUrl),
1497
+ lines: describeLegacyBaseUrl(legacyBaseUrl, { providers: settings.provider, workspace })
1498
+ });
1499
+ }
1500
+ const pending = candidates.filter((candidate) => candidate.state === "pending");
1501
+ const printCandidate = (candidate, prefix) => {
1502
+ const [first, ...rest] = candidate.lines;
1503
+ console.log(`${prefix}${first}`);
1504
+ for (const line of rest) console.log(` ${line}`);
1505
+ };
1506
+ const push = (candidate) => {
1507
+ if (!options.reject) printCandidate(candidate, " ");
1508
+ decisions.push({
1509
+ key: candidate.id,
1510
+ label: candidate.id,
1511
+ fingerprint: candidate.fingerprint
1512
+ });
1513
+ };
1514
+ const reserved = providerGate.partition.reserved.map(
1515
+ (entry) => ` [!] ${describeReservedProvider(entry)}`
1516
+ );
1517
+ if (listsProviders) {
1518
+ for (const line of reserved) console.log(line);
1519
+ if (pending.length === 0) {
1520
+ console.log(`No workspace-declared providers are awaiting a decision in ${workspace}.`);
1521
+ return;
1522
+ }
1523
+ console.log(`Workspace-declared providers awaiting a decision in ${workspace}:`);
1524
+ for (const candidate of pending) printCandidate(candidate, " [!] ");
1525
+ for (const candidate of pending) {
1526
+ const { command, note } = providerTrustCommand(workspace, candidate.id);
1527
+ console.log(`Approve ${renderUntrustedText(candidate.id, 64)}:`);
1528
+ console.log(` ${command}${note}`);
1529
+ }
1530
+ const all = trustCommandText(workspace, "provider --all-pending");
1531
+ console.log(`Approve all: ${all.command}${all.note} (add --reject to refuse)`);
1532
+ return;
1533
+ }
1534
+ if (options.allPending) {
1535
+ if (pending.length === 0) {
1536
+ console.log(`No workspace-declared providers are awaiting a decision in ${workspace}.`);
1537
+ return;
1538
+ }
1539
+ for (const candidate of pending) push(candidate);
1540
+ } else {
1541
+ const candidate = candidates.find((known) => known.id === target);
1542
+ if (!candidate) {
1543
+ const known = candidates.map((entry) => ` ${renderUntrustedText(entry.id, 64)} (${entry.state})`).join("\n");
1544
+ return fail(
1545
+ `book trust provider: ${workspace} declares no provider "${target}" that can be decided.` + (known ? `
1546
+ Declared by the workspace:
1547
+ ${known}` : "\nNothing is declared.") + (reserved.length > 0 ? `
1548
+ ${reserved.join("\n")}` : "")
1549
+ );
1550
+ }
1551
+ push(candidate);
1552
+ }
991
1553
  } else {
992
1554
  const declared = projectSettings?.permissions?.allow ?? [];
993
1555
  const partition = partitionProjectAllowRules(declared, settings.permissions.projectAllowRules);
@@ -1010,8 +1572,13 @@ ${known}` : "\nNothing is awaiting a decision.")
1010
1572
  }
1011
1573
  }
1012
1574
  const failures = [];
1575
+ for (const directory of directoryDecisions) {
1576
+ const result = persistProjectDirectoryChoice(workspace, directory.realPath, choice);
1577
+ if (result.ok) console.log(`${verb} ${directory.realPath}`);
1578
+ else failures.push(`${directory.realPath}: ${result.error ?? "unknown error"}`);
1579
+ }
1013
1580
  for (const decision of decisions) {
1014
- const result = kind === "command" ? persistProjectCommandChoice(workspace, decision.key, decision.fingerprint, choice) : kind === "hook" ? persistProjectHookChoice(workspace, decision.key, choice) : persistProjectAllowRuleChoice(workspace, decision.key, choice);
1581
+ const result = kind === "command" ? persistProjectCommandChoice(workspace, decision.key, decision.fingerprint, choice) : kind === "provider" ? persistWorkspaceProviderChoice(workspace, decision.key, decision.fingerprint, choice) : kind === "hook" ? persistProjectHookChoice(workspace, decision.key, choice) : persistProjectAllowRuleChoice(workspace, decision.key, choice);
1015
1582
  if (result.ok) console.log(`${verb} ${decision.label}`);
1016
1583
  else failures.push(`${decision.label}: ${result.error ?? "unknown error"}`);
1017
1584
  }
@@ -1023,10 +1590,6 @@ ${failures.map((f) => ` ${f}`).join("\n")}`
1023
1590
  }
1024
1591
  }
1025
1592
 
1026
- // src/cli/run.ts
1027
- import { render } from "ink";
1028
- import { createElement } from "react";
1029
-
1030
1593
  // src/mcp-host.ts
1031
1594
  var log = createDebugLogger("mcp");
1032
1595
  var MAX_EVENTS = 50;
@@ -1221,13 +1784,49 @@ var McpSessionHost = class {
1221
1784
  }
1222
1785
  };
1223
1786
 
1787
+ // src/cli/print-interrupt.ts
1788
+ function installPrintInterrupt(options = {}) {
1789
+ const controller = new AbortController();
1790
+ let interrupted;
1791
+ const onSignal = (signal) => {
1792
+ if (interrupted) {
1793
+ (options.onSecondSignal ?? ((second) => exit(second === "SIGINT" ? 130 : 143)))(signal);
1794
+ return;
1795
+ }
1796
+ interrupted = signal;
1797
+ (options.stderr ?? process.stderr).write(
1798
+ "\nCancelling: running SessionEnd hooks. Press Ctrl+C again to exit now.\n"
1799
+ );
1800
+ controller.abort(
1801
+ Object.assign(new Error(`Cancelled by ${signal}`), { bookTerminalReason: "user_cancelled" })
1802
+ );
1803
+ };
1804
+ const onSigint = () => onSignal("SIGINT");
1805
+ const onSigterm = () => onSignal("SIGTERM");
1806
+ process.on("SIGINT", onSigint);
1807
+ process.on("SIGTERM", onSigterm);
1808
+ return {
1809
+ signal: controller.signal,
1810
+ interruptedBy: () => interrupted,
1811
+ dispose: () => {
1812
+ process.off("SIGINT", onSigint);
1813
+ process.off("SIGTERM", onSigterm);
1814
+ }
1815
+ };
1816
+ }
1817
+ function printExitCode(run) {
1818
+ if (run.interruptedBy) return run.interruptedBy === "SIGINT" ? 130 : 143;
1819
+ if (run.outcome) return run.outcome.status === "failed" ? 1 : 0;
1820
+ return run.aborted ? 0 : 1;
1821
+ }
1822
+
1224
1823
  // src/cli/run.ts
1225
1824
  import { join as join6 } from "path";
1226
1825
 
1227
1826
  // src/rewind/snapshot-store.ts
1228
1827
  import {
1229
1828
  chmodSync,
1230
- existsSync,
1829
+ existsSync as existsSync2,
1231
1830
  lstatSync,
1232
1831
  mkdirSync,
1233
1832
  readFileSync,
@@ -1241,7 +1840,7 @@ import {
1241
1840
  } from "fs";
1242
1841
  import { lstat, mkdir, readFile, readdir, readlink, rename, rm, writeFile } from "fs/promises";
1243
1842
  import { createHash, randomUUID } from "crypto";
1244
- import { dirname, isAbsolute, join as join3, relative, resolve as resolve2, sep } from "path";
1843
+ import { dirname, isAbsolute, join as join3, relative, resolve, sep } from "path";
1245
1844
  import ignore from "ignore";
1246
1845
  var REWIND_MAX_ENTRIES = 2e4;
1247
1846
  var REWIND_MAX_FILE_BYTES = 16 * 1024 * 1024;
@@ -1274,7 +1873,7 @@ var DEFAULT_IGNORE_PATTERNS = [
1274
1873
  function sha256(value) {
1275
1874
  return createHash("sha256").update(value).digest("hex");
1276
1875
  }
1277
- function pathExists(path) {
1876
+ function pathExists2(path) {
1278
1877
  try {
1279
1878
  lstatSync(path);
1280
1879
  return true;
@@ -1283,7 +1882,7 @@ function pathExists(path) {
1283
1882
  }
1284
1883
  }
1285
1884
  function normalizeWorkspace(workspace) {
1286
- const normalized = resolve2(workspace);
1885
+ const normalized = resolve(workspace);
1287
1886
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
1288
1887
  }
1289
1888
  function rewindWorkspaceHash(workspace) {
@@ -1339,11 +1938,12 @@ function createRewindSnapshotStore(workspace, root) {
1339
1938
  }
1340
1939
  function readCustomIgnorePatterns(workspace) {
1341
1940
  const path = join3(workspace, ".book", "rewindignore");
1342
- if (!existsSync(path)) return [];
1941
+ if (reachesUncPath(path) || !existsSync2(path)) return [];
1343
1942
  return readFileSync(path, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
1344
1943
  }
1345
1944
  async function readCustomIgnorePatternsAsync(workspace) {
1346
1945
  const path = join3(workspace, ".book", "rewindignore");
1946
+ if (reachesUncPath(path)) return [];
1347
1947
  try {
1348
1948
  return (await readFile(path, "utf-8")).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
1349
1949
  } catch (error) {
@@ -1364,33 +1964,39 @@ function expandNegatedParents(patterns) {
1364
1964
  }
1365
1965
  return expanded;
1366
1966
  }
1367
- function readOptionalText(path) {
1967
+ function readOptionalText(path, reachesUnc) {
1968
+ if (reachesUnc(path)) return void 0;
1368
1969
  try {
1369
1970
  return readFileSync(path, "utf-8").trim();
1370
1971
  } catch {
1371
1972
  return void 0;
1372
1973
  }
1373
1974
  }
1374
- function resolveGitDirectory(workspace) {
1975
+ function resolveGitDirectory(workspace, reachesUnc) {
1375
1976
  const dotGit = join3(workspace, ".git");
1977
+ if (reachesUnc(dotGit)) return void 0;
1376
1978
  try {
1377
1979
  if (lstatSync(dotGit).isDirectory()) return dotGit;
1378
1980
  } catch {
1379
1981
  return void 0;
1380
1982
  }
1381
- const match = /^gitdir:\s*(.+)$/i.exec(readOptionalText(dotGit) ?? "");
1382
- return match ? resolve2(dirname(dotGit), match[1]) : void 0;
1983
+ const match = /^gitdir:\s*(.+)$/i.exec(readOptionalText(dotGit, reachesUnc) ?? "");
1984
+ if (!match) return void 0;
1985
+ const gitDirectory = resolve(dirname(dotGit), match[1]);
1986
+ return reachesUnc(gitDirectory) ? void 0 : gitDirectory;
1383
1987
  }
1384
- function resolveCommonGitDirectory(gitDirectory) {
1385
- const commonDirectory = readOptionalText(join3(gitDirectory, "commondir"));
1386
- return commonDirectory ? resolve2(gitDirectory, commonDirectory) : gitDirectory;
1988
+ function resolveCommonGitDirectory(gitDirectory, reachesUnc) {
1989
+ const commonDirectory = readOptionalText(join3(gitDirectory, "commondir"), reachesUnc);
1990
+ if (!commonDirectory) return gitDirectory;
1991
+ const common = resolve(gitDirectory, commonDirectory);
1992
+ return reachesUnc(common) ? gitDirectory : common;
1387
1993
  }
1388
1994
  function parseGitHash(value) {
1389
1995
  const hash = value?.trim();
1390
1996
  return hash && /^[0-9a-f]{40,64}$/i.test(hash) ? hash : void 0;
1391
1997
  }
1392
- function readPackedRef(directory, ref) {
1393
- const packedRefs = readOptionalText(join3(directory, "packed-refs"));
1998
+ function readPackedRef(directory, ref, reachesUnc) {
1999
+ const packedRefs = readOptionalText(join3(directory, "packed-refs"), reachesUnc);
1394
2000
  if (!packedRefs) return void 0;
1395
2001
  for (const line of packedRefs.split(/\r?\n/)) {
1396
2002
  if (!line || line.startsWith("#") || line.startsWith("^")) continue;
@@ -1401,12 +2007,12 @@ function readPackedRef(directory, ref) {
1401
2007
  }
1402
2008
  return void 0;
1403
2009
  }
1404
- function resolveGitRef(gitDirectory, commonDirectory, ref, depth = 0) {
2010
+ function resolveGitRef(gitDirectory, commonDirectory, ref, reachesUnc, depth = 0) {
1405
2011
  if (depth > 4 || !ref.startsWith("refs/") || ref.includes("..") || ref.includes("\\") || ref.includes("\0")) {
1406
2012
  return void 0;
1407
2013
  }
1408
2014
  for (const directory of /* @__PURE__ */ new Set([gitDirectory, commonDirectory])) {
1409
- const value = readOptionalText(join3(directory, ...ref.split("/")));
2015
+ const value = readOptionalText(join3(directory, ...ref.split("/")), reachesUnc);
1410
2016
  const hash = parseGitHash(value);
1411
2017
  if (hash) return hash;
1412
2018
  if (value?.startsWith("ref:")) {
@@ -1414,24 +2020,27 @@ function resolveGitRef(gitDirectory, commonDirectory, ref, depth = 0) {
1414
2020
  gitDirectory,
1415
2021
  commonDirectory,
1416
2022
  value.slice("ref:".length).trim(),
2023
+ reachesUnc,
1417
2024
  depth + 1
1418
2025
  );
1419
2026
  if (resolved) return resolved;
1420
2027
  }
1421
2028
  }
1422
- return readPackedRef(gitDirectory, ref) ?? (commonDirectory === gitDirectory ? void 0 : readPackedRef(commonDirectory, ref));
2029
+ return readPackedRef(gitDirectory, ref, reachesUnc) ?? (commonDirectory === gitDirectory ? void 0 : readPackedRef(commonDirectory, ref, reachesUnc));
1423
2030
  }
1424
2031
  function gitHead(workspace) {
1425
- const gitDirectory = resolveGitDirectory(workspace);
2032
+ const reachesUnc = createUncGuard();
2033
+ const gitDirectory = resolveGitDirectory(workspace, reachesUnc);
1426
2034
  if (!gitDirectory) return void 0;
1427
- const head = readOptionalText(join3(gitDirectory, "HEAD"));
2035
+ const head = readOptionalText(join3(gitDirectory, "HEAD"), reachesUnc);
1428
2036
  const directHash = parseGitHash(head);
1429
2037
  if (directHash) return directHash;
1430
2038
  if (!head?.startsWith("ref:")) return void 0;
1431
2039
  return resolveGitRef(
1432
2040
  gitDirectory,
1433
- resolveCommonGitDirectory(gitDirectory),
1434
- head.slice("ref:".length).trim()
2041
+ resolveCommonGitDirectory(gitDirectory, reachesUnc),
2042
+ head.slice("ref:".length).trim(),
2043
+ reachesUnc
1435
2044
  );
1436
2045
  }
1437
2046
  async function gitHeadAsync(workspace) {
@@ -1449,7 +2058,7 @@ function assertSafeRelativePath(path) {
1449
2058
  }
1450
2059
  function resolveEntryPath(workspace, path) {
1451
2060
  assertSafeRelativePath(path);
1452
- const target = resolve2(workspace, ...path.split("/"));
2061
+ const target = resolve(workspace, ...path.split("/"));
1453
2062
  const rel = relative(workspace, target);
1454
2063
  if (rel.startsWith(`..${sep}`) || rel === ".." || isAbsolute(rel)) {
1455
2064
  throw new Error(`Rewind path escapes the workspace: ${path}`);
@@ -1539,7 +2148,7 @@ function writeAtomic(path, contents) {
1539
2148
  writeFileSync(temporary, contents);
1540
2149
  renameSync(temporary, path);
1541
2150
  } finally {
1542
- if (pathExists(temporary)) unlinkSync(temporary);
2151
+ if (pathExists2(temporary)) unlinkSync(temporary);
1543
2152
  }
1544
2153
  }
1545
2154
  async function writeAtomicAsync(path, contents) {
@@ -1552,18 +2161,10 @@ async function writeAtomicAsync(path, contents) {
1552
2161
  await rm(temporary, { force: true });
1553
2162
  }
1554
2163
  }
1555
- async function pathExistsAsync(path) {
1556
- try {
1557
- await lstat(path);
1558
- return true;
1559
- } catch {
1560
- return false;
1561
- }
1562
- }
1563
2164
  var RewindSnapshotStore = class {
1564
2165
  constructor(workspace, root = persistentRewindRoot(workspace), limits = {}) {
1565
2166
  this.root = root;
1566
- this.workspace = resolve2(workspace);
2167
+ this.workspace = resolve(workspace);
1567
2168
  this.limits = {
1568
2169
  maxEntries: limits.maxEntries ?? REWIND_MAX_ENTRIES,
1569
2170
  maxFileBytes: limits.maxFileBytes ?? REWIND_MAX_FILE_BYTES,
@@ -1626,7 +2227,7 @@ var RewindSnapshotStore = class {
1626
2227
  }
1627
2228
  const blobHash = sha256(contents);
1628
2229
  const blobPath = join3(this.blobsRoot, blobHash);
1629
- if (!existsSync(blobPath)) writeAtomic(blobPath, contents);
2230
+ if (!existsSync2(blobPath)) writeAtomic(blobPath, contents);
1630
2231
  const snapshotEntry = {
1631
2232
  path: entry.path.replaceAll("\\", "/"),
1632
2233
  kind: entry.kind,
@@ -1713,7 +2314,7 @@ var RewindSnapshotStore = class {
1713
2314
  }
1714
2315
  const blobHash = sha256(contents);
1715
2316
  const blobPath = join3(this.blobsRoot, blobHash);
1716
- if (!await pathExistsAsync(blobPath)) await writeAtomicAsync(blobPath, contents);
2317
+ if (!await pathExists(blobPath)) await writeAtomicAsync(blobPath, contents);
1717
2318
  const snapshotEntry = {
1718
2319
  path: entry.path.replaceAll("\\", "/"),
1719
2320
  kind: entry.kind,
@@ -1754,14 +2355,14 @@ var RewindSnapshotStore = class {
1754
2355
  getManifest(id) {
1755
2356
  if (!/^[a-zA-Z0-9-]+$/.test(id)) return void 0;
1756
2357
  const path = join3(this.manifestsRoot, `${id}.json`);
1757
- if (!existsSync(path)) return void 0;
2358
+ if (!existsSync2(path)) return void 0;
1758
2359
  try {
1759
2360
  const stored = JSON.parse(readFileSync(path, "utf-8"));
1760
2361
  let manifest = stored;
1761
2362
  if (!Array.isArray(stored.entries) && stored.entrySetHash) {
1762
2363
  if (!/^[a-f0-9]{64}$/.test(stored.entrySetHash)) return void 0;
1763
2364
  const entriesPath = join3(this.entrySetsRoot, `${stored.entrySetHash}.json`);
1764
- if (!existsSync(entriesPath)) return void 0;
2365
+ if (!existsSync2(entriesPath)) return void 0;
1765
2366
  const serializedEntries = readFileSync(entriesPath, "utf-8");
1766
2367
  if (sha256(serializedEntries) !== stored.entrySetHash) return void 0;
1767
2368
  manifest = {
@@ -1832,12 +2433,12 @@ var RewindSnapshotStore = class {
1832
2433
  discardManifest(id) {
1833
2434
  if (!/^[a-zA-Z0-9-]+$/.test(id)) return;
1834
2435
  const path = join3(this.manifestsRoot, `${id}.json`);
1835
- if (existsSync(path)) unlinkSync(path);
2436
+ if (existsSync2(path)) unlinkSync(path);
1836
2437
  }
1837
2438
  writeManifest(manifest) {
1838
2439
  const entriesHash = sha256(JSON.stringify(manifest.entries));
1839
2440
  const entriesPath = join3(this.entrySetsRoot, `${entriesHash}.json`);
1840
- if (!existsSync(entriesPath)) writeAtomic(entriesPath, JSON.stringify(manifest.entries));
2441
+ if (!existsSync2(entriesPath)) writeAtomic(entriesPath, JSON.stringify(manifest.entries));
1841
2442
  const metadata = { ...manifest };
1842
2443
  delete metadata.entries;
1843
2444
  writeAtomic(
@@ -1848,7 +2449,7 @@ var RewindSnapshotStore = class {
1848
2449
  async writeManifestAsync(manifest) {
1849
2450
  const entriesHash = sha256(JSON.stringify(manifest.entries));
1850
2451
  const entriesPath = join3(this.entrySetsRoot, `${entriesHash}.json`);
1851
- if (!await pathExistsAsync(entriesPath)) {
2452
+ if (!await pathExists(entriesPath)) {
1852
2453
  await writeAtomicAsync(entriesPath, JSON.stringify(manifest.entries));
1853
2454
  }
1854
2455
  const metadata = { ...manifest };
@@ -1920,7 +2521,7 @@ var RewindSnapshotStore = class {
1920
2521
  throw new Error(`Invalid blob hash for ${entry.path}.`);
1921
2522
  }
1922
2523
  const blobPath = join3(this.blobsRoot, entry.blobHash);
1923
- if (!existsSync(blobPath)) throw new Error(`Missing rewind blob for ${entry.path}.`);
2524
+ if (!existsSync2(blobPath)) throw new Error(`Missing rewind blob for ${entry.path}.`);
1924
2525
  const contents = readFileSync(blobPath);
1925
2526
  if (sha256(contents) !== entry.blobHash)
1926
2527
  throw new Error(`Corrupt rewind blob for ${entry.path}.`);
@@ -1943,7 +2544,7 @@ var RewindSnapshotStore = class {
1943
2544
  for (const entry of manifest.entries) {
1944
2545
  const target = resolveEntryPath(this.workspace, entry.path);
1945
2546
  mkdirSync(dirname(target), { recursive: true });
1946
- if (pathExists(target) && lstatSync(target).isDirectory()) {
2547
+ if (pathExists2(target) && lstatSync(target).isDirectory()) {
1947
2548
  throw new Error(`Cannot restore ${entry.path} because a directory exists at that path.`);
1948
2549
  }
1949
2550
  const contents = readFileSync(join3(this.blobsRoot, entry.blobHash));
@@ -1957,25 +2558,25 @@ var RewindSnapshotStore = class {
1957
2558
  }
1958
2559
  renameSync(temporary, target);
1959
2560
  } finally {
1960
- if (pathExists(temporary)) unlinkSync(temporary);
2561
+ if (pathExists2(temporary)) unlinkSync(temporary);
1961
2562
  }
1962
2563
  }
1963
2564
  }
1964
2565
  };
1965
2566
 
1966
2567
  // src/rewind/environment.ts
1967
- import { existsSync as existsSync2, mkdtempSync, rmSync as rmSync2 } from "fs";
2568
+ import { existsSync as existsSync3, mkdtempSync, rmSync as rmSync2 } from "fs";
1968
2569
  import { tmpdir } from "os";
1969
- import { join as join4, resolve as resolve3, sep as sep2 } from "path";
2570
+ import { join as join4, resolve as resolve2, sep as sep2 } from "path";
1970
2571
  function createEphemeralRewindEnvironment(workspace, temporaryRoot = tmpdir()) {
1971
- const resolvedTemporaryRoot = resolve3(temporaryRoot);
2572
+ const resolvedTemporaryRoot = resolve2(temporaryRoot);
1972
2573
  const root = mkdtempSync(join4(resolvedTemporaryRoot, "book-rewind-"));
1973
2574
  let disposed = false;
1974
2575
  const dispose = () => {
1975
2576
  if (disposed) return;
1976
2577
  disposed = true;
1977
- const resolvedRoot = resolve3(root);
1978
- if (resolvedRoot.startsWith(`${resolvedTemporaryRoot}${sep2}`) && resolvedRoot.split(sep2).at(-1)?.startsWith("book-rewind-") && existsSync2(resolvedRoot)) {
2578
+ const resolvedRoot = resolve2(root);
2579
+ if (resolvedRoot.startsWith(`${resolvedTemporaryRoot}${sep2}`) && resolvedRoot.split(sep2).at(-1)?.startsWith("book-rewind-") && existsSync3(resolvedRoot)) {
1979
2580
  rmSync2(resolvedRoot, { recursive: true, force: true });
1980
2581
  }
1981
2582
  process.off("exit", dispose);
@@ -1989,34 +2590,63 @@ function createEphemeralRewindEnvironment(workspace, temporaryRoot = tmpdir()) {
1989
2590
  };
1990
2591
  }
1991
2592
 
1992
- // src/cli/ink-patch.ts
1993
- import { readFileSync as readFileSync2 } from "fs";
1994
- import { createRequire } from "module";
1995
- import { dirname as dirname2, join as join5 } from "path";
1996
- var INK_PATCH_MARKER = "ansiEscapes.cursorUp(previousLines.length - 1)";
1997
- function isInkIncrementalRendererPatched() {
2593
+ // src/cli/ink-frame-throttle.ts
2594
+ import { join as join5 } from "path";
2595
+ import { pathToFileURL } from "url";
2596
+ function createFrameThrottle(draw, intervalMs, now = () => performance.now()) {
2597
+ let lastDrawAt = Number.NEGATIVE_INFINITY;
2598
+ let timer = null;
2599
+ const fire = () => {
2600
+ timer = null;
2601
+ lastDrawAt = now();
2602
+ draw();
2603
+ };
2604
+ const throttled = (() => {
2605
+ if (timer !== null) return;
2606
+ const wait = lastDrawAt + intervalMs - now();
2607
+ if (wait <= 0) fire();
2608
+ else timer = setTimeout(fire, wait);
2609
+ });
2610
+ throttled.flush = () => {
2611
+ if (timer === null) return;
2612
+ clearTimeout(timer);
2613
+ fire();
2614
+ };
2615
+ throttled.cancel = () => {
2616
+ if (timer === null) return;
2617
+ clearTimeout(timer);
2618
+ timer = null;
2619
+ };
2620
+ return throttled;
2621
+ }
2622
+ function isThrottledInk(ink) {
2623
+ if (typeof ink !== "object" || ink === null) return false;
2624
+ const candidate = ink;
2625
+ return typeof candidate.onRender === "function" && typeof candidate.renderThrottleMs === "number" && candidate.renderThrottleMs > 0 && typeof candidate.throttledOnRender?.flush === "function" && typeof candidate.throttledOnRender.cancel === "function" && typeof candidate.rootNode?.onRender === "function";
2626
+ }
2627
+ async function installInkFrameThrottle(stdout) {
1998
2628
  try {
1999
- const require2 = createRequire(import.meta.url);
2000
- const logUpdatePath = join5(dirname2(require2.resolve("ink")), "log-update.js");
2001
- return readFileSync2(logUpdatePath, "utf8").includes(INK_PATCH_MARKER);
2629
+ const module = await import(pathToFileURL(join5(inkBuildDir(), "instances.js")).href);
2630
+ const ink = module.default.get(stdout);
2631
+ if (!isThrottledInk(ink)) return false;
2632
+ ink.throttledOnRender.flush();
2633
+ const throttle = createFrameThrottle(() => ink.onRender(), ink.renderThrottleMs);
2634
+ ink.throttledOnRender = throttle;
2635
+ ink.rootNode.onRender = () => {
2636
+ ink.hasPendingThrottledRender = true;
2637
+ throttle();
2638
+ };
2639
+ return true;
2002
2640
  } catch {
2003
2641
  return false;
2004
2642
  }
2005
2643
  }
2006
2644
 
2007
- // src/cli/tui-renderer-mode.ts
2008
- function resolveTuiRendererMode(value = process.env.BOOK_TUI_RENDERER, context = {}) {
2009
- if (context.isTTY === false || context.screenReader) return "safe";
2010
- if (context.incrementalRendererPatched === false) return "safe";
2011
- if (value === "incremental") return "incremental";
2012
- if (value === "experimental-scroll") return "experimental-scroll";
2013
- if (value === void 0) return context.platform === "win32" ? "safe" : "incremental";
2014
- return "safe";
2015
- }
2016
-
2017
2645
  // src/cli/run.ts
2018
2646
  import { spawn } from "child_process";
2019
- var SESSION_ROOT = join6(resolveBookHome(), "sessions");
2647
+ function sessionRoot() {
2648
+ return join6(resolveBookHome(), "sessions");
2649
+ }
2020
2650
  var ENTER_ALT_SCREEN = "\x1B[?1049h";
2021
2651
  var EXIT_ALT_SCREEN = "\x1B[?1049l";
2022
2652
  var DISABLE_MOUSE_REPORTING = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1015l";
@@ -2060,7 +2690,8 @@ function writeTerminalControl(stdout, sequence) {
2060
2690
  {
2061
2691
  detached: true,
2062
2692
  stdio: "ignore",
2063
- windowsHide: true
2693
+ windowsHide: true,
2694
+ env: buildChildEnv()
2064
2695
  }
2065
2696
  );
2066
2697
  bridge.on("error", () => {
@@ -2072,6 +2703,12 @@ function writeTerminalControl(stdout, sequence) {
2072
2703
  function collectSnapshotReferences(store, cwd) {
2073
2704
  return store.listSnapshotReferences(cwd);
2074
2705
  }
2706
+ function printPipeErrorHandler(stream, printFormat, readerGone) {
2707
+ return (error) => {
2708
+ if (!isReaderGoneError(error)) throw error;
2709
+ if (stream === "stdout" && printFormat === "stream-json") readerGone.abort();
2710
+ };
2711
+ }
2075
2712
  function enterInteractiveScreen(stdout = process.stdout, bridgeWslTerminal = shouldBridgeWslTerminal()) {
2076
2713
  if (!stdout.isTTY && !bridgeWslTerminal) return () => {
2077
2714
  };
@@ -2093,9 +2730,17 @@ function enterInteractiveScreen(stdout = process.stdout, bridgeWslTerminal = sho
2093
2730
  return restore;
2094
2731
  }
2095
2732
  async function runMainAction(options) {
2733
+ let printInterrupt;
2734
+ let printSignal;
2735
+ let launched;
2736
+ const createdSessions = /* @__PURE__ */ new Set();
2096
2737
  try {
2097
2738
  const requestedWorkspace = options.workspace;
2098
2739
  const effortOverride = options.effort ? parseEffortLevel(String(options.effort), "--effort") : void 0;
2740
+ const explicitDriver = options.sessionDriver === void 0 ? void 0 : parseSessionDriver(String(options.sessionDriver));
2741
+ if (options.sessionDriver !== void 0 && !explicitDriver) {
2742
+ throw new Error("--session-driver must be human or agent");
2743
+ }
2099
2744
  const config = loadConfig(requestedWorkspace, {
2100
2745
  settingsOverridePath: options.settings,
2101
2746
  noSettings: options.settings === false,
@@ -2105,6 +2750,11 @@ async function runMainAction(options) {
2105
2750
  allowMissingApiKey: options.print === void 0 && !options.scrollback
2106
2751
  });
2107
2752
  if (config.modelProviderWarning) console.warn("\u26A0 " + config.modelProviderWarning);
2753
+ const providerNotices = options.print === void 0 && config.workspaceProviderGate ? collectWithheldProviderNotices(config.workspaceProviderGate, config.workspace) : [];
2754
+ const instructionNotices = collectSkippedProjectFileNotices(config.workspace);
2755
+ if (options.scrollback) {
2756
+ for (const notice of [...providerNotices, ...instructionNotices]) console.warn(notice);
2757
+ }
2108
2758
  const interactiveMaxTurns = parseNumericFlag(options.maxTurns, "--max-turns", {
2109
2759
  integer: true
2110
2760
  });
@@ -2123,14 +2773,22 @@ async function runMainAction(options) {
2123
2773
  config.provider = raw;
2124
2774
  }
2125
2775
  }
2776
+ const sessionDriver = resolveSessionDriver({ explicit: explicitDriver, env: process.env });
2777
+ config.sessionDriver = sessionDriver.driver;
2778
+ createDebugLogger("memory").info("session driver", { ...sessionDriver });
2126
2779
  freezeAgentConfig(config);
2127
2780
  cleanupDebugLogs(DEFAULT_LOCAL_DATA_RETENTION_DAYS, getDebugLogPath());
2781
+ cleanupRunStatus(DEFAULT_LOCAL_DATA_RETENTION_DAYS);
2128
2782
  if (options.print !== void 0) {
2129
- for (const notice of collectWithheldProjectNotices({
2130
- workspace: config.workspace,
2131
- settings: config.settings,
2132
- settingsEnabled: options.settings !== false
2133
- })) {
2783
+ for (const notice of [
2784
+ ...collectWithheldProjectNotices({
2785
+ workspace: config.workspace,
2786
+ settings: config.settings,
2787
+ settingsEnabled: options.settings !== false,
2788
+ providerGate: config.workspaceProviderGate
2789
+ }),
2790
+ ...instructionNotices
2791
+ ]) {
2134
2792
  console.warn(notice);
2135
2793
  }
2136
2794
  const declaredMcpServers = resolveMcpServerList(config.workspace);
@@ -2150,16 +2808,29 @@ async function runMainAction(options) {
2150
2808
  registry.registerAll(mcp.tools);
2151
2809
  }
2152
2810
  const mode2 = resolvePermissionMode(config.settings, options.permissionMode);
2153
- const sessionStore2 = options.sessionPersistence ? new SessionStore(SESSION_ROOT) : void 0;
2811
+ const sessionStore2 = options.sessionPersistence ? new SessionStore(sessionRoot()) : void 0;
2154
2812
  const bootstrap2 = resolveSessionBootstrap(sessionStore2, {
2155
2813
  cwd: config.workspace,
2156
2814
  resume: options.resume,
2157
2815
  continue: options.continue,
2158
2816
  sessionId: options.sessionId,
2159
2817
  sessionName: options.name,
2160
- forkSession: options.forkSession
2818
+ forkSession: options.forkSession,
2819
+ driver: config.sessionDriver
2161
2820
  });
2821
+ launched = { store: sessionStore2, bootstrap: bootstrap2 };
2162
2822
  sessionStore2?.cleanup(DEFAULT_LOCAL_DATA_RETENTION_DAYS, /* @__PURE__ */ new Set([bootstrap2.sessionId]));
2823
+ const printFormat = options.outputFormat;
2824
+ const readerGone = new AbortController();
2825
+ printInterrupt = installPrintInterrupt();
2826
+ const callerSignal = options.signal;
2827
+ printSignal = AbortSignal.any(
2828
+ [callerSignal, readerGone.signal, printInterrupt.signal].filter(
2829
+ (signal) => signal !== void 0
2830
+ )
2831
+ );
2832
+ process.stderr.on("error", printPipeErrorHandler("stderr", printFormat, readerGone));
2833
+ process.stdout.on("error", printPipeErrorHandler("stdout", printFormat, readerGone));
2163
2834
  result = await runHeadless(config, registry, {
2164
2835
  prompt: typeof options.print === "string" ? options.print : void 0,
2165
2836
  inputFormat: options.inputFormat,
@@ -2168,13 +2839,15 @@ async function runMainAction(options) {
2168
2839
  transcript: bootstrap2.transcript,
2169
2840
  compactBoundaries: bootstrap2.compactBoundaries,
2170
2841
  plan: bootstrap2.plan,
2842
+ goal: bootstrap2.goal,
2171
2843
  carriedUsage: bootstrap2.carriedUsage,
2172
2844
  carriedModels: bootstrap2.carriedModels,
2173
2845
  mode: mode2,
2174
- signal: options.signal,
2846
+ signal: printSignal,
2175
2847
  maxTurns: parseNumericFlag(options.maxTurns, "--max-turns", { integer: true }),
2176
2848
  maxBudgetUsd: parseNumericFlag(options.maxBudgetUsd, "--max-budget-usd"),
2177
2849
  verbose: options.verbose,
2850
+ quiet: options.quiet,
2178
2851
  jsonSchema: options.jsonSchema ? JSON.parse(options.jsonSchema) : void 0,
2179
2852
  sessionStore: sessionStore2,
2180
2853
  sessionId: bootstrap2.sessionId,
@@ -2184,23 +2857,28 @@ async function runMainAction(options) {
2184
2857
  persistSession: options.sessionPersistence,
2185
2858
  includeHookEvents: options.includeHookEvents,
2186
2859
  includePartialMessages: options.includePartialMessages,
2860
+ includeResultMessages: options.includeResultMessages,
2187
2861
  promptSuggestions: options.promptSuggestions
2188
2862
  });
2189
2863
  } finally {
2864
+ printInterrupt?.dispose();
2190
2865
  await disconnectMcpServers(mcp.connections);
2191
2866
  }
2192
- if (result?.outcome.status === "failed") {
2193
- exit(1);
2194
- }
2867
+ const code = printExitCode({
2868
+ outcome: result?.outcome,
2869
+ aborted: printSignal?.aborted === true,
2870
+ interruptedBy: printInterrupt?.interruptedBy()
2871
+ });
2872
+ if (code !== 0) setExitCode(code);
2195
2873
  return;
2196
2874
  }
2197
2875
  const mode = resolvePermissionMode(config.settings, options.permissionMode);
2198
2876
  if (options.scrollback) {
2199
- const { runScrollbackSession } = await import("./scrollback-LUKLIBVI.js");
2877
+ const { runScrollbackSession } = await import("./scrollback-GD76GNM4.js");
2200
2878
  await runScrollbackSession(config, { mode });
2201
2879
  return;
2202
2880
  }
2203
- const sessionStore = options.sessionPersistence ? new SessionStore(SESSION_ROOT) : void 0;
2881
+ const sessionStore = options.sessionPersistence ? new SessionStore(sessionRoot()) : void 0;
2204
2882
  const ephemeralRewind = sessionStore ? void 0 : createEphemeralRewindEnvironment(config.workspace);
2205
2883
  const timelineStore = sessionStore ?? ephemeralRewind.timelineStore;
2206
2884
  let snapshotStore = sessionStore ? createRewindSnapshotStore(config.workspace) : ephemeralRewind.snapshotStore;
@@ -2219,26 +2897,30 @@ async function runMainAction(options) {
2219
2897
  continue: options.continue,
2220
2898
  sessionId: options.sessionId,
2221
2899
  sessionName: options.name,
2222
- forkSession: options.forkSession
2900
+ forkSession: options.forkSession,
2901
+ driver: config.sessionDriver
2223
2902
  });
2903
+ launched = { store: sessionStore, bootstrap };
2224
2904
  sessionStore?.cleanup(DEFAULT_LOCAL_DATA_RETENTION_DAYS, /* @__PURE__ */ new Set([bootstrap.sessionId]));
2225
2905
  if (!sessionStore) {
2226
2906
  timelineStore.create({
2227
2907
  id: bootstrap.sessionId,
2228
2908
  cwd: config.workspace,
2229
- name: bootstrap.sessionName
2909
+ name: bootstrap.sessionName,
2910
+ driver: config.sessionDriver
2230
2911
  });
2231
2912
  }
2232
- const [{ App }, { loadInteractiveAssets }] = await Promise.all([
2233
- import("./app-MOXXWEOY.js"),
2234
- import("./interactive-assets-CCAAWVKH.js")
2913
+ const [{ App }, { loadInteractiveAssets }, { render }, { createElement }] = await Promise.all([
2914
+ import("./app-J7BVPSUH.js"),
2915
+ import("./interactive-assets-73AQO5WP.js"),
2916
+ import("ink"),
2917
+ import("react")
2235
2918
  ]);
2236
2919
  const interactiveAssets = loadInteractiveAssets(config);
2237
2920
  const mcpHost = new McpSessionHost(config.workspace, config.settings);
2238
2921
  mcpHost.start();
2239
2922
  let app;
2240
2923
  const redrawViewport = () => {
2241
- app?.clear();
2242
2924
  process.stdout.write("\x1B[H\x1B[2J");
2243
2925
  };
2244
2926
  const restoreScreen = config.accessibility.screenReader ? () => {
@@ -2247,27 +2929,42 @@ async function runMainAction(options) {
2247
2929
  const rendererMode = resolveTuiRendererMode(process.env.BOOK_TUI_RENDERER, {
2248
2930
  isTTY: process.stdout.isTTY === true,
2249
2931
  screenReader: config.accessibility.screenReader,
2250
- incrementalRendererPatched: isInkIncrementalRendererPatched(),
2932
+ incrementalRendererFixed: hasInkTrailingNewlineFix(),
2251
2933
  platform: process.platform
2252
2934
  });
2253
2935
  await installInkScrollRenderer(rendererMode === "experimental-scroll");
2254
2936
  await installFrameCapture();
2937
+ await installInkOutputCache();
2938
+ await installInkRenderCull();
2939
+ await installInkFrameClip();
2940
+ const flushSync = await loadInkFlushSync();
2255
2941
  app = render(
2256
2942
  createElement(App, {
2257
2943
  config,
2258
2944
  permissionMode: mode,
2259
2945
  interactiveAssets,
2260
2946
  redrawViewport,
2947
+ flushSync,
2261
2948
  mcp: mcpHost,
2262
- session: { ...bootstrap, store: sessionStore, timelineStore, snapshotStore }
2949
+ session: {
2950
+ ...bootstrap,
2951
+ store: sessionStore,
2952
+ timelineStore,
2953
+ snapshotStore,
2954
+ createdSessions
2955
+ },
2956
+ startupNotices: [...providerNotices, ...instructionNotices]
2263
2957
  }),
2264
2958
  {
2265
2959
  exitOnCtrlC: false,
2266
2960
  isScreenReaderEnabled: config.accessibility.screenReader,
2267
2961
  incrementalRendering: rendererMode !== "safe",
2268
- maxFps: 60
2962
+ maxFps: resolveInkMaxFps(process.platform),
2963
+ // Ink 7 also turns live frames off for a non-TTY stdout, which the WSL bridge uses.
2964
+ interactive: !isCiEnvironment()
2269
2965
  }
2270
2966
  );
2967
+ await installInkFrameThrottle(process.stdout);
2271
2968
  await app.waitUntilExit();
2272
2969
  } finally {
2273
2970
  app?.cleanup();
@@ -2278,12 +2975,23 @@ async function runMainAction(options) {
2278
2975
  } catch (e) {
2279
2976
  if (isExiting()) throw e;
2280
2977
  console.error(e instanceof Error ? e.message : String(e));
2281
- exit(1);
2978
+ if (options.print !== void 0) {
2979
+ printInterrupt?.dispose();
2980
+ const code = printExitCode({
2981
+ aborted: printSignal?.aborted === true,
2982
+ interruptedBy: printInterrupt?.interruptedBy()
2983
+ });
2984
+ if (code !== 0) setExitCode(code);
2985
+ return;
2986
+ }
2987
+ setExitCode(1);
2988
+ } finally {
2989
+ if (launched) discardUntouchedSessions(launched.store, launched.bootstrap, createdSessions);
2282
2990
  }
2283
2991
  }
2284
2992
 
2285
2993
  // src/cli/mcp.ts
2286
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2994
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
2287
2995
  import { join as join7 } from "path";
2288
2996
  function configPath(workspace, scope, home) {
2289
2997
  return scope === "project" ? join7(workspace, ".mcp.json") : home ? join7(home, ".book", "mcp.json") : join7(resolveBookHome(), "mcp.json");
@@ -2296,10 +3004,12 @@ function validScope(scope) {
2296
3004
  return normalized;
2297
3005
  }
2298
3006
  function readConfigDocument(path) {
2299
- if (!existsSync3(path)) return {};
3007
+ const refusal = projectFileWriteRefusal(path);
3008
+ if (refusal) throw new Error(`Cannot update ${path}: ${refusal}.`);
3009
+ if (!existsSync4(path)) return {};
2300
3010
  let parsed;
2301
3011
  try {
2302
- parsed = JSON.parse(readFileSync3(path, "utf8"));
3012
+ parsed = JSON.parse(readFileSync2(path, "utf8"));
2303
3013
  } catch (error) {
2304
3014
  throw new Error(
2305
3015
  `Cannot update ${path}: ${error instanceof Error ? error.message : String(error)}`
@@ -2480,13 +3190,19 @@ function parseEffortOption(value) {
2480
3190
  function resolveWorkspace(workspace) {
2481
3191
  return workspace ?? program.opts().workspace;
2482
3192
  }
2483
- program.name("book").description("AI coding agent with rich TUI").enablePositionalOptions().version(getPackageVersion()).option("-w, --workspace <path>", "Workspace root directory", process.cwd()).option("-m, --model <model>", "Model to use").option(
3193
+ program.name("book").description("AI coding agent with rich TUI").enablePositionalOptions().version(getPackageVersion()).argument("[prompt]", "Prompt for print mode; the same as the -p value, in either position").option("-w, --workspace <path>", "Workspace root directory", process.cwd()).option("-m, --model <model>", "Model to use").option(
2484
3194
  "-p, --print [prompt]",
2485
- "Print mode (non-interactive). Reads prompt from the flag or stdin."
3195
+ "Print mode (non-interactive). Reads the prompt from the flag, the [prompt] argument, or stdin."
2486
3196
  ).option("--output-format <format>", "text | json | stream-json (print mode)", "text").option("--input-format <format>", "text | stream-json (print mode)", "text").option("--max-turns <n>", "Max agent turns (print mode; omit for unlimited)").option("--max-budget-usd <amount>", "Max USD spend (print mode)").option(
2487
3197
  "--permission-mode <mode>",
2488
3198
  "default | acceptEdits | plan | auto | dontAsk | bypassPermissions (overrides settings.defaultMode)"
2489
- ).option("--verbose", "Full turn-by-turn output").option("--json-schema <schema>", "Return validated JSON matching a JSON Schema (print mode)").option("-r, --resume <id|name>", "Resume a session by id or name").option("-c, --continue", "Resume the most recent session in this directory").option("--session-id <uuid>", "Use a specific session id").option("-n, --name <name>", "Set a display name for the session").option("--no-session-persistence", "Do not save the session to disk").option("--fork-session", "On resume, create a new session id instead of reusing").option("--include-hook-events", "Emit hook lifecycle events in stream-json output").option("--include-partial-messages", "Emit partial assistant text deltas in stream-json output").option("--prompt-suggestions", "Ask model for follow-up prompt suggestions after completion").option("--agents <mode>", "Managed agents: adaptive, manual, off").option("--scrollback", "Use terminal-native scrollback instead of the full-screen TUI").option("--settings <path>", "Path to an ad-hoc settings file (overrides all scopes)").option("--no-settings", "Skip all settings.json layers (use defaults + legacy .bookrc.json)").option(
3199
+ ).option("--verbose", "Print mode: add each tool result to the progress lines on stderr").option("-q, --quiet", "Print mode: no progress lines on stderr").option("--json-schema <schema>", "Return validated JSON matching a JSON Schema (print mode)").option("-r, --resume <id|name>", "Resume a session by id or name").option("-c, --continue", "Resume the most recent session in this directory that has a message").option("--session-id <uuid>", "Use a specific session id").option("-n, --name <name>", "Set a display name for the session").option("--no-session-persistence", "Do not save the session to disk").option("--fork-session", "On resume, create a new session id instead of reusing").option(
3200
+ "--session-driver <driver>",
3201
+ "human or agent (default: agent under another agent harness); an agent-driven session saves only what it learned, never its instructions"
3202
+ ).option("--include-hook-events", "Emit hook lifecycle events in stream-json output").option("--include-partial-messages", "Emit partial assistant text deltas in stream-json output").option(
3203
+ "--include-result-messages",
3204
+ "Include the full message history in the stream-json result event"
3205
+ ).option("--prompt-suggestions", "Ask model for follow-up prompt suggestions after completion").option("--agents <mode>", "Managed agents: adaptive, manual, off").option("--scrollback", "Use terminal-native scrollback instead of the full-screen TUI").option("--settings <path>", "Path to an ad-hoc settings file (overrides all scopes)").option("--no-settings", "Skip all settings.json layers (use defaults + legacy .bookrc.json)").option(
2490
3206
  "--effort <level>",
2491
3207
  "Thinking effort: low, medium, high, xhigh, max (default: high)",
2492
3208
  parseEffortOption
@@ -2496,7 +3212,10 @@ program.command("doctor").description("Diagnose configuration and environment").
2496
3212
  noSettings: options.settings === false
2497
3213
  });
2498
3214
  });
2499
- program.command("status").description("Report what a session is working on and what it has spent (no credentials needed)").argument("[session]", "Session id or name (defaults to the most recent in this workspace)").option("-w, --workspace <path>", "Workspace root directory (defaults to the root -w, then cwd)").option("--json", "Emit the status as JSON").action((session, options) => {
3215
+ program.command("status").description("Report what a session is working on and what it has spent (no credentials needed)").argument(
3216
+ "[session]",
3217
+ "Session id or name (defaults to the most recent session in this workspace with a message or a run record)"
3218
+ ).option("-w, --workspace <path>", "Workspace root directory (defaults to the root -w, then cwd)").option("--json", "Emit the status as JSON").action((session, options) => {
2500
3219
  const code = runStatusCommand({
2501
3220
  session,
2502
3221
  workspace: resolveWorkspace(options.workspace),
@@ -2552,7 +3271,29 @@ trustCommand.command("rule").description("Approve or reject a project-declared p
2552
3271
  });
2553
3272
  }
2554
3273
  );
2555
- program.command("config").description("Read and write settings").option("-w, --workspace <path>", "Workspace root directory (defaults to the root -w, then cwd)").option("--settings <path>", "Path to an ad-hoc settings file (overrides all scopes)").option("--no-settings", "Skip all settings.json layers (use defaults + legacy .bookrc.json)").option("-g, --global", "Act on user-global settings (<BOOK_HOME>/settings.json) [default]").option("--project", "Act on checked-in project settings (.book/settings.json)").option("--local", "Act on gitignored project-local settings (.book/settings.local.json)").argument("[action]", "get <key>, set <key> <value>, unset <key>, or list").argument("[key]", "Dot-separated key path (e.g. permissions.deny)").argument("[value]", "Value to set (JSON-parsed)").addHelpText("after", `
3274
+ trustCommand.command("dir").description("Approve or reject a project-declared additionalDirectories entry").argument("[path]", "The declared entry, or the real path `book doctor` reported for it").option("-w, --workspace <path>", "Workspace root directory (defaults to the root -w, then cwd)").option("--all-pending", "Apply to every directory currently awaiting a decision").option("--reject", "Record a refusal instead of an approval").action(
3275
+ async (path, options) => {
3276
+ await runTrustCommand("dir", path, {
3277
+ ...options,
3278
+ workspace: resolveWorkspace(options.workspace)
3279
+ });
3280
+ }
3281
+ );
3282
+ trustCommand.command("provider").description("Approve or reject a provider (origin and key) a repository declared").argument(
3283
+ "[id]",
3284
+ "Provider id, or .bookrc.json for a legacy baseUrl; omit to list what is pending"
3285
+ ).option("-w, --workspace <path>", "Workspace root directory (defaults to the root -w, then cwd)").option("--all-pending", "Apply to every provider currently awaiting a decision").option("--reject", "Record a refusal instead of an approval").action(
3286
+ async (id, options) => {
3287
+ await runTrustCommand("provider", id, {
3288
+ ...options,
3289
+ workspace: resolveWorkspace(options.workspace)
3290
+ });
3291
+ }
3292
+ );
3293
+ program.command("config").description("Read and write settings").option("-w, --workspace <path>", "Workspace root directory (defaults to the root -w, then cwd)").option("--settings <path>", "Path to an ad-hoc settings file (overrides all scopes)").option("--no-settings", "Skip all settings.json layers (use defaults + legacy .bookrc.json)").option("-g, --global", "Act on user-global settings (<BOOK_HOME>/settings.json) [default]").option("--project", "Act on checked-in project settings (.book/settings.json)").option("--local", "Act on gitignored project-local settings (.book/settings.local.json)").option("--no-check", "provider-key: save without testing the key against the endpoint").option("--force", "provider-key: save even when the endpoint rejects the key").argument(
3294
+ "[action]",
3295
+ "get <key>, set <key> <value>, unset <key>, list, or provider-key <id> (key read from stdin)"
3296
+ ).argument("[key]", "Dot-separated key path (e.g. permissions.deny), or the provider id").argument("[value]", "Value to set (JSON-parsed)").addHelpText("after", `
2556
3297
  ${formatSettingsKeyHelp()}`).action(
2557
3298
  async (action, key, value, options) => {
2558
3299
  const rootSettings = program.opts().settings;
@@ -2571,12 +3312,15 @@ ${formatSettingsKeyHelp()}`).action(
2571
3312
  await runConfigCommand(resolveWorkspace(options.workspace), action, key, value, {
2572
3313
  settingsOverridePath: typeof settings === "string" ? settings : void 0,
2573
3314
  noSettings: settings === false,
2574
- scope: selected[0]?.[0]
3315
+ scope: selected[0]?.[0],
3316
+ providerKey: { check: options.check, force: options.force }
2575
3317
  });
2576
3318
  }
2577
3319
  );
2578
- program.action(async (options) => {
2579
- await runMainAction(options);
3320
+ program.action(async (prompt, options) => {
3321
+ const resolved = resolvePrintPrompt(options.print, prompt);
3322
+ if ("error" in resolved) program.error(`error: ${resolved.error}`);
3323
+ await runMainAction({ ...options, print: resolved.print });
2580
3324
  });
2581
3325
  program.parse();
2582
3326
  //# sourceMappingURL=index.js.map