neuvikon 0.2.29 → 0.2.31
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/dist/index.mjs +121 -28
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -276,12 +276,12 @@ function fail(message, hint) {
|
|
|
276
276
|
}
|
|
277
277
|
//#endregion
|
|
278
278
|
//#region src/commands/board.ts
|
|
279
|
-
function requireCredentials$
|
|
279
|
+
function requireCredentials$5() {
|
|
280
280
|
const credentials = readCredentials();
|
|
281
281
|
if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
|
|
282
282
|
return credentials;
|
|
283
283
|
}
|
|
284
|
-
function resolveTicketRef$
|
|
284
|
+
function resolveTicketRef$5(flags) {
|
|
285
285
|
const ref = flags.get("ticket") ?? readSession()?.ticket;
|
|
286
286
|
if (!ref) fail("No ticket to look up a board for.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
|
|
287
287
|
return ref;
|
|
@@ -291,8 +291,8 @@ function resolveTicketRef$4(flags) {
|
|
|
291
291
|
* a column name — boards vary per project, and some have no "done" at all.
|
|
292
292
|
*/
|
|
293
293
|
async function board(flags) {
|
|
294
|
-
const credentials = requireCredentials$
|
|
295
|
-
const ticketRef = resolveTicketRef$
|
|
294
|
+
const credentials = requireCredentials$5();
|
|
295
|
+
const ticketRef = resolveTicketRef$5(flags);
|
|
296
296
|
let result;
|
|
297
297
|
try {
|
|
298
298
|
result = await post(credentials.url, "/api/agent/board", {
|
|
@@ -339,20 +339,20 @@ async function readStdin() {
|
|
|
339
339
|
}
|
|
340
340
|
//#endregion
|
|
341
341
|
//#region src/commands/comment.ts
|
|
342
|
-
function requireCredentials$
|
|
342
|
+
function requireCredentials$4() {
|
|
343
343
|
const credentials = readCredentials();
|
|
344
344
|
if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
|
|
345
345
|
return credentials;
|
|
346
346
|
}
|
|
347
|
-
function resolveTicketRef$
|
|
347
|
+
function resolveTicketRef$4(flags) {
|
|
348
348
|
const ref = flags.get("ticket") ?? readSession()?.ticket;
|
|
349
349
|
if (!ref) fail("No ticket to comment on.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
|
|
350
350
|
return ref;
|
|
351
351
|
}
|
|
352
352
|
/** A note on a ticket that doesn't move it — e.g. explaining a handoff mid-ticket. */
|
|
353
353
|
async function comment(flags) {
|
|
354
|
-
const credentials = requireCredentials$
|
|
355
|
-
const ticketRef = resolveTicketRef$
|
|
354
|
+
const credentials = requireCredentials$4();
|
|
355
|
+
const ticketRef = resolveTicketRef$4(flags);
|
|
356
356
|
const text = (flags.get("text") || await readStdin()).trim();
|
|
357
357
|
if (!text) fail("No comment text given.", "Pipe the comment in on stdin, or pass --text.");
|
|
358
358
|
try {
|
|
@@ -374,18 +374,18 @@ async function comment(flags) {
|
|
|
374
374
|
//#endregion
|
|
375
375
|
//#region src/commands/create.ts
|
|
376
376
|
const TYPES = /* @__PURE__ */ new Set(["feature", "bug"]);
|
|
377
|
-
const PRIORITIES = /* @__PURE__ */ new Set([
|
|
377
|
+
const PRIORITIES$1 = /* @__PURE__ */ new Set([
|
|
378
378
|
"low",
|
|
379
379
|
"medium",
|
|
380
380
|
"high",
|
|
381
381
|
"urgent"
|
|
382
382
|
]);
|
|
383
|
-
function requireCredentials$
|
|
383
|
+
function requireCredentials$3() {
|
|
384
384
|
const credentials = readCredentials();
|
|
385
385
|
if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
|
|
386
386
|
return credentials;
|
|
387
387
|
}
|
|
388
|
-
function resolveTicketRef$
|
|
388
|
+
function resolveTicketRef$3(flags) {
|
|
389
389
|
const ref = flags.get("ticket") ?? readSession()?.ticket;
|
|
390
390
|
if (!ref) fail("No ticket to file this against.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
|
|
391
391
|
return ref;
|
|
@@ -413,16 +413,18 @@ function resolveParentRef(flags, ticketRef) {
|
|
|
413
413
|
* own "+ sub-ticket" control.
|
|
414
414
|
*/
|
|
415
415
|
async function create(flags, positional) {
|
|
416
|
-
const credentials = requireCredentials$
|
|
417
|
-
const ticketRef = resolveTicketRef$
|
|
416
|
+
const credentials = requireCredentials$3();
|
|
417
|
+
const ticketRef = resolveTicketRef$3(flags);
|
|
418
418
|
const parentRef = resolveParentRef(flags, ticketRef);
|
|
419
419
|
const title = flags.get("title") ?? positional[0];
|
|
420
420
|
if (!title) fail("A ticket title is required.", `Pass --title "Invite emails aren't localized", or give it as the first argument.`);
|
|
421
421
|
const type = flags.get("type");
|
|
422
422
|
if (type && !TYPES.has(type)) fail(`Unknown type "${type}".`, "Expected feature or bug.");
|
|
423
423
|
const priority = flags.get("priority");
|
|
424
|
-
if (priority && !PRIORITIES.has(priority)) fail(`Unknown priority "${priority}".`, "Expected low, medium, high, or urgent.");
|
|
424
|
+
if (priority && !PRIORITIES$1.has(priority)) fail(`Unknown priority "${priority}".`, "Expected low, medium, high, or urgent.");
|
|
425
425
|
const description = (flags.get("description") || await readStdin()).trim() || void 0;
|
|
426
|
+
const assignee = flags.get("assignee");
|
|
427
|
+
const sprint = flags.get("sprint");
|
|
426
428
|
let result;
|
|
427
429
|
try {
|
|
428
430
|
result = await post(credentials.url, "/api/agent/tickets/create", {
|
|
@@ -432,7 +434,9 @@ async function create(flags, positional) {
|
|
|
432
434
|
description,
|
|
433
435
|
type,
|
|
434
436
|
priority,
|
|
435
|
-
parentRef
|
|
437
|
+
parentRef,
|
|
438
|
+
assignee,
|
|
439
|
+
sprint
|
|
436
440
|
});
|
|
437
441
|
} catch (error) {
|
|
438
442
|
if (error instanceof NetworkError) fail("Could not reach Neuvikon.");
|
|
@@ -733,6 +737,27 @@ function parsePayload(raw) {
|
|
|
733
737
|
}
|
|
734
738
|
}
|
|
735
739
|
/**
|
|
740
|
+
* What actually gets the model/effort switch to happen, versus the
|
|
741
|
+
* AGENTS.md wording alone: tested against real tickets, an agent reading
|
|
742
|
+
* "judge difficulty and switch model" in its rules file consistently read
|
|
743
|
+
* past it and started working at whatever model/effort it already had —
|
|
744
|
+
* the instruction competed with everything else in that file and lost.
|
|
745
|
+
*
|
|
746
|
+
* `UserPromptSubmit` can inject `additionalContext`, which Claude Code
|
|
747
|
+
* surfaces as a system-level reminder on the *same turn* the ticket lands,
|
|
748
|
+
* not as background file content the agent may or may not re-read. It's
|
|
749
|
+
* still just text — this can't force a `/model`/`/effort` call, only put it
|
|
750
|
+
* in front of the model at the one moment it's actually relevant instead of
|
|
751
|
+
* buried in a rules file next to a dozen other instructions.
|
|
752
|
+
*/
|
|
753
|
+
const MODEL_TRIAGE_CONTEXT = "A Neuvikon ticket just landed in this prompt. Before reading any file or running `start`: judge how difficult it actually is. If it's a small, mechanical change (button placement, a copy/label edit, a trivial styling tweak, a one-line config change), send `/model haiku` and `/effort low` now, as their own messages, before touching the ticket — there's no reason to spend a frontier model thinking hard about something this small. If it needs real design judgment, touches multiple systems, or you're unsure, stay on the current model and effort level instead.";
|
|
754
|
+
function hookResponse(additionalContext) {
|
|
755
|
+
return JSON.stringify({ hookSpecificOutput: {
|
|
756
|
+
hookEventName: "UserPromptSubmit",
|
|
757
|
+
additionalContext
|
|
758
|
+
} });
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
736
761
|
* The `UserPromptSubmit` counterpart to `start`. Unlike `start`, this runs on
|
|
737
762
|
* *every* prompt regardless of whether it mentions a ticket, so it must never
|
|
738
763
|
* fail the hook and block the user's message — no credentials, no ticket ref,
|
|
@@ -745,6 +770,7 @@ async function hookStart() {
|
|
|
745
770
|
const { prompt, transcriptPath } = parsePayload(await readStdin());
|
|
746
771
|
const [ref] = extractTicketRefs(prompt ?? "");
|
|
747
772
|
if (!ref) return;
|
|
773
|
+
process.stdout.write(hookResponse(MODEL_TRIAGE_CONTEXT));
|
|
748
774
|
const existing = readSession();
|
|
749
775
|
if (existing?.ticket === ref) return;
|
|
750
776
|
const transcript = transcriptPath ?? claudeTranscriptPath();
|
|
@@ -1272,12 +1298,12 @@ async function logout() {
|
|
|
1272
1298
|
}
|
|
1273
1299
|
//#endregion
|
|
1274
1300
|
//#region src/commands/move.ts
|
|
1275
|
-
function requireCredentials$
|
|
1301
|
+
function requireCredentials$2() {
|
|
1276
1302
|
const credentials = readCredentials();
|
|
1277
1303
|
if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
|
|
1278
1304
|
return credentials;
|
|
1279
1305
|
}
|
|
1280
|
-
function resolveTicketRef$
|
|
1306
|
+
function resolveTicketRef$2(flags) {
|
|
1281
1307
|
const ref = flags.get("ticket") ?? readSession()?.ticket;
|
|
1282
1308
|
if (!ref) fail("No ticket to move.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
|
|
1283
1309
|
return ref;
|
|
@@ -1289,8 +1315,8 @@ function resolveTicketRef$1(flags) {
|
|
|
1289
1315
|
* that silently.
|
|
1290
1316
|
*/
|
|
1291
1317
|
async function move(flags) {
|
|
1292
|
-
const credentials = requireCredentials$
|
|
1293
|
-
const ticketRef = resolveTicketRef$
|
|
1318
|
+
const credentials = requireCredentials$2();
|
|
1319
|
+
const ticketRef = resolveTicketRef$2(flags);
|
|
1294
1320
|
const column = flags.get("column");
|
|
1295
1321
|
if (!column) fail("A target column is required.", `Pass --column "Code Review" — run \`npx neuvikon board\` first if you're not sure what this project calls it.`);
|
|
1296
1322
|
const comment = (flags.get("comment") || await readStdin()).trim();
|
|
@@ -1315,13 +1341,13 @@ async function move(flags) {
|
|
|
1315
1341
|
}
|
|
1316
1342
|
//#endregion
|
|
1317
1343
|
//#region src/commands/pages.ts
|
|
1318
|
-
function requireCredentials() {
|
|
1344
|
+
function requireCredentials$1() {
|
|
1319
1345
|
const credentials = readCredentials();
|
|
1320
1346
|
if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
|
|
1321
1347
|
return credentials;
|
|
1322
1348
|
}
|
|
1323
1349
|
/** `--ticket` wins; otherwise fall back to whatever this directory is already tracking. */
|
|
1324
|
-
function resolveTicketRef(flags) {
|
|
1350
|
+
function resolveTicketRef$1(flags) {
|
|
1325
1351
|
const ref = flags.get("ticket") ?? readSession()?.ticket;
|
|
1326
1352
|
if (!ref) fail("No ticket to look up pages for.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
|
|
1327
1353
|
return ref;
|
|
@@ -1340,8 +1366,8 @@ async function request(credentials, path, body) {
|
|
|
1340
1366
|
}
|
|
1341
1367
|
}
|
|
1342
1368
|
async function list(flags) {
|
|
1343
|
-
const credentials = requireCredentials();
|
|
1344
|
-
const ticketRef = resolveTicketRef(flags);
|
|
1369
|
+
const credentials = requireCredentials$1();
|
|
1370
|
+
const ticketRef = resolveTicketRef$1(flags);
|
|
1345
1371
|
const { pages } = await request(credentials, "/api/agent/pages", {
|
|
1346
1372
|
token: credentials.token,
|
|
1347
1373
|
ticketRef
|
|
@@ -1353,9 +1379,9 @@ async function list(flags) {
|
|
|
1353
1379
|
const body = pages.map((page) => `# ${page.title}\n\n${page.text}`).join("\n\n---\n\n");
|
|
1354
1380
|
process.stdout.write(`${body}\n`);
|
|
1355
1381
|
}
|
|
1356
|
-
async function update(flags, positional) {
|
|
1357
|
-
const credentials = requireCredentials();
|
|
1358
|
-
const ticketRef = resolveTicketRef(flags);
|
|
1382
|
+
async function update$1(flags, positional) {
|
|
1383
|
+
const credentials = requireCredentials$1();
|
|
1384
|
+
const ticketRef = resolveTicketRef$1(flags);
|
|
1359
1385
|
const title = flags.get("title") ?? positional[0];
|
|
1360
1386
|
if (!title) fail("A page title is required.", `Pass --title "Auth flow notes", or give it as the first argument.`);
|
|
1361
1387
|
const text = (flags.get("text") || await readStdin()).trim();
|
|
@@ -1372,7 +1398,7 @@ async function pages(flags, positional) {
|
|
|
1372
1398
|
switch (subcommand) {
|
|
1373
1399
|
case void 0:
|
|
1374
1400
|
case "list": return list(flags);
|
|
1375
|
-
case "update": return update(flags, rest);
|
|
1401
|
+
case "update": return update$1(flags, rest);
|
|
1376
1402
|
default: fail(`Unknown "pages ${subcommand}".`, "Try `pages` or `pages update`.");
|
|
1377
1403
|
}
|
|
1378
1404
|
}
|
|
@@ -1888,6 +1914,67 @@ async function stop(flags) {
|
|
|
1888
1914
|
success(`Stopped tracking ${session.ticket}`);
|
|
1889
1915
|
}
|
|
1890
1916
|
//#endregion
|
|
1917
|
+
//#region src/commands/update.ts
|
|
1918
|
+
const PRIORITIES = /* @__PURE__ */ new Set([
|
|
1919
|
+
"low",
|
|
1920
|
+
"medium",
|
|
1921
|
+
"high",
|
|
1922
|
+
"urgent"
|
|
1923
|
+
]);
|
|
1924
|
+
/** What `--assignee none` / `--sprint none` means: clear the field. */
|
|
1925
|
+
const NONE = "none";
|
|
1926
|
+
function requireCredentials() {
|
|
1927
|
+
const credentials = readCredentials();
|
|
1928
|
+
if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
|
|
1929
|
+
return credentials;
|
|
1930
|
+
}
|
|
1931
|
+
function resolveTicketRef(flags) {
|
|
1932
|
+
const ref = flags.get("ticket") ?? readSession()?.ticket;
|
|
1933
|
+
if (!ref) fail("No ticket to update.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
|
|
1934
|
+
return ref;
|
|
1935
|
+
}
|
|
1936
|
+
/** `undefined` means "leave it alone", `null` means "clear it", a string means "set it to this". */
|
|
1937
|
+
function resolveClearable(flags, name) {
|
|
1938
|
+
const value = flags.get(name);
|
|
1939
|
+
if (value === void 0) return void 0;
|
|
1940
|
+
return value.toLowerCase() === NONE ? null : value;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Changes fields on a ticket that already exists, e.g.
|
|
1944
|
+
* `npx neuvikon update --ticket NS-91 --assignee Ogulcan --sprint "Sprint 1"`.
|
|
1945
|
+
* `--assignee none` / `--sprint none` clears that field instead of setting it.
|
|
1946
|
+
*/
|
|
1947
|
+
async function update(flags) {
|
|
1948
|
+
const credentials = requireCredentials();
|
|
1949
|
+
const ticketRef = resolveTicketRef(flags);
|
|
1950
|
+
const assignee = resolveClearable(flags, "assignee");
|
|
1951
|
+
const sprint = resolveClearable(flags, "sprint");
|
|
1952
|
+
const priority = flags.get("priority");
|
|
1953
|
+
if (priority && !PRIORITIES.has(priority)) fail(`Unknown priority "${priority}".`, "Expected low, medium, high, or urgent.");
|
|
1954
|
+
const labelsFlag = flags.get("labels");
|
|
1955
|
+
const labels = labelsFlag !== void 0 ? labelsFlag.split(",").map((label) => label.trim()).filter(Boolean) : void 0;
|
|
1956
|
+
if (assignee === void 0 && sprint === void 0 && priority === void 0 && labels === void 0) fail("Nothing to update.", "Pass at least one of --assignee, --sprint, --priority, --labels.");
|
|
1957
|
+
let result;
|
|
1958
|
+
try {
|
|
1959
|
+
result = await post(credentials.url, "/api/agent/tickets/update", {
|
|
1960
|
+
token: credentials.token,
|
|
1961
|
+
ticketRef,
|
|
1962
|
+
assignee,
|
|
1963
|
+
sprint,
|
|
1964
|
+
priority,
|
|
1965
|
+
labels
|
|
1966
|
+
});
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
if (error instanceof NetworkError) fail("Could not reach Neuvikon.");
|
|
1969
|
+
if (error instanceof ApiError) {
|
|
1970
|
+
if (error.status === 401) fail(error.message, "Run `npx neuvikon login` again.");
|
|
1971
|
+
fail(error.message);
|
|
1972
|
+
}
|
|
1973
|
+
throw error;
|
|
1974
|
+
}
|
|
1975
|
+
success(`Updated ${bold(result.ref)}: ${result.changed.join(", ")}`);
|
|
1976
|
+
}
|
|
1977
|
+
//#endregion
|
|
1891
1978
|
//#region src/index.ts
|
|
1892
1979
|
const HELP = `${bold("neuvikon")} — tell Neuvikon which ticket your coding agent is on
|
|
1893
1980
|
|
|
@@ -1911,7 +1998,12 @@ ${bold("Board")}
|
|
|
1911
1998
|
File something you noticed as its own ticket, same project
|
|
1912
1999
|
npx neuvikon create "Add French copy" --sub
|
|
1913
2000
|
...or as a sub-ticket (--sub for this one, --parent NS-12 for another)
|
|
1914
|
-
|
|
2001
|
+
npx neuvikon create "..." --assignee Ogulcan --sprint "Sprint 1"
|
|
2002
|
+
File it already assigned and/or planned in a sprint
|
|
2003
|
+
npx neuvikon update --assignee Ogulcan --sprint "Sprint 1" --priority high
|
|
2004
|
+
Change assignee, sprint, priority, or labels on an existing ticket
|
|
2005
|
+
("none" clears --assignee/--sprint)
|
|
2006
|
+
All five accept --ticket EWWQ-1 instead of relying on a tracked session.
|
|
1915
2007
|
|
|
1916
2008
|
${bold("Project pages")}
|
|
1917
2009
|
npx neuvikon pages List the current ticket's project pages
|
|
@@ -1973,6 +2065,7 @@ async function main() {
|
|
|
1973
2065
|
case "move": return move(flags);
|
|
1974
2066
|
case "comment": return comment(flags);
|
|
1975
2067
|
case "create": return create(flags, positional);
|
|
2068
|
+
case "update": return update(flags);
|
|
1976
2069
|
case void 0:
|
|
1977
2070
|
case "help":
|
|
1978
2071
|
case "--help":
|