@wrongstack/cli 1.0.19 → 1.0.26

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 (195) hide show
  1. package/data/providers.json +44 -0
  2. package/dist/{acp-INCNOH42.js → acp-LI665LO2.js} +18 -7
  3. package/dist/arg-parser.d.ts +1 -0
  4. package/dist/ascii-mode-SG4PRNLP.js +7 -0
  5. package/dist/{auth-YXK3EDUL.js → auth-TE55JF4Z.js} +68 -22
  6. package/dist/auth-menu/direct.d.ts +1 -0
  7. package/dist/auth-menu/local.d.ts +2 -0
  8. package/dist/auth-menu/oauth-menu.d.ts +3 -1
  9. package/dist/auth-menu/panel-service.d.ts +20 -0
  10. package/dist/{bench-YBU6BVQP.js → bench-GTLPM22B.js} +2 -2
  11. package/dist/boot/append-system-prompt.d.ts +10 -0
  12. package/dist/boot/ascii-mode.d.ts +12 -0
  13. package/dist/boot/config-backup-recovery.d.ts +2 -1
  14. package/dist/boot/container-wiring.d.ts +4 -2
  15. package/dist/boot/dispatch-singleshot.d.ts +4 -1
  16. package/dist/boot/dispatch-webui.d.ts +11 -0
  17. package/dist/boot/mcp-config-flag.d.ts +22 -0
  18. package/dist/boot/piped-stdin.d.ts +40 -0
  19. package/dist/boot/restricted-mode.d.ts +15 -0
  20. package/dist/boot/safe-mode.d.ts +30 -0
  21. package/dist/boot/short-circuit-desktop.d.ts +1 -0
  22. package/dist/boot/single-shot-budget.d.ts +30 -0
  23. package/dist/boot/stream-json.d.ts +36 -0
  24. package/dist/boot/structured-output.d.ts +16 -0
  25. package/dist/boot/system-prompt-builder.d.ts +2 -0
  26. package/dist/boot/tool-restriction-flags.d.ts +36 -0
  27. package/dist/boot/tui-project-picker-callback.d.ts +1 -1
  28. package/dist/boot/tui-settings-adapter.d.ts +15 -2
  29. package/dist/boot/worktree-sessions.d.ts +21 -0
  30. package/dist/{boot-auto-thin-MANHOYHE.js → boot-auto-thin-6Q56FY3F.js} +3 -2
  31. package/dist/boot-provider-selection.d.ts +67 -0
  32. package/dist/boot.d.ts +1 -38
  33. package/dist/{chunk-JY52HWL6.js → chunk-2EBVTYJR.js} +2 -2
  34. package/dist/{chunk-RDHFXOJM.js → chunk-2ERX3RPN.js} +112 -65
  35. package/dist/chunk-4TVYGG6G.js +62 -0
  36. package/dist/chunk-5JYDKG5B.js +154 -0
  37. package/dist/{chunk-KEPABQYT.js → chunk-5SEYGOCH.js} +8 -82
  38. package/dist/{chunk-3VXYY5JC.js → chunk-5UZBUV2H.js} +3 -19
  39. package/dist/chunk-BBHWW6LR.js +549 -0
  40. package/dist/chunk-BEHGRNOQ.js +58 -0
  41. package/dist/chunk-CZTXF6HY.js +140 -0
  42. package/dist/{chunk-JX6KL7OF.js → chunk-EPR42SWM.js} +39 -8
  43. package/dist/chunk-F5DCFAKI.js +36 -0
  44. package/dist/chunk-HLSHFXTO.js +97 -0
  45. package/dist/{chunk-CJTBLTS4.js → chunk-KJS5543T.js} +4 -3
  46. package/dist/{chunk-DE7BZDNK.js → chunk-KOEO4CJG.js} +27 -4
  47. package/dist/{chunk-SIS37L3W.js → chunk-L2RVEQWO.js} +211 -193
  48. package/dist/chunk-LBPLTTZ6.js +105 -0
  49. package/dist/chunk-LDCGZJTC.js +46 -0
  50. package/dist/chunk-LKPEBZBE.js +143 -0
  51. package/dist/chunk-LUMETJFO.js +171 -0
  52. package/dist/{chunk-KK2LB3ZS.js → chunk-OAVBM55Q.js} +9 -5
  53. package/dist/chunk-PZQBGUVZ.js +18 -0
  54. package/dist/{chunk-C3Z4N6A6.js → chunk-Q4INH2DG.js} +51 -3
  55. package/dist/chunk-R4QGL6GB.js +10 -0
  56. package/dist/{chunk-YLQEGYV4.js → chunk-RZ4ZDRYA.js} +14 -4
  57. package/dist/chunk-S4FFHDJG.js +93 -0
  58. package/dist/chunk-SFBFYIOW.js +271 -0
  59. package/dist/{chunk-RMQNCHGO.js → chunk-TI7KLHYX.js} +136 -11
  60. package/dist/{chunk-2ENZU66X.js → chunk-WQV2NP57.js} +5 -5
  61. package/dist/{chunk-B4JLSXZB.js → chunk-WSTVGC2C.js} +73 -45
  62. package/dist/chunk-X756ZS72.js +329 -0
  63. package/dist/{chunk-XJ7K67PP.js → chunk-YHYINL47.js} +63 -24
  64. package/dist/{chunk-2VMJ3E5Z.js → chunk-YTEOAWOJ.js} +6 -2
  65. package/dist/{chunk-GPPRNAKW.js → chunk-ZMX5HRLI.js} +2 -2
  66. package/dist/{cli-context-33F74CSZ.js → cli-context-UVGAN3NO.js} +496 -384
  67. package/dist/cli-entry-point.d.ts +2 -0
  68. package/dist/{cli-main-FNGMKPBA.js → cli-main-TN3KZ2P6.js} +3561 -2724
  69. package/dist/cli-update-notice.d.ts +2 -1
  70. package/dist/config-doctor.d.ts +10 -2
  71. package/dist/{config-transfer-2YF3CRTN.js → config-transfer-RUWLDCNK.js} +34 -13
  72. package/dist/{diag-doctor-ANPRUYFM.js → diag-doctor-BIZAFIAT.js} +2 -2
  73. package/dist/execute-deps.d.ts +2 -1
  74. package/dist/{execution-A6WJLSOC.js → execution-7QIP6GRY.js} +2384 -2055
  75. package/dist/fleet/host-types.d.ts +3 -1
  76. package/dist/goal-commands.d.ts +47 -0
  77. package/dist/goal-host.d.ts +1 -14
  78. package/dist/{hq-XQWQTRDA.js → hq-GLZ7QESM.js} +277 -261
  79. package/dist/hq-command-controller.d.ts +1 -1
  80. package/dist/{hq-server-IIBZQZU4.js → hq-server-YTKDVQPY.js} +2 -2
  81. package/dist/{hq-service-L5INQFM4.js → hq-service-BMMJAHRT.js} +11 -4
  82. package/dist/import-claude-code-32PCSIQI.js +236 -0
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.js +19 -10
  85. package/dist/kanban-supervisor-67ECABTD.js +7 -0
  86. package/dist/live-settings-input.d.ts +7 -0
  87. package/dist/{mailbox-serve-3GFOK2I7.js → mailbox-serve-NW5VZG7R.js} +3 -3
  88. package/dist/{mcp-DC5WSBOO.js → mcp-LBS45HWZ.js} +4 -4
  89. package/dist/modeldiag-36VODVIH.js +828 -0
  90. package/dist/{per-subcommand-help-DHTYU524.js → per-subcommand-help-QDAQASPZ.js} +3 -3
  91. package/dist/permission-prompt-mirror.d.ts +1 -1
  92. package/dist/permission-prompt.d.ts +16 -2
  93. package/dist/permission-rules-view.d.ts +18 -0
  94. package/dist/{permissions-CFDLXETM.js → permissions-PL27UHYD.js} +67 -45
  95. package/dist/plugin-config.d.ts +12 -0
  96. package/dist/plugin-management-types.d.ts +31 -0
  97. package/dist/plugin-management.d.ts +2 -30
  98. package/dist/plugin-package-install.d.ts +3 -0
  99. package/dist/{plugin-usage-M6KET4OZ.js → plugin-usage-CIGOOTX3.js} +6 -5
  100. package/dist/{plugins-WJLW6ULV.js → plugins-HQTXUD6A.js} +6 -3
  101. package/dist/preflight.d.ts +1 -1
  102. package/dist/{projects-LMNAGGEU.js → projects-T4CZCA25.js} +3 -6
  103. package/dist/provider-config-utils.d.ts +1 -6
  104. package/dist/provider-helpers.d.ts +7 -11
  105. package/dist/{providers-models-LFNZ5WI7.js → providers-models-5Z2PJTEW.js} +44 -34
  106. package/dist/release-asset-download.d.ts +13 -0
  107. package/dist/release-attestation.d.ts +28 -0
  108. package/dist/remote/remote-session.d.ts +64 -0
  109. package/dist/remote/remote-target.d.ts +69 -0
  110. package/dist/remote-O5WXHLWV.js +561 -0
  111. package/dist/{replay-Z3FTJEMM.js → replay-DEI566CW.js} +2 -2
  112. package/dist/services/commit-message.d.ts +2 -1
  113. package/dist/services/dispatch-classifier.d.ts +39 -5
  114. package/dist/session-move-P2EC2COA.js +44 -0
  115. package/dist/session-move.d.ts +20 -0
  116. package/dist/session-stats.d.ts +10 -0
  117. package/dist/{sessions-config-UL45XKX7.js → sessions-config-SXTTLNXA.js} +47 -9
  118. package/dist/settings-menu.d.ts +2 -0
  119. package/dist/short-circuit-flags-74Y5N6CF.js +10 -0
  120. package/dist/{simpleui-dist-CXSIDAXZ.js → simpleui-dist-7BQISSEP.js} +7 -3
  121. package/dist/skill-suggest-L2EVWB6R.js +290 -0
  122. package/dist/slash-commands/command-context.d.ts +15 -5
  123. package/dist/slash-commands/effort.d.ts +1 -1
  124. package/dist/slash-commands/fallback.d.ts +1 -1
  125. package/dist/slash-commands/gitid.d.ts +1 -1
  126. package/dist/slash-commands/goal-refiner.d.ts +4 -49
  127. package/dist/slash-commands/jev.d.ts +4 -0
  128. package/dist/slash-commands/mcp.d.ts +2 -1
  129. package/dist/slash-commands/openai-quota.d.ts +15 -0
  130. package/dist/slash-commands/permissions.d.ts +4 -0
  131. package/dist/slash-commands/sdd-task-mutations.d.ts +4 -0
  132. package/dist/slash-commands/settings-context-mutations.d.ts +5 -0
  133. package/dist/slash-commands/settings-keys.d.ts +2 -0
  134. package/dist/slash-commands/settings-limits-mutations.d.ts +12 -0
  135. package/dist/slash-commands/settings-reset.d.ts +6 -0
  136. package/dist/standalone-auto-update-TOTRGCSA.js +249 -0
  137. package/dist/standalone-auto-update.d.ts +65 -0
  138. package/dist/standalone-update-LZO3MNLH.js +24 -0
  139. package/dist/standalone-update.d.ts +55 -0
  140. package/dist/subcommands/handlers/hq-alerts.d.ts +3 -0
  141. package/dist/subcommands/handlers/hq-audit.d.ts +46 -0
  142. package/dist/subcommands/handlers/hq-data-dir.d.ts +13 -0
  143. package/dist/subcommands/handlers/import-claude-code.d.ts +42 -0
  144. package/dist/subcommands/handlers/permissions.d.ts +9 -3
  145. package/dist/subcommands/handlers/remote.d.ts +6 -0
  146. package/dist/subcommands/handlers/skill-suggest.d.ts +21 -0
  147. package/dist/subcommands/handlers/typesafe-brain-replay.d.ts +26 -0
  148. package/dist/subcommands/handlers/typesafe-judgment-check.d.ts +3 -0
  149. package/dist/subcommands/handlers/typesafe-topic-replay.d.ts +19 -0
  150. package/dist/subcommands/handlers/typesafe-triage-replay.d.ts +18 -0
  151. package/dist/subcommands/handlers/typesafe.d.ts +23 -0
  152. package/dist/subcommands/handlers/version-help.d.ts +5 -0
  153. package/dist/{subcommands-XSLXMIDP.js → subcommands-Q6ORSB2E.js} +2 -2
  154. package/dist/tui-resume-callback.d.ts +18 -0
  155. package/dist/typesafe-U7YB2RRV.js +316 -0
  156. package/dist/typesafe-brain-replay-C5276FVR.js +222 -0
  157. package/dist/typesafe-judgment-check-GGYYV5UF.js +29 -0
  158. package/dist/typesafe-topic-replay-ZILIU3WS.js +234 -0
  159. package/dist/typesafe-triage-replay-FGAW7QM7.js +215 -0
  160. package/dist/{update-5DPB4YD4.js → update-TDZX4IL2.js} +4 -3
  161. package/dist/{update-check-BS3DHTSI.js → update-check-J5ZVKS73.js} +3 -2
  162. package/dist/update-check.d.ts +1 -1
  163. package/dist/version-JB2UAV2A.js +11 -0
  164. package/dist/version-help-XJJQ7YSH.js +10 -0
  165. package/dist/version.d.ts +3 -2
  166. package/dist/webui-client-observability.d.ts +7 -0
  167. package/dist/webui-server/route-contexts.d.ts +1 -1
  168. package/dist/webui-server/session-start-payload.d.ts +5 -1
  169. package/dist/{webui-server-MRPGTTKL.js → webui-server-YTHLRFD2.js} +577 -370
  170. package/dist/webui-server-lifecycle.d.ts +57 -0
  171. package/dist/webui-server-options.d.ts +11 -0
  172. package/dist/webui-server.d.ts +1 -1
  173. package/dist/wiring/brain-and-orchestration.d.ts +3 -1
  174. package/dist/wiring/cli-prompt-and-tools-setup.d.ts +9 -0
  175. package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
  176. package/dist/wiring/domain-glossary.d.ts +5 -4
  177. package/dist/wiring/lifecycle-plugins.d.ts +4 -2
  178. package/dist/wiring/metrics.d.ts +9 -1
  179. package/dist/wiring/pipeline.d.ts +2 -7
  180. package/dist/wiring/plugins.d.ts +4 -1
  181. package/dist/wiring/provider-persisters.d.ts +24 -0
  182. package/dist/wiring/provider-runtime.d.ts +9 -1
  183. package/dist/wiring/provider-status.d.ts +1 -0
  184. package/dist/wiring/provider-utility-tools.d.ts +3 -1
  185. package/dist/wiring/runtime-dispatch-state.d.ts +3 -1
  186. package/package.json +28 -28
  187. package/dist/chunk-2CZEORLV.js +0 -54
  188. package/dist/chunk-672UXYL3.js +0 -65
  189. package/dist/chunk-6WRKAACA.js +0 -96
  190. package/dist/chunk-DR2OXU46.js +0 -174
  191. package/dist/chunk-JACOMTIH.js +0 -275
  192. package/dist/chunk-XJXDOF63.js +0 -28
  193. package/dist/modeldiag-CJSFHZ3J.js +0 -1356
  194. package/dist/short-circuit-flags-7GQIGMVD.js +0 -10
  195. package/dist/version-help-WXI42PZ3.js +0 -10
