@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.
- package/README.md +4 -4
- package/bin/run.js +2 -2
- package/bin/runCli.js +3 -3
- package/crew.config.example.ts +23 -25
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +28 -17
- package/dist/commands/upgrade.js +2 -2
- package/dist/lib/adapters/linear/fetch.d.ts +1 -1
- package/dist/lib/adapters/linear/fetch.d.ts.map +1 -1
- package/dist/lib/adapters/linear/fetch.js +7 -7
- package/dist/lib/adapters/linear/parsing.d.ts +1 -1
- package/dist/lib/adapters/linear/parsing.d.ts.map +1 -1
- package/dist/lib/adapters/linear/parsing.js +9 -9
- package/dist/lib/adapters/registry.js +2 -2
- package/dist/lib/config.d.ts +13 -19
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +82 -76
- package/dist/lib/launchCommand.js +7 -7
- package/dist/lib/npmGlobal.d.ts +1 -1
- package/dist/lib/npmGlobal.d.ts.map +1 -1
- package/dist/lib/npmGlobal.js +7 -7
- package/dist/lib/runState.js +7 -7
- package/dist/lib/stagedLaunch.js +5 -5
- package/dist/lib/util.d.ts +1 -1
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/lib/util.js +4 -4
- package/dist/lib/worktrees.js +18 -18
- package/dist/lib/xdg.js +5 -5
- package/docs/configuration.md +46 -36
- package/docs/troubleshooting.md +3 -3
- package/package.json +7 -7
package/dist/lib/config.js
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
125
|
+
function requireString(value, configKey) {
|
|
102
126
|
if (!isNonEmptyString(value)) {
|
|
103
|
-
fail(`${
|
|
127
|
+
fail(`${configKey} must be a non-empty string (got ${JSON.stringify(value)})`);
|
|
104
128
|
}
|
|
105
129
|
}
|
|
106
|
-
function requirePositiveInt(value,
|
|
130
|
+
function requirePositiveInt(value, configKey, min = 1) {
|
|
107
131
|
if (typeof value !== "number" || !Number.isInteger(value) || value < min) {
|
|
108
|
-
fail(`${
|
|
132
|
+
fail(`${configKey} must be an integer ≥ ${min} (got ${JSON.stringify(value)})`);
|
|
109
133
|
}
|
|
110
134
|
}
|
|
111
|
-
function requirePercent(value,
|
|
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(`${
|
|
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,
|
|
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(`${
|
|
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
|
|
157
|
+
const configPath = `models.definitions.${modelName}.preLaunchEnv`;
|
|
134
158
|
if (!Array.isArray(value)) {
|
|
135
|
-
fail(`${
|
|
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(`${
|
|
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(`${
|
|
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,
|
|
192
|
+
function normalizeWorkspaceKind(value, configKey) {
|
|
169
193
|
if (value === undefined) {
|
|
170
194
|
return undefined;
|
|
171
195
|
}
|
|
172
196
|
if (!isWorkspaceKindSetting(value)) {
|
|
173
|
-
fail(`${
|
|
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,
|
|
204
|
+
function normalizeLocalRunner(value, configKey) {
|
|
181
205
|
if (value === undefined) {
|
|
182
206
|
return undefined;
|
|
183
207
|
}
|
|
184
208
|
if (!isLocalRunnerSetting(value)) {
|
|
185
|
-
fail(`${
|
|
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,
|
|
213
|
+
function normalizeSandbox(value, configKey) {
|
|
190
214
|
if (!isPlainObject(value)) {
|
|
191
|
-
fail(`${
|
|
215
|
+
fail(`${configKey} must be an object`);
|
|
192
216
|
}
|
|
193
217
|
if (Object.hasOwn(value, "template")) {
|
|
194
|
-
failRemovedConfigKey(`${
|
|
218
|
+
failRemovedConfigKey(`${configKey}.template`, "Groundcrew no longer creates or re-templates sdx sandboxes.");
|
|
195
219
|
}
|
|
196
220
|
if (Object.hasOwn(value, "kits")) {
|
|
197
|
-
failRemovedConfigKey(`${
|
|
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, `${
|
|
224
|
+
requireString(agent, `${configKey}.agent`);
|
|
201
225
|
const trimmedAgent = agent.trim();
|
|
202
226
|
if (trimmedAgent.length === 0) {
|
|
203
|
-
fail(`${
|
|
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, `${
|
|
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(
|
|
213
|
-
fail(`${
|
|
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
|
-
|
|
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
|
|
235
|
-
*
|
|
236
|
-
*
|
|
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
|
|
240
|
-
return (Object.hasOwn(
|
|
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
|
|
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 =
|
|
281
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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,
|
|
332
|
+
function requireObject(value, configKey) {
|
|
326
333
|
if (!isPlainObject(value)) {
|
|
327
|
-
fail(`${
|
|
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
|
|
372
|
+
const configPath = `sources[${index}]`;
|
|
366
373
|
if (!isPlainObject(entry)) {
|
|
367
|
-
fail(`${
|
|
374
|
+
fail(`${configPath} must be an object`);
|
|
368
375
|
}
|
|
369
376
|
const { kind, name } = entry;
|
|
370
|
-
requireString(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, `${
|
|
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(`${
|
|
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
|
|
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
|
|
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
|
-
//
|
|
500
|
-
// the user gets the specific
|
|
501
|
-
//
|
|
502
|
-
if (
|
|
503
|
-
fail(`models.default ("${config.models.default}") is
|
|
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((
|
|
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
|
|
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,
|
package/dist/lib/npmGlobal.d.ts
CHANGED
|
@@ -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(
|
|
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,
|
|
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"}
|
package/dist/lib/npmGlobal.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { lstatSync } from "node:fs";
|
|
3
|
-
import
|
|
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(
|
|
38
|
+
export function detectIsSymlink(filePath) {
|
|
39
39
|
try {
|
|
40
|
-
return lstatSync(
|
|
40
|
+
return lstatSync(filePath).isSymbolicLink();
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
43
|
return false;
|
package/dist/lib/runState.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import
|
|
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
|
|
84
|
-
mkdirSync(dirname(
|
|
85
|
-
const tmpPath = `${
|
|
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,
|
|
87
|
+
renameSync(tmpPath, statePath);
|
|
88
88
|
}
|
|
89
89
|
export function readRunState(config, ticket) {
|
|
90
90
|
let raw;
|
package/dist/lib/stagedLaunch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
|
-
import
|
|
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
|
}
|
package/dist/lib/util.d.ts
CHANGED
|
@@ -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(
|
|
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;
|
package/dist/lib/util.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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(
|
|
66
|
-
logFilePath =
|
|
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 {
|