@allocator-one/harness-cli 1.4.0 → 1.4.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/dist/cli.js +384 -157
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -372,6 +372,16 @@ function defaultOpenBrowser(url) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// src/core/stdio.ts
|
|
376
|
+
var BROKEN_PIPE_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED"]);
|
|
377
|
+
function guardBrokenPipes(streams = [process.stdout, process.stderr]) {
|
|
378
|
+
for (const stream of streams) {
|
|
379
|
+
stream.on("error", (error) => {
|
|
380
|
+
if (!BROKEN_PIPE_CODES.has(error.code ?? "")) throw error;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
375
385
|
// src/core/upgrade.ts
|
|
376
386
|
import { spawn, spawnSync } from "child_process";
|
|
377
387
|
import {
|
|
@@ -390,6 +400,7 @@ import {
|
|
|
390
400
|
import { basename, dirname, join as join2, parse, sep } from "path";
|
|
391
401
|
import { fileURLToPath } from "url";
|
|
392
402
|
var PACKAGE_NAME = "@allocator-one/harness-cli";
|
|
403
|
+
var UPGRADE_INSTALL_SPEC = `${PACKAGE_NAME}@latest`;
|
|
393
404
|
var UPDATE_CHECK_COMMAND = "__update-check";
|
|
394
405
|
var REEXEC_SENTINEL = "HARNESS_UPGRADED";
|
|
395
406
|
var DEFAULT_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -399,7 +410,11 @@ var NPM_ROOT_TIMEOUT_MS = 15e3;
|
|
|
399
410
|
var INSTALL_TIMEOUT_MS = 12e4;
|
|
400
411
|
var LOCK_STALE_MS = NPM_ROOT_TIMEOUT_MS + INSTALL_TIMEOUT_MS + 6e4;
|
|
401
412
|
function detectInstall(from = fileURLToPath(import.meta.url)) {
|
|
402
|
-
const unknown = {
|
|
413
|
+
const unknown = {
|
|
414
|
+
kind: "unknown",
|
|
415
|
+
packageRoot: null,
|
|
416
|
+
nodeModules: null
|
|
417
|
+
};
|
|
403
418
|
if (from.split(sep).includes("_npx")) return { ...unknown, kind: "npx" };
|
|
404
419
|
const packageRoot = findPackageRoot(from);
|
|
405
420
|
if (packageRoot === null) return unknown;
|
|
@@ -432,7 +447,9 @@ function packageNameAt(path) {
|
|
|
432
447
|
}
|
|
433
448
|
function versionAt(packageRoot) {
|
|
434
449
|
try {
|
|
435
|
-
const parsed = JSON.parse(
|
|
450
|
+
const parsed = JSON.parse(
|
|
451
|
+
readFileSync(join2(packageRoot, "package.json"), "utf8")
|
|
452
|
+
);
|
|
436
453
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
437
454
|
const version2 = parsed["version"];
|
|
438
455
|
return typeof version2 === "string" ? version2 : null;
|
|
@@ -463,7 +480,8 @@ function isNewer(candidate, current) {
|
|
|
463
480
|
const b = parseVersion(current);
|
|
464
481
|
if (a === null || b === null) return false;
|
|
465
482
|
for (let index = 0; index < 3; index++) {
|
|
466
|
-
if (a.parts[index] !== b.parts[index])
|
|
483
|
+
if (a.parts[index] !== b.parts[index])
|
|
484
|
+
return a.parts[index] > b.parts[index];
|
|
467
485
|
}
|
|
468
486
|
if (a.pre === b.pre) return false;
|
|
469
487
|
if (a.pre === null) return true;
|
|
@@ -499,7 +517,8 @@ function readStamp(env = process.env) {
|
|
|
499
517
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
500
518
|
const record = parsed;
|
|
501
519
|
const checkedAt = record["checkedAt"];
|
|
502
|
-
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))
|
|
520
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))
|
|
521
|
+
return null;
|
|
503
522
|
const latest = record["latest"];
|
|
504
523
|
return { checkedAt, latest: typeof latest === "string" ? latest : null };
|
|
505
524
|
} catch {
|
|
@@ -571,7 +590,8 @@ async function fetchLatestVersion(env) {
|
|
|
571
590
|
const parsed = await response.json();
|
|
572
591
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
573
592
|
const version2 = parsed["version"];
|
|
574
|
-
if (typeof version2 !== "string" || parseVersion(version2) === null)
|
|
593
|
+
if (typeof version2 !== "string" || parseVersion(version2) === null)
|
|
594
|
+
return null;
|
|
575
595
|
return version2;
|
|
576
596
|
} catch {
|
|
577
597
|
return null;
|
|
@@ -581,10 +601,18 @@ function selfHeal(options = {}) {
|
|
|
581
601
|
const env = options.env ?? process.env;
|
|
582
602
|
const argv2 = options.argv ?? process.argv;
|
|
583
603
|
if (truthy(env[REEXEC_SENTINEL])) {
|
|
584
|
-
return {
|
|
604
|
+
return {
|
|
605
|
+
kind: "skipped",
|
|
606
|
+
reason: "already upgraded once for this command"
|
|
607
|
+
};
|
|
585
608
|
}
|
|
586
|
-
if (!autoUpgradeEnabled(env))
|
|
587
|
-
|
|
609
|
+
if (!autoUpgradeEnabled(env))
|
|
610
|
+
return { kind: "skipped", reason: "automatic upgrades are off" };
|
|
611
|
+
if (argv2[2] === "mcp")
|
|
612
|
+
return {
|
|
613
|
+
kind: "skipped",
|
|
614
|
+
reason: "an MCP session must not be replaced mid-stream"
|
|
615
|
+
};
|
|
588
616
|
const install = options.install ?? detectInstall();
|
|
589
617
|
if (install.kind !== "global" || install.packageRoot === null) {
|
|
590
618
|
return { kind: "skipped", reason: describeInstall(install.kind) };
|
|
@@ -595,17 +623,27 @@ function selfHeal(options = {}) {
|
|
|
595
623
|
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
596
624
|
}
|
|
597
625
|
const lock = takeLock(env);
|
|
598
|
-
if (lock === null)
|
|
626
|
+
if (lock === null)
|
|
627
|
+
return {
|
|
628
|
+
kind: "skipped",
|
|
629
|
+
reason: "another harness process is already upgrading"
|
|
630
|
+
};
|
|
599
631
|
try {
|
|
600
632
|
const stillHeld = () => readHolder(lock.path)?.pid === lock.pid;
|
|
601
633
|
const result = (options.runInstall ?? npmInstallGlobal)(install, stillHeld);
|
|
602
634
|
if (!result.ok) return { kind: "failed", reason: result.detail };
|
|
603
635
|
const after = versionAt(packageRoot);
|
|
604
636
|
if (after !== null && after === installed) {
|
|
605
|
-
return {
|
|
637
|
+
return {
|
|
638
|
+
kind: "failed",
|
|
639
|
+
reason: `still ${after} after upgrading \u2014 no newer release is published`
|
|
640
|
+
};
|
|
606
641
|
}
|
|
607
642
|
if (after !== null && installed !== null && !isNewer(after, installed)) {
|
|
608
|
-
return {
|
|
643
|
+
return {
|
|
644
|
+
kind: "failed",
|
|
645
|
+
reason: `npm left ${after} installed, which is not newer than ${installed}`
|
|
646
|
+
};
|
|
609
647
|
}
|
|
610
648
|
} finally {
|
|
611
649
|
releaseLock(lock);
|
|
@@ -613,7 +651,10 @@ function selfHeal(options = {}) {
|
|
|
613
651
|
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
614
652
|
}
|
|
615
653
|
function reexec(options, env, argv2) {
|
|
616
|
-
return (options.reexec ?? reexecSelf)(
|
|
654
|
+
return (options.reexec ?? reexecSelf)(
|
|
655
|
+
{ ...env, [REEXEC_SENTINEL]: "1" },
|
|
656
|
+
argv2
|
|
657
|
+
);
|
|
617
658
|
}
|
|
618
659
|
function describeInstall(kind) {
|
|
619
660
|
switch (kind) {
|
|
@@ -629,9 +670,13 @@ function describeInstall(kind) {
|
|
|
629
670
|
}
|
|
630
671
|
function npmInstallGlobal(install, stillHeld) {
|
|
631
672
|
const globalRoot = npmGlobalRoot();
|
|
632
|
-
if (globalRoot === null)
|
|
673
|
+
if (globalRoot === null)
|
|
674
|
+
return { ok: false, detail: "npm did not say where its global root is" };
|
|
633
675
|
if (globalRoot.includes("***")) {
|
|
634
|
-
return {
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
detail: "npm redacted its own global root, so this copy's location cannot be confirmed"
|
|
679
|
+
};
|
|
635
680
|
}
|
|
636
681
|
if (install.nodeModules === null || !samePath(globalRoot, install.nodeModules)) {
|
|
637
682
|
return {
|
|
@@ -642,10 +687,16 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
642
687
|
try {
|
|
643
688
|
accessSync2(globalRoot, constants.W_OK);
|
|
644
689
|
} catch {
|
|
645
|
-
return {
|
|
690
|
+
return {
|
|
691
|
+
ok: false,
|
|
692
|
+
detail: `${globalRoot} is not writable \u2014 upgrade it yourself`
|
|
693
|
+
};
|
|
646
694
|
}
|
|
647
695
|
if (!stillHeld()) {
|
|
648
|
-
return {
|
|
696
|
+
return {
|
|
697
|
+
ok: false,
|
|
698
|
+
detail: "lost the upgrade lock while preparing \u2014 another process took over"
|
|
699
|
+
};
|
|
649
700
|
}
|
|
650
701
|
process.stderr.write(`harness: upgrading ${PACKAGE_NAME} \u2026
|
|
651
702
|
`);
|
|
@@ -657,7 +708,7 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
657
708
|
"--no-fund",
|
|
658
709
|
"--no-update-notifier",
|
|
659
710
|
"--loglevel=error",
|
|
660
|
-
|
|
711
|
+
UPGRADE_INSTALL_SPEC
|
|
661
712
|
];
|
|
662
713
|
const result = spawnSync(npmCommand(), args, {
|
|
663
714
|
// npm's diagnostics belong on stderr with ours; stdout stays the
|
|
@@ -666,9 +717,13 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
666
717
|
timeout: INSTALL_TIMEOUT_MS,
|
|
667
718
|
...npmSpawnOptions()
|
|
668
719
|
});
|
|
669
|
-
if (result.error !== void 0)
|
|
720
|
+
if (result.error !== void 0)
|
|
721
|
+
return { ok: false, detail: result.error.message };
|
|
670
722
|
if (result.status !== 0) {
|
|
671
|
-
return {
|
|
723
|
+
return {
|
|
724
|
+
ok: false,
|
|
725
|
+
detail: `npm install exited ${result.status ?? `on ${String(result.signal)}`}`
|
|
726
|
+
};
|
|
672
727
|
}
|
|
673
728
|
return { ok: true, detail: "" };
|
|
674
729
|
}
|
|
@@ -710,8 +765,10 @@ function reexecSelf(env, argv2) {
|
|
|
710
765
|
env
|
|
711
766
|
});
|
|
712
767
|
if (result.error !== void 0) {
|
|
713
|
-
process.stderr.write(
|
|
714
|
-
`
|
|
768
|
+
process.stderr.write(
|
|
769
|
+
`harness: upgraded, but re-running failed: ${result.error.message}
|
|
770
|
+
`
|
|
771
|
+
);
|
|
715
772
|
return 1;
|
|
716
773
|
}
|
|
717
774
|
return result.status ?? 1;
|
|
@@ -794,7 +851,10 @@ function autoUpgradeEnabled(env) {
|
|
|
794
851
|
|
|
795
852
|
// src/cli.ts
|
|
796
853
|
function promptLine(question) {
|
|
797
|
-
const readline = createInterface2({
|
|
854
|
+
const readline = createInterface2({
|
|
855
|
+
input: process.stdin,
|
|
856
|
+
output: process.stderr
|
|
857
|
+
});
|
|
798
858
|
return new Promise((resolve) => {
|
|
799
859
|
readline.question(question, (answer) => {
|
|
800
860
|
readline.close();
|
|
@@ -804,15 +864,19 @@ function promptLine(question) {
|
|
|
804
864
|
}
|
|
805
865
|
function exitWith(code) {
|
|
806
866
|
const drain = (stream) => new Promise((resolve) => stream.write("", () => resolve()));
|
|
807
|
-
return Promise.all([drain(process.stdout), drain(process.stderr)]).then(
|
|
867
|
+
return Promise.all([drain(process.stdout), drain(process.stderr)]).then(
|
|
868
|
+
() => process.exit(code)
|
|
869
|
+
);
|
|
808
870
|
}
|
|
871
|
+
guardBrokenPipes();
|
|
809
872
|
var program = new Command();
|
|
810
873
|
program.name("harness").description("issue tracking for humans and agents (harness.infra.one)").exitOverride().configureOutput({
|
|
811
874
|
writeOut: (text) => process.stdout.write(text),
|
|
812
875
|
writeErr: (text) => process.stderr.write(text)
|
|
813
876
|
});
|
|
814
877
|
function integer(value) {
|
|
815
|
-
if (!/^\d+$/.test(value))
|
|
878
|
+
if (!/^\d+$/.test(value))
|
|
879
|
+
throw new InvalidArgumentError("expected a non-negative integer");
|
|
816
880
|
return Number.parseInt(value, 10);
|
|
817
881
|
}
|
|
818
882
|
function collect(value, previous) {
|
|
@@ -858,8 +922,10 @@ async function withConfig(jsonFlag, mutating, run) {
|
|
|
858
922
|
process.stderr.write(loginResult.human);
|
|
859
923
|
const again = resolveConfig();
|
|
860
924
|
if (again.kind !== "ok") {
|
|
861
|
-
process.stderr.write(
|
|
862
|
-
`
|
|
925
|
+
process.stderr.write(
|
|
926
|
+
`harness: still not signed in to ${resolved.baseUrl} after logging in
|
|
927
|
+
`
|
|
928
|
+
);
|
|
863
929
|
return exitWith(1);
|
|
864
930
|
}
|
|
865
931
|
return execute(again.config, jsonFlag, mutating, run);
|
|
@@ -891,16 +957,20 @@ async function fail(error, hostNotice) {
|
|
|
891
957
|
return exitWith(1);
|
|
892
958
|
}
|
|
893
959
|
if (error instanceof TransportError) {
|
|
894
|
-
process.stderr.write(
|
|
895
|
-
`)
|
|
960
|
+
process.stderr.write(
|
|
961
|
+
hostNotice + `harness: cannot reach the API: ${clean(error.message)}
|
|
962
|
+
`
|
|
963
|
+
);
|
|
896
964
|
return exitWith(3);
|
|
897
965
|
}
|
|
898
966
|
if (error instanceof UpgradeRequiredError) {
|
|
899
967
|
const healed = selfHeal();
|
|
900
968
|
if (healed.kind === "upgraded") return exitWith(healed.code);
|
|
901
969
|
const why = healed.kind === "failed" ? ` (tried to upgrade: ${clean(healed.reason)})` : "";
|
|
902
|
-
process.stderr.write(
|
|
903
|
-
`)
|
|
970
|
+
process.stderr.write(
|
|
971
|
+
hostNotice + `harness: ${clean(error.message)} (HTTP 426)${why}
|
|
972
|
+
`
|
|
973
|
+
);
|
|
904
974
|
return exitWith(1);
|
|
905
975
|
}
|
|
906
976
|
if (error instanceof HttpError) {
|
|
@@ -919,7 +989,8 @@ async function fail(error, hostNotice) {
|
|
|
919
989
|
}
|
|
920
990
|
function errorDetails(body) {
|
|
921
991
|
const details = body["details"];
|
|
922
|
-
if (typeof details !== "object" || details === null || Array.isArray(details))
|
|
992
|
+
if (typeof details !== "object" || details === null || Array.isArray(details))
|
|
993
|
+
return "";
|
|
923
994
|
return Object.entries(details).map(([field, messages]) => {
|
|
924
995
|
const text = Array.isArray(messages) ? messages.map(String).join("; ") : String(messages);
|
|
925
996
|
return `
|
|
@@ -927,24 +998,53 @@ function errorDetails(body) {
|
|
|
927
998
|
}).join("");
|
|
928
999
|
}
|
|
929
1000
|
program.command("ready").description("issues ready to work, highest priority first").option("--claim", "atomically claim the first claimable issue").option("--team <key>", "team filter").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
930
|
-
await withConfig(
|
|
1001
|
+
await withConfig(
|
|
1002
|
+
opts.json === true,
|
|
1003
|
+
opts.claim === true,
|
|
1004
|
+
(ctx) => ready(ctx, opts)
|
|
1005
|
+
);
|
|
931
1006
|
});
|
|
932
1007
|
program.command("blocked").description("issues waiting on a blocker").option("--team <key>", "team filter").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
933
|
-
await withConfig(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1008
|
+
await withConfig(
|
|
1009
|
+
opts.json === true,
|
|
1010
|
+
false,
|
|
1011
|
+
(ctx) => blocked(ctx, opts)
|
|
1012
|
+
);
|
|
937
1013
|
});
|
|
938
|
-
program.command("
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
await withConfig(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1014
|
+
program.command("list").description("list issues with filters").option("--status <status>", "open | in_progress | closed").option(
|
|
1015
|
+
"--assignee <who>",
|
|
1016
|
+
'me, a user id, an email, or a full name; "" for unassigned'
|
|
1017
|
+
).option("--priority <name>", "urgent | high | medium | low | none").option("--label <name>", "label filter").option("--type <type>", "task | bug | feature | epic").option("--search <query>", "full-text search").option("--team <key>", "team filter").option("--parent <id>", "children of an epic").option("--scope <scope>", "all | archived").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
1018
|
+
await withConfig(
|
|
1019
|
+
opts.json === true,
|
|
1020
|
+
false,
|
|
1021
|
+
(ctx) => list(ctx, opts)
|
|
1022
|
+
);
|
|
947
1023
|
});
|
|
1024
|
+
program.command("search").description(
|
|
1025
|
+
"search by meaning \u2014 ranked, exact identifier first; use list --search for paginated filtering"
|
|
1026
|
+
).argument("[query...]").option("--limit <n>", "maximum results (default 10, capped at 20)", integer).option(
|
|
1027
|
+
"--page <n>",
|
|
1028
|
+
"(deprecated, ignored \u2014 results are ranked and capped)",
|
|
1029
|
+
integer
|
|
1030
|
+
).option("--all", "(deprecated, ignored \u2014 results are ranked and capped)").option("--json", "emit JSON").action(
|
|
1031
|
+
async (query, opts) => {
|
|
1032
|
+
await withConfig(
|
|
1033
|
+
opts.json === true,
|
|
1034
|
+
false,
|
|
1035
|
+
(ctx) => search(ctx, query, opts)
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
1039
|
+
program.command("show").description("full issue detail; downloads attached images locally").argument("[id]").option("--no-images", "skip attachment downloads").option("--json", "emit JSON").action(
|
|
1040
|
+
async (id, opts) => {
|
|
1041
|
+
await withConfig(opts.json === true, false, (ctx) => {
|
|
1042
|
+
if (id === void 0) throw new UsageError("usage: harness show <id>");
|
|
1043
|
+
requireIdentifiers2([id]);
|
|
1044
|
+
return show(ctx, id, opts);
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
);
|
|
948
1048
|
program.command("stats").description("per-team and total counts").option("--json", "emit JSON").action(async (opts) => {
|
|
949
1049
|
await withConfig(opts.json === true, false, (ctx) => stats(ctx));
|
|
950
1050
|
});
|
|
@@ -956,169 +1056,286 @@ program.command("version").description("print CLI and server API versions").opti
|
|
|
956
1056
|
let config = null;
|
|
957
1057
|
if (resolved.kind === "ok") config = resolved.config;
|
|
958
1058
|
else if (resolved.kind === "needs-login") {
|
|
959
|
-
config = {
|
|
1059
|
+
config = {
|
|
1060
|
+
baseUrl: resolved.baseUrl,
|
|
1061
|
+
token: "",
|
|
1062
|
+
actor: null,
|
|
1063
|
+
team: null,
|
|
1064
|
+
cacheDir: ""
|
|
1065
|
+
};
|
|
960
1066
|
}
|
|
961
1067
|
const output = await version(config);
|
|
962
|
-
process.stdout.write(
|
|
1068
|
+
process.stdout.write(
|
|
1069
|
+
opts.json === true ? json(output.json) : output.human
|
|
1070
|
+
);
|
|
963
1071
|
await exitWith(0);
|
|
964
1072
|
});
|
|
965
|
-
program.command("create").description("create an issue").option("--title <text>", "issue title (required)").option("--description <text>", "problem statement").option("--design <text>", "design notes").option("--notes <text>", "working notes").option("--acceptance <text>", "acceptance criteria").option(
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
await withConfig(
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
return update(ctx, id, opts);
|
|
975
|
-
});
|
|
976
|
-
});
|
|
977
|
-
program.command("close").description("close issues; the response lists newly unblocked issues").argument("[ids...]").option("--reason <text>", "why it is done (e.g. the commit hash)").option("--cancel", "close as canceled instead of completed").option("--json", "emit JSON").action(async (ids, opts) => {
|
|
978
|
-
await withConfig(opts.json === true, true, (ctx) => {
|
|
979
|
-
if (ids.length === 0) {
|
|
980
|
-
throw new UsageError("usage: harness close <id> [<id>\u2026] [--reason <text>] [--cancel]");
|
|
981
|
-
}
|
|
982
|
-
requireIdentifiers2(ids);
|
|
983
|
-
return close(ctx, ids, opts);
|
|
984
|
-
});
|
|
985
|
-
});
|
|
986
|
-
program.command("archive").description("archive issues; --restore brings them back").argument("[ids...]").option("--restore", "restore instead of archive").option("--json", "emit JSON").action(async (ids, opts) => {
|
|
987
|
-
await withConfig(opts.json === true, true, (ctx) => {
|
|
988
|
-
if (ids.length === 0) throw new UsageError("usage: harness archive <id> [<id>\u2026] [--restore]");
|
|
989
|
-
requireIdentifiers2(ids);
|
|
990
|
-
return archive(ctx, ids, opts);
|
|
991
|
-
});
|
|
1073
|
+
program.command("create").description("create an issue").option("--title <text>", "issue title (required)").option("--description <text>", "problem statement").option("--design <text>", "design notes").option("--notes <text>", "working notes").option("--acceptance <text>", "acceptance criteria").option(
|
|
1074
|
+
"--type <type>",
|
|
1075
|
+
"task | bug | feature | epic (applied as a type/ label)"
|
|
1076
|
+
).option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", "YYYY-MM-DD").option("--parent <id>", "parent epic").option("--assignee <who>", "me, a user id, an email, or a full name").option("--team <key>", "team key (or HARNESS_TEAM)").option("--label <name>", "label to apply (repeatable)", collect, []).option("--json", "emit JSON").action(async (opts) => {
|
|
1077
|
+
await withConfig(
|
|
1078
|
+
opts.json === true,
|
|
1079
|
+
true,
|
|
1080
|
+
(ctx) => create(ctx, opts)
|
|
1081
|
+
);
|
|
992
1082
|
});
|
|
1083
|
+
program.command("update").description("update an issue; --claim takes it atomically").argument("[id]").option("--claim", "claim atomically and move to the first started state").option("--state <name>", "workflow state by name or type").option("--title <text>").option("--description <text>").option("--design <text>").option("--notes <text>", "replace the notes").option("--append-notes <text>", "append to the notes").option("--acceptance <text>").option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", 'YYYY-MM-DD; "" clears').option("--parent <id>").option(
|
|
1084
|
+
"--assignee <who>",
|
|
1085
|
+
'me, a user id, an email, or a full name; "" unassigns'
|
|
1086
|
+
).option("--json", "emit JSON").action(
|
|
1087
|
+
async (id, opts) => {
|
|
1088
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1089
|
+
if (id === void 0) {
|
|
1090
|
+
throw new UsageError(
|
|
1091
|
+
"usage: harness update <id> --claim | --title \u2026 | --state <name> | \u2026"
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
requireIdentifiers2([id]);
|
|
1095
|
+
return update(ctx, id, opts);
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1099
|
+
program.command("close").description("close issues; the response lists newly unblocked issues").argument("[ids...]").option("--reason <text>", "why it is done (e.g. the commit hash)").option("--cancel", "close as canceled instead of completed").option("--json", "emit JSON").action(
|
|
1100
|
+
async (ids, opts) => {
|
|
1101
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1102
|
+
if (ids.length === 0) {
|
|
1103
|
+
throw new UsageError(
|
|
1104
|
+
"usage: harness close <id> [<id>\u2026] [--reason <text>] [--cancel]"
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
requireIdentifiers2(ids);
|
|
1108
|
+
return close(ctx, ids, opts);
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
program.command("archive").description("archive issues; --restore brings them back").argument("[ids...]").option("--restore", "restore instead of archive").option("--json", "emit JSON").action(
|
|
1113
|
+
async (ids, opts) => {
|
|
1114
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1115
|
+
if (ids.length === 0)
|
|
1116
|
+
throw new UsageError(
|
|
1117
|
+
"usage: harness archive <id> [<id>\u2026] [--restore]"
|
|
1118
|
+
);
|
|
1119
|
+
requireIdentifiers2(ids);
|
|
1120
|
+
return archive(ctx, ids, opts);
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
993
1124
|
program.command("comments").description("an issue's full comment thread, untruncated").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
994
1125
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
995
|
-
if (id === void 0)
|
|
1126
|
+
if (id === void 0)
|
|
1127
|
+
throw new UsageError("usage: harness comments <id>");
|
|
996
1128
|
requireIdentifiers2([id]);
|
|
997
1129
|
return comments(ctx, id);
|
|
998
1130
|
});
|
|
999
1131
|
});
|
|
1000
|
-
program.command("dep").description("blocking dependencies: dep add|rm <issue> <blocker>").argument("[verb]").argument("[issue]").argument("[blocker]").option("--json", "emit JSON").action(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1132
|
+
program.command("dep").description("blocking dependencies: dep add|rm <issue> <blocker>").argument("[verb]").argument("[issue]").argument("[blocker]").option("--json", "emit JSON").action(
|
|
1133
|
+
async (verb, issue, blocker, opts) => {
|
|
1134
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1135
|
+
if (verb === void 0 || issue === void 0 || blocker === void 0) {
|
|
1136
|
+
throw new UsageError("usage: harness dep add|rm <issue> <blocker>");
|
|
1137
|
+
}
|
|
1138
|
+
requireIdentifiers2([issue, blocker]);
|
|
1139
|
+
return dep(ctx, verb, issue, blocker);
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
program.command("comment").description("comment on an issue").argument("[id]").argument("[text...]").option("--mention <name>", "notify a person (repeatable)", collect, []).option("--json", "emit JSON").action(
|
|
1144
|
+
async (id, text, opts) => {
|
|
1145
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1146
|
+
if (id === void 0 || text.length === 0) {
|
|
1147
|
+
throw new UsageError(
|
|
1148
|
+
"usage: harness comment <id> <text> [--mention NAME]\u2026"
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
requireIdentifiers2([id]);
|
|
1152
|
+
const mentions = opts.mention.filter((name) => name.trim() !== "");
|
|
1153
|
+
return comment(ctx, id, text, mentions);
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1019
1157
|
program.command("attach-image").description("upload an image; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1020
1158
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1021
1159
|
if (path === void 0) {
|
|
1022
|
-
throw new UsageError(
|
|
1160
|
+
throw new UsageError(
|
|
1161
|
+
"usage: harness attach-image <path> (one png, jpg, webp or gif, up to 25 MB)"
|
|
1162
|
+
);
|
|
1023
1163
|
}
|
|
1024
1164
|
return attachImage(ctx, path);
|
|
1025
1165
|
});
|
|
1026
1166
|
});
|
|
1027
1167
|
var project = program.command("project").description("projects");
|
|
1028
1168
|
project.command("list").option("--search <query>", "full-text search").option("--state <state>", "project state filter").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
1029
|
-
await withConfig(
|
|
1169
|
+
await withConfig(
|
|
1170
|
+
opts.json === true,
|
|
1171
|
+
false,
|
|
1172
|
+
(ctx) => projectList(ctx, opts)
|
|
1173
|
+
);
|
|
1030
1174
|
});
|
|
1031
1175
|
project.command("show").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1032
1176
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1033
|
-
if (id === void 0)
|
|
1177
|
+
if (id === void 0)
|
|
1178
|
+
throw new UsageError("usage: harness project show <id>");
|
|
1034
1179
|
return projectShow(ctx, id);
|
|
1035
1180
|
});
|
|
1036
1181
|
});
|
|
1037
1182
|
project.command("create").option("--name <name>", "project name (required)").option("--description <text>").option("--summary <text>").option("--state <state>").option("--lead <user-id>", "lead by user id").option("--start-date <date>", "YYYY-MM-DD").option("--target-date <date>", "YYYY-MM-DD").option("--json", "emit JSON").action(async (opts) => {
|
|
1038
|
-
await withConfig(
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
return projectUpdate(ctx, id, opts);
|
|
1044
|
-
});
|
|
1183
|
+
await withConfig(
|
|
1184
|
+
opts.json === true,
|
|
1185
|
+
true,
|
|
1186
|
+
(ctx) => projectCreate(ctx, opts)
|
|
1187
|
+
);
|
|
1045
1188
|
});
|
|
1189
|
+
project.command("update").argument("[id]").option("--name <name>").option("--description <text>").option("--summary <text>").option("--state <state>").option("--lead <user-id>", "lead by user id").option("--start-date <date>", 'YYYY-MM-DD; "" clears').option("--target-date <date>", 'YYYY-MM-DD; "" clears').option("--json", "emit JSON").action(
|
|
1190
|
+
async (id, opts) => {
|
|
1191
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1192
|
+
if (id === void 0)
|
|
1193
|
+
throw new UsageError(
|
|
1194
|
+
"usage: harness project update <id> --name \u2026 | --state \u2026 | \u2026"
|
|
1195
|
+
);
|
|
1196
|
+
return projectUpdate(ctx, id, opts);
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
1046
1200
|
var milestone = program.command("milestone").description("project milestones");
|
|
1047
1201
|
milestone.command("list").option("--project <id>", "project id (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1048
|
-
await withConfig(
|
|
1202
|
+
await withConfig(
|
|
1203
|
+
opts.json === true,
|
|
1204
|
+
false,
|
|
1205
|
+
(ctx) => milestoneList(ctx, opts.project)
|
|
1206
|
+
);
|
|
1049
1207
|
});
|
|
1050
1208
|
milestone.command("create").option("--project <id>", "project id (required)").option("--name <name>", "milestone name (required)").option("--description <text>").option("--target-date <date>", "YYYY-MM-DD").option("--json", "emit JSON").action(async (opts) => {
|
|
1051
|
-
await withConfig(
|
|
1209
|
+
await withConfig(
|
|
1210
|
+
opts.json === true,
|
|
1211
|
+
true,
|
|
1212
|
+
(ctx) => milestoneCreate(ctx, opts)
|
|
1213
|
+
);
|
|
1052
1214
|
});
|
|
1053
1215
|
var doc = program.command("doc").description("documents");
|
|
1054
1216
|
doc.command("list").option("--search <query>", "full-text search").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
1055
|
-
await withConfig(
|
|
1217
|
+
await withConfig(
|
|
1218
|
+
opts.json === true,
|
|
1219
|
+
false,
|
|
1220
|
+
(ctx) => docList(ctx, opts)
|
|
1221
|
+
);
|
|
1056
1222
|
});
|
|
1057
1223
|
doc.command("show").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1058
1224
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1059
|
-
if (id === void 0)
|
|
1225
|
+
if (id === void 0)
|
|
1226
|
+
throw new UsageError("usage: harness doc show <id>");
|
|
1060
1227
|
return docShow(ctx, id);
|
|
1061
1228
|
});
|
|
1062
1229
|
});
|
|
1063
1230
|
doc.command("create").option("--title <title>", "document title (required)").option("--content <text>").option("--project <id>", "attach to a project").option("--json", "emit JSON").action(async (opts) => {
|
|
1064
|
-
await withConfig(
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
return docUpdate(ctx, id, opts);
|
|
1070
|
-
});
|
|
1231
|
+
await withConfig(
|
|
1232
|
+
opts.json === true,
|
|
1233
|
+
true,
|
|
1234
|
+
(ctx) => docCreate(ctx, opts)
|
|
1235
|
+
);
|
|
1071
1236
|
});
|
|
1237
|
+
doc.command("update").argument("[id]").option("--title <title>").option("--content <text>").option("--project <id>").option("--json", "emit JSON").action(
|
|
1238
|
+
async (id, opts) => {
|
|
1239
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1240
|
+
if (id === void 0)
|
|
1241
|
+
throw new UsageError(
|
|
1242
|
+
"usage: harness doc update <id> --title \u2026 | --content \u2026 | --project \u2026"
|
|
1243
|
+
);
|
|
1244
|
+
return docUpdate(ctx, id, opts);
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
1072
1248
|
var link = program.command("link").description("URLs attached to an issue (distinct from dep relations)");
|
|
1073
1249
|
link.command("list").argument("[issue]").option("--json", "emit JSON").action(async (issue, opts) => {
|
|
1074
1250
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1075
|
-
if (issue === void 0)
|
|
1251
|
+
if (issue === void 0)
|
|
1252
|
+
throw new UsageError("usage: harness link list <issue>");
|
|
1076
1253
|
requireIdentifiers2([issue]);
|
|
1077
1254
|
return linkList(ctx, issue);
|
|
1078
1255
|
});
|
|
1079
1256
|
});
|
|
1080
|
-
link.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1257
|
+
link.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
|
|
1258
|
+
async (issue, url, opts) => {
|
|
1259
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1260
|
+
if (issue === void 0 || url === void 0) {
|
|
1261
|
+
throw new UsageError(
|
|
1262
|
+
"usage: harness link add <issue> <url> [--title <title>]"
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
requireIdentifiers2([issue]);
|
|
1266
|
+
return linkAdd(ctx, issue, url, opts.title);
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
|
|
1271
|
+
async (issue, idOrUrl, opts) => {
|
|
1272
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1273
|
+
if (issue === void 0 || idOrUrl === void 0) {
|
|
1274
|
+
throw new UsageError(
|
|
1275
|
+
"usage: harness link rm <issue> <link-id-or-url>"
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
requireIdentifiers2([issue]);
|
|
1279
|
+
return linkRm(ctx, issue, idOrUrl);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
);
|
|
1098
1283
|
var label = program.command("label").description("labels");
|
|
1099
1284
|
label.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1100
|
-
await withConfig(
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1285
|
+
await withConfig(
|
|
1286
|
+
opts.json === true,
|
|
1287
|
+
false,
|
|
1288
|
+
(ctx) => labelList(ctx)
|
|
1289
|
+
);
|
|
1104
1290
|
});
|
|
1291
|
+
label.command("create").argument("[name]").option("--color <color>", "label color").option("--json", "emit JSON").action(
|
|
1292
|
+
async (name, opts) => {
|
|
1293
|
+
await withConfig(
|
|
1294
|
+
opts.json === true,
|
|
1295
|
+
true,
|
|
1296
|
+
(ctx) => labelCreate(ctx, name, opts.color)
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1105
1300
|
var team = program.command("team").description("teams");
|
|
1106
1301
|
team.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1107
|
-
await withConfig(
|
|
1302
|
+
await withConfig(
|
|
1303
|
+
opts.json === true,
|
|
1304
|
+
false,
|
|
1305
|
+
(ctx) => teamList(ctx)
|
|
1306
|
+
);
|
|
1108
1307
|
});
|
|
1109
|
-
team.command("create").description("create a team, seeded with the default workflow states").option(
|
|
1110
|
-
|
|
1308
|
+
team.command("create").description("create a team, seeded with the default workflow states").option(
|
|
1309
|
+
"--key <key>",
|
|
1310
|
+
"identifier prefix for the team's issues, e.g. IOS (required)"
|
|
1311
|
+
).option("--name <name>", "display name (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1312
|
+
await withConfig(
|
|
1313
|
+
opts.json === true,
|
|
1314
|
+
true,
|
|
1315
|
+
(ctx) => teamCreate(ctx, opts)
|
|
1316
|
+
);
|
|
1111
1317
|
});
|
|
1112
1318
|
program.command("states").description("a team's workflow states").option("--team <key>", "team key (or HARNESS_TEAM)").option("--json", "emit JSON").action(async (opts) => {
|
|
1113
|
-
await withConfig(
|
|
1319
|
+
await withConfig(
|
|
1320
|
+
opts.json === true,
|
|
1321
|
+
false,
|
|
1322
|
+
(ctx) => workflowStates(ctx, opts.team)
|
|
1323
|
+
);
|
|
1114
1324
|
});
|
|
1115
1325
|
var user = program.command("user").description("users");
|
|
1116
1326
|
user.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1117
|
-
await withConfig(
|
|
1327
|
+
await withConfig(
|
|
1328
|
+
opts.json === true,
|
|
1329
|
+
false,
|
|
1330
|
+
(ctx) => userList(ctx)
|
|
1331
|
+
);
|
|
1118
1332
|
});
|
|
1119
1333
|
program.command("upload").description("upload a file; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1120
1334
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1121
|
-
if (path === void 0)
|
|
1335
|
+
if (path === void 0)
|
|
1336
|
+
throw new UsageError(
|
|
1337
|
+
"usage: harness upload <path> (png/jpg/webp/gif, up to 25 MB)"
|
|
1338
|
+
);
|
|
1122
1339
|
return upload(ctx, path);
|
|
1123
1340
|
});
|
|
1124
1341
|
});
|
|
@@ -1126,25 +1343,35 @@ program.command("mcp").description("run the MCP server over stdio (for agents)")
|
|
|
1126
1343
|
const { startMcpServer } = await import("./server-LJXJLWNK.js");
|
|
1127
1344
|
await startMcpServer();
|
|
1128
1345
|
});
|
|
1129
|
-
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option(
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1346
|
+
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option(
|
|
1347
|
+
"--timeout <ms>",
|
|
1348
|
+
"how long to wait for the browser callback",
|
|
1349
|
+
integer
|
|
1350
|
+
).option("--json", "emit JSON").action(
|
|
1351
|
+
async (opts) => {
|
|
1352
|
+
const repoLookup = findRepoConfig();
|
|
1353
|
+
for (const warning of repoLookup.warnings)
|
|
1354
|
+
process.stderr.write(warning + "\n");
|
|
1355
|
+
const repoUrl = repoLookup.config?.url;
|
|
1356
|
+
const url = opts.url ?? blankToUndefined(process.env["HARNESS_API_URL"]) ?? repoUrl ?? storedLoginUrl() ?? DEFAULT_BASE_URL;
|
|
1357
|
+
if (url === repoUrl && opts.url === void 0 && blankToUndefined(process.env["HARNESS_API_URL"]) === void 0) {
|
|
1358
|
+
process.stderr.write(
|
|
1359
|
+
`harness: host ${url} comes from ${repoLookup.config?.path}
|
|
1360
|
+
`
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
const options = { noBrowser: !opts.browser };
|
|
1364
|
+
if (opts.timeout !== void 0) options.timeoutMs = opts.timeout;
|
|
1365
|
+
await finishAuth(await login(url, options), opts.json === true);
|
|
1137
1366
|
}
|
|
1138
|
-
|
|
1139
|
-
if (opts.timeout !== void 0) options.timeoutMs = opts.timeout;
|
|
1140
|
-
await finishAuth(await login(url, options), opts.json === true);
|
|
1141
|
-
});
|
|
1367
|
+
);
|
|
1142
1368
|
program.command("logout").description("revoke and forget the stored login").option("--json", "emit JSON").action(async (opts) => {
|
|
1143
1369
|
await finishAuth(await logout(), opts.json === true);
|
|
1144
1370
|
});
|
|
1145
1371
|
program.command("whoami").description("show the resolved host and credential source").option("--json", "emit JSON").action(async (opts) => {
|
|
1146
1372
|
const resolved = resolveConfig();
|
|
1147
|
-
for (const warning of resolved.warnings)
|
|
1373
|
+
for (const warning of resolved.warnings)
|
|
1374
|
+
process.stderr.write(warning + "\n");
|
|
1148
1375
|
if (resolved.kind === "error") {
|
|
1149
1376
|
process.stderr.write(`harness: ${resolved.message}
|
|
1150
1377
|
`);
|