@@ -0,0 +1,271 @@
1
+ import {
2
+ appendHistory,
3
+ backupCurrent
4
+ } from "./chunk-WSTVGC2C.js";
5
+
6
+ // src/settings-menu.ts
7
+ import * as fs from "node:fs/promises";
8
+ import * as path from "node:path";
9
+ import { decryptConfigSecretsForRewrite, encryptConfigSecrets } from "@wrongstack/core/security";
10
+ import { ConfigError, ERROR_CODES, FsError } from "@wrongstack/core/types";
11
+ import { atomicWrite, deepMerge, withFileLock } from "@wrongstack/core/utils";
12
+ function resolvePersistPath(deps) {
13
+ if (deps.forceGlobal) return deps.profileConfigPath;
14
+ const scope = deps.configStore.get().configScope;
15
+ if (scope === "project" && deps.inProjectConfigPath) {
16
+ return deps.inProjectConfigPath;
17
+ }
18
+ return deps.profileConfigPath;
19
+ }
20
+ function resolveActualTarget(deps, decrypted, fallbackPath) {
21
+ if (deps.forceGlobal) return deps.profileConfigPath;
22
+ const newScope = decrypted.configScope;
23
+ const newProfileName = decrypted.activeProfile;
24
+ const effectiveProfilePath = newProfileName && deps.resolveProfilePath ? deps.resolveProfilePath(newProfileName) : deps.profileConfigPath;
25
+ if (newScope === "project" && deps.inProjectConfigPath) {
26
+ return deps.inProjectConfigPath;
27
+ }
28
+ if (newScope === "global") {
29
+ return effectiveProfilePath;
30
+ }
31
+ return fallbackPath;
32
+ }
33
+ function isProfileOrGlobalTarget(actualTarget, deps) {
34
+ return !deps.inProjectConfigPath || path.resolve(actualTarget) !== path.resolve(deps.inProjectConfigPath);
35
+ }
36
+ async function ensureProjectDir(filePath) {
37
+ const dir = path.dirname(filePath);
38
+ try {
39
+ await fs.mkdir(dir, { recursive: true });
40
+ } catch {
41
+ }
42
+ }
43
+ var PROJECT_SAFE_FIELDS = /* @__PURE__ */ new Set([
44
+ "version",
45
+ "model",
46
+ "cwd",
47
+ "fallbackModels",
48
+ "fallbackBridge",
49
+ // Kept in sync with core's IN_PROJECT_ALLOWED_KEYS (config-loader.ts): these
50
+ // model-routing prefs are explicitly safe to READ from a per-project config,
51
+ // so the writer must not strip them — otherwise a scope=project /fallback
52
+ // profile/fav write (or a TUI full-config write under project scope) would be
53
+ // silently dropped while the loader would happily accept them on next boot.
54
+ "fallbackProfiles",
55
+ "fallbackProfile",
56
+ "favoriteModels",
57
+ "disabledModels",
58
+ "favoriteModelsOnly",
59
+ "modelAvailabilitySchedule",
60
+ "fallbackAuto",
61
+ "fallbackStickiness",
62
+ "fallbackGateSeconds",
63
+ "models",
64
+ "modelMatrix",
65
+ "modelTiers",
66
+ "maxConcurrent",
67
+ "autonomy",
68
+ "hints",
69
+ "nextPrediction",
70
+ "debugStream",
71
+ "configScope",
72
+ "features",
73
+ "context",
74
+ "log",
75
+ "session",
76
+ "indexing",
77
+ "tools",
78
+ "launch",
79
+ "circuitBreaker",
80
+ "adaptiveConcurrency",
81
+ "modelRuntime",
82
+ "Sage",
83
+ "skills",
84
+ "chronicle",
85
+ "uiLocale",
86
+ "themePreset"
87
+ ]);
88
+ var PROJECT_DENIED_PATHS = [
89
+ "tools.exec.allow",
90
+ "tools.exec.danger",
91
+ "tools.council",
92
+ "skills.extraDirs",
93
+ "skills.registryUrl",
94
+ "skills.suggest",
95
+ "Sage.storage.directory",
96
+ "autonomy.yolo",
97
+ "autonomy.defaultMode",
98
+ "autonomy.yoloConfirm",
99
+ "launch.autonomy",
100
+ "features.allowOutsideProjectRoot",
101
+ "tools.restrictToProjectRoot",
102
+ "tools.loopDetection",
103
+ "tools.maxIterations",
104
+ "tools.autoExtendLimit",
105
+ "tools.maxAutoExtensions",
106
+ "tools.kanbanGovernance",
107
+ "tools.autoThin",
108
+ "tools.disabledToolMeta",
109
+ "tools.wrongProxy",
110
+ "features.mailboxBridge",
111
+ "features.pluginsTrust"
112
+ ];
113
+ function deleteProjectDeniedPath(target, dotted) {
114
+ const parts = dotted.split(".");
115
+ const leaf = parts.pop();
116
+ if (!leaf) return;
117
+ let cursor = target;
118
+ for (const part of parts) {
119
+ const next = cursor[part];
120
+ if (!next || typeof next !== "object" || Array.isArray(next)) return;
121
+ cursor = next;
122
+ }
123
+ delete cursor[leaf];
124
+ }
125
+ function filterSafeForProject(cfg) {
126
+ const out = {};
127
+ for (const [key, value] of Object.entries(cfg)) {
128
+ if (PROJECT_SAFE_FIELDS.has(key)) {
129
+ out[key] = structuredClone(value);
130
+ }
131
+ }
132
+ for (const denied of PROJECT_DENIED_PATHS) deleteProjectDeniedPath(out, denied);
133
+ return out;
134
+ }
135
+ async function readConfigForRewrite(filePath, vault) {
136
+ let raw;
137
+ let fileExists = true;
138
+ try {
139
+ raw = await fs.readFile(filePath, "utf8");
140
+ } catch (err) {
141
+ if (err.code !== "ENOENT") {
142
+ throw new FsError({
143
+ message: `Could not read ${filePath}`,
144
+ code: ERROR_CODES.FS_READ_FAILED,
145
+ path: filePath,
146
+ cause: err
147
+ });
148
+ }
149
+ fileExists = false;
150
+ raw = "{}";
151
+ }
152
+ try {
153
+ const parsed = JSON.parse(raw);
154
+ return decryptConfigSecretsForRewrite(parsed, vault);
155
+ } catch (err) {
156
+ if (fileExists) {
157
+ throw new ConfigError({
158
+ message: `Config at ${filePath} is not valid JSON`,
159
+ code: ERROR_CODES.CONFIG_PARSE_FAILED,
160
+ context: { path: filePath },
161
+ cause: err
162
+ });
163
+ }
164
+ return {};
165
+ }
166
+ }
167
+ async function writeConfigMutation(deps, mutator) {
168
+ const sourcePath = resolvePersistPath(deps);
169
+ await ensureProjectDir(sourcePath);
170
+ let mutated = {};
171
+ let sourceBefore = {};
172
+ let actualTarget = sourcePath;
173
+ let wroteSource = false;
174
+ await withFileLock(sourcePath, async () => {
175
+ mutated = await readConfigForRewrite(sourcePath, deps.vault);
176
+ sourceBefore = structuredClone(mutated);
177
+ mutator(mutated);
178
+ actualTarget = resolveActualTarget(deps, mutated, sourcePath);
179
+ if (actualTarget !== sourcePath) return;
180
+ const toWrite = isProfileOrGlobalTarget(actualTarget, deps) ? mutated : filterSafeForProject(mutated);
181
+ await backupCurrent(void 0, actualTarget);
182
+ await atomicWrite(
183
+ actualTarget,
184
+ JSON.stringify(encryptConfigSecrets(toWrite, deps.vault), null, 2),
185
+ { mode: 384 }
186
+ );
187
+ try {
188
+ await appendHistory(sourceBefore, toWrite, "Settings updated", void 0, actualTarget);
189
+ } catch {
190
+ }
191
+ wroteSource = true;
192
+ });
193
+ if (!wroteSource) {
194
+ await ensureProjectDir(actualTarget);
195
+ await withFileLock(actualTarget, async () => {
196
+ const destinationBefore = await readConfigForRewrite(actualTarget, deps.vault);
197
+ const effectiveConfig = deepMerge(destinationBefore, mutated);
198
+ const toWrite = isProfileOrGlobalTarget(actualTarget, deps) ? effectiveConfig : filterSafeForProject(effectiveConfig);
199
+ await backupCurrent(void 0, actualTarget);
200
+ await atomicWrite(
201
+ actualTarget,
202
+ JSON.stringify(encryptConfigSecrets(toWrite, deps.vault), null, 2),
203
+ { mode: 384 }
204
+ );
205
+ try {
206
+ await appendHistory(
207
+ destinationBefore,
208
+ toWrite,
209
+ "Settings scope changed and settings were updated",
210
+ void 0,
211
+ actualTarget
212
+ );
213
+ } catch {
214
+ }
215
+ });
216
+ }
217
+ return mutated;
218
+ }
219
+ async function persistAutonomySetting(deps, mutator) {
220
+ const decrypted = await writeConfigMutation(deps, (config) => {
221
+ const autonomy = config.autonomy ?? {};
222
+ mutator(
223
+ autonomy
224
+ );
225
+ config.autonomy = autonomy;
226
+ });
227
+ if (deps.updateStore !== false) {
228
+ deps.configStore.update({
229
+ autonomy: decrypted.autonomy
230
+ });
231
+ }
232
+ }
233
+ async function persistConfigSetting(deps, mutator) {
234
+ const decrypted = await writeConfigMutation(deps, mutator);
235
+ if (deps.updateStore !== false) {
236
+ deps.configStore.update(decrypted);
237
+ }
238
+ }
239
+ async function persistTelegramConfig(deps, mutator) {
240
+ const decrypted = await writeConfigMutation({ ...deps, forceGlobal: true }, (config) => {
241
+ const extensions = config.extensions ?? {};
242
+ const telegram = extensions.telegram ?? {};
243
+ mutator(telegram);
244
+ extensions.telegram = telegram;
245
+ config.extensions = extensions;
246
+ });
247
+ await deps.vault.flushHardening?.();
248
+ deps.configStore.update({
249
+ extensions: decrypted.extensions
250
+ });
251
+ }
252
+ function deriveFsAccessPair(input) {
253
+ if (input.allowOutsideProjectRoot !== void 0) {
254
+ const allow = input.allowOutsideProjectRoot;
255
+ return { allowOutsideProjectRoot: allow, restrictToProjectRoot: !allow };
256
+ }
257
+ if (input.restrictFsToRoot !== void 0) {
258
+ const restrict = input.restrictFsToRoot;
259
+ return { allowOutsideProjectRoot: !restrict, restrictToProjectRoot: restrict };
260
+ }
261
+ return void 0;
262
+ }
263
+
264
+ export {
265
+ filterSafeForProject,
266
+ persistAutonomySetting,
267
+ persistConfigSetting,
268
+ persistTelegramConfig,
269
+ deriveFsAccessPair
270
+ };
271
+ //# sourceMappingURL=chunk-SFBFYIOW.js.map
@@ -2,6 +2,115 @@ import {
2
2
  theme
3
3
  } from "./chunk-QD544J2B.js";
