neuvikon 0.2.18 → 0.2.19
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 +10 -7
- package/dist/index.mjs +127 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,13 +55,16 @@ pbpaste | npx neuvikon start
|
|
|
55
55
|
npx neuvikon start --tickets EWWQ-1,EWWQ-2,EWWQ-3 --spawn
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
If [Herdr](https://herdr.dev) is installed, every ticket in the batch lands as
|
|
59
|
+
its own tab in one Herdr workspace instead of separate OS windows — Herdr's
|
|
60
|
+
own UI then shows each agent's live working/blocked/done state. Otherwise it
|
|
61
|
+
opens one terminal per ticket the usual way (a tmux window if you're already
|
|
62
|
+
in tmux, an iTerm2 or Terminal.app tab on macOS, a Windows Terminal tab on
|
|
63
|
+
Windows), each `cd`'d into this directory. Either way, if a coding agent CLI
|
|
64
|
+
is installed (Claude Code, Cursor, Codex) it's launched with that ticket's
|
|
65
|
+
full text as its first prompt — same substance a manual paste would give it.
|
|
66
|
+
With more than one installed, you're asked once which to use and that choice
|
|
67
|
+
is remembered (`--agent <name>` overrides it any time). Nothing is tracked
|
|
65
68
|
automatically, even then: a session only scopes safely once an agent is
|
|
66
69
|
actually running, so the launched agent runs `npx neuvikon start --ticket
|
|
67
70
|
EWWQ-1` itself once it's up, via its rules file, same as a manual paste
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { chmodSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { delimiter, dirname, join } from "node:path";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
5
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
//#region src/api.ts
|
|
8
8
|
var ApiError = class extends Error {
|
|
@@ -1262,6 +1262,116 @@ async function resolveAgentCli(flags) {
|
|
|
1262
1262
|
return picked;
|
|
1263
1263
|
}
|
|
1264
1264
|
//#endregion
|
|
1265
|
+
//#region src/herdr.ts
|
|
1266
|
+
/** Herdr's `--kind` values for the agents we already know how to pick between. */
|
|
1267
|
+
const HERDR_KIND = {
|
|
1268
|
+
"claude-code": "claude",
|
|
1269
|
+
codex: "codex",
|
|
1270
|
+
cursor: "cursor"
|
|
1271
|
+
};
|
|
1272
|
+
function herdrAvailable() {
|
|
1273
|
+
return isOnPath("herdr");
|
|
1274
|
+
}
|
|
1275
|
+
/** Every mutating Herdr socket-API command prints one JSON object; a failure prints to stderr instead. */
|
|
1276
|
+
function runHerdr(args) {
|
|
1277
|
+
try {
|
|
1278
|
+
const output = execFileSync("herdr", args, {
|
|
1279
|
+
encoding: "utf8",
|
|
1280
|
+
stdio: [
|
|
1281
|
+
"ignore",
|
|
1282
|
+
"pipe",
|
|
1283
|
+
"ignore"
|
|
1284
|
+
]
|
|
1285
|
+
});
|
|
1286
|
+
return JSON.parse(output);
|
|
1287
|
+
} catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Herdr's `agent start` requires a name matching `[a-z][a-z0-9_-]{0,31}`,
|
|
1293
|
+
* unique among live agents. A ticket ref (`NS-60`) lowercases straight into
|
|
1294
|
+
* that shape, and reusing the ref as the name is what makes `herdr agent
|
|
1295
|
+
* get NS-60`-style lookups obvious later.
|
|
1296
|
+
*/
|
|
1297
|
+
function agentName(ref) {
|
|
1298
|
+
return ref.toLowerCase();
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* One Herdr workspace per `--spawn` call, one tab per ticket, so a whole
|
|
1302
|
+
* batch shows up together in Herdr's own UI — with its live
|
|
1303
|
+
* working/blocked/done state per agent — instead of scattered across OS
|
|
1304
|
+
* windows. The first successful ref seeds the workspace; every later ref in
|
|
1305
|
+
* the same batch adds a tab to it.
|
|
1306
|
+
*/
|
|
1307
|
+
var HerdrBatch = class {
|
|
1308
|
+
workspaceId = null;
|
|
1309
|
+
openPane(ref, cwd) {
|
|
1310
|
+
if (!this.workspaceId) {
|
|
1311
|
+
const created = runHerdr([
|
|
1312
|
+
"workspace",
|
|
1313
|
+
"create",
|
|
1314
|
+
"--cwd",
|
|
1315
|
+
cwd,
|
|
1316
|
+
"--label",
|
|
1317
|
+
ref,
|
|
1318
|
+
"--focus"
|
|
1319
|
+
]);
|
|
1320
|
+
if (!created) return null;
|
|
1321
|
+
this.workspaceId = created.result.workspace.workspace_id;
|
|
1322
|
+
return created.result.root_pane.pane_id;
|
|
1323
|
+
}
|
|
1324
|
+
return runHerdr([
|
|
1325
|
+
"tab",
|
|
1326
|
+
"create",
|
|
1327
|
+
"--workspace",
|
|
1328
|
+
this.workspaceId,
|
|
1329
|
+
"--cwd",
|
|
1330
|
+
cwd,
|
|
1331
|
+
"--label",
|
|
1332
|
+
ref,
|
|
1333
|
+
"--focus"
|
|
1334
|
+
])?.result.root_pane.pane_id ?? null;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* True once this ref has a live Herdr pane — even if starting an agent in
|
|
1338
|
+
* it failed, in which case the pane still exists with the same
|
|
1339
|
+
* print-and-wait fallback a plain spawned terminal gets. False only means
|
|
1340
|
+
* Herdr itself couldn't be reached, and the caller should fall back to
|
|
1341
|
+
* opening a native OS terminal for this ref instead.
|
|
1342
|
+
*/
|
|
1343
|
+
spawn(ref, cwd, agent, ticketText) {
|
|
1344
|
+
const paneId = this.openPane(ref, cwd);
|
|
1345
|
+
if (!paneId) return false;
|
|
1346
|
+
if (agent && ticketText) {
|
|
1347
|
+
if (runHerdr([
|
|
1348
|
+
"agent",
|
|
1349
|
+
"start",
|
|
1350
|
+
agentName(ref),
|
|
1351
|
+
"--kind",
|
|
1352
|
+
HERDR_KIND[agent.id],
|
|
1353
|
+
"--pane",
|
|
1354
|
+
paneId
|
|
1355
|
+
])) {
|
|
1356
|
+
runHerdr([
|
|
1357
|
+
"agent",
|
|
1358
|
+
"prompt",
|
|
1359
|
+
agentName(ref),
|
|
1360
|
+
ticketText
|
|
1361
|
+
]);
|
|
1362
|
+
return true;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
runHerdr([
|
|
1366
|
+
"pane",
|
|
1367
|
+
"run",
|
|
1368
|
+
paneId,
|
|
1369
|
+
`echo 'Ticket: ${ref}' && echo 'Paste it into your agent, or run: npx neuvikon start --ticket ${ref}'`
|
|
1370
|
+
]);
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
//#endregion
|
|
1265
1375
|
//#region src/terminal.ts
|
|
1266
1376
|
function shQuote(value) {
|
|
1267
1377
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -1416,14 +1526,6 @@ async function fetchTicketText(credentials, ref) {
|
|
|
1416
1526
|
return null;
|
|
1417
1527
|
}
|
|
1418
1528
|
}
|
|
1419
|
-
async function buildLaunch(agent, credentials, ref) {
|
|
1420
|
-
const text = await fetchTicketText(credentials, ref);
|
|
1421
|
-
if (!text) return null;
|
|
1422
|
-
return {
|
|
1423
|
-
binary: agent.binary,
|
|
1424
|
-
args: agent.buildArgs(text)
|
|
1425
|
-
};
|
|
1426
|
-
}
|
|
1427
1529
|
/**
|
|
1428
1530
|
* Opens one terminal per ref and stops there — it deliberately never calls
|
|
1429
1531
|
* `start` itself. A session file written outside of a running agent has no
|
|
@@ -1434,13 +1536,27 @@ async function buildLaunch(agent, credentials, ref) {
|
|
|
1434
1536
|
* as its first prompt, but this never calls `start` itself — the agent does,
|
|
1435
1537
|
* once it's actually running, via the rules file, exactly as if the ticket
|
|
1436
1538
|
* had been pasted in by hand.
|
|
1539
|
+
*
|
|
1540
|
+
* Herdr, when installed, wins over a native OS terminal: it's a multiplexer
|
|
1541
|
+
* built for exactly this (multiple coding agents, one UI, live
|
|
1542
|
+
* working/blocked/done state), so a whole `--tickets` batch lands as one
|
|
1543
|
+
* Herdr workspace with one tab per ticket instead of scattered app windows.
|
|
1437
1544
|
*/
|
|
1438
1545
|
async function spawnMode(flags, credentials, refs) {
|
|
1439
1546
|
const cwd = process.cwd();
|
|
1440
1547
|
const agent = await resolveAgentCli(flags);
|
|
1441
|
-
|
|
1548
|
+
const herdr = herdrAvailable() ? new HerdrBatch() : null;
|
|
1549
|
+
info(`Opening ${refs.length} terminal${refs.length === 1 ? "" : "s"}${herdr ? " in Herdr" : ` in ${cwd}`}...`);
|
|
1442
1550
|
for (const ref of refs) {
|
|
1443
|
-
const
|
|
1551
|
+
const ticketText = agent ? await fetchTicketText(credentials, ref) : null;
|
|
1552
|
+
if (herdr?.spawn(ref, cwd, agent, ticketText)) {
|
|
1553
|
+
success(`${bold(ref)} — ${agent && ticketText ? `${agent.label} starting in Herdr` : "opened in Herdr"}`);
|
|
1554
|
+
continue;
|
|
1555
|
+
}
|
|
1556
|
+
const launch = agent && ticketText ? {
|
|
1557
|
+
binary: agent.binary,
|
|
1558
|
+
args: agent.buildArgs(ticketText)
|
|
1559
|
+
} : null;
|
|
1444
1560
|
const startingLabel = launch && agent ? agent.label : null;
|
|
1445
1561
|
if (spawnTerminal({
|
|
1446
1562
|
ref,
|