serpentstack 0.2.11 → 0.2.12
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/bin/serpentstack.js +10 -3
- package/lib/commands/persistent.js +205 -163
- package/lib/commands/skills-init.js +3 -4
- package/package.json +1 -1
package/bin/serpentstack.js
CHANGED
|
@@ -68,9 +68,11 @@ function showHelp() {
|
|
|
68
68
|
divider('Any project');
|
|
69
69
|
console.log(` ${cyan('skills')} Download all skills + persistent agent configs`);
|
|
70
70
|
console.log(` ${cyan('skills update')} Update base skills to latest versions`);
|
|
71
|
-
console.log(` ${cyan('persistent')}
|
|
71
|
+
console.log(` ${cyan('persistent')} Status dashboard (first run = full setup)`);
|
|
72
|
+
console.log(` ${cyan('persistent')} ${dim('--configure')} Edit project settings`);
|
|
73
|
+
console.log(` ${cyan('persistent')} ${dim('--agents')} Change agent models, enable/disable`);
|
|
74
|
+
console.log(` ${cyan('persistent')} ${dim('--start')} Launch enabled agents`);
|
|
72
75
|
console.log(` ${cyan('persistent')} ${dim('--stop')} Stop all running agents`);
|
|
73
|
-
console.log(` ${cyan('persistent')} ${dim('--reconfigure')} Change models, enable/disable agents`);
|
|
74
76
|
console.log();
|
|
75
77
|
|
|
76
78
|
divider('Options');
|
|
@@ -134,7 +136,12 @@ async function main() {
|
|
|
134
136
|
}
|
|
135
137
|
} else if (noun === 'persistent') {
|
|
136
138
|
const { persistent } = await import('../lib/commands/persistent.js');
|
|
137
|
-
await persistent({
|
|
139
|
+
await persistent({
|
|
140
|
+
stop: !!flags.stop,
|
|
141
|
+
configure: !!flags.configure,
|
|
142
|
+
agents: !!flags.agents,
|
|
143
|
+
start: !!flags.start,
|
|
144
|
+
});
|
|
138
145
|
} else {
|
|
139
146
|
error(`Unknown command: ${bold(noun)}`);
|
|
140
147
|
const suggestion = suggestCommand(noun);
|
|
@@ -65,7 +65,7 @@ async function pickModel(rl, agentName, currentModel, available) {
|
|
|
65
65
|
const params = m.params ? dim(` ${m.params}`) : '';
|
|
66
66
|
const quant = m.quant ? dim(` ${m.quant}`) : '';
|
|
67
67
|
const size = m.size ? dim(` (${m.size})`) : '';
|
|
68
|
-
const tag = isCurrent ? green('
|
|
68
|
+
const tag = isCurrent ? green(' ← current') : '';
|
|
69
69
|
console.log(` ${marker} ${num} ${label}${params}${quant}${size}${tag}`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -82,7 +82,7 @@ async function pickModel(rl, agentName, currentModel, available) {
|
|
|
82
82
|
const num = dim(`${idx + 1}.`);
|
|
83
83
|
const label = isCurrent ? bold(m.name) : m.name;
|
|
84
84
|
const provider = m.provider ? dim(` (${m.provider})`) : '';
|
|
85
|
-
const tag = isCurrent ? green('
|
|
85
|
+
const tag = isCurrent ? green(' ← current') : '';
|
|
86
86
|
console.log(` ${marker} ${num} ${label}${provider}${tag}`);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -90,7 +90,6 @@ async function pickModel(rl, agentName, currentModel, available) {
|
|
|
90
90
|
// If current model isn't in either list, add it
|
|
91
91
|
if (!choices.some(c => c.id === currentModel)) {
|
|
92
92
|
choices.unshift({ id: currentModel, name: modelShortName(currentModel), tier: 'custom' });
|
|
93
|
-
// Re-render isn't needed since we'll just note it
|
|
94
93
|
console.log(` ${dim(`Current: ${modelShortName(currentModel)} (not in detected models)`)}`);
|
|
95
94
|
}
|
|
96
95
|
|
|
@@ -157,7 +156,6 @@ end tell`;
|
|
|
157
156
|
try {
|
|
158
157
|
const child = spawn(bin, args, { stdio: 'ignore', detached: true });
|
|
159
158
|
child.unref();
|
|
160
|
-
// Verify it didn't immediately fail
|
|
161
159
|
const alive = child.pid && !child.killed;
|
|
162
160
|
if (alive) return bin;
|
|
163
161
|
} catch { continue; }
|
|
@@ -197,7 +195,6 @@ function stopAllAgents(projectDir) {
|
|
|
197
195
|
} catch (err) {
|
|
198
196
|
if (err.code === 'ESRCH') {
|
|
199
197
|
removePid(projectDir, name);
|
|
200
|
-
// Don't count already-dead processes as "stopped"
|
|
201
198
|
} else {
|
|
202
199
|
error(`Failed to stop ${bold(name)}: ${err.message}`);
|
|
203
200
|
}
|
|
@@ -235,124 +232,24 @@ function printAgentLine(name, agentMd, config, statusInfo) {
|
|
|
235
232
|
}
|
|
236
233
|
}
|
|
237
234
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
printHeader();
|
|
244
|
-
|
|
245
|
-
// ── Stop ──
|
|
246
|
-
if (stop) {
|
|
247
|
-
stopAllAgents(projectDir);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ── Preflight checks ──
|
|
252
|
-
const soulPath = join(projectDir, '.openclaw/SOUL.md');
|
|
253
|
-
if (!existsSync(soulPath)) {
|
|
254
|
-
error('No .openclaw/ workspace found.');
|
|
255
|
-
console.log(` Run ${bold('serpentstack skills')} first to download the workspace files.`);
|
|
256
|
-
console.log();
|
|
257
|
-
process.exit(1);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const agents = discoverAgents(projectDir);
|
|
261
|
-
if (agents.length === 0) {
|
|
262
|
-
error('No agents found in .openclaw/agents/');
|
|
263
|
-
console.log(` Run ${bold('serpentstack skills')} to download the default agents,`);
|
|
264
|
-
console.log(` or create your own at ${bold('.openclaw/agents/<name>/AGENT.md')}`);
|
|
265
|
-
console.log();
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Check OpenClaw early — don't waste time configuring if it's missing
|
|
270
|
-
const hasOpenClaw = await which('openclaw');
|
|
271
|
-
if (!hasOpenClaw) {
|
|
272
|
-
warn('OpenClaw is not installed.');
|
|
273
|
-
console.log();
|
|
274
|
-
console.log(` ${dim('OpenClaw is the persistent agent runtime.')}`);
|
|
275
|
-
console.log(` ${dim('Install it first, then re-run this command:')}`);
|
|
276
|
-
console.log();
|
|
277
|
-
console.log(` ${dim('$')} ${bold('npm install -g openclaw@latest')}`);
|
|
278
|
-
console.log(` ${dim('$')} ${bold('serpentstack persistent')}`);
|
|
279
|
-
console.log();
|
|
280
|
-
process.exit(1);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
cleanStalePids(projectDir);
|
|
284
|
-
|
|
285
|
-
// Parse agent definitions
|
|
286
|
-
const parsed = [];
|
|
287
|
-
for (const agent of agents) {
|
|
288
|
-
try {
|
|
289
|
-
const agentMd = parseAgentMd(agent.agentMdPath);
|
|
290
|
-
parsed.push({ ...agent, agentMd });
|
|
291
|
-
} catch (err) {
|
|
292
|
-
warn(`Skipping ${bold(agent.name)}: ${err.message}`);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (parsed.length === 0) {
|
|
296
|
-
error('No valid AGENT.md files found.');
|
|
297
|
-
console.log();
|
|
298
|
-
process.exit(1);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Load config
|
|
302
|
-
let config = readConfig(projectDir) || { project: {}, agents: {} };
|
|
303
|
-
const needsSetup = !config.project?.name || reconfigure;
|
|
304
|
-
|
|
305
|
-
// Detect models in background while we show status
|
|
306
|
-
const modelsPromise = detectModels();
|
|
307
|
-
|
|
308
|
-
// ── If configured, show status dashboard ──
|
|
309
|
-
if (!needsSetup) {
|
|
310
|
-
console.log(` ${bold(config.project.name)} ${dim(`— ${config.project.framework}`)}`);
|
|
311
|
-
console.log(` ${dim(`Dev: ${config.project.devCmd} · Test: ${config.project.testCmd}`)}`);
|
|
312
|
-
console.log();
|
|
313
|
-
|
|
314
|
-
for (const { name, agentMd } of parsed) {
|
|
315
|
-
const statusInfo = getAgentStatus(projectDir, name, config);
|
|
316
|
-
printAgentLine(name, agentMd, config, statusInfo);
|
|
317
|
-
}
|
|
318
|
-
console.log();
|
|
319
|
-
|
|
320
|
-
// Determine what to do
|
|
321
|
-
const enabledAgents = parsed.filter(a => isAgentEnabled(a.name, config));
|
|
322
|
-
const runningNames = new Set(listPids(projectDir).map(p => p.name));
|
|
323
|
-
const startable = enabledAgents.filter(a => !runningNames.has(a.name));
|
|
324
|
-
|
|
325
|
-
if (startable.length === 0 && runningNames.size > 0) {
|
|
326
|
-
info('All enabled agents are running.');
|
|
327
|
-
console.log(` ${dim('Run')} ${bold('serpentstack persistent --stop')} ${dim('to stop them.')}`);
|
|
328
|
-
console.log(` ${dim('Run')} ${bold('serpentstack persistent --reconfigure')} ${dim('to change settings.')}`);
|
|
329
|
-
console.log();
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (startable.length === 0) {
|
|
334
|
-
info('No agents are enabled.');
|
|
335
|
-
console.log(` ${dim('Run')} ${bold('serpentstack persistent --reconfigure')} ${dim('to enable agents.')}`);
|
|
336
|
-
console.log();
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
235
|
+
function printStatusDashboard(config, parsed, projectDir) {
|
|
236
|
+
console.log(` ${bold(config.project.name)} ${dim(`— ${config.project.framework}`)}`);
|
|
237
|
+
console.log(` ${dim(`Dev: ${config.project.devCmd} · Test: ${config.project.testCmd}`)}`);
|
|
238
|
+
console.log();
|
|
339
239
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
240
|
+
for (const { name, agentMd } of parsed) {
|
|
241
|
+
const statusInfo = getAgentStatus(projectDir, name, config);
|
|
242
|
+
printAgentLine(name, agentMd, config, statusInfo);
|
|
343
243
|
}
|
|
244
|
+
console.log();
|
|
245
|
+
}
|
|
344
246
|
|
|
345
|
-
|
|
346
|
-
if (reconfigure) {
|
|
347
|
-
info('Reconfiguring...');
|
|
348
|
-
console.log();
|
|
349
|
-
}
|
|
247
|
+
// ─── Configure Flow (project settings) ─────────────────────
|
|
350
248
|
|
|
249
|
+
async function runConfigure(projectDir, config, soulPath) {
|
|
351
250
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
352
|
-
let configDirty = false;
|
|
353
251
|
|
|
354
252
|
try {
|
|
355
|
-
// ── Project configuration ──
|
|
356
253
|
const detected = detectProjectDefaults(projectDir);
|
|
357
254
|
const template = detectTemplateDefaults(projectDir);
|
|
358
255
|
const existing = config.project || {};
|
|
@@ -382,13 +279,12 @@ export async function persistent({ stop = false, reconfigure = false } = {}) {
|
|
|
382
279
|
testCmd: await ask(rl, 'Test command', defaults.testCmd),
|
|
383
280
|
conventions: await ask(rl, 'Key conventions', defaults.conventions),
|
|
384
281
|
};
|
|
385
|
-
configDirty = true;
|
|
386
282
|
|
|
387
|
-
// Update SOUL.md
|
|
283
|
+
// Update SOUL.md with project context
|
|
388
284
|
if (existsSync(soulPath)) {
|
|
389
285
|
let soul = readFileSync(soulPath, 'utf8');
|
|
390
286
|
const ctx = [
|
|
391
|
-
`# ${config.project.name}
|
|
287
|
+
`# ${config.project.name} — Persistent Development Agents`,
|
|
392
288
|
'',
|
|
393
289
|
`**Project:** ${config.project.name}`,
|
|
394
290
|
`**Language:** ${config.project.language}`,
|
|
@@ -405,25 +301,40 @@ export async function persistent({ stop = false, reconfigure = false } = {}) {
|
|
|
405
301
|
console.log();
|
|
406
302
|
success(`Updated ${bold('.openclaw/SOUL.md')}`);
|
|
407
303
|
console.log();
|
|
304
|
+
} finally {
|
|
305
|
+
rl.close();
|
|
306
|
+
}
|
|
408
307
|
|
|
409
|
-
|
|
410
|
-
|
|
308
|
+
// Mark as user-confirmed
|
|
309
|
+
config._configured = true;
|
|
310
|
+
writeConfig(projectDir, config);
|
|
311
|
+
success(`Saved ${bold('.openclaw/config.json')}`);
|
|
312
|
+
console.log();
|
|
313
|
+
}
|
|
411
314
|
|
|
412
|
-
|
|
413
|
-
info(`${available.local.length} local model(s) detected via Ollama`);
|
|
414
|
-
} else {
|
|
415
|
-
warn('No local models found. Install Ollama and pull a model for free persistent agents:');
|
|
416
|
-
console.log(` ${dim('$')} ${bold('ollama pull llama3.2')}`);
|
|
417
|
-
}
|
|
418
|
-
if (available.hasApiKey) {
|
|
419
|
-
info('API key configured for cloud models');
|
|
420
|
-
}
|
|
421
|
-
console.log();
|
|
315
|
+
// ─── Agents Flow (enable/disable + model selection) ─────────
|
|
422
316
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
console.log();
|
|
317
|
+
async function runAgents(projectDir, config, parsed) {
|
|
318
|
+
const available = await detectModels();
|
|
426
319
|
|
|
320
|
+
if (available.local.length > 0) {
|
|
321
|
+
info(`${available.local.length} local model(s) detected via Ollama`);
|
|
322
|
+
} else {
|
|
323
|
+
warn('No local models found. Install Ollama and pull a model for free persistent agents:');
|
|
324
|
+
console.log(` ${dim('$')} ${bold('ollama pull llama3.2')}`);
|
|
325
|
+
}
|
|
326
|
+
if (available.hasApiKey) {
|
|
327
|
+
info('API key configured for cloud models');
|
|
328
|
+
}
|
|
329
|
+
console.log();
|
|
330
|
+
|
|
331
|
+
divider('Agents');
|
|
332
|
+
console.log(` ${dim('Enable/disable each agent and pick a model.')}`);
|
|
333
|
+
console.log();
|
|
334
|
+
|
|
335
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
336
|
+
|
|
337
|
+
try {
|
|
427
338
|
for (const { name, agentMd } of parsed) {
|
|
428
339
|
const existingAgent = config.agents?.[name];
|
|
429
340
|
const currentEnabled = existingAgent?.enabled !== false;
|
|
@@ -443,43 +354,41 @@ export async function persistent({ stop = false, reconfigure = false } = {}) {
|
|
|
443
354
|
|
|
444
355
|
config.agents[name] = { enabled, model };
|
|
445
356
|
|
|
446
|
-
const status = enabled ? green('
|
|
357
|
+
const status = enabled ? green('✓ enabled') : dim('✗ disabled');
|
|
447
358
|
const modelLabel = enabled ? `, ${modelShortName(model)}` : '';
|
|
448
359
|
console.log(` ${status}${modelLabel}`);
|
|
449
360
|
console.log();
|
|
450
361
|
}
|
|
451
|
-
|
|
452
|
-
configDirty = true;
|
|
453
362
|
} finally {
|
|
454
363
|
rl.close();
|
|
455
|
-
// Only save if we completed configuration
|
|
456
|
-
if (configDirty) {
|
|
457
|
-
writeConfig(projectDir, config);
|
|
458
|
-
success(`Saved ${bold('.openclaw/config.json')}`);
|
|
459
|
-
console.log();
|
|
460
|
-
}
|
|
461
364
|
}
|
|
462
365
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
printAgentLine(name, agentMd, config, statusInfo);
|
|
467
|
-
}
|
|
366
|
+
config._configured = true;
|
|
367
|
+
writeConfig(projectDir, config);
|
|
368
|
+
success(`Saved ${bold('.openclaw/config.json')}`);
|
|
468
369
|
console.log();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ─── Start Flow ─────────────────────────────────────────────
|
|
469
373
|
|
|
374
|
+
async function runStart(projectDir, parsed, config, soulPath) {
|
|
470
375
|
const enabledAgents = parsed.filter(a => isAgentEnabled(a.name, config));
|
|
471
|
-
|
|
472
|
-
|
|
376
|
+
const runningNames = new Set(listPids(projectDir).map(p => p.name));
|
|
377
|
+
const startable = enabledAgents.filter(a => !runningNames.has(a.name));
|
|
378
|
+
|
|
379
|
+
if (startable.length === 0 && runningNames.size > 0) {
|
|
380
|
+
info('All enabled agents are already running.');
|
|
473
381
|
console.log();
|
|
474
382
|
return;
|
|
475
383
|
}
|
|
476
384
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
385
|
+
if (startable.length === 0) {
|
|
386
|
+
info('No agents are enabled.');
|
|
387
|
+
console.log(` ${dim('Run')} ${bold('serpentstack persistent --agents')} ${dim('to enable agents.')}`);
|
|
388
|
+
console.log();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
481
391
|
|
|
482
|
-
async function launchAgents(projectDir, agentsToStart, config, soulPath) {
|
|
483
392
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
484
393
|
const toStart = [];
|
|
485
394
|
|
|
@@ -487,7 +396,7 @@ async function launchAgents(projectDir, agentsToStart, config, soulPath) {
|
|
|
487
396
|
divider('Launch');
|
|
488
397
|
console.log();
|
|
489
398
|
|
|
490
|
-
for (const agent of
|
|
399
|
+
for (const agent of startable) {
|
|
491
400
|
const model = getEffectiveModel(agent.name, agent.agentMd.meta, config);
|
|
492
401
|
const yes = await askYesNo(rl, `Start ${bold(agent.name)} ${dim(`(${modelShortName(model)})`)}?`, true);
|
|
493
402
|
if (yes) toStart.push(agent);
|
|
@@ -525,14 +434,11 @@ async function launchAgents(projectDir, agentsToStart, config, soulPath) {
|
|
|
525
434
|
const method = openInTerminal(`SerpentStack: ${name}`, openclawCmd, absProject);
|
|
526
435
|
|
|
527
436
|
if (method) {
|
|
528
|
-
// For terminal-spawned agents, record workspace path so we can track it
|
|
529
|
-
// The terminal process will create its own PID — we record ours as a marker
|
|
530
437
|
writePid(projectDir, name, -1); // -1 = terminal-managed
|
|
531
438
|
success(`${bold(name)} opened in ${method} ${dim(`(${modelShortName(effectiveModel)})`)}`);
|
|
532
439
|
started++;
|
|
533
440
|
} else {
|
|
534
|
-
|
|
535
|
-
warn(`No terminal detected \u2014 starting ${bold(name)} in background`);
|
|
441
|
+
warn(`No terminal detected — starting ${bold(name)} in background`);
|
|
536
442
|
const child = spawn('openclaw', ['start', '--workspace', absWorkspace], {
|
|
537
443
|
stdio: 'ignore',
|
|
538
444
|
detached: true,
|
|
@@ -554,9 +460,145 @@ async function launchAgents(projectDir, agentsToStart, config, soulPath) {
|
|
|
554
460
|
success(`${started} agent(s) launched — fangs out 🐍`);
|
|
555
461
|
console.log();
|
|
556
462
|
printBox('Manage agents', [
|
|
557
|
-
`${dim('$')} ${bold('serpentstack persistent')}
|
|
558
|
-
`${dim('$')} ${bold('serpentstack persistent --
|
|
559
|
-
`${dim('$')} ${bold('serpentstack persistent --
|
|
463
|
+
`${dim('$')} ${bold('serpentstack persistent')} ${dim('# status dashboard')}`,
|
|
464
|
+
`${dim('$')} ${bold('serpentstack persistent --start')} ${dim('# launch agents')}`,
|
|
465
|
+
`${dim('$')} ${bold('serpentstack persistent --stop')} ${dim('# stop all')}`,
|
|
466
|
+
`${dim('$')} ${bold('serpentstack persistent --configure')} ${dim('# edit project settings')}`,
|
|
467
|
+
`${dim('$')} ${bold('serpentstack persistent --agents')} ${dim('# change models')}`,
|
|
560
468
|
]);
|
|
561
469
|
}
|
|
562
470
|
}
|
|
471
|
+
|
|
472
|
+
// ─── Main Entry Point ───────────────────────────────────────
|
|
473
|
+
|
|
474
|
+
export async function persistent({ stop = false, configure = false, agents = false, start = false } = {}) {
|
|
475
|
+
const projectDir = process.cwd();
|
|
476
|
+
|
|
477
|
+
printHeader();
|
|
478
|
+
|
|
479
|
+
// ── Stop ──
|
|
480
|
+
if (stop) {
|
|
481
|
+
stopAllAgents(projectDir);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Preflight checks ──
|
|
486
|
+
const soulPath = join(projectDir, '.openclaw/SOUL.md');
|
|
487
|
+
if (!existsSync(soulPath)) {
|
|
488
|
+
error('No .openclaw/ workspace found.');
|
|
489
|
+
console.log(` Run ${bold('serpentstack skills')} first to download the workspace files.`);
|
|
490
|
+
console.log();
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const agentDirs = discoverAgents(projectDir);
|
|
495
|
+
if (agentDirs.length === 0) {
|
|
496
|
+
error('No agents found in .openclaw/agents/');
|
|
497
|
+
console.log(` Run ${bold('serpentstack skills')} to download the default agents,`);
|
|
498
|
+
console.log(` or create your own at ${bold('.openclaw/agents/<name>/AGENT.md')}`);
|
|
499
|
+
console.log();
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Check OpenClaw early
|
|
504
|
+
const hasOpenClaw = await which('openclaw');
|
|
505
|
+
if (!hasOpenClaw) {
|
|
506
|
+
warn('OpenClaw is not installed.');
|
|
507
|
+
console.log();
|
|
508
|
+
console.log(` ${dim('OpenClaw is the persistent agent runtime.')}`);
|
|
509
|
+
console.log(` ${dim('Install it first, then re-run this command:')}`);
|
|
510
|
+
console.log();
|
|
511
|
+
console.log(` ${dim('$')} ${bold('npm install -g openclaw@latest')}`);
|
|
512
|
+
console.log(` ${dim('$')} ${bold('serpentstack persistent')}`);
|
|
513
|
+
console.log();
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
cleanStalePids(projectDir);
|
|
518
|
+
|
|
519
|
+
// Parse agent definitions
|
|
520
|
+
const parsed = [];
|
|
521
|
+
for (const agent of agentDirs) {
|
|
522
|
+
try {
|
|
523
|
+
const agentMd = parseAgentMd(agent.agentMdPath);
|
|
524
|
+
parsed.push({ ...agent, agentMd });
|
|
525
|
+
} catch (err) {
|
|
526
|
+
warn(`Skipping ${bold(agent.name)}: ${err.message}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (parsed.length === 0) {
|
|
530
|
+
error('No valid AGENT.md files found.');
|
|
531
|
+
console.log();
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Load config
|
|
536
|
+
let config = readConfig(projectDir) || { project: {}, agents: {} };
|
|
537
|
+
const isConfigured = !!config._configured;
|
|
538
|
+
|
|
539
|
+
// ── Explicit flag: --configure ──
|
|
540
|
+
if (configure) {
|
|
541
|
+
await runConfigure(projectDir, config, soulPath);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ── Explicit flag: --agents ──
|
|
546
|
+
if (agents) {
|
|
547
|
+
config = readConfig(projectDir) || config; // re-read in case --configure was run first
|
|
548
|
+
await runAgents(projectDir, config, parsed);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ── Explicit flag: --start ──
|
|
553
|
+
if (start) {
|
|
554
|
+
await runStart(projectDir, parsed, config, soulPath);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ── Default: bare `serpentstack persistent` ──
|
|
559
|
+
if (isConfigured) {
|
|
560
|
+
// Already set up — show dashboard
|
|
561
|
+
printStatusDashboard(config, parsed, projectDir);
|
|
562
|
+
|
|
563
|
+
const enabledAgents = parsed.filter(a => isAgentEnabled(a.name, config));
|
|
564
|
+
const runningNames = new Set(listPids(projectDir).map(p => p.name));
|
|
565
|
+
const startable = enabledAgents.filter(a => !runningNames.has(a.name));
|
|
566
|
+
|
|
567
|
+
if (startable.length === 0 && runningNames.size > 0) {
|
|
568
|
+
info('All enabled agents are running.');
|
|
569
|
+
} else if (startable.length === 0) {
|
|
570
|
+
info('No agents are enabled.');
|
|
571
|
+
} else {
|
|
572
|
+
info(`${startable.length} agent(s) ready to start.`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
console.log();
|
|
576
|
+
printBox('Commands', [
|
|
577
|
+
`${dim('$')} ${bold('serpentstack persistent --start')} ${dim('# launch agents')}`,
|
|
578
|
+
`${dim('$')} ${bold('serpentstack persistent --stop')} ${dim('# stop all')}`,
|
|
579
|
+
`${dim('$')} ${bold('serpentstack persistent --configure')} ${dim('# edit project settings')}`,
|
|
580
|
+
`${dim('$')} ${bold('serpentstack persistent --agents')} ${dim('# change models')}`,
|
|
581
|
+
]);
|
|
582
|
+
console.log();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ── First-time setup: full walkthrough ──
|
|
587
|
+
info('First-time setup — let\'s configure your project and agents.');
|
|
588
|
+
console.log();
|
|
589
|
+
|
|
590
|
+
// Step 1: Project settings
|
|
591
|
+
await runConfigure(projectDir, config, soulPath);
|
|
592
|
+
|
|
593
|
+
// Re-read config (runConfigure saved it)
|
|
594
|
+
config = readConfig(projectDir) || config;
|
|
595
|
+
|
|
596
|
+
// Step 2: Agent settings
|
|
597
|
+
await runAgents(projectDir, config, parsed);
|
|
598
|
+
|
|
599
|
+
// Re-read config (runAgents saved it)
|
|
600
|
+
config = readConfig(projectDir) || config;
|
|
601
|
+
|
|
602
|
+
// Step 3: Launch
|
|
603
|
+
await runStart(projectDir, parsed, config, soulPath);
|
|
604
|
+
}
|
|
@@ -139,11 +139,10 @@ export async function skillsInit({ force = false } = {}) {
|
|
|
139
139
|
]);
|
|
140
140
|
|
|
141
141
|
printBox('Want persistent background agents too?', [
|
|
142
|
-
`${dim('$')} ${bold('serpentstack persistent')}`,
|
|
142
|
+
`${dim('$')} ${bold('serpentstack persistent')} ${dim('# first-time setup walkthrough')}`,
|
|
143
143
|
'',
|
|
144
|
-
`${dim('
|
|
145
|
-
`${dim('
|
|
146
|
-
`${dim('run and choose local or cloud models.')}`,
|
|
144
|
+
`${dim('Configures your project, picks models, and launches')}`,
|
|
145
|
+
`${dim('agents — each in its own terminal window.')}`,
|
|
147
146
|
]);
|
|
148
147
|
console.log();
|
|
149
148
|
}
|