4
4
 
5
+ // src/boot/tool-restriction-flags.ts
6
+ function parseToolList(flag, value) {
7
+ if (value === void 0) return void 0;
8
+ if (value === true || typeof value === "string" && !value.trim()) {
9
+ throw new Error(`--${flag} needs a comma-separated list of tool names`);
10
+ }
11
+ const names = String(value).split(/[\s,]+/).map((name) => name.trim()).filter(Boolean);
12
+ const scoped = names.find((name) => name.includes("("));
13
+ if (scoped) {
14
+ throw new Error(
15
+ `--${flag}: scoped rules like "${scoped}" are not supported; list whole tool names (exec policy lives in config tools.exec)`
16
+ );
17
+ }
18
+ return names;
19
+ }
20
+ function resolveLaunchAllowedTools(flags) {
21
+ return parseToolList("allowed-tools", flags["allowed-tools"]);
22
+ }
23
+ function resolveToolRestriction(flags) {
24
+ const only = parseToolList("only-tools", flags["only-tools"]);
25
+ const deny = parseToolList("disallowed-tools", flags["disallowed-tools"]);
26
+ if (only === void 0 && deny === void 0) return void 0;
27
+ return { ...only ? { only } : {}, deny: deny ?? [] };
28
+ }
29
+ function unknownRestrictedToolNames(restriction, isRegistered) {
30
+ return [...restriction.only ?? [], ...restriction.deny].filter(
31
+ (name) => !name.endsWith("*") && !name.includes("__") && !isRegistered(name)
32
+ );
33
+ }
34
+
35
+ // src/boot/restricted-mode.ts
36
+ import { lockToProjectRoot, lockYoloOff, ToolCapabilities } from "@wrongstack/core/security";
37
+ var RESTRICTED_ENV = "WRONGSTACK_RESTRICTED";
38
+ var RESTRICTED_DENY_CAPABILITIES = [
39
+ ToolCapabilities.SHELL_ARBITRARY,
40
+ ToolCapabilities.SHELL_RESTRICTED,
41
+ ToolCapabilities.SHELL_EXEC,
42
+ ToolCapabilities.NET_OUTBOUND,
43
+ ToolCapabilities.PACKAGE_INSTALL,
44
+ ToolCapabilities.CONFIG_MUTATE,
45
+ ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
46
+ ToolCapabilities.TOOL_MUTATE_ANY,
47
+ ToolCapabilities.MCP_PROXY
48
+ ];
49
+ var CONFLICTING_FLAGS = [
50
+ "yolo",
51
+ "yolo-destructive",
52
+ "full-auto",
53
+ "mcp-config",
54
+ // Pre-approving a tool would let writes skip the prompt this mode promises.
55
+ "allowed-tools"
56
+ ];
57
+ function isRestrictedMode(flags) {
58
+ return flags["restricted"] === true || process.env[RESTRICTED_ENV] === "1";
59
+ }
60
+ function validateRestrictedMode(flags) {
61
+ if (!isRestrictedMode(flags)) return false;
62
+ const conflict = CONFLICTING_FLAGS.find(
63
+ (name) => flags[name] !== void 0 && flags[name] !== false
64
+ );
65
+ if (conflict) throw new Error(`--${conflict} cannot be combined with --restricted`);
66
+ return true;
67
+ }
68
+ function withRestrictedTools(restriction, restricted) {
69
+ if (!restricted) return restriction;
70
+ return {
71
+ ...restriction?.only ? { only: restriction.only } : {},
72
+ deny: [...restriction?.deny ?? [], "mcp__*"],
73
+ denyCapabilities: RESTRICTED_DENY_CAPABILITIES,
74
+ requireDeclaredCapabilities: true
75
+ };
76
+ }
77
+ var RESTRICTED_NOTICE = "Restricted mode: no shell, network, installs or MCP tools; files stay inside the project; YOLO is off.";
78
+ var announced = false;
79
+ function activateRestrictedMode(flags, write) {
80
+ if (!validateRestrictedMode(flags)) return false;
81
+ lockToProjectRoot();
82
+ lockYoloOff();
83
+ process.env[RESTRICTED_ENV] = "1";
84
+ if (!announced) {
85
+ announced = true;
86
+ write(`${RESTRICTED_NOTICE}
87
+ `);
88
+ }
89
+ return true;
90
+ }
91
+
92
+ // src/boot/safe-mode.ts
93
+ var SAFE_MODE_ENV = "WRONGSTACK_SAFE_MODE";
94
+ function isSafeMode(flags) {
95
+ return flags["safe-mode"] === true || process.env[SAFE_MODE_ENV] === "1";
96
+ }
97
+ function propagateSafeMode(flags) {
98
+ const on = isSafeMode(flags);
99
+ if (on) process.env[SAFE_MODE_ENV] = "1";
100
+ return on;
101
+ }
102
+ var SAFE_MODE_NOTICE = "Safe mode: third-party plugins, hooks, configured MCP servers, skills and instruction overrides are off.";
103
+ var announced2 = false;
104
+ function announceSafeMode(flags, write) {
105
+ const on = propagateSafeMode(flags);
106
+ if (on && !announced2) {
107
+ announced2 = true;
108
+ write(`${SAFE_MODE_NOTICE}
109
+ `);
110
+ }
111
+ return on;
112
+ }
113
+
5
114
  // src/diff-renderer.ts
