@clipboard-health/groundcrew 4.7.2 → 4.8.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.
@@ -1,7 +1,7 @@
1
1
  import { __rewriteRelativeImportExtension } from "tslib";
2
2
  import { existsSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { resolve } from "node:path";
4
+ import path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { cosmiconfig } from "cosmiconfig";
7
7
  import { debug, log, readEnvironmentVariable, setLogFile } from "./util.js";
@@ -35,7 +35,7 @@ const DEFAULT_ORCHESTRATOR = {
35
35
  pollIntervalMilliseconds: 120_000,
36
36
  sessionLimitPercentage: 85,
37
37
  };
38
- const DEFAULT_MODEL_DEFINITIONS = {
38
+ const BUILT_IN_MODEL_DEFINITIONS = {
39
39
  claude: {
40
40
  cmd: "claude --permission-mode auto",
41
41
  color: "#C15F3C",
@@ -47,6 +47,30 @@ const DEFAULT_MODEL_DEFINITIONS = {
47
47
  usage: { codexbar: { provider: "codex" } },
48
48
  },
49
49
  };
50
+ const MODEL_DEFINITIONS_MIGRATION_MESSAGE = [
51
+ "configuration migration required: models are no longer enabled by default.",
52
+ "",
53
+ "Add the models you want to use:",
54
+ "",
55
+ "models: {",
56
+ ' default: "claude",',
57
+ " definitions: {",
58
+ " claude: {},",
59
+ " },",
60
+ "},",
61
+ "",
62
+ "To keep the previous claude+codex behavior:",
63
+ "",
64
+ "models: {",
65
+ ' default: "claude",',
66
+ " definitions: {",
67
+ " claude: {},",
68
+ " codex: {},",
69
+ " },",
70
+ "},",
71
+ "",
72
+ "`disabled: true` is no longer supported; remove disabled model entries instead.",
73
+ ].join("\n");
50
74
  const DEFAULT_PROMPT_INITIAL = [
51
75
  "You are working on Linear ticket {{ticket}} ({{title}}) in the {{worktree}} worktree subdirectory.",
52
76
  "",
@@ -88,7 +112,7 @@ function expandHome(p) {
88
112
  return homedir();
89
113
  }
90
114
  if (p.startsWith("~/")) {
91
- return resolve(homedir(), p.slice(2));
115
+ return path.resolve(homedir(), p.slice(2));
92
116
  }
93
117
  return p;
94
118
  }
@@ -98,45 +122,45 @@ function fail(message) {
98
122
  function isNonEmptyString(value) {
99
123
  return typeof value === "string" && value.length > 0;
100
124
  }
101
- function requireString(value, path) {
125
+ function requireString(value, configKey) {
102
126
  if (!isNonEmptyString(value)) {
103
- fail(`${path} must be a non-empty string (got ${JSON.stringify(value)})`);
127
+ fail(`${configKey} must be a non-empty string (got ${JSON.stringify(value)})`);
104
128
  }
105
129
  }
106
- function requirePositiveInt(value, path, min = 1) {
130
+ function requirePositiveInt(value, configKey, min = 1) {
107
131
  if (typeof value !== "number" || !Number.isInteger(value) || value < min) {
108
- fail(`${path} must be an integer ≥ ${min} (got ${JSON.stringify(value)})`);
132
+ fail(`${configKey} must be an integer ≥ ${min} (got ${JSON.stringify(value)})`);
109
133
  }
110
134
  }
111
- function requirePercent(value, path) {
135
+ function requirePercent(value, configKey) {
112
136
  if (typeof value !== "number" ||
113
137
  !Number.isFinite(value) ||
114
138
  value <= PERCENT_MIN_EXCLUSIVE ||
115
139
  value > PERCENT_MAX) {
116
- fail(`${path} must be a finite number in (0, 100] (got ${JSON.stringify(value)})`);
140
+ fail(`${configKey} must be a finite number in (0, 100] (got ${JSON.stringify(value)})`);
117
141
  }
118
142
  }
119
143
  function cloneModelDefinition(definition) {
120
144
  return structuredClone(definition);
121
145
  }
122
- function normalizeOptionalString(value, path) {
146
+ function normalizeOptionalString(value, configKey) {
123
147
  if (value === undefined) {
124
148
  return undefined;
125
149
  }
126
150
  if (typeof value !== "string" || value.trim().length === 0) {
127
- fail(`${path} must be a non-empty string`);
151
+ fail(`${configKey} must be a non-empty string`);
128
152
  }
129
153
  return value.trim();
130
154
  }
131
155
  const ENV_VAR_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
132
156
  function validatePreLaunchEnv(modelName, value) {
133
- const path = `models.definitions.${modelName}.preLaunchEnv`;
157
+ const configPath = `models.definitions.${modelName}.preLaunchEnv`;
134
158
  if (!Array.isArray(value)) {
135
- fail(`${path} must be an array of env var names (got ${JSON.stringify(value)})`);
159
+ fail(`${configPath} must be an array of env var names (got ${JSON.stringify(value)})`);
136
160
  }
137
161
  for (const [index, entry] of value.entries()) {
138
162
  if (typeof entry !== "string" || !ENV_VAR_NAME_PATTERN.test(entry)) {
139
- fail(`${path}[${index}] must be a POSIX env var name matching ${ENV_VAR_NAME_PATTERN.source} (got ${JSON.stringify(entry)})`);
163
+ fail(`${configPath}[${index}] must be a POSIX env var name matching ${ENV_VAR_NAME_PATTERN.source} (got ${JSON.stringify(entry)})`);
140
164
  }
141
165
  // Build secrets are sourced into the host launch shell, forwarded only to
142
166
  // the Safehouse *setup* wrap, and `unset` on the host before the agent
@@ -144,7 +168,7 @@ function validatePreLaunchEnv(modelName, value) {
144
168
  // fail loudly so the operator picks a different name (or removes the
145
169
  // entry) instead of debugging a missing env var at runtime.
146
170
  if (BUILD_SECRET_NAMES.includes(entry)) {
147
- fail(`${path}[${index}] cannot be a BUILD_SECRET_NAMES entry (${BUILD_SECRET_NAMES.join(", ")}); ` +
171
+ fail(`${configPath}[${index}] cannot be a BUILD_SECRET_NAMES entry (${BUILD_SECRET_NAMES.join(", ")}); ` +
148
172
  "those are unset on the host before the agent wrap is exec'd, so forwarding them via --env-pass would be a no-op.");
149
173
  }
150
174
  }
@@ -165,52 +189,52 @@ export function hasPreLaunchEnv(definition) {
165
189
  function isWorkspaceKindSetting(value) {
166
190
  return (typeof value === "string" && WORKSPACE_KIND_SETTINGS.includes(value));
167
191
  }
168
- function normalizeWorkspaceKind(value, path) {
192
+ function normalizeWorkspaceKind(value, configKey) {
169
193
  if (value === undefined) {
170
194
  return undefined;
171
195
  }
172
196
  if (!isWorkspaceKindSetting(value)) {
173
- fail(`${path} must be one of ${WORKSPACE_KIND_SETTINGS.join(", ")} (got ${JSON.stringify(value)})`);
197
+ fail(`${configKey} must be one of ${WORKSPACE_KIND_SETTINGS.join(", ")} (got ${JSON.stringify(value)})`);
174
198
  }
175
199
  return value;
176
200
  }
177
201
  function isLocalRunnerSetting(value) {
178
202
  return typeof value === "string" && LOCAL_RUNNER_SETTINGS.includes(value);
179
203
  }
180
- function normalizeLocalRunner(value, path) {
204
+ function normalizeLocalRunner(value, configKey) {
181
205
  if (value === undefined) {
182
206
  return undefined;
183
207
  }
184
208
  if (!isLocalRunnerSetting(value)) {
185
- fail(`${path} must be one of ${LOCAL_RUNNER_SETTINGS.join(", ")} (got ${JSON.stringify(value)})`);
209
+ fail(`${configKey} must be one of ${LOCAL_RUNNER_SETTINGS.join(", ")} (got ${JSON.stringify(value)})`);
186
210
  }
187
211
  return value;
188
212
  }
189
- function normalizeSandbox(value, path) {
213
+ function normalizeSandbox(value, configKey) {
190
214
  if (!isPlainObject(value)) {
191
- fail(`${path} must be an object`);
215
+ fail(`${configKey} must be an object`);
192
216
  }
193
217
  if (Object.hasOwn(value, "template")) {
194
- failRemovedConfigKey(`${path}.template`, "Groundcrew no longer creates or re-templates sdx sandboxes.");
218
+ failRemovedConfigKey(`${configKey}.template`, "Groundcrew no longer creates or re-templates sdx sandboxes.");
195
219
  }
196
220
  if (Object.hasOwn(value, "kits")) {
197
- failRemovedConfigKey(`${path}.kits`, "Groundcrew no longer creates sdx sandboxes or applies sandbox kits.");
221
+ failRemovedConfigKey(`${configKey}.kits`, "Groundcrew no longer creates sdx sandboxes or applies sandbox kits.");
198
222
  }
199
223
  const { agent, setupCommand } = value;
200
- requireString(agent, `${path}.agent`);
224
+ requireString(agent, `${configKey}.agent`);
201
225
  const trimmedAgent = agent.trim();
202
226
  if (trimmedAgent.length === 0) {
203
- fail(`${path}.agent must be a non-empty string (got ${JSON.stringify(agent)})`);
227
+ fail(`${configKey}.agent must be a non-empty string (got ${JSON.stringify(agent)})`);
204
228
  }
205
229
  const sandbox = { agent: trimmedAgent };
206
- const normalizedSetup = normalizeOptionalString(setupCommand, `${path}.setupCommand`);
230
+ const normalizedSetup = normalizeOptionalString(setupCommand, `${configKey}.setupCommand`);
207
231
  if (normalizedSetup !== undefined) {
208
232
  sandbox.setupCommand = normalizedSetup;
209
233
  }
210
234
  return sandbox;
211
235
  }
212
- function failRemovedConfigKey(path, reason) {
213
- fail(`${path} is no longer supported: ${reason} ` +
236
+ function failRemovedConfigKey(configKey, reason) {
237
+ fail(`${configKey} is no longer supported: ${reason} ` +
214
238
  "Provision and manage the sandbox yourself with `sbx` (for example `sbx create --name groundcrew-<agent> <agent> <projectDir>`), then keep only `models.definitions.<model>.sandbox.agent` plus optional `setupCommand` in crew.config.ts.");
215
239
  }
216
240
  function failIfLegacyModelKeys(name, override) {
@@ -221,23 +245,16 @@ function failIfLegacyModelKeys(name, override) {
221
245
  fail(`models.definitions.${name}.isolation is no longer supported: per-model isolation is no longer supported`);
222
246
  }
223
247
  if (Object.hasOwn(override, "disabled")) {
224
- if (override["disabled"] !== true) {
225
- fail(`models.definitions.${name}.disabled must be exactly \`true\` when set (got ${JSON.stringify(override["disabled"])})`);
226
- }
227
- const conflicting = ["cmd", "color", "usage", "sandbox", "preLaunch", "preLaunchEnv"].filter((key) => Object.hasOwn(override, key));
228
- if (conflicting.length > 0) {
229
- fail(`models.definitions.${name}: cannot combine \`disabled: true\` with other fields (${conflicting.join(", ")}). Either disable the model or override its fields, not both.`);
230
- }
248
+ fail(MODEL_DEFINITIONS_MIGRATION_MESSAGE);
231
249
  }
232
250
  }
233
251
  /**
234
- * True when `name` is a shipped default the user removed via `disabled: true`.
235
- * Derived from absence in `definitions` that's the only path that removes a
236
- * shipped default, codified in `failIfLegacyModelKeys` + `mergeDefinitions`.
237
- * Consumers needing to distinguish disabled-by-user from unknown-label use this.
252
+ * True when `name` is a built-in preset but not present in the enabled
253
+ * definitions. Consumers use this to distinguish `agent-codex` when codex is
254
+ * not enabled from an arbitrary unknown label like `agent-typo`.
238
255
  */
239
- export function isShippedDefaultDisabled(config, name) {
240
- return (Object.hasOwn(DEFAULT_MODEL_DEFINITIONS, name) &&
256
+ export function isBuiltInModelNotEnabled(config, name) {
257
+ return (Object.hasOwn(BUILT_IN_MODEL_DEFINITIONS, name) &&
241
258
  !Object.hasOwn(config.models.definitions, name));
242
259
  }
243
260
  function isUsageDisableSentinel(usage) {
@@ -274,27 +291,17 @@ function buildOverrideCandidate(name, override, existing) {
274
291
  return candidate;
275
292
  }
276
293
  function mergeDefinitions(user) {
277
- if (user !== undefined && !isPlainObject(user)) {
294
+ if (user === undefined) {
295
+ fail(MODEL_DEFINITIONS_MIGRATION_MESSAGE);
296
+ }
297
+ if (!isPlainObject(user)) {
278
298
  fail("models.definitions must be an object");
279
299
  }
280
- const merged = Object.fromEntries(Object.entries(DEFAULT_MODEL_DEFINITIONS).map(([name, definition]) => [
281
- name,
282
- cloneModelDefinition(definition),
283
- ]));
284
- for (const [name, override] of Object.entries(user ?? {})) {
300
+ const merged = {};
301
+ for (const [name, override] of Object.entries(user)) {
285
302
  failIfLegacyModelKeys(name, override);
286
- if (override.disabled === true) {
287
- if (!Object.hasOwn(DEFAULT_MODEL_DEFINITIONS, name)) {
288
- fail(`models.definitions.${name}: \`disabled: true\` is only valid for shipped defaults (${Object.keys(DEFAULT_MODEL_DEFINITIONS).join(", ")}). Remove the entry instead.`);
289
- }
290
- // Drop the key so downstream iterators (doctor, eligibility, usage) ignore
291
- // the model automatically; `isShippedDefaultDisabled` lets the few consumers
292
- // that need to distinguish disabled from unknown re-derive the set.
293
- // oxlint-disable-next-line typescript/no-dynamic-delete -- `merged` is a fresh function-local clone of DEFAULT_MODEL_DEFINITIONS; no V8 dictionary-mode/pollution concerns
294
- delete merged[name];
295
- continue;
296
- }
297
- const candidate = buildOverrideCandidate(name, override, merged[name]);
303
+ const builtIn = BUILT_IN_MODEL_DEFINITIONS[name];
304
+ const candidate = buildOverrideCandidate(name, override, builtIn);
298
305
  const { cmd, color, usage, sandbox, preLaunch, preLaunchEnv } = candidate;
299
306
  if (typeof cmd !== "string" || cmd.length === 0) {
300
307
  fail(`models.definitions.${name}.cmd must be a non-empty string`);
@@ -322,9 +329,9 @@ function mergeDefinitions(user) {
322
329
  function isPlainObject(value) {
323
330
  return typeof value === "object" && value !== null && !Array.isArray(value);
324
331
  }
325
- function requireObject(value, path) {
332
+ function requireObject(value, configKey) {
326
333
  if (!isPlainObject(value)) {
327
- fail(`${path} must be an object (got ${JSON.stringify(value)})`);
334
+ fail(`${configKey} must be an object (got ${JSON.stringify(value)})`);
328
335
  }
329
336
  }
330
337
  function failOnLegacyLinearShape(user) {
@@ -362,12 +369,12 @@ function normalizeSources(raw) {
362
369
  }
363
370
  const names = new Map();
364
371
  for (const [index, entry] of raw.entries()) {
365
- const path = `sources[${index}]`;
372
+ const configPath = `sources[${index}]`;
366
373
  if (!isPlainObject(entry)) {
367
- fail(`${path} must be an object`);
374
+ fail(`${configPath} must be an object`);
368
375
  }
369
376
  const { kind, name } = entry;
370
- requireString(kind, `${path}.kind`);
377
+ requireString(kind, `${configPath}.kind`);
371
378
  // Per-adapter Zod validation runs in `buildSources`. Here we check name
372
379
  // uniqueness — the Board composer relies on it for writeback routing.
373
380
  // When `name` is omitted, the adapter's runtime default is `kind` (the
@@ -375,14 +382,14 @@ function normalizeSources(raw) {
375
382
  // dedup on the effective runtime name to catch e.g. two `{kind: "linear"}`
376
383
  // entries that would both produce a source named `"linear"`.
377
384
  if (name !== undefined) {
378
- requireString(name, `${path}.name`);
385
+ requireString(name, `${configPath}.name`);
379
386
  }
380
387
  /* v8 ignore next @preserve -- both `name`-set and `name`-unset paths are covered by separate dedup tests; coverage for the fallback's `kind` arm only fires when both entries in the dedup set come from `name`, which the second test already covers */
381
388
  const effectiveName = name ?? kind;
382
389
  const previous = names.get(effectiveName);
383
390
  if (previous !== undefined) {
384
391
  /* v8 ignore next 3 @preserve -- the `name === undefined` ternary arm requires two unnamed entries colliding; we keep the conditional for the better error message but only one path is exercised in tests */
385
- fail(`${path} would produce a source named "${effectiveName}" (from ${name === undefined ? "default `kind` since `name` is omitted" : "`name`"}), duplicating sources[${previous}]. Configure distinct \`name\` fields.`);
392
+ fail(`${configPath} would produce a source named "${effectiveName}" (from ${name === undefined ? "default `kind` since `name` is omitted" : "`name`"}), duplicating sources[${previous}]. Configure distinct \`name\` fields.`);
386
393
  }
387
394
  names.set(effectiveName, index);
388
395
  }
@@ -456,7 +463,6 @@ function validate(config) {
456
463
  requirePositiveInt(config.orchestrator.pollIntervalMilliseconds, "orchestrator.pollIntervalMilliseconds");
457
464
  requirePercent(config.orchestrator.sessionLimitPercentage, "orchestrator.sessionLimitPercentage");
458
465
  const { definitions } = config.models;
459
- /* v8 ignore next 3 @preserve -- mergeDefinitions seeds claude+codex defaults, so an empty map is unreachable */
460
466
  if (Object.keys(definitions).length === 0) {
461
467
  fail("models.definitions must contain at least one model");
462
468
  }
@@ -468,12 +474,12 @@ function validate(config) {
468
474
  requireString(definition.color, `models.definitions.${name}.color`);
469
475
  if (definition.usage !== undefined) {
470
476
  const usagePath = `models.definitions.${name}.usage`;
471
- /* v8 ignore next 3 @preserve -- mergeDefinitions only assigns usage from validated overrides or shipped defaults; reaching this guard requires hand-mutating the resolved config */
477
+ /* v8 ignore next 3 @preserve -- mergeDefinitions only assigns usage from validated overrides or built-in presets; reaching this guard requires hand-mutating the resolved config */
472
478
  if (typeof definition.usage !== "object" || definition.usage === null) {
473
479
  fail(`${usagePath} must be an object`);
474
480
  }
475
481
  const { codexbar } = definition.usage;
476
- /* v8 ignore next 3 @preserve -- mergeDefinitions only assigns usage from validated overrides or shipped defaults; reaching this guard requires hand-mutating the resolved config */
482
+ /* v8 ignore next 3 @preserve -- mergeDefinitions only assigns usage from validated overrides or built-in presets; reaching this guard requires hand-mutating the resolved config */
477
483
  if (typeof codexbar !== "object" || codexbar === null) {
478
484
  fail(`${usagePath}.codexbar must be an object`);
479
485
  }
@@ -496,11 +502,11 @@ function validate(config) {
496
502
  if (!LOCAL_RUNNER_SETTINGS.includes(config.local.runner)) {
497
503
  fail(`local.runner must be one of ${LOCAL_RUNNER_SETTINGS.join(", ")} (got ${JSON.stringify(config.local.runner)})`);
498
504
  }
499
- // Disabled-default check must run before the generic "not a key" check so
500
- // the user gets the specific "is disabled" message instead of a stale-list
501
- // message they can't act on without realizing they need to re-enable.
502
- if (isShippedDefaultDisabled(config, config.models.default)) {
503
- fail(`models.default ("${config.models.default}") is disabled. Either re-enable it or set models.default to an enabled model.`);
505
+ // Built-in-not-enabled check must run before the generic "not a key" check
506
+ // so the user gets the specific migration-oriented message for `codex`
507
+ // instead of a stale-list message.
508
+ if (isBuiltInModelNotEnabled(config, config.models.default)) {
509
+ fail(`models.default ("${config.models.default}") is not enabled. Add \`models.definitions.${config.models.default}: {}\` or set models.default to an enabled model.`);
504
510
  }
505
511
  if (!(config.models.default in definitions)) {
506
512
  fail(`models.default ("${config.models.default}") is not a key in models.definitions (have: ${Object.keys(definitions).join(", ")})`);
@@ -567,12 +573,12 @@ async function loadAt(filepath) {
567
573
  return result;
568
574
  }
569
575
  function findXdgConfigFile() {
570
- return XDG_FALLBACK_NAMES.map((name) => xdgConfigPath("groundcrew", name)).find((path) => existsSync(path));
576
+ return XDG_FALLBACK_NAMES.map((name) => xdgConfigPath("groundcrew", name)).find((p) => existsSync(p));
571
577
  }
572
578
  async function discoverUserConfig() {
573
579
  const override = readEnvironmentVariable("GROUNDCREW_CONFIG");
574
580
  if (override !== undefined && override.length > 0) {
575
- const overridePath = resolve(override);
581
+ const overridePath = path.resolve(override);
576
582
  if (!existsSync(overridePath)) {
577
583
  fail(`GROUNDCREW_CONFIG=${overridePath} not found`);
578
584
  }
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { basename, dirname, resolve } from "node:path";
2
+ import path from "node:path";
3
3
  import { BUILD_SECRET_NAMES, hasPreLaunchEnv, } from "./config.js";
4
4
  import { shellSingleQuote } from "./shell.js";
5
5
  export { shellSingleQuote } from "./shell.js";
@@ -22,7 +22,7 @@ export function resolveSafehouseClearancePath(baseUrl = import.meta.url) {
22
22
  throw new Error("@clipboard-health/clearance is required by @clipboard-health/groundcrew but could not be resolved. " +
23
23
  "Install it alongside groundcrew (for example: `npm install -g @clipboard-health/clearance`).", { cause: error });
24
24
  }
25
- return resolve(dirname(clearancePackageJson), "safehouse", "safehouse-clearance");
25
+ return path.resolve(path.dirname(clearancePackageJson), "safehouse", "safehouse-clearance");
26
26
  }
27
27
  const SAFEHOUSE_CLEARANCE_WRAPPER_PATH = resolveSafehouseClearancePath();
28
28
  /**
@@ -155,7 +155,7 @@ function safehouseProfileCommandName(agentCmd) {
155
155
  if (commandToken === undefined) {
156
156
  throw new Error(`Cannot infer Safehouse agent profile command from model cmd ${JSON.stringify(agentCmd)}.`);
157
157
  }
158
- const commandName = basename(commandToken);
158
+ const commandName = path.basename(commandToken);
159
159
  if (commandName === "." ||
160
160
  commandName === ".." ||
161
161
  commandName.startsWith("-") ||
@@ -211,7 +211,7 @@ function shouldWrapWithSafehouse(arguments_) {
211
211
  * host shell because there is no groundcrew-managed sandbox to run them inside.
212
212
  */
213
213
  function buildUnwrappedHostLaunchCommand(arguments_) {
214
- const promptDir = dirname(arguments_.promptFile);
214
+ const promptDir = path.dirname(arguments_.promptFile);
215
215
  const agentCmd = renderAgentCommand({
216
216
  agentCmd: arguments_.definition.cmd,
217
217
  worktreeDir: arguments_.worktreeDir,
@@ -243,7 +243,7 @@ function buildUnwrappedHostLaunchCommand(arguments_) {
243
243
  * 2. **Agent wrap**: `safehouse-clearance "$shim" -c '<exec agent>' sh "$_p"`
244
244
  * where `$shim` is a `mktemp`-d symlink to `/bin/sh` named after the
245
245
  * agent (e.g. `claude`). Safehouse selects the matching agent profile
246
- * from the wrapped command's basename (`claude-code.sb` etc.) without
246
+ * from the wrapped command's path.basename (`claude-code.sb` etc.) without
247
247
  * needing every agent profile enabled globally.
248
248
  *
249
249
  * Host ordering matters: when a `preLaunch` hook is present, inherited
@@ -263,7 +263,7 @@ function buildUnwrappedHostLaunchCommand(arguments_) {
263
263
  * reach the profile-neutral setup phase.
264
264
  */
265
265
  function buildSafehouseLaunchCommand(arguments_) {
266
- const promptDir = dirname(arguments_.promptFile);
266
+ const promptDir = path.dirname(arguments_.promptFile);
267
267
  const safehouseCommandName = safehouseProfileCommandName(arguments_.definition.cmd);
268
268
  const agentCmd = renderAgentCommand({
269
269
  agentCmd: arguments_.definition.cmd,
@@ -315,7 +315,7 @@ function buildSdxLaunchCommand(arguments_) {
315
315
  if (arguments_.sandboxName === undefined || arguments_.definition.sandbox === undefined) {
316
316
  throw new Error("buildLaunchCommand: runner='sdx' requires sandboxName and a model `sandbox` config block (set sandbox.agent on the model in config.ts).");
317
317
  }
318
- const promptDir = dirname(arguments_.promptFile);
318
+ const promptDir = path.dirname(arguments_.promptFile);
319
319
  const agentCmd = renderAgentCommand({
320
320
  agentCmd: arguments_.definition.cmd,
321
321
  worktreeDir: arguments_.worktreeDir,
@@ -25,6 +25,6 @@ export declare function runNpmInstallGlobal(options: RunNpmInstallOptions): Prom
25
25
  export declare function detectInstallPath(cliMetaUrl: string): string;
26
26
  export type NpmRootRunner = (command: string, args: readonly string[]) => string;
27
27
  export declare function detectNpmRootGlobal(npmBin: string, runner: NpmRootRunner): string | undefined;
28
- export declare function detectIsSymlink(path: string): boolean;
28
+ export declare function detectIsSymlink(filePath: string): boolean;
29
29
  export declare function createDefaultNpmSpawner(): NpmSpawner;
30
30
  //# sourceMappingURL=npmGlobal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"npmGlobal.d.ts","sourceRoot":"","sources":["../../src/lib/npmGlobal.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,WAAW,CAY5E;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEjG,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAQ9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAEjF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAM7F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,wBAAgB,uBAAuB,IAAI,UAAU,CAmBpD"}
1
+ {"version":3,"file":"npmGlobal.d.ts","sourceRoot":"","sources":["../../src/lib/npmGlobal.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,WAAW,CAY5E;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAEjG,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAQ9F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;AAEjF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAM7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMzD;AAED,wBAAgB,uBAAuB,IAAI,UAAU,CAmBpD"}
@@ -1,16 +1,16 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { lstatSync } from "node:fs";
3
- import { dirname, sep } from "node:path";
3
+ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  export function classifyInstall(options) {
6
6
  const { installPath, npmRootGlobal, isSymlink } = options;
7
- if (npmRootGlobal !== undefined && installPath.startsWith(`${npmRootGlobal}${sep}`)) {
7
+ if (npmRootGlobal !== undefined && installPath.startsWith(`${npmRootGlobal}${path.sep}`)) {
8
8
  return isSymlink(installPath) ? "linked" : "global";
9
9
  }
10
- if (installPath.includes(`${sep}_npx${sep}`)) {
10
+ if (installPath.includes(`${path.sep}_npx${path.sep}`)) {
11
11
  return "npx";
12
12
  }
13
- if (installPath.includes(`${sep}node_modules${sep}`)) {
13
+ if (installPath.includes(`${path.sep}node_modules${path.sep}`)) {
14
14
  return "project";
15
15
  }
16
16
  return "unknown";
@@ -25,7 +25,7 @@ export async function runNpmInstallGlobal(options) {
25
25
  };
26
26
  }
27
27
  export function detectInstallPath(cliMetaUrl) {
28
- return dirname(dirname(fileURLToPath(cliMetaUrl)));
28
+ return path.dirname(path.dirname(fileURLToPath(cliMetaUrl)));
29
29
  }
30
30
  export function detectNpmRootGlobal(npmBin, runner) {
31
31
  try {
@@ -35,9 +35,9 @@ export function detectNpmRootGlobal(npmBin, runner) {
35
35
  return undefined;
36
36
  }
37
37
  }
38
- export function detectIsSymlink(path) {
38
+ export function detectIsSymlink(filePath) {
39
39
  try {
40
- return lstatSync(path).isSymbolicLink();
40
+ return lstatSync(filePath).isSymbolicLink();
41
41
  }
42
42
  catch {
43
43
  return false;
@@ -1,5 +1,5 @@
1
1
  import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
- import { dirname, resolve } from "node:path";
2
+ import path from "node:path";
3
3
  const TICKET_RE = /^[a-z][\da-z]*-\d+$/;
4
4
  const RUN_STATE_DIRECTORY_NAME = "runs";
5
5
  function ticketKey(ticket) {
@@ -10,10 +10,10 @@ function ticketKey(ticket) {
10
10
  return normalized;
11
11
  }
12
12
  export function runStateDirectory(config) {
13
- return resolve(dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
13
+ return path.resolve(path.dirname(config.logging.file), RUN_STATE_DIRECTORY_NAME);
14
14
  }
15
15
  export function runStatePath(config, ticket) {
16
- return resolve(runStateDirectory(config), `${ticketKey(ticket)}.json`);
16
+ return path.resolve(runStateDirectory(config), `${ticketKey(ticket)}.json`);
17
17
  }
18
18
  function nowIso() {
19
19
  return new Date().toISOString();
@@ -80,11 +80,11 @@ function parseRunState(value) {
80
80
  };
81
81
  }
82
82
  function writeState(config, state) {
83
- const path = runStatePath(config, state.ticket);
84
- mkdirSync(dirname(path), { recursive: true });
85
- const tmpPath = `${path}.${process.pid}.tmp`;
83
+ const statePath = runStatePath(config, state.ticket);
84
+ mkdirSync(path.dirname(statePath), { recursive: true });
85
+ const tmpPath = `${statePath}.${process.pid}.tmp`;
86
86
  writeFileSync(tmpPath, `${JSON.stringify(state, undefined, 2)}\n`, { mode: 0o600 });
87
- renameSync(tmpPath, path);
87
+ renameSync(tmpPath, statePath);
88
88
  }
89
89
  export function readRunState(config, ticket) {
90
90
  let raw;
@@ -1,6 +1,6 @@
1
1
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
- import { join } from "node:path";
3
+ import path from "node:path";
4
4
  import { BUILD_SECRET_NAMES } from "./config.js";
5
5
  import { shellSingleQuote } from "./launchCommand.js";
6
6
  import { readEnvironmentVariable } from "./util.js";
@@ -13,8 +13,8 @@ function renderPromptTemplate(template, variables) {
13
13
  .replaceAll("{{workspaceContinuationInstruction}}", variables.workspaceContinuationInstruction);
14
14
  }
15
15
  export function stagePromptText(input) {
16
- const promptDir = mkdtempSync(join(tmpdir(), `${input.prefix}-${input.ticket}-`));
17
- const promptFile = join(promptDir, "prompt.txt");
16
+ const promptDir = mkdtempSync(path.join(tmpdir(), `${input.prefix}-${input.ticket}-`));
17
+ const promptFile = path.join(promptDir, "prompt.txt");
18
18
  writeFileSync(promptFile, input.text);
19
19
  return { directory: promptDir, file: promptFile };
20
20
  }
@@ -42,12 +42,12 @@ export function stageBuildSecrets(promptDir) {
42
42
  if (lines.length === 0) {
43
43
  return undefined;
44
44
  }
45
- const secretsFile = join(promptDir, "secrets.env");
45
+ const secretsFile = path.join(promptDir, "secrets.env");
46
46
  writeFileSync(secretsFile, `${lines.join("\n")}\n`, { mode: 0o600 });
47
47
  return secretsFile;
48
48
  }
49
49
  function stageLaunchScript(promptDir, command) {
50
- const launcherFile = join(promptDir, "launch.sh");
50
+ const launcherFile = path.join(promptDir, "launch.sh");
51
51
  writeFileSync(launcherFile, `#!/usr/bin/env bash\n${command}\n`, { mode: 0o700 });
52
52
  return launcherFile;
53
53
  }
@@ -7,7 +7,7 @@ export declare function okMark(): string;
7
7
  export declare function failMark(): string;
8
8
  export declare function styleWarning(text: string): string;
9
9
  export declare function styleDim(text: string): string;
10
- export declare function setLogFile(path: string | undefined): void;
10
+ export declare function setLogFile(filePath: string | undefined): void;
11
11
  export declare function withLogOutputSuppressed<T>(operation: () => Promise<T>): Promise<T>;
12
12
  /** Important tier: always on the console (dimmed timestamp) and the log file. */
13
13
  export declare function log(message: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAIA,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/C;AAED,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAWD,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAQD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAuBD,iFAAiF;AACjF,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAOzC;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS3C;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAiBxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAcvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/lib/util.ts"],"names":[],"mappings":"AAIA,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAGhD;AAQD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE/C;AAED,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAWD,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAQD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE7D;AAED,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxF;AAuBD,iFAAiF;AACjF,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAOzC;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS3C;AAED,KAAK,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAUpF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAiBxF;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAcvF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAcnD"}
package/dist/lib/util.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
- import { dirname } from "node:path";
2
+ import path from "node:path";
3
3
  import { styleText } from "node:util";
4
4
  export async function sleep(ms, signal) {
5
5
  if (signal?.aborted === true) {
@@ -62,8 +62,8 @@ export function styleDim(text) {
62
62
  // loadConfig() resolves `logging.file`.
63
63
  let logFilePath;
64
64
  let suppressedLogDepth = 0;
65
- export function setLogFile(path) {
66
- logFilePath = path;
65
+ export function setLogFile(filePath) {
66
+ logFilePath = filePath;
67
67
  }
68
68
  export async function withLogOutputSuppressed(operation) {
69
69
  suppressedLogDepth += 1;
@@ -79,7 +79,7 @@ function appendLogLine(line) {
79
79
  return;
80
80
  }
81
81
  try {
82
- mkdirSync(dirname(logFilePath), { recursive: true });
82
+ mkdirSync(path.dirname(logFilePath), { recursive: true });
83
83
  appendFileSync(logFilePath, `${line}\n`);
84
84
  }
85
85
  catch {