c8ctl-plugin-nano 1.24.2 → 1.25.1
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 +8 -3
- package/c8ctl-plugin.js +95 -14
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -479,6 +479,7 @@ c8ctl nano supervisor
|
|
|
479
479
|
c8ctl nano supervisor status # id, state, pid, restarts, uptime
|
|
480
480
|
c8ctl nano supervisor add reviewer --max-parallel 2 # add + spawn a worker (forwards work flags)
|
|
481
481
|
c8ctl nano supervisor add reviewer --name reviewer-2 # a SECOND reviewer, named so it stays distinct
|
|
482
|
+
c8ctl nano supervisor add reviewer --instances 3 # add 3 distinct auto-named reviewers in one call
|
|
482
483
|
c8ctl nano supervisor restart reviewer # by worker id or profile name
|
|
483
484
|
c8ctl nano supervisor remove coder # stop + drop a worker (also: `all`)
|
|
484
485
|
c8ctl nano supervisor logs reviewer --follow # tail a worker's log (or the daemon's)
|
|
@@ -489,9 +490,13 @@ Each worker has a **name** — its supervisor id and the broker `workerName` it
|
|
|
489
490
|
registers under. Pass `--name` on `supervisor add` (or `work`) to set it;
|
|
490
491
|
omit it and one is auto-generated as `‹host›-‹profile›-‹random›`, so you can
|
|
491
492
|
run **several instances of the same profile** and they stay distinct
|
|
492
|
-
end-to-end (status, logs, and at the broker).
|
|
493
|
-
|
|
494
|
-
|
|
493
|
+
end-to-end (status, logs, and at the broker). To scale a hire to several
|
|
494
|
+
instances in one call, pass `--instances N` on `supervisor add` — it spawns N
|
|
495
|
+
distinct auto-named workers of the profile at once (default 1, capped per call).
|
|
496
|
+
Because each instance needs its own distinct name, `--instances N` (for N > 1)
|
|
497
|
+
cannot be combined with `--name`; omit `--name` to let them auto-name.
|
|
498
|
+
`restart`/`remove` accept either a worker id **or** a profile name — targeting a
|
|
499
|
+
profile affects *every* instance of it.
|
|
495
500
|
|
|
496
501
|
Each worker takes the **same flags as `nano work`** (`--max-parallel`,
|
|
497
502
|
`--recovery-window`, `--idle-timeout`, `--job-timeout`, `--poll-timeout`,
|
package/c8ctl-plugin.js
CHANGED
|
@@ -1730,13 +1730,14 @@ function resolveAssignInputs(req, flags) {
|
|
|
1730
1730
|
* capabilities are the remaining positionals and/or `--capabilities a,b`.
|
|
1731
1731
|
* Capabilities are unioned with the profile's existing set (additive; assign
|
|
1732
1732
|
* never removes a role) and the updated rank×capability job-type matrix is
|
|
1733
|
-
* printed.
|
|
1733
|
+
* printed. Running workers hot-reload the new job types within ~1.5s — no
|
|
1734
|
+
* restart needed.
|
|
1734
1735
|
*/
|
|
1735
1736
|
async function assignCapabilities(req, flags) {
|
|
1736
1737
|
const logger = getLogger();
|
|
1737
1738
|
const { name, incomingRaw } = resolveAssignInputs(req, flags);
|
|
1738
1739
|
if (!name) {
|
|
1739
|
-
logger.error('Usage: c8ctl nano assign <profileName> [
|
|
1740
|
+
logger.error('Usage: c8ctl nano assign <profileName> <cap[,cap...]> [--name <n>] [--capabilities <a,b>]');
|
|
1740
1741
|
logger.info('Grant new capabilities to an existing hire. List profiles with: c8ctl nano hire --list');
|
|
1741
1742
|
process.exit(1);
|
|
1742
1743
|
}
|
|
@@ -1747,7 +1748,7 @@ async function assignCapabilities(req, flags) {
|
|
|
1747
1748
|
|
|
1748
1749
|
if (normalizeCapabilities(incomingRaw).length === 0) {
|
|
1749
1750
|
logger.error('Provide at least one capability to assign.');
|
|
1750
|
-
logger.info(`Example: c8ctl nano assign ${name} code-review
|
|
1751
|
+
logger.info(`Example: c8ctl nano assign ${name} code-review,testing`);
|
|
1751
1752
|
process.exit(1);
|
|
1752
1753
|
}
|
|
1753
1754
|
|
|
@@ -1778,7 +1779,7 @@ async function assignCapabilities(req, flags) {
|
|
|
1778
1779
|
logger.info(`Assigned to "${name}" [${profile.rank}]: +${added.join(', ')}`);
|
|
1779
1780
|
logger.info(` capabilities: ${profile.capabilities.join(', ')}`);
|
|
1780
1781
|
logger.info(` job types (${matrix.length}): ${matrix.join(' ')}`);
|
|
1781
|
-
logger.info(`
|
|
1782
|
+
logger.info(`Running workers pick this up automatically within ~1.5s — no restart needed.`);
|
|
1782
1783
|
}
|
|
1783
1784
|
|
|
1784
1785
|
/**
|
|
@@ -3701,6 +3702,58 @@ function extractNameFlag(parts) {
|
|
|
3701
3702
|
return { name: name != null && name.trim() !== '' ? name.trim() : undefined, rest };
|
|
3702
3703
|
}
|
|
3703
3704
|
|
|
3705
|
+
/**
|
|
3706
|
+
* Upper bound on how many workers a single `supervisor add --instances N` may
|
|
3707
|
+
* spawn. Not a fleet cap (add again to grow further) — a typo guard, so a
|
|
3708
|
+
* fat-fingered `--instances 100000` can't fork-bomb the host in one keystroke.
|
|
3709
|
+
*/
|
|
3710
|
+
const MAX_ADD_INSTANCES = 64;
|
|
3711
|
+
|
|
3712
|
+
/**
|
|
3713
|
+
* Parse the `--instances N` count for `supervisor add`. Accepts undefined/blank
|
|
3714
|
+
* (defaults to 1), and a whole number in `[1, MAX_ADD_INSTANCES]`. Rejects
|
|
3715
|
+
* non-integers, zero/negatives, and anything over the cap with a clear message.
|
|
3716
|
+
* When a flag is repeated the last occurrence wins (arrays are tolerated).
|
|
3717
|
+
* Returns `{ count }` on success or `{ error }` on rejection. Pure.
|
|
3718
|
+
*/
|
|
3719
|
+
function parseInstancesCount(raw) {
|
|
3720
|
+
const v = Array.isArray(raw) ? raw[raw.length - 1] : raw;
|
|
3721
|
+
if (v === undefined || v === null || (typeof v === 'string' && v.trim() === '')) {
|
|
3722
|
+
return { count: 1 };
|
|
3723
|
+
}
|
|
3724
|
+
const s = String(v).trim();
|
|
3725
|
+
if (!/^\d+$/.test(s)) return { error: `Invalid --instances "${v}": use a whole number between 1 and ${MAX_ADD_INSTANCES}.` };
|
|
3726
|
+
const n = Number.parseInt(s, 10);
|
|
3727
|
+
if (n < 1) return { error: `Invalid --instances "${v}": use a whole number between 1 and ${MAX_ADD_INSTANCES}.` };
|
|
3728
|
+
if (n > MAX_ADD_INSTANCES) return { error: `--instances ${n} exceeds the ${MAX_ADD_INSTANCES}-per-command cap; run "supervisor add" again to add more.` };
|
|
3729
|
+
return { count: n };
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
/**
|
|
3733
|
+
* Split `--instances N` / `--instances=N` out of a raw token list, returning
|
|
3734
|
+
* `{ count, rest, error }` where `rest` is the remaining work flags (with the
|
|
3735
|
+
* flag removed so it is never forwarded to `nano work`). Used by the interactive
|
|
3736
|
+
* console's `add`, whose tokens aren't parsed by the CLI flag layer. Last
|
|
3737
|
+
* occurrence wins; a trailing `--instances` with no value is treated as absent
|
|
3738
|
+
* (count 1), mirroring `extractNameFlag`. Pure.
|
|
3739
|
+
*/
|
|
3740
|
+
function extractInstancesFlag(parts) {
|
|
3741
|
+
const rest = [];
|
|
3742
|
+
let raw;
|
|
3743
|
+
const list = Array.isArray(parts) ? parts : [];
|
|
3744
|
+
for (let i = 0; i < list.length; i++) {
|
|
3745
|
+
const tok = String(list[i]);
|
|
3746
|
+
const eq = /^--instances=(.*)$/.exec(tok);
|
|
3747
|
+
if (eq) { raw = eq[1]; continue; }
|
|
3748
|
+
if (tok === '--instances') {
|
|
3749
|
+
if (i + 1 < list.length) { raw = String(list[i + 1]); i++; }
|
|
3750
|
+
continue;
|
|
3751
|
+
}
|
|
3752
|
+
rest.push(tok);
|
|
3753
|
+
}
|
|
3754
|
+
return { ...parseInstancesCount(raw), rest };
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3704
3757
|
/** Assign a unique, stable worker id from a profile name (pure). */
|
|
3705
3758
|
function supervisorWorkerId(profile, taken) {
|
|
3706
3759
|
const base = String(profile || '').trim() || 'worker';
|
|
@@ -4479,14 +4532,31 @@ async function supervisorAddCmd(req, flags) {
|
|
|
4479
4532
|
const logger = getLogger();
|
|
4480
4533
|
// The positional profile is what runs; `--name` names this worker instance
|
|
4481
4534
|
// (forwarded to the child as `nano work … --name`, and used as its supervisor
|
|
4482
|
-
// id). Omit `--name` to auto-generate ‹host›-‹profile›-‹random›.
|
|
4535
|
+
// id). Omit `--name` to auto-generate ‹host›-‹profile›-‹random›. `--instances N`
|
|
4536
|
+
// spawns N distinct auto-named workers of the profile in one call.
|
|
4483
4537
|
const profile = req.positional[1];
|
|
4484
|
-
if (!profile) { logger.error('Usage: c8ctl nano supervisor add <profile> [--name <worker>] [work flags]'); process.exit(1); }
|
|
4538
|
+
if (!profile) { logger.error('Usage: c8ctl nano supervisor add <profile> [--name <worker>] [--instances <n>] [work flags]'); process.exit(1); }
|
|
4539
|
+
const { count, error } = parseInstancesCount(flags?.instances);
|
|
4540
|
+
if (error) { logger.error(error); process.exit(1); }
|
|
4485
4541
|
const name = flags?.name ? String(flags.name).trim() : undefined;
|
|
4542
|
+
// A single `--name` can't apply to several distinct workers (each needs its
|
|
4543
|
+
// own broker workerName / supervisor id), so reject the combination and steer
|
|
4544
|
+
// the operator to auto-naming.
|
|
4545
|
+
if (name && count > 1) {
|
|
4546
|
+
logger.error('--name cannot be combined with --instances > 1 (each instance needs a distinct name); omit --name to auto-name them.');
|
|
4547
|
+
process.exit(1);
|
|
4548
|
+
}
|
|
4486
4549
|
await startSupervisorDaemon();
|
|
4487
|
-
const
|
|
4488
|
-
|
|
4489
|
-
|
|
4550
|
+
const workArgs = reconstructWorkArgs(flags);
|
|
4551
|
+
let added = 0;
|
|
4552
|
+
let failed = 0;
|
|
4553
|
+
for (let i = 0; i < count; i++) {
|
|
4554
|
+
const res = await supervisorRequest({ op: 'add', profile, name, args: workArgs });
|
|
4555
|
+
if (res.ok) { added++; logger.info(`Added worker "${res.worker.id}" (profile ${profile}); pid ${res.worker.pid ?? 'starting'}.`); }
|
|
4556
|
+
else { failed++; logger.error(`Could not add "${profile}": ${res.error}`); }
|
|
4557
|
+
}
|
|
4558
|
+
if (count > 1) logger.info(`Added ${added}/${count} instance(s) of "${profile}".`);
|
|
4559
|
+
if (failed > 0) process.exit(1);
|
|
4490
4560
|
}
|
|
4491
4561
|
|
|
4492
4562
|
async function supervisorRemoveCmd(req) {
|
|
@@ -4636,16 +4706,21 @@ async function attachSupervisorConsole(state) {
|
|
|
4636
4706
|
switch (cmd) {
|
|
4637
4707
|
case '': break;
|
|
4638
4708
|
case 'help':
|
|
4639
|
-
out('Commands: status | add <profile> [--name <worker>] [work flags] |');
|
|
4709
|
+
out('Commands: status | add <profile> [--name <worker>] [--instances <n>] [work flags] |');
|
|
4640
4710
|
out(' remove <id|profile|all> | restart <id|profile|all> |');
|
|
4641
4711
|
out(' logs [id] | detach | stop | help');
|
|
4642
4712
|
break;
|
|
4643
4713
|
case 'status': sock.write(encodeFrame({ op: 'status' })); break;
|
|
4644
4714
|
case 'add': {
|
|
4645
4715
|
const profile = parts.shift();
|
|
4646
|
-
if (!profile) { out('usage: add <profile> [--name <worker>] [work flags]'); break; }
|
|
4716
|
+
if (!profile) { out('usage: add <profile> [--name <worker>] [--instances <n>] [work flags]'); break; }
|
|
4647
4717
|
const { name, rest } = extractNameFlag(parts);
|
|
4648
|
-
|
|
4718
|
+
const { count, rest: workArgs, error } = extractInstancesFlag(rest);
|
|
4719
|
+
if (error) { out(error); break; }
|
|
4720
|
+
if (name && count > 1) { out('--name cannot be combined with --instances > 1; omit --name to auto-name them.'); break; }
|
|
4721
|
+
for (let i = 0; i < count; i++) {
|
|
4722
|
+
sock.write(encodeFrame({ op: 'add', profile, name, args: workArgs }));
|
|
4723
|
+
}
|
|
4649
4724
|
break;
|
|
4650
4725
|
}
|
|
4651
4726
|
case 'remove': case 'rm': {
|
|
@@ -6107,6 +6182,8 @@ export {
|
|
|
6107
6182
|
isValidWorkerName,
|
|
6108
6183
|
randomNameSuffix,
|
|
6109
6184
|
extractNameFlag,
|
|
6185
|
+
parseInstancesCount,
|
|
6186
|
+
extractInstancesFlag,
|
|
6110
6187
|
redactWorkArgs,
|
|
6111
6188
|
supervisorBackoffMs,
|
|
6112
6189
|
encodeFrame,
|
|
@@ -6120,6 +6197,7 @@ export {
|
|
|
6120
6197
|
runSupervisorDaemon,
|
|
6121
6198
|
startSupervisorDaemon,
|
|
6122
6199
|
supervisorRequest,
|
|
6200
|
+
supervisorAddCmd,
|
|
6123
6201
|
runningSupervisor,
|
|
6124
6202
|
readSupervisorState,
|
|
6125
6203
|
clearSupervisorState,
|
|
@@ -6163,12 +6241,14 @@ export const metadata = {
|
|
|
6163
6241
|
{ command: 'c8ctl nano hire --name coder --rank senior --command copilot --env COPILOT_ENABLE_ALL_TOOLS=1', description: 'Persist a harness startup env var (e.g. permissions) on the profile' },
|
|
6164
6242
|
{ command: 'c8ctl nano hire --list', description: 'List hired agent profiles' },
|
|
6165
6243
|
{ command: 'c8ctl nano hire --name coder --rank senior --command "agent-harness" --sandbox docker --image ghcr.io/acme/agent:1', description: 'Create a profile that runs each job in a throwaway Docker container' },
|
|
6244
|
+
{ command: 'c8ctl nano assign reviewer code-review,testing', description: 'Grant more capabilities (comma-separated, like hire) to an existing hire — additive; running workers hot-reload it' },
|
|
6166
6245
|
{ command: 'c8ctl nano work reviewer', description: 'Spawn Nano job workers for the "reviewer" profile and poll for work' },
|
|
6167
6246
|
{ command: 'c8ctl nano work coder --sandbox docker --image ghcr.io/acme/agent:1', description: 'Run jobs in isolated containers with disk-hygiene reaping' },
|
|
6168
6247
|
{ command: 'c8ctl nano supervisor start --worker reviewer --worker coder', description: 'Start a detached supervisor managing several workers from one terminal' },
|
|
6169
6248
|
{ command: 'c8ctl nano supervisor', description: 'Attach an interactive console to the supervisor (detach with Ctrl-D, leaving it running)' },
|
|
6170
6249
|
{ command: 'c8ctl nano supervisor status', description: 'List supervised workers (pid, state, serviced job / idle, restarts, uptime) without the console' },
|
|
6171
6250
|
{ command: 'c8ctl nano supervisor add decider --max-parallel 2', description: 'Add a supervised worker (forwarding work flags) to the running supervisor' },
|
|
6251
|
+
{ command: 'c8ctl nano supervisor add reviewer --instances 3', description: 'Add 3 distinct auto-named instances of a profile in one call' },
|
|
6172
6252
|
{ command: 'c8ctl nano supervisor restart reviewer', description: 'Restart a supervised worker by id or profile' },
|
|
6173
6253
|
{ command: 'c8ctl nano supervisor stop', description: 'Stop the supervisor daemon and all its workers' },
|
|
6174
6254
|
],
|
|
@@ -6236,6 +6316,7 @@ export const commands = {
|
|
|
6236
6316
|
'poll-timeout': { type: 'string', description: 'work: broker long-poll window in ms each activateJobs request is held open (fewer reconnects → fewer transient connect errors); default 30000, 0 = broker default, negative = return immediately' },
|
|
6237
6317
|
'job-type': { type: 'string', multiple: true, description: 'work: extra job type to service alongside the rank×capability matrix (repeatable)' },
|
|
6238
6318
|
worker: { type: 'string', multiple: true, description: 'supervisor start: profile to launch as a supervised worker (repeatable)' },
|
|
6319
|
+
instances: { type: 'string', description: `supervisor add: spawn N distinct auto-named instances of the profile in one call (default 1, max ${MAX_ADD_INSTANCES}; cannot combine with --name)` },
|
|
6239
6320
|
attach: { type: 'boolean', description: 'supervisor start: attach the interactive console after starting the daemon' },
|
|
6240
6321
|
},
|
|
6241
6322
|
handler: async (args, flags) => {
|
|
@@ -6386,7 +6467,7 @@ function printUsage() {
|
|
|
6386
6467
|
console.log(' c8ctl nano config');
|
|
6387
6468
|
console.log(' c8ctl nano update [--check]');
|
|
6388
6469
|
console.log(' c8ctl nano hire [--name <n>] [--rank <r>] [--command <c>] [--arg <switch> ...] [--model <m>] [--capabilities <a,b>] [--sandbox none|docker|podman] [--image <ref>] [--env NAME=VALUE ...] [--list]');
|
|
6389
|
-
console.log(' c8ctl nano assign <profileName> [
|
|
6470
|
+
console.log(' c8ctl nano assign <profileName> <cap[,cap...]> [--name <n>] [--capabilities <a,b>]');
|
|
6390
6471
|
console.log(' c8ctl nano work <profileName> [--arg <switch> ...] [--max-parallel <n>] [--recovery-window <ms>] [--idle-timeout <ms>] [--job-timeout <ms>] [--poll-timeout <ms>] [--job-type <token> ...] [--sandbox none|docker|podman] [--image <ref>] [--env NAME=VALUE ...] [--secret-resolver host] [--min-free-mb <n>] [--clone-timeout <ms>] [--keep-runs] [--stream]');
|
|
6391
6472
|
console.log(' c8ctl nano supervisor [start|status|add|remove|restart|stop|logs|attach] ... (manage many workers from one terminal)');
|
|
6392
6473
|
console.log('');
|
|
@@ -6403,7 +6484,7 @@ function printUsage() {
|
|
|
6403
6484
|
console.log(' config Show current configuration and on-disk locations');
|
|
6404
6485
|
console.log(' update Pull the latest published nano release (--check to only report)');
|
|
6405
6486
|
console.log(' hire Create a CLI agent worker profile (rank + capabilities → job-type matrix)');
|
|
6406
|
-
console.log(' assign Grant new capabilities (roles) to an existing hire (additive)');
|
|
6487
|
+
console.log(' assign Grant new capabilities (roles) to an existing hire (additive; comma-separated; workers hot-reload)');
|
|
6407
6488
|
console.log(' work Run a hired profile as Nano job workers, polling for work until Ctrl-C');
|
|
6408
6489
|
console.log(' supervisor Run/manage a fleet of workers from one terminal (detachable console + non-interactive control)');
|
|
6409
6490
|
console.log('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"semantic-release": "^25.0.3"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.
|
|
51
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.
|
|
52
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.
|
|
53
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.
|
|
54
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.
|
|
55
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.
|
|
56
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.
|
|
50
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.25.1",
|
|
51
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.25.1",
|
|
52
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.25.1",
|
|
53
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.25.1",
|
|
54
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.25.1",
|
|
55
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.25.1",
|
|
56
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.25.1"
|
|
57
57
|
}
|
|
58
58
|
}
|