6
115
  import { color, sanitizeTerminalText } from "@wrongstack/core/utils";
7
116
  function diffLineStyle(line) {
@@ -17,7 +126,6 @@ function renderDiff(diff) {
17
126
  }
18
127
 
19
128
  // src/permission-prompt.ts
20
- import { alwaysAllowUnavailableReason } from "@wrongstack/core/security";
21
129
  import {
22
130
  color as color2,
23
131
  sanitizeTerminalPreview,
@@ -26,6 +134,15 @@ import {
26
134
  unifiedDiff,
27
135
  writeOut
28
136
  } from "@wrongstack/core/utils";
137
+ function makeHeadlessPromptDelegate(write = (text) => process.stderr.write(text)) {
138
+ return async (tool) => {
139
+ write(
140
+ `${theme.warn("\u26A0")} ${tool.name} needs approval but no terminal is attached to ask \u2014 denied. Grant it up front with --allowed-tools ${tool.name} or --yolo.
141
+ `
142
+ );
143
+ return "no";
144
+ };
145
+ }
29
146
  function makePromptDelegate(reader) {
30
147
  return async (tool, input, suggestedPattern, signal) => {
31
148
  if (signal?.aborted) return "no";
@@ -41,18 +158,15 @@ ${theme.warn("\u26A0 APPROVAL REQUIRED")} ${theme.primary("\u2502")} ${theme.bol
41
158
  if (preview) writeOut(`${preview}
42
159
  `);
43
160
  writeOut(color2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
44
- const noAlwaysReason = alwaysAllowUnavailableReason(tool, input);
45
- if (noAlwaysReason) {
46
- writeOut(`${color2.dim(`(no "always" for this call \u2014 ${noAlwaysReason})`)}
47
- `);
48
- }
49
161
  const options = [
50
162
  { key: "y", label: "yes", value: "yes" },
51
163
  { key: "n", label: "no", value: "no" },
52
- ...noAlwaysReason ? [] : [{ key: "a", label: "always", value: "always" }],
164
+ { key: "a", label: "remember same input/args", value: "always-exact" },
165
+ ...tool.name === "exec" ? [{ key: "c", label: "remember executable with any args", value: "always-command" }] : [],
166
+ { key: "t", label: "remember tool with any input", value: "always-tool" },
53
167
  { key: "d", label: "deny", value: "deny" }
54
168
  ];
55
- const alwaysHint = noAlwaysReason ? "" : ` ${theme.bold("[a]")}lways allow (${sanitizeTerminalText2(suggestedPattern)})`;
169
+ const alwaysHint = ` ${theme.bold("[a]")} same input (${sanitizeTerminalText2(suggestedPattern)})${tool.name === "exec" ? " [c] command, any args" : ""} [t] tool, any input`;
56
170
  const answer = await reader.readKey(
57
171
  `${theme.bold("[y]")}es ${theme.bold("[n]")}o${alwaysHint} ${theme.bold("[d]")}eny: `,
58
172
  options,
@@ -61,8 +175,11 @@ ${theme.warn("\u26A0 APPROVAL REQUIRED")} ${theme.primary("\u2502")} ${theme.bol
61
175
  return answer;
62
176
  };
63
177
  }
178
+ function makeStdinPromptDelegate(reader, stdinIsTTY = process.stdin.isTTY === true) {
179
+ return stdinIsTTY ? makePromptDelegate(reader) : makeHeadlessPromptDelegate();
180
+ }
64
181
  function makeConfirmAwaiter(reader) {
65
- const delegate = makePromptDelegate(reader);
182
+ const delegate = makeStdinPromptDelegate(reader);
66
183
  return async (tool, input, _toolUseId, suggestedPattern) => {
67
184
  const result = await delegate(tool, input, suggestedPattern);
68
185
  return result;
@@ -474,12 +591,20 @@ on disconnect with exponential backoff, up to 3 attempts"._
474
591
 
475
592
  export {
476
593
  renderDiff,
477
- makePromptDelegate,
594
+ makeStdinPromptDelegate,
478
595
  makeConfirmAwaiter,
596
+ resolveLaunchAllowedTools,
597
+ resolveToolRestriction,
598
+ unknownRestrictedToolNames,
599
+ isRestrictedMode,
600
+ withRestrictedTools,
601
+ activateRestrictedMode,
602
+ isSafeMode,
603
+ announceSafeMode,
479
604
  isSimpleUiFullAuto,
480
605
  applySimpleUiFullAutoProfile,
481
606
  configureSimpleUiRuntimeContext,
482
607
  detectProjectFacts,
483
608
  renderAgentsTemplate
484
609
  };
485
- //# sourceMappingURL=chunk-RMQNCHGO.js.map
610
+ //# sourceMappingURL=chunk-TI7KLHYX.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  helpCmd,
3
3
  versionCmd
4
- } from "./chunk-6WRKAACA.js";
4
+ } from "./chunk-5JYDKG5B.js";
5
5
  import {
6
6
  parseArgs
7
- } from "./chunk-C3Z4N6A6.js";
7
+ } from "./chunk-Q4INH2DG.js";
8
8
 
9
9
  // src/boot/short-circuit-flags.ts
10
10
  async function handleHelpVersionShortCircuit(argv) {
@@ -22,11 +22,11 @@ async function handleHelpVersionShortCircuit(argv) {
22
22
  }
23
23
  if (earlyFlags["help"] === true) {
24
24
  if (positional.length > 0) {
25
- const { subcommandNames } = await import("./subcommands-XSLXMIDP.js");
25
+ const { subcommandNames } = await import("./subcommands-Q6ORSB2E.js");
26
26
  if (subcommandNames.includes(positional[0])) {
27
27
  const first = positional[0];
28
28
  const deep = positional[1];
29
- const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-DHTYU524.js");
29
+ const { renderDeepHelp, renderFocusedHelp } = await import("./per-subcommand-help-QDAQASPZ.js");
30
30
  if (deep && renderDeepHelp(`${first}:${deep}`, stubRenderer)) {
31
31
  return 0;
32
32
  }
@@ -43,4 +43,4 @@ async function handleHelpVersionShortCircuit(argv) {
43
43
  export {
44
44
  handleHelpVersionShortCircuit
45
45
  };
46
- //# sourceMappingURL=chunk-2ENZU66X.js.map
46
+ //# sourceMappingURL=chunk-WQV2NP57.js.map
@@ -6,7 +6,13 @@ import os from "node:os";
6
6
  import * as path from "node:path";
7
7
  import { isSecretField } from "@wrongstack/core/security";
8
8
  import { ERROR_CODES, FsError } from "@wrongstack/core/types";
9
- import { atomicWrite, resolveWstackPaths, toErrorMessage, writeErr } from "@wrongstack/core/utils";
9
+ import {
10
+ atomicWrite,
11
+ resolveWstackPaths,
12
+ toErrorMessage,
13
+ withFileLock,
14
+ writeErr
15
+ } from "@wrongstack/core/utils";
10
16
  var defaultUidFn = () => os.userInfo().uid;
11
17
  async function getFileUid(filePath) {
12
18
  try {
@@ -89,7 +95,11 @@ function maskConfigSecrets(cfg) {
89
95
  if (typeof cfg !== "object" || cfg === null) return {};
90
96
  const out = {};
91
97
  for (const [k, v] of Object.entries(cfg)) {
92
- if (isSecretField(k)) {
98
+ if (k.toLowerCase() === "apikeys" && Array.isArray(v) && v.every(
99
+ (entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) && ("label" in entry || "id" in entry) && "apiKey" in entry
100
+ )) {
101
+ out[k] = maskValue(v);
102
+ } else if (isSecretField(k)) {
93
103
  out[k] = REDACTED;
94
104
  } else {
95
105
  out[k] = maskValue(v);
@@ -101,7 +111,14 @@ function reviveSecrets(masked, current) {
101
111
  if (masked === REDACTED) return current;
102
112
  if (Array.isArray(masked)) {
103
113
  const cur = Array.isArray(current) ? current : [];
104
- return masked.map((item, i) => reviveSecrets(item, cur[i]));
114
+ return masked.map((item, i) => {
115
+ const record = item && typeof item === "object" && !Array.isArray(item) ? item : void 0;
116
+ const identityKey = typeof record?.["label"] === "string" ? "label" : typeof record?.["id"] === "string" ? "id" : void 0;
117
+ const matched = identityKey ? cur.find(
118
+ (candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate[identityKey] === record?.[identityKey]
119
+ ) : cur[i];
120
+ return reviveSecrets(item, matched);
121
+ });
105
122
  }
106
123
  if (typeof masked !== "object" || masked === null) return masked;
107
124
  const curObj = typeof current === "object" && current !== null && !Array.isArray(current) ? current : {};
@@ -271,10 +288,12 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
271
288
  cause: err
272
289
  });
273
290
  }
274
- const idx = await readIndex(homeFn, targetConfigPath);
275
- idx.entries.unshift({ id, timestamp, description });
276
- await pruneHistoryEntries(idx, homeFn, targetConfigPath);
277
- await writeIndex(idx, homeFn, targetConfigPath);
291
+ await withFileLock(historyIndexPath(homeFn, targetConfigPath), async () => {
292
+ const idx = await readIndex(homeFn, targetConfigPath);
293
+ idx.entries.unshift({ id, timestamp, description });
294
+ await pruneHistoryEntries(idx, homeFn, targetConfigPath);
295
+ await writeIndex(idx, homeFn, targetConfigPath);
296
+ });
278
297
  return id;
279
298
  }
280
299
  async function listHistory(homeFn = defaultHomeDir, targetConfigPath) {
@@ -302,39 +321,34 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir, targetConfigPath)
302
321
  error: "Operation denied: config file is not owned by current user"
303
322
  };
304
323
  }
305
- await backupCurrent(homeFn, targetConfigPath);
306
- let oldCfg = {};
307
- try {
308
- const raw = await fs.readFile(configPath(homeFn, targetConfigPath), "utf8");
309
- oldCfg = JSON.parse(raw);
310
- } catch {
311
- }
312
- const restored = reviveSecrets(entry.snapshotMasked, oldCfg);
313
- try {
314
- await atomicWrite(configPath(homeFn, targetConfigPath), JSON.stringify(restored, null, 2), {
315
- mode: 384
316
- });
317
- } catch (err) {
318
- return { ok: false, backupId: null, error: String(err) };
319
- }
320
- const backupId = await appendHistory(
321
- oldCfg,
322
- restored,
323
- `Restored from history ${id}`,
324
- homeFn,
325
- targetConfigPath
326
- );
327
- return { ok: true, backupId };
324
+ const cfg = configPath(homeFn, targetConfigPath);
325
+ return withFileLock(cfg, async () => {
326
+ await backupCurrent(homeFn, targetConfigPath);
327
+ let oldCfg = {};
328
+ try {
329
+ const raw = await fs.readFile(cfg, "utf8");
330
+ oldCfg = JSON.parse(raw);
331
+ } catch {
332
+ }
333
+ const restored = reviveSecrets(entry.snapshotMasked, oldCfg);
334
+ try {
335
+ await atomicWrite(cfg, JSON.stringify(restored, null, 2), { mode: 384 });
336
+ } catch (err) {
337
+ return { ok: false, backupId: null, error: String(err) };
338
+ }
339
+ const backupId = await appendHistory(
340
+ oldCfg,
341
+ restored,
342
+ `Restored from history ${id}`,
343
+ homeFn,
344
+ targetConfigPath
345
+ );
346
+ return { ok: true, backupId };
347
+ });
328
348
  }
329
349
  async function restoreLast(homeFn = defaultHomeDir, targetConfigPath) {
330
350
  const last = backupLastPath(homeFn, targetConfigPath);
331
351
  const cfg = configPath(homeFn, targetConfigPath);
332
- let oldCfg = {};
333
- try {
334
- const raw = await fs.readFile(cfg, "utf8");
335
- oldCfg = JSON.parse(raw);
336
- } catch {
337
- }
338
352
  let lastCfg = {};
339
353
  try {
340
354
  const raw = await fs.readFile(last, "utf8");
@@ -345,14 +359,28 @@ async function restoreLast(homeFn = defaultHomeDir, targetConfigPath) {
345
359
  if (!await checkConfigOwnership(homeFn, defaultUidFn, targetConfigPath)) {
346
360
  return { ok: false, error: "Operation denied: config file is not owned by current user" };
347
361
  }
348
- await backupCurrent(homeFn, targetConfigPath);
349
- try {
350
- await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2));
351
- } catch (err) {
352
- return { ok: false, error: String(err) };
353
- }
354
- await appendHistory(oldCfg, lastCfg, "Restored from config.json.last", homeFn, targetConfigPath);
355
- return { ok: true };
362
+ return withFileLock(cfg, async () => {
363
+ let oldCfg = {};
364
+ try {
365
+ const raw = await fs.readFile(cfg, "utf8");
366
+ oldCfg = JSON.parse(raw);
367
+ } catch {
368
+ }
369
+ await backupCurrent(homeFn, targetConfigPath);
370
+ try {
371
+ await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2), { mode: 384 });
372
+ } catch (err) {
373
+ return { ok: false, error: String(err) };
374
+ }
375
+ await appendHistory(
376
+ oldCfg,
377
+ lastCfg,
378
+ "Restored from config.json.last",
379
+ homeFn,
380
+ targetConfigPath
381
+ );
382
+ return { ok: true };
383
+ });
356
384
  }
357
385
 
358
386
  export {
@@ -363,4 +391,4 @@ export {
363
391
  restoreFromHistory,
364
392
  restoreLast
365
393
  };
366
- //# sourceMappingURL=chunk-B4JLSXZB.js.map
394
+ //# sourceMappingURL=chunk-WSTVGC2C.js.map