swarmkit 0.0.2 → 0.0.4
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 +67 -131
- package/dist/commands/add.js +45 -2
- package/dist/commands/init/phases/global-setup.js +1 -1
- package/dist/commands/init/phases/project.js +5 -3
- package/dist/commands/init/state.d.ts +2 -0
- package/dist/commands/init/state.js +1 -0
- package/dist/commands/init/state.test.js +1 -0
- package/dist/commands/init/wizard.d.ts +5 -1
- package/dist/commands/init/wizard.js +78 -8
- package/dist/commands/init.js +7 -2
- package/dist/commands/remove.js +6 -0
- package/dist/config/global.d.ts +12 -0
- package/dist/config/global.js +34 -1
- package/dist/config/global.test.js +48 -15
- package/dist/doctor/checks.d.ts +1 -1
- package/dist/doctor/checks.js +26 -10
- package/dist/doctor/checks.test.js +52 -27
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/packages/installer.d.ts +9 -0
- package/dist/packages/installer.js +43 -12
- package/dist/packages/installer.test.js +84 -1
- package/dist/packages/plugin.d.ts +13 -0
- package/dist/packages/plugin.js +33 -0
- package/dist/packages/plugin.test.d.ts +1 -0
- package/dist/packages/plugin.test.js +99 -0
- package/dist/packages/registry.d.ts +1 -1
- package/dist/packages/registry.js +25 -50
- package/dist/packages/registry.test.js +38 -49
- package/dist/packages/setup.d.ts +11 -4
- package/dist/packages/setup.js +208 -103
- package/dist/packages/setup.test.js +337 -246
- package/package.json +1 -1
package/dist/packages/setup.js
CHANGED
|
@@ -1,48 +1,146 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
// ─── Project-level setup ─────────────────────────────────────────────────────
|
|
8
|
-
/**
|
|
8
|
+
/** Root directory for all swarmkit project-level config */
|
|
9
|
+
export const PROJECT_ROOT = ".swarm";
|
|
10
|
+
/** Config directories with .swarm/ prefix (default layout) */
|
|
9
11
|
export const PROJECT_CONFIG_DIRS = {
|
|
12
|
+
opentasks: ".swarm/opentasks",
|
|
13
|
+
minimem: ".swarm/minimem",
|
|
14
|
+
"cognitive-core": ".swarm/cognitive-core",
|
|
15
|
+
"skill-tree": ".swarm/skilltree",
|
|
16
|
+
"self-driving-repo": ".swarm/self-driving",
|
|
17
|
+
openteams: ".swarm/openteams",
|
|
18
|
+
sessionlog: ".swarm/sessionlog",
|
|
19
|
+
"claude-code-swarm": ".swarm/claude-swarm",
|
|
20
|
+
};
|
|
21
|
+
/** Config directories without prefix (flat layout, --no-prefix) */
|
|
22
|
+
export const FLAT_PROJECT_CONFIG_DIRS = {
|
|
10
23
|
opentasks: ".opentasks",
|
|
11
24
|
minimem: ".minimem",
|
|
12
25
|
"cognitive-core": ".cognitive-core",
|
|
13
|
-
"
|
|
26
|
+
"skill-tree": ".skilltree",
|
|
14
27
|
"self-driving-repo": ".self-driving",
|
|
28
|
+
openteams: ".openteams",
|
|
29
|
+
sessionlog: ".sessionlog",
|
|
30
|
+
"claude-code-swarm": ".claude-swarm",
|
|
15
31
|
};
|
|
32
|
+
/** Get config dirs for the given mode */
|
|
33
|
+
export function projectConfigDirs(usePrefix) {
|
|
34
|
+
return usePrefix ? PROJECT_CONFIG_DIRS : FLAT_PROJECT_CONFIG_DIRS;
|
|
35
|
+
}
|
|
16
36
|
/**
|
|
17
37
|
* Project-level packages in correct init order.
|
|
18
|
-
* Order matters: minimem before cognitive-core (runtime detection)
|
|
19
|
-
* opentasks before macro-agent (task backend wiring).
|
|
38
|
+
* Order matters: minimem before cognitive-core (runtime detection).
|
|
20
39
|
*/
|
|
21
40
|
export const PROJECT_INIT_ORDER = [
|
|
22
41
|
"opentasks",
|
|
23
42
|
"minimem",
|
|
24
43
|
"cognitive-core",
|
|
25
|
-
"
|
|
44
|
+
"skill-tree",
|
|
26
45
|
"self-driving-repo",
|
|
46
|
+
"openteams",
|
|
47
|
+
"sessionlog",
|
|
48
|
+
"claude-code-swarm",
|
|
27
49
|
];
|
|
28
|
-
/** Check whether a package is already initialized in a project */
|
|
50
|
+
/** Check whether a package is already initialized in a project (checks both layouts) */
|
|
29
51
|
export function isProjectInit(cwd, pkg) {
|
|
30
|
-
const
|
|
31
|
-
|
|
52
|
+
const prefixed = PROJECT_CONFIG_DIRS[pkg];
|
|
53
|
+
const flat = FLAT_PROJECT_CONFIG_DIRS[pkg];
|
|
54
|
+
return (prefixed ? existsSync(join(cwd, prefixed)) : false) ||
|
|
55
|
+
(flat ? existsSync(join(cwd, flat)) : false);
|
|
56
|
+
}
|
|
57
|
+
/** Ensure the .swarm/ project root directory exists */
|
|
58
|
+
function ensureProjectRoot(cwd) {
|
|
59
|
+
const root = join(cwd, PROJECT_ROOT);
|
|
60
|
+
if (!existsSync(root)) {
|
|
61
|
+
mkdirSync(root, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* After init, relocate a package's config dir into .swarm/ if the CLI
|
|
66
|
+
* created it at the legacy top-level location, then leave a symlink at
|
|
67
|
+
* the old path so the package can still find its data at runtime.
|
|
68
|
+
* This is a no-op when the package already respects its env-var override.
|
|
69
|
+
*/
|
|
70
|
+
function relocate(cwd, legacyName, targetName) {
|
|
71
|
+
const src = join(cwd, legacyName);
|
|
72
|
+
const dest = join(cwd, PROJECT_ROOT, targetName);
|
|
73
|
+
// Move the directory if it was created at the legacy location
|
|
74
|
+
if (existsSync(src) && !isSymlink(src) && !existsSync(dest)) {
|
|
75
|
+
try {
|
|
76
|
+
renameSync(src, dest);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return; // Non-fatal — leave in legacy location
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Create a symlink at the legacy location so packages find their data
|
|
83
|
+
ensureSymlink(cwd, legacyName, targetName);
|
|
84
|
+
}
|
|
85
|
+
/** Create a relative symlink: cwd/<legacyName> → .swarm/<targetName> */
|
|
86
|
+
function ensureSymlink(cwd, legacyName, targetName) {
|
|
87
|
+
const link = join(cwd, legacyName);
|
|
88
|
+
const target = join(PROJECT_ROOT, targetName); // relative path
|
|
89
|
+
if (isSymlink(link))
|
|
90
|
+
return; // Already symlinked
|
|
91
|
+
if (existsSync(link))
|
|
92
|
+
return; // Real directory exists — don't overwrite
|
|
93
|
+
try {
|
|
94
|
+
symlinkSync(target, link);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Non-fatal — package can still be configured via env var
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function isSymlink(path) {
|
|
101
|
+
try {
|
|
102
|
+
return lstatSync(path).isSymbolicLink();
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
32
107
|
}
|
|
33
108
|
/** Initialize a single project-level package */
|
|
34
109
|
export async function initProjectPackage(pkg, ctx) {
|
|
110
|
+
if (ctx.usePrefix) {
|
|
111
|
+
ensureProjectRoot(ctx.cwd);
|
|
112
|
+
}
|
|
35
113
|
switch (pkg) {
|
|
36
|
-
case "opentasks":
|
|
37
|
-
|
|
114
|
+
case "opentasks": {
|
|
115
|
+
const result = await shellInit("opentasks", ["init", "--name", getProjectName(ctx.cwd)], ctx.cwd, ctx.usePrefix
|
|
116
|
+
? { OPENTASKS_PROJECT_DIR: join(PROJECT_ROOT, "opentasks") }
|
|
117
|
+
: undefined);
|
|
118
|
+
// relocate handles packages that don't yet respect the env var;
|
|
119
|
+
// for packages that create directly in .swarm/, this is a no-op.
|
|
120
|
+
if (ctx.usePrefix)
|
|
121
|
+
relocate(ctx.cwd, ".opentasks", "opentasks");
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
38
124
|
case "minimem":
|
|
39
125
|
return initMinimem(ctx);
|
|
40
|
-
case "cognitive-core":
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
126
|
+
case "cognitive-core": {
|
|
127
|
+
const result = await shellInit("cognitive-core", ["init"], ctx.cwd, ctx.usePrefix
|
|
128
|
+
? { COGNITIVE_CORE_HOME: join(ctx.cwd, PROJECT_ROOT, "cognitive-core") }
|
|
129
|
+
: undefined);
|
|
130
|
+
if (ctx.usePrefix)
|
|
131
|
+
relocate(ctx.cwd, ".cognitive-core", "cognitive-core");
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
case "skill-tree":
|
|
135
|
+
return initSkillTreeProject(ctx);
|
|
44
136
|
case "self-driving-repo":
|
|
45
|
-
return
|
|
137
|
+
return initSdr(ctx);
|
|
138
|
+
case "openteams":
|
|
139
|
+
return initOpenteamsProject(ctx);
|
|
140
|
+
case "sessionlog":
|
|
141
|
+
return initSessionlogProject(ctx);
|
|
142
|
+
case "claude-code-swarm":
|
|
143
|
+
return initClaudeSwarmProject(ctx);
|
|
46
144
|
default:
|
|
47
145
|
return { package: pkg, success: false, message: "Unknown package" };
|
|
48
146
|
}
|
|
@@ -50,9 +148,7 @@ export async function initProjectPackage(pkg, ctx) {
|
|
|
50
148
|
// ─── Global-level setup ──────────────────────────────────────────────────────
|
|
51
149
|
/** Config directories for global packages (relative to homedir) */
|
|
52
150
|
export const GLOBAL_CONFIG_DIRS = {
|
|
53
|
-
"agent-iam": ".agent-credentials",
|
|
54
151
|
"skill-tree": ".skill-tree",
|
|
55
|
-
openswarm: ".openswarm",
|
|
56
152
|
openhive: ".openhive",
|
|
57
153
|
};
|
|
58
154
|
/** Check whether a global package is already configured */
|
|
@@ -65,12 +161,8 @@ export function isGlobalInit(pkg) {
|
|
|
65
161
|
/** Initialize a single global package */
|
|
66
162
|
export async function initGlobalPackage(pkg, ctx, openhiveOpts) {
|
|
67
163
|
switch (pkg) {
|
|
68
|
-
case "agent-iam":
|
|
69
|
-
return initAgentIam(ctx);
|
|
70
164
|
case "skill-tree":
|
|
71
165
|
return initSkillTree();
|
|
72
|
-
case "openswarm":
|
|
73
|
-
return initOpenswarm(ctx);
|
|
74
166
|
case "openhive":
|
|
75
167
|
return openhiveOpts
|
|
76
168
|
? initOpenhive(openhiveOpts)
|
|
@@ -83,6 +175,8 @@ export async function initGlobalPackage(pkg, ctx, openhiveOpts) {
|
|
|
83
175
|
return { package: "openteams", success: true, message: "no setup required" };
|
|
84
176
|
case "sessionlog":
|
|
85
177
|
return { package: "sessionlog", success: true, message: "no setup required" };
|
|
178
|
+
case "claude-code-swarm":
|
|
179
|
+
return { package: "claude-code-swarm", success: true, message: "no setup required" };
|
|
86
180
|
default:
|
|
87
181
|
return { package: pkg, success: false, message: "Unknown package" };
|
|
88
182
|
}
|
|
@@ -97,10 +191,13 @@ function cleanEnv() {
|
|
|
97
191
|
return env;
|
|
98
192
|
}
|
|
99
193
|
/** Shell out to a package's CLI init command */
|
|
100
|
-
async function shellInit(command, args, cwd) {
|
|
194
|
+
async function shellInit(command, args, cwd, envOverrides) {
|
|
101
195
|
const pkg = command === "sdr" ? "self-driving-repo" : command;
|
|
102
196
|
try {
|
|
103
|
-
|
|
197
|
+
const env = envOverrides
|
|
198
|
+
? { ...cleanEnv(), ...envOverrides }
|
|
199
|
+
: cleanEnv();
|
|
200
|
+
await execFileAsync(command, args, { cwd, timeout: 30_000, env });
|
|
104
201
|
return { package: pkg, success: true };
|
|
105
202
|
}
|
|
106
203
|
catch (err) {
|
|
@@ -108,15 +205,25 @@ async function shellInit(command, args, cwd) {
|
|
|
108
205
|
}
|
|
109
206
|
}
|
|
110
207
|
async function initMinimem(ctx) {
|
|
208
|
+
const targetDir = ctx.usePrefix
|
|
209
|
+
? join(ctx.cwd, PROJECT_ROOT, "minimem")
|
|
210
|
+
: join(ctx.cwd, ".minimem");
|
|
111
211
|
try {
|
|
112
212
|
await execFileAsync("minimem", ["init"], {
|
|
113
213
|
cwd: ctx.cwd,
|
|
114
214
|
timeout: 30_000,
|
|
115
|
-
env:
|
|
215
|
+
env: ctx.usePrefix
|
|
216
|
+
? { ...cleanEnv(), MINIMEM_CONFIG_DIR: join(PROJECT_ROOT, "minimem") }
|
|
217
|
+
: cleanEnv(),
|
|
116
218
|
});
|
|
219
|
+
// relocate handles packages that don't yet respect the env var;
|
|
220
|
+
// for packages that create directly in .swarm/, this is a no-op.
|
|
221
|
+
if (ctx.usePrefix) {
|
|
222
|
+
relocate(ctx.cwd, ".minimem", "minimem");
|
|
223
|
+
}
|
|
117
224
|
// Patch embedding provider if configured
|
|
118
225
|
if (ctx.embeddingProvider && ctx.embeddingProvider !== "local") {
|
|
119
|
-
const configPath = join(
|
|
226
|
+
const configPath = join(targetDir, "config.json");
|
|
120
227
|
if (existsSync(configPath)) {
|
|
121
228
|
try {
|
|
122
229
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
@@ -135,65 +242,71 @@ async function initMinimem(ctx) {
|
|
|
135
242
|
return { package: "minimem", success: false, message: formatError(err) };
|
|
136
243
|
}
|
|
137
244
|
}
|
|
138
|
-
async function
|
|
245
|
+
async function initSdr(ctx) {
|
|
246
|
+
const result = await shellInit("sdr", ["init", "-t", "triage-only"], ctx.cwd, ctx.usePrefix
|
|
247
|
+
? { SDR_CONFIG_DIR: join(PROJECT_ROOT, "self-driving") }
|
|
248
|
+
: undefined);
|
|
249
|
+
// relocate handles packages that don't yet respect the env var;
|
|
250
|
+
// for packages that create directly in .swarm/, this is a no-op.
|
|
251
|
+
if (ctx.usePrefix) {
|
|
252
|
+
relocate(ctx.cwd, ".self-driving", "self-driving");
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
async function initSkillTreeProject(ctx) {
|
|
139
257
|
try {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
258
|
+
const targetDir = ctx.usePrefix
|
|
259
|
+
? join(ctx.cwd, PROJECT_ROOT, "skilltree")
|
|
260
|
+
: join(ctx.cwd, ".skilltree");
|
|
261
|
+
mkdirSync(targetDir, { recursive: true });
|
|
262
|
+
mkdirSync(join(targetDir, "skills"), { recursive: true });
|
|
263
|
+
return { package: "skill-tree", success: true };
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
return {
|
|
267
|
+
package: "skill-tree",
|
|
268
|
+
success: false,
|
|
269
|
+
message: formatError(err),
|
|
146
270
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async function initOpenteamsProject(ctx) {
|
|
274
|
+
try {
|
|
275
|
+
const targetDir = ctx.usePrefix
|
|
276
|
+
? join(ctx.cwd, PROJECT_ROOT, "openteams")
|
|
277
|
+
: join(ctx.cwd, ".openteams");
|
|
278
|
+
mkdirSync(targetDir, { recursive: true });
|
|
279
|
+
// Write default config.json (matches `openteams template init` with no options)
|
|
280
|
+
const configPath = join(targetDir, "config.json");
|
|
281
|
+
if (!existsSync(configPath)) {
|
|
282
|
+
writeFileSync(configPath, JSON.stringify({}, null, 2) + "\n");
|
|
153
283
|
}
|
|
154
|
-
|
|
155
|
-
return { package: "macro-agent", success: true };
|
|
284
|
+
return { package: "openteams", success: true };
|
|
156
285
|
}
|
|
157
286
|
catch (err) {
|
|
158
287
|
return {
|
|
159
|
-
package: "
|
|
288
|
+
package: "openteams",
|
|
160
289
|
success: false,
|
|
161
290
|
message: formatError(err),
|
|
162
291
|
};
|
|
163
292
|
}
|
|
164
293
|
}
|
|
165
|
-
async function
|
|
294
|
+
async function initSessionlogProject(ctx) {
|
|
166
295
|
try {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
//
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
await execFileAsync("agent-iam", [
|
|
177
|
-
"apikey",
|
|
178
|
-
"add",
|
|
179
|
-
"--name",
|
|
180
|
-
provider,
|
|
181
|
-
"--provider",
|
|
182
|
-
provider,
|
|
183
|
-
"--key",
|
|
184
|
-
key,
|
|
185
|
-
], { timeout: 15_000, env });
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
// Non-fatal — agent-iam CLI may not be fully installed yet
|
|
189
|
-
}
|
|
190
|
-
}
|
|
296
|
+
const targetDir = ctx.usePrefix
|
|
297
|
+
? join(ctx.cwd, PROJECT_ROOT, "sessionlog")
|
|
298
|
+
: join(ctx.cwd, ".sessionlog");
|
|
299
|
+
mkdirSync(targetDir, { recursive: true });
|
|
300
|
+
// Write default settings.json
|
|
301
|
+
const settingsPath = join(targetDir, "settings.json");
|
|
302
|
+
if (!existsSync(settingsPath)) {
|
|
303
|
+
writeFileSync(settingsPath, JSON.stringify({ enabled: false, strategy: "manual-commit" }, null, 2) + "\n");
|
|
191
304
|
}
|
|
192
|
-
return { package: "
|
|
305
|
+
return { package: "sessionlog", success: true };
|
|
193
306
|
}
|
|
194
307
|
catch (err) {
|
|
195
308
|
return {
|
|
196
|
-
package: "
|
|
309
|
+
package: "sessionlog",
|
|
197
310
|
success: false,
|
|
198
311
|
message: formatError(err),
|
|
199
312
|
};
|
|
@@ -224,40 +337,6 @@ async function initSkillTree() {
|
|
|
224
337
|
};
|
|
225
338
|
}
|
|
226
339
|
}
|
|
227
|
-
async function initOpenswarm(ctx) {
|
|
228
|
-
const configDir = join(homedir(), ".openswarm");
|
|
229
|
-
const configPath = join(configDir, "server.json");
|
|
230
|
-
if (existsSync(configPath)) {
|
|
231
|
-
return {
|
|
232
|
-
package: "openswarm",
|
|
233
|
-
success: true,
|
|
234
|
-
message: "already configured",
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
try {
|
|
238
|
-
mkdirSync(configDir, { recursive: true });
|
|
239
|
-
const config = {
|
|
240
|
-
host: "localhost",
|
|
241
|
-
port: 3000,
|
|
242
|
-
auth: { mode: "none" },
|
|
243
|
-
storage: { type: "memory" },
|
|
244
|
-
logging: { level: "info" },
|
|
245
|
-
};
|
|
246
|
-
// Configure macro-agent adapter if installed
|
|
247
|
-
if (ctx.packages.includes("macro-agent")) {
|
|
248
|
-
config.adapter = { id: "macro-agent" };
|
|
249
|
-
}
|
|
250
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
251
|
-
return { package: "openswarm", success: true };
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
return {
|
|
255
|
-
package: "openswarm",
|
|
256
|
-
success: false,
|
|
257
|
-
message: formatError(err),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
340
|
async function initOpenhive(opts) {
|
|
262
341
|
try {
|
|
263
342
|
const args = [
|
|
@@ -286,6 +365,32 @@ async function initOpenhive(opts) {
|
|
|
286
365
|
};
|
|
287
366
|
}
|
|
288
367
|
}
|
|
368
|
+
async function initClaudeSwarmProject(ctx) {
|
|
369
|
+
try {
|
|
370
|
+
const targetDir = ctx.usePrefix
|
|
371
|
+
? join(ctx.cwd, PROJECT_ROOT, "claude-swarm")
|
|
372
|
+
: join(ctx.cwd, ".claude-swarm");
|
|
373
|
+
mkdirSync(targetDir, { recursive: true });
|
|
374
|
+
// Write default config.json
|
|
375
|
+
const configPath = join(targetDir, "config.json");
|
|
376
|
+
if (!existsSync(configPath)) {
|
|
377
|
+
writeFileSync(configPath, JSON.stringify({}, null, 2) + "\n");
|
|
378
|
+
}
|
|
379
|
+
// Write .gitignore for tmp/
|
|
380
|
+
const gitignorePath = join(targetDir, ".gitignore");
|
|
381
|
+
if (!existsSync(gitignorePath)) {
|
|
382
|
+
writeFileSync(gitignorePath, "tmp/\n");
|
|
383
|
+
}
|
|
384
|
+
return { package: "claude-code-swarm", success: true };
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
return {
|
|
388
|
+
package: "claude-code-swarm",
|
|
389
|
+
success: false,
|
|
390
|
+
message: formatError(err),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
289
394
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
290
395
|
function getProjectName(cwd) {
|
|
291
396
|
const pkgPath = join(cwd, "package.json");
|