@allocator-one/harness-cli 1.4.0 → 1.5.0
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/{chunk-MF7MQ2L6.js → chunk-FGBXAAKW.js} +154 -6
- package/dist/cli.js +441 -163
- package/dist/{server-LJXJLWNK.js → server-ZRD4RTFU.js} +126 -1
- package/package.json +1 -1
- package/spec/openapi.json +1122 -464
package/dist/cli.js
CHANGED
|
@@ -28,12 +28,17 @@ import {
|
|
|
28
28
|
isHostTrusted,
|
|
29
29
|
json,
|
|
30
30
|
labelCreate,
|
|
31
|
+
labelDelete,
|
|
31
32
|
labelList,
|
|
33
|
+
labelUpdate,
|
|
32
34
|
linkAdd,
|
|
33
35
|
linkList,
|
|
34
36
|
linkRm,
|
|
35
37
|
list,
|
|
36
38
|
loginHelp,
|
|
39
|
+
memberAdd,
|
|
40
|
+
memberList,
|
|
41
|
+
memberSetRole,
|
|
37
42
|
milestoneCreate,
|
|
38
43
|
milestoneList,
|
|
39
44
|
prime,
|
|
@@ -48,9 +53,15 @@ import {
|
|
|
48
53
|
resolveConfig,
|
|
49
54
|
search,
|
|
50
55
|
show,
|
|
56
|
+
stateAdd,
|
|
57
|
+
stateDelete,
|
|
58
|
+
stateOrder,
|
|
59
|
+
stateUpdate,
|
|
51
60
|
stats,
|
|
52
61
|
teamCreate,
|
|
53
62
|
teamList,
|
|
63
|
+
teamRename,
|
|
64
|
+
teamSetDefault,
|
|
54
65
|
trustHost,
|
|
55
66
|
update,
|
|
56
67
|
upload,
|
|
@@ -59,7 +70,7 @@ import {
|
|
|
59
70
|
version,
|
|
60
71
|
workflowStates,
|
|
61
72
|
writeCredentials
|
|
62
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-FGBXAAKW.js";
|
|
63
74
|
|
|
64
75
|
// src/cli.ts
|
|
65
76
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -150,7 +161,7 @@ function isSet(value) {
|
|
|
150
161
|
}
|
|
151
162
|
async function loopbackLogin(baseUrl, options) {
|
|
152
163
|
const notify = options.notify ?? defaultNotify;
|
|
153
|
-
const
|
|
164
|
+
const state2 = generateState();
|
|
154
165
|
const verifier = generateVerifier();
|
|
155
166
|
const challenge = challengeFor(verifier);
|
|
156
167
|
const server = createServer();
|
|
@@ -165,13 +176,13 @@ async function loopbackLogin(baseUrl, options) {
|
|
|
165
176
|
return { ok: false, message: "could not open a loopback port for the browser handoff" };
|
|
166
177
|
}
|
|
167
178
|
try {
|
|
168
|
-
const query = new URLSearchParams({ port: String(port), state, challenge });
|
|
179
|
+
const query = new URLSearchParams({ port: String(port), state: state2, challenge });
|
|
169
180
|
const url = `${baseUrl}/cli/login?${query.toString()}`;
|
|
170
181
|
const openBrowser = options.openBrowser ?? defaultOpenBrowser;
|
|
171
182
|
openBrowser(url);
|
|
172
183
|
notify(`Opening ${url}`);
|
|
173
184
|
notify("Waiting for the browser authorization (Ctrl-C to abort)\u2026");
|
|
174
|
-
const code = await awaitCallback(server,
|
|
185
|
+
const code = await awaitCallback(server, state2, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
175
186
|
if (!code.ok) return code;
|
|
176
187
|
const token = await exchangeCode(baseUrl, code.code, verifier, options);
|
|
177
188
|
if (!token.ok) return token;
|
|
@@ -181,7 +192,7 @@ async function loopbackLogin(baseUrl, options) {
|
|
|
181
192
|
server.close();
|
|
182
193
|
}
|
|
183
194
|
}
|
|
184
|
-
function awaitCallback(server,
|
|
195
|
+
function awaitCallback(server, state2, timeoutMs) {
|
|
185
196
|
return new Promise((resolve) => {
|
|
186
197
|
const timer = setTimeout(() => {
|
|
187
198
|
resolve({ ok: false, message: "timed out waiting for the browser authorization" });
|
|
@@ -203,7 +214,7 @@ function awaitCallback(server, state, timeoutMs) {
|
|
|
203
214
|
return;
|
|
204
215
|
}
|
|
205
216
|
const code = url.searchParams.get("code");
|
|
206
|
-
if (url.searchParams.get("state") ===
|
|
217
|
+
if (url.searchParams.get("state") === state2 && code !== null && code !== "") {
|
|
207
218
|
respond(200, "Signed in \u2014 you can close this tab and return to the terminal.");
|
|
208
219
|
clearTimeout(timer);
|
|
209
220
|
resolve({ ok: true, code });
|
|
@@ -372,6 +383,16 @@ function defaultOpenBrowser(url) {
|
|
|
372
383
|
}
|
|
373
384
|
}
|
|
374
385
|
|
|
386
|
+
// src/core/stdio.ts
|
|
387
|
+
var BROKEN_PIPE_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED"]);
|
|
388
|
+
function guardBrokenPipes(streams = [process.stdout, process.stderr]) {
|
|
389
|
+
for (const stream of streams) {
|
|
390
|
+
stream.on("error", (error) => {
|
|
391
|
+
if (!BROKEN_PIPE_CODES.has(error.code ?? "")) throw error;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
375
396
|
// src/core/upgrade.ts
|
|
376
397
|
import { spawn, spawnSync } from "child_process";
|
|
377
398
|
import {
|
|
@@ -390,6 +411,7 @@ import {
|
|
|
390
411
|
import { basename, dirname, join as join2, parse, sep } from "path";
|
|
391
412
|
import { fileURLToPath } from "url";
|
|
392
413
|
var PACKAGE_NAME = "@allocator-one/harness-cli";
|
|
414
|
+
var UPGRADE_INSTALL_SPEC = `${PACKAGE_NAME}@latest`;
|
|
393
415
|
var UPDATE_CHECK_COMMAND = "__update-check";
|
|
394
416
|
var REEXEC_SENTINEL = "HARNESS_UPGRADED";
|
|
395
417
|
var DEFAULT_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -399,7 +421,11 @@ var NPM_ROOT_TIMEOUT_MS = 15e3;
|
|
|
399
421
|
var INSTALL_TIMEOUT_MS = 12e4;
|
|
400
422
|
var LOCK_STALE_MS = NPM_ROOT_TIMEOUT_MS + INSTALL_TIMEOUT_MS + 6e4;
|
|
401
423
|
function detectInstall(from = fileURLToPath(import.meta.url)) {
|
|
402
|
-
const unknown = {
|
|
424
|
+
const unknown = {
|
|
425
|
+
kind: "unknown",
|
|
426
|
+
packageRoot: null,
|
|
427
|
+
nodeModules: null
|
|
428
|
+
};
|
|
403
429
|
if (from.split(sep).includes("_npx")) return { ...unknown, kind: "npx" };
|
|
404
430
|
const packageRoot = findPackageRoot(from);
|
|
405
431
|
if (packageRoot === null) return unknown;
|
|
@@ -432,7 +458,9 @@ function packageNameAt(path) {
|
|
|
432
458
|
}
|
|
433
459
|
function versionAt(packageRoot) {
|
|
434
460
|
try {
|
|
435
|
-
const parsed = JSON.parse(
|
|
461
|
+
const parsed = JSON.parse(
|
|
462
|
+
readFileSync(join2(packageRoot, "package.json"), "utf8")
|
|
463
|
+
);
|
|
436
464
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
437
465
|
const version2 = parsed["version"];
|
|
438
466
|
return typeof version2 === "string" ? version2 : null;
|
|
@@ -463,7 +491,8 @@ function isNewer(candidate, current) {
|
|
|
463
491
|
const b = parseVersion(current);
|
|
464
492
|
if (a === null || b === null) return false;
|
|
465
493
|
for (let index = 0; index < 3; index++) {
|
|
466
|
-
if (a.parts[index] !== b.parts[index])
|
|
494
|
+
if (a.parts[index] !== b.parts[index])
|
|
495
|
+
return a.parts[index] > b.parts[index];
|
|
467
496
|
}
|
|
468
497
|
if (a.pre === b.pre) return false;
|
|
469
498
|
if (a.pre === null) return true;
|
|
@@ -499,7 +528,8 @@ function readStamp(env = process.env) {
|
|
|
499
528
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
500
529
|
const record = parsed;
|
|
501
530
|
const checkedAt = record["checkedAt"];
|
|
502
|
-
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))
|
|
531
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))
|
|
532
|
+
return null;
|
|
503
533
|
const latest = record["latest"];
|
|
504
534
|
return { checkedAt, latest: typeof latest === "string" ? latest : null };
|
|
505
535
|
} catch {
|
|
@@ -571,7 +601,8 @@ async function fetchLatestVersion(env) {
|
|
|
571
601
|
const parsed = await response.json();
|
|
572
602
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
573
603
|
const version2 = parsed["version"];
|
|
574
|
-
if (typeof version2 !== "string" || parseVersion(version2) === null)
|
|
604
|
+
if (typeof version2 !== "string" || parseVersion(version2) === null)
|
|
605
|
+
return null;
|
|
575
606
|
return version2;
|
|
576
607
|
} catch {
|
|
577
608
|
return null;
|
|
@@ -581,10 +612,18 @@ function selfHeal(options = {}) {
|
|
|
581
612
|
const env = options.env ?? process.env;
|
|
582
613
|
const argv2 = options.argv ?? process.argv;
|
|
583
614
|
if (truthy(env[REEXEC_SENTINEL])) {
|
|
584
|
-
return {
|
|
615
|
+
return {
|
|
616
|
+
kind: "skipped",
|
|
617
|
+
reason: "already upgraded once for this command"
|
|
618
|
+
};
|
|
585
619
|
}
|
|
586
|
-
if (!autoUpgradeEnabled(env))
|
|
587
|
-
|
|
620
|
+
if (!autoUpgradeEnabled(env))
|
|
621
|
+
return { kind: "skipped", reason: "automatic upgrades are off" };
|
|
622
|
+
if (argv2[2] === "mcp")
|
|
623
|
+
return {
|
|
624
|
+
kind: "skipped",
|
|
625
|
+
reason: "an MCP session must not be replaced mid-stream"
|
|
626
|
+
};
|
|
588
627
|
const install = options.install ?? detectInstall();
|
|
589
628
|
if (install.kind !== "global" || install.packageRoot === null) {
|
|
590
629
|
return { kind: "skipped", reason: describeInstall(install.kind) };
|
|
@@ -595,17 +634,27 @@ function selfHeal(options = {}) {
|
|
|
595
634
|
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
596
635
|
}
|
|
597
636
|
const lock = takeLock(env);
|
|
598
|
-
if (lock === null)
|
|
637
|
+
if (lock === null)
|
|
638
|
+
return {
|
|
639
|
+
kind: "skipped",
|
|
640
|
+
reason: "another harness process is already upgrading"
|
|
641
|
+
};
|
|
599
642
|
try {
|
|
600
643
|
const stillHeld = () => readHolder(lock.path)?.pid === lock.pid;
|
|
601
644
|
const result = (options.runInstall ?? npmInstallGlobal)(install, stillHeld);
|
|
602
645
|
if (!result.ok) return { kind: "failed", reason: result.detail };
|
|
603
646
|
const after = versionAt(packageRoot);
|
|
604
647
|
if (after !== null && after === installed) {
|
|
605
|
-
return {
|
|
648
|
+
return {
|
|
649
|
+
kind: "failed",
|
|
650
|
+
reason: `still ${after} after upgrading \u2014 no newer release is published`
|
|
651
|
+
};
|
|
606
652
|
}
|
|
607
653
|
if (after !== null && installed !== null && !isNewer(after, installed)) {
|
|
608
|
-
return {
|
|
654
|
+
return {
|
|
655
|
+
kind: "failed",
|
|
656
|
+
reason: `npm left ${after} installed, which is not newer than ${installed}`
|
|
657
|
+
};
|
|
609
658
|
}
|
|
610
659
|
} finally {
|
|
611
660
|
releaseLock(lock);
|
|
@@ -613,7 +662,10 @@ function selfHeal(options = {}) {
|
|
|
613
662
|
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
614
663
|
}
|
|
615
664
|
function reexec(options, env, argv2) {
|
|
616
|
-
return (options.reexec ?? reexecSelf)(
|
|
665
|
+
return (options.reexec ?? reexecSelf)(
|
|
666
|
+
{ ...env, [REEXEC_SENTINEL]: "1" },
|
|
667
|
+
argv2
|
|
668
|
+
);
|
|
617
669
|
}
|
|
618
670
|
function describeInstall(kind) {
|
|
619
671
|
switch (kind) {
|
|
@@ -629,9 +681,13 @@ function describeInstall(kind) {
|
|
|
629
681
|
}
|
|
630
682
|
function npmInstallGlobal(install, stillHeld) {
|
|
631
683
|
const globalRoot = npmGlobalRoot();
|
|
632
|
-
if (globalRoot === null)
|
|
684
|
+
if (globalRoot === null)
|
|
685
|
+
return { ok: false, detail: "npm did not say where its global root is" };
|
|
633
686
|
if (globalRoot.includes("***")) {
|
|
634
|
-
return {
|
|
687
|
+
return {
|
|
688
|
+
ok: false,
|
|
689
|
+
detail: "npm redacted its own global root, so this copy's location cannot be confirmed"
|
|
690
|
+
};
|
|
635
691
|
}
|
|
636
692
|
if (install.nodeModules === null || !samePath(globalRoot, install.nodeModules)) {
|
|
637
693
|
return {
|
|
@@ -642,10 +698,16 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
642
698
|
try {
|
|
643
699
|
accessSync2(globalRoot, constants.W_OK);
|
|
644
700
|
} catch {
|
|
645
|
-
return {
|
|
701
|
+
return {
|
|
702
|
+
ok: false,
|
|
703
|
+
detail: `${globalRoot} is not writable \u2014 upgrade it yourself`
|
|
704
|
+
};
|
|
646
705
|
}
|
|
647
706
|
if (!stillHeld()) {
|
|
648
|
-
return {
|
|
707
|
+
return {
|
|
708
|
+
ok: false,
|
|
709
|
+
detail: "lost the upgrade lock while preparing \u2014 another process took over"
|
|
710
|
+
};
|
|
649
711
|
}
|
|
650
712
|
process.stderr.write(`harness: upgrading ${PACKAGE_NAME} \u2026
|
|
651
713
|
`);
|
|
@@ -657,7 +719,7 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
657
719
|
"--no-fund",
|
|
658
720
|
"--no-update-notifier",
|
|
659
721
|
"--loglevel=error",
|
|
660
|
-
|
|
722
|
+
UPGRADE_INSTALL_SPEC
|
|
661
723
|
];
|
|
662
724
|
const result = spawnSync(npmCommand(), args, {
|
|
663
725
|
// npm's diagnostics belong on stderr with ours; stdout stays the
|
|
@@ -666,9 +728,13 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
666
728
|
timeout: INSTALL_TIMEOUT_MS,
|
|
667
729
|
...npmSpawnOptions()
|
|
668
730
|
});
|
|
669
|
-
if (result.error !== void 0)
|
|
731
|
+
if (result.error !== void 0)
|
|
732
|
+
return { ok: false, detail: result.error.message };
|
|
670
733
|
if (result.status !== 0) {
|
|
671
|
-
return {
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
detail: `npm install exited ${result.status ?? `on ${String(result.signal)}`}`
|
|
737
|
+
};
|
|
672
738
|
}
|
|
673
739
|
return { ok: true, detail: "" };
|
|
674
740
|
}
|
|
@@ -710,8 +776,10 @@ function reexecSelf(env, argv2) {
|
|
|
710
776
|
env
|
|
711
777
|
});
|
|
712
778
|
if (result.error !== void 0) {
|
|
713
|
-
process.stderr.write(
|
|
714
|
-
`
|
|
779
|
+
process.stderr.write(
|
|
780
|
+
`harness: upgraded, but re-running failed: ${result.error.message}
|
|
781
|
+
`
|
|
782
|
+
);
|
|
715
783
|
return 1;
|
|
716
784
|
}
|
|
717
785
|
return result.status ?? 1;
|
|
@@ -794,7 +862,10 @@ function autoUpgradeEnabled(env) {
|
|
|
794
862
|
|
|
795
863
|
// src/cli.ts
|
|
796
864
|
function promptLine(question) {
|
|
797
|
-
const readline = createInterface2({
|
|
865
|
+
const readline = createInterface2({
|
|
866
|
+
input: process.stdin,
|
|
867
|
+
output: process.stderr
|
|
868
|
+
});
|
|
798
869
|
return new Promise((resolve) => {
|
|
799
870
|
readline.question(question, (answer) => {
|
|
800
871
|
readline.close();
|
|
@@ -804,15 +875,19 @@ function promptLine(question) {
|
|
|
804
875
|
}
|
|
805
876
|
function exitWith(code) {
|
|
806
877
|
const drain = (stream) => new Promise((resolve) => stream.write("", () => resolve()));
|
|
807
|
-
return Promise.all([drain(process.stdout), drain(process.stderr)]).then(
|
|
878
|
+
return Promise.all([drain(process.stdout), drain(process.stderr)]).then(
|
|
879
|
+
() => process.exit(code)
|
|
880
|
+
);
|
|
808
881
|
}
|
|
882
|
+
guardBrokenPipes();
|
|
809
883
|
var program = new Command();
|
|
810
884
|
program.name("harness").description("issue tracking for humans and agents (harness.infra.one)").exitOverride().configureOutput({
|
|
811
885
|
writeOut: (text) => process.stdout.write(text),
|
|
812
886
|
writeErr: (text) => process.stderr.write(text)
|
|
813
887
|
});
|
|
814
888
|
function integer(value) {
|
|
815
|
-
if (!/^\d+$/.test(value))
|
|
889
|
+
if (!/^\d+$/.test(value))
|
|
890
|
+
throw new InvalidArgumentError("expected a non-negative integer");
|
|
816
891
|
return Number.parseInt(value, 10);
|
|
817
892
|
}
|
|
818
893
|
function collect(value, previous) {
|
|
@@ -858,8 +933,10 @@ async function withConfig(jsonFlag, mutating, run) {
|
|
|
858
933
|
process.stderr.write(loginResult.human);
|
|
859
934
|
const again = resolveConfig();
|
|
860
935
|
if (again.kind !== "ok") {
|
|
861
|
-
process.stderr.write(
|
|
862
|
-
`
|
|
936
|
+
process.stderr.write(
|
|
937
|
+
`harness: still not signed in to ${resolved.baseUrl} after logging in
|
|
938
|
+
`
|
|
939
|
+
);
|
|
863
940
|
return exitWith(1);
|
|
864
941
|
}
|
|
865
942
|
return execute(again.config, jsonFlag, mutating, run);
|
|
@@ -891,16 +968,20 @@ async function fail(error, hostNotice) {
|
|
|
891
968
|
return exitWith(1);
|
|
892
969
|
}
|
|
893
970
|
if (error instanceof TransportError) {
|
|
894
|
-
process.stderr.write(
|
|
895
|
-
`)
|
|
971
|
+
process.stderr.write(
|
|
972
|
+
hostNotice + `harness: cannot reach the API: ${clean(error.message)}
|
|
973
|
+
`
|
|
974
|
+
);
|
|
896
975
|
return exitWith(3);
|
|
897
976
|
}
|
|
898
977
|
if (error instanceof UpgradeRequiredError) {
|
|
899
978
|
const healed = selfHeal();
|
|
900
979
|
if (healed.kind === "upgraded") return exitWith(healed.code);
|
|
901
980
|
const why = healed.kind === "failed" ? ` (tried to upgrade: ${clean(healed.reason)})` : "";
|
|
902
|
-
process.stderr.write(
|
|
903
|
-
`)
|
|
981
|
+
process.stderr.write(
|
|
982
|
+
hostNotice + `harness: ${clean(error.message)} (HTTP 426)${why}
|
|
983
|
+
`
|
|
984
|
+
);
|
|
904
985
|
return exitWith(1);
|
|
905
986
|
}
|
|
906
987
|
if (error instanceof HttpError) {
|
|
@@ -919,7 +1000,8 @@ async function fail(error, hostNotice) {
|
|
|
919
1000
|
}
|
|
920
1001
|
function errorDetails(body) {
|
|
921
1002
|
const details = body["details"];
|
|
922
|
-
if (typeof details !== "object" || details === null || Array.isArray(details))
|
|
1003
|
+
if (typeof details !== "object" || details === null || Array.isArray(details))
|
|
1004
|
+
return "";
|
|
923
1005
|
return Object.entries(details).map(([field, messages]) => {
|
|
924
1006
|
const text = Array.isArray(messages) ? messages.map(String).join("; ") : String(messages);
|
|
925
1007
|
return `
|
|
@@ -927,24 +1009,53 @@ function errorDetails(body) {
|
|
|
927
1009
|
}).join("");
|
|
928
1010
|
}
|
|
929
1011
|
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(
|
|
1012
|
+
await withConfig(
|
|
1013
|
+
opts.json === true,
|
|
1014
|
+
opts.claim === true,
|
|
1015
|
+
(ctx) => ready(ctx, opts)
|
|
1016
|
+
);
|
|
931
1017
|
});
|
|
932
1018
|
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
|
-
|
|
1019
|
+
await withConfig(
|
|
1020
|
+
opts.json === true,
|
|
1021
|
+
false,
|
|
1022
|
+
(ctx) => blocked(ctx, opts)
|
|
1023
|
+
);
|
|
937
1024
|
});
|
|
938
|
-
program.command("
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
await withConfig(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1025
|
+
program.command("list").description("list issues with filters").option("--status <status>", "open | in_progress | closed").option(
|
|
1026
|
+
"--assignee <who>",
|
|
1027
|
+
'me, a user id, an email, or a full name; "" for unassigned'
|
|
1028
|
+
).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) => {
|
|
1029
|
+
await withConfig(
|
|
1030
|
+
opts.json === true,
|
|
1031
|
+
false,
|
|
1032
|
+
(ctx) => list(ctx, opts)
|
|
1033
|
+
);
|
|
947
1034
|
});
|
|
1035
|
+
program.command("search").description(
|
|
1036
|
+
"search by meaning \u2014 ranked, exact identifier first; use list --search for paginated filtering"
|
|
1037
|
+
).argument("[query...]").option("--limit <n>", "maximum results (default 10, capped at 20)", integer).option(
|
|
1038
|
+
"--page <n>",
|
|
1039
|
+
"(deprecated, ignored \u2014 results are ranked and capped)",
|
|
1040
|
+
integer
|
|
1041
|
+
).option("--all", "(deprecated, ignored \u2014 results are ranked and capped)").option("--json", "emit JSON").action(
|
|
1042
|
+
async (query, opts) => {
|
|
1043
|
+
await withConfig(
|
|
1044
|
+
opts.json === true,
|
|
1045
|
+
false,
|
|
1046
|
+
(ctx) => search(ctx, query, opts)
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
);
|
|
1050
|
+
program.command("show").description("full issue detail; downloads attached images locally").argument("[id]").option("--no-images", "skip attachment downloads").option("--json", "emit JSON").action(
|
|
1051
|
+
async (id, opts) => {
|
|
1052
|
+
await withConfig(opts.json === true, false, (ctx) => {
|
|
1053
|
+
if (id === void 0) throw new UsageError("usage: harness show <id>");
|
|
1054
|
+
requireIdentifiers2([id]);
|
|
1055
|
+
return show(ctx, id, opts);
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
);
|
|
948
1059
|
program.command("stats").description("per-team and total counts").option("--json", "emit JSON").action(async (opts) => {
|
|
949
1060
|
await withConfig(opts.json === true, false, (ctx) => stats(ctx));
|
|
950
1061
|
});
|
|
@@ -956,195 +1067,362 @@ program.command("version").description("print CLI and server API versions").opti
|
|
|
956
1067
|
let config = null;
|
|
957
1068
|
if (resolved.kind === "ok") config = resolved.config;
|
|
958
1069
|
else if (resolved.kind === "needs-login") {
|
|
959
|
-
config = {
|
|
1070
|
+
config = {
|
|
1071
|
+
baseUrl: resolved.baseUrl,
|
|
1072
|
+
token: "",
|
|
1073
|
+
actor: null,
|
|
1074
|
+
team: null,
|
|
1075
|
+
cacheDir: ""
|
|
1076
|
+
};
|
|
960
1077
|
}
|
|
961
1078
|
const output = await version(config);
|
|
962
|
-
process.stdout.write(
|
|
1079
|
+
process.stdout.write(
|
|
1080
|
+
opts.json === true ? json(output.json) : output.human
|
|
1081
|
+
);
|
|
963
1082
|
await exitWith(0);
|
|
964
1083
|
});
|
|
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
|
-
});
|
|
1084
|
+
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(
|
|
1085
|
+
"--type <type>",
|
|
1086
|
+
"task | bug | feature | epic (applied as a type/ label)"
|
|
1087
|
+
).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) => {
|
|
1088
|
+
await withConfig(
|
|
1089
|
+
opts.json === true,
|
|
1090
|
+
true,
|
|
1091
|
+
(ctx) => create(ctx, opts)
|
|
1092
|
+
);
|
|
992
1093
|
});
|
|
1094
|
+
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(
|
|
1095
|
+
"--assignee <who>",
|
|
1096
|
+
'me, a user id, an email, or a full name; "" unassigns'
|
|
1097
|
+
).option("--json", "emit JSON").action(
|
|
1098
|
+
async (id, opts) => {
|
|
1099
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1100
|
+
if (id === void 0) {
|
|
1101
|
+
throw new UsageError(
|
|
1102
|
+
"usage: harness update <id> --claim | --title \u2026 | --state <name> | \u2026"
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
requireIdentifiers2([id]);
|
|
1106
|
+
return update(ctx, id, opts);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
);
|
|
1110
|
+
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(
|
|
1111
|
+
async (ids, opts) => {
|
|
1112
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1113
|
+
if (ids.length === 0) {
|
|
1114
|
+
throw new UsageError(
|
|
1115
|
+
"usage: harness close <id> [<id>\u2026] [--reason <text>] [--cancel]"
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
requireIdentifiers2(ids);
|
|
1119
|
+
return close(ctx, ids, opts);
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1123
|
+
program.command("archive").description("archive issues; --restore brings them back").argument("[ids...]").option("--restore", "restore instead of archive").option("--json", "emit JSON").action(
|
|
1124
|
+
async (ids, opts) => {
|
|
1125
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1126
|
+
if (ids.length === 0)
|
|
1127
|
+
throw new UsageError(
|
|
1128
|
+
"usage: harness archive <id> [<id>\u2026] [--restore]"
|
|
1129
|
+
);
|
|
1130
|
+
requireIdentifiers2(ids);
|
|
1131
|
+
return archive(ctx, ids, opts);
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
993
1135
|
program.command("comments").description("an issue's full comment thread, untruncated").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
994
1136
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
995
|
-
if (id === void 0)
|
|
1137
|
+
if (id === void 0)
|
|
1138
|
+
throw new UsageError("usage: harness comments <id>");
|
|
996
1139
|
requireIdentifiers2([id]);
|
|
997
1140
|
return comments(ctx, id);
|
|
998
1141
|
});
|
|
999
1142
|
});
|
|
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
|
-
}
|
|
1143
|
+
program.command("dep").description("blocking dependencies: dep add|rm <issue> <blocker>").argument("[verb]").argument("[issue]").argument("[blocker]").option("--json", "emit JSON").action(
|
|
1144
|
+
async (verb, issue, blocker, opts) => {
|
|
1145
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1146
|
+
if (verb === void 0 || issue === void 0 || blocker === void 0) {
|
|
1147
|
+
throw new UsageError("usage: harness dep add|rm <issue> <blocker>");
|
|
1148
|
+
}
|
|
1149
|
+
requireIdentifiers2([issue, blocker]);
|
|
1150
|
+
return dep(ctx, verb, issue, blocker);
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
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(
|
|
1155
|
+
async (id, text, opts) => {
|
|
1156
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1157
|
+
if (id === void 0 || text.length === 0) {
|
|
1158
|
+
throw new UsageError(
|
|
1159
|
+
"usage: harness comment <id> <text> [--mention NAME]\u2026"
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
requireIdentifiers2([id]);
|
|
1163
|
+
const mentions = opts.mention.filter((name) => name.trim() !== "");
|
|
1164
|
+
return comment(ctx, id, text, mentions);
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
);
|
|
1019
1168
|
program.command("attach-image").description("upload an image; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1020
1169
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1021
1170
|
if (path === void 0) {
|
|
1022
|
-
throw new UsageError(
|
|
1171
|
+
throw new UsageError(
|
|
1172
|
+
"usage: harness attach-image <path> (one png, jpg, webp or gif, up to 25 MB)"
|
|
1173
|
+
);
|
|
1023
1174
|
}
|
|
1024
1175
|
return attachImage(ctx, path);
|
|
1025
1176
|
});
|
|
1026
1177
|
});
|
|
1027
1178
|
var project = program.command("project").description("projects");
|
|
1028
1179
|
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(
|
|
1180
|
+
await withConfig(
|
|
1181
|
+
opts.json === true,
|
|
1182
|
+
false,
|
|
1183
|
+
(ctx) => projectList(ctx, opts)
|
|
1184
|
+
);
|
|
1030
1185
|
});
|
|
1031
1186
|
project.command("show").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1032
1187
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1033
|
-
if (id === void 0)
|
|
1188
|
+
if (id === void 0)
|
|
1189
|
+
throw new UsageError("usage: harness project show <id>");
|
|
1034
1190
|
return projectShow(ctx, id);
|
|
1035
1191
|
});
|
|
1036
1192
|
});
|
|
1037
1193
|
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
|
-
});
|
|
1194
|
+
await withConfig(
|
|
1195
|
+
opts.json === true,
|
|
1196
|
+
true,
|
|
1197
|
+
(ctx) => projectCreate(ctx, opts)
|
|
1198
|
+
);
|
|
1045
1199
|
});
|
|
1200
|
+
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(
|
|
1201
|
+
async (id, opts) => {
|
|
1202
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1203
|
+
if (id === void 0)
|
|
1204
|
+
throw new UsageError(
|
|
1205
|
+
"usage: harness project update <id> --name \u2026 | --state \u2026 | \u2026"
|
|
1206
|
+
);
|
|
1207
|
+
return projectUpdate(ctx, id, opts);
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
);
|
|
1046
1211
|
var milestone = program.command("milestone").description("project milestones");
|
|
1047
1212
|
milestone.command("list").option("--project <id>", "project id (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1048
|
-
await withConfig(
|
|
1213
|
+
await withConfig(
|
|
1214
|
+
opts.json === true,
|
|
1215
|
+
false,
|
|
1216
|
+
(ctx) => milestoneList(ctx, opts.project)
|
|
1217
|
+
);
|
|
1049
1218
|
});
|
|
1050
1219
|
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(
|
|
1220
|
+
await withConfig(
|
|
1221
|
+
opts.json === true,
|
|
1222
|
+
true,
|
|
1223
|
+
(ctx) => milestoneCreate(ctx, opts)
|
|
1224
|
+
);
|
|
1052
1225
|
});
|
|
1053
1226
|
var doc = program.command("doc").description("documents");
|
|
1054
1227
|
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(
|
|
1228
|
+
await withConfig(
|
|
1229
|
+
opts.json === true,
|
|
1230
|
+
false,
|
|
1231
|
+
(ctx) => docList(ctx, opts)
|
|
1232
|
+
);
|
|
1056
1233
|
});
|
|
1057
1234
|
doc.command("show").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1058
1235
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1059
|
-
if (id === void 0)
|
|
1236
|
+
if (id === void 0)
|
|
1237
|
+
throw new UsageError("usage: harness doc show <id>");
|
|
1060
1238
|
return docShow(ctx, id);
|
|
1061
1239
|
});
|
|
1062
1240
|
});
|
|
1063
1241
|
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
|
-
});
|
|
1242
|
+
await withConfig(
|
|
1243
|
+
opts.json === true,
|
|
1244
|
+
true,
|
|
1245
|
+
(ctx) => docCreate(ctx, opts)
|
|
1246
|
+
);
|
|
1071
1247
|
});
|
|
1248
|
+
doc.command("update").argument("[id]").option("--title <title>").option("--content <text>").option("--project <id>").option("--json", "emit JSON").action(
|
|
1249
|
+
async (id, opts) => {
|
|
1250
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1251
|
+
if (id === void 0)
|
|
1252
|
+
throw new UsageError(
|
|
1253
|
+
"usage: harness doc update <id> --title \u2026 | --content \u2026 | --project \u2026"
|
|
1254
|
+
);
|
|
1255
|
+
return docUpdate(ctx, id, opts);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
);
|
|
1072
1259
|
var link = program.command("link").description("URLs attached to an issue (distinct from dep relations)");
|
|
1073
1260
|
link.command("list").argument("[issue]").option("--json", "emit JSON").action(async (issue, opts) => {
|
|
1074
1261
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1075
|
-
if (issue === void 0)
|
|
1262
|
+
if (issue === void 0)
|
|
1263
|
+
throw new UsageError("usage: harness link list <issue>");
|
|
1076
1264
|
requireIdentifiers2([issue]);
|
|
1077
1265
|
return linkList(ctx, issue);
|
|
1078
1266
|
});
|
|
1079
1267
|
});
|
|
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
|
-
|
|
1268
|
+
link.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
|
|
1269
|
+
async (issue, url, opts) => {
|
|
1270
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1271
|
+
if (issue === void 0 || url === void 0) {
|
|
1272
|
+
throw new UsageError(
|
|
1273
|
+
"usage: harness link add <issue> <url> [--title <title>]"
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
requireIdentifiers2([issue]);
|
|
1277
|
+
return linkAdd(ctx, issue, url, opts.title);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
|
|
1282
|
+
async (issue, idOrUrl, opts) => {
|
|
1283
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1284
|
+
if (issue === void 0 || idOrUrl === void 0) {
|
|
1285
|
+
throw new UsageError(
|
|
1286
|
+
"usage: harness link rm <issue> <link-id-or-url>"
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
requireIdentifiers2([issue]);
|
|
1290
|
+
return linkRm(ctx, issue, idOrUrl);
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
);
|
|
1098
1294
|
var label = program.command("label").description("labels");
|
|
1099
1295
|
label.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1100
|
-
await withConfig(
|
|
1296
|
+
await withConfig(
|
|
1297
|
+
opts.json === true,
|
|
1298
|
+
false,
|
|
1299
|
+
(ctx) => labelList(ctx)
|
|
1300
|
+
);
|
|
1301
|
+
});
|
|
1302
|
+
label.command("create").argument("[name]").option("--color <color>", "label color").option("--json", "emit JSON").action(
|
|
1303
|
+
async (name, opts) => {
|
|
1304
|
+
await withConfig(
|
|
1305
|
+
opts.json === true,
|
|
1306
|
+
true,
|
|
1307
|
+
(ctx) => labelCreate(ctx, name, opts.color)
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
label.command("update").description("rename or recolor a label").argument("[id]").option("--name <name>", "new name").option("--color <color>", "swatch name (red, blue, \u2026) or a hex code").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1312
|
+
await withConfig(opts.json === true, true, (ctx) => labelUpdate(ctx, id, opts));
|
|
1101
1313
|
});
|
|
1102
|
-
label.command("
|
|
1103
|
-
await withConfig(opts.json === true, true, (ctx) =>
|
|
1314
|
+
label.command("delete").description("delete a label (refused while issues carry it)").argument("[id]").option("--yes", "confirm the deletion").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1315
|
+
await withConfig(opts.json === true, true, (ctx) => labelDelete(ctx, id, opts));
|
|
1104
1316
|
});
|
|
1105
1317
|
var team = program.command("team").description("teams");
|
|
1106
1318
|
team.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1107
|
-
await withConfig(
|
|
1319
|
+
await withConfig(
|
|
1320
|
+
opts.json === true,
|
|
1321
|
+
false,
|
|
1322
|
+
(ctx) => teamList(ctx)
|
|
1323
|
+
);
|
|
1324
|
+
});
|
|
1325
|
+
team.command("create").description("create a team, seeded with the default workflow states").option(
|
|
1326
|
+
"--key <key>",
|
|
1327
|
+
"identifier prefix for the team's issues, e.g. IOS (required)"
|
|
1328
|
+
).option("--name <name>", "display name (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1329
|
+
await withConfig(
|
|
1330
|
+
opts.json === true,
|
|
1331
|
+
true,
|
|
1332
|
+
(ctx) => teamCreate(ctx, opts)
|
|
1333
|
+
);
|
|
1334
|
+
});
|
|
1335
|
+
team.command("rename").description("rename a team (the key is create-only)").argument("[key]").option("--name <name>", "new display name").option("--json", "emit JSON").action(async (key, opts) => {
|
|
1336
|
+
await withConfig(opts.json === true, true, (ctx) => teamRename(ctx, key, opts.name));
|
|
1108
1337
|
});
|
|
1109
|
-
team.command("
|
|
1110
|
-
await withConfig(opts.json === true, true, (ctx) =>
|
|
1338
|
+
team.command("set-default").description("make this team the organization's default filing target").argument("[key]").option("--json", "emit JSON").action(async (key, opts) => {
|
|
1339
|
+
await withConfig(opts.json === true, true, (ctx) => teamSetDefault(ctx, key));
|
|
1111
1340
|
});
|
|
1112
|
-
program.command("
|
|
1341
|
+
var state = program.command("state").description("a team's workflow states");
|
|
1342
|
+
state.command("list").description("list a team's workflow states").option("--team <key>", "team key (or HARNESS_TEAM)").option("--json", "emit JSON").action(async (opts) => {
|
|
1113
1343
|
await withConfig(opts.json === true, false, (ctx) => workflowStates(ctx, opts.team));
|
|
1114
1344
|
});
|
|
1345
|
+
state.command("add").description("add a workflow state, appended after the team's existing ones").option("--team <key>", "team key (or HARNESS_TEAM)").option("--name <name>", "display name (required)").option("--type <type>", "backlog | unstarted | started | completed | canceled (required)").option("--color <color>", "swatch name").option("--json", "emit JSON").action(async (opts) => {
|
|
1346
|
+
await withConfig(opts.json === true, true, (ctx) => stateAdd(ctx, opts));
|
|
1347
|
+
});
|
|
1348
|
+
state.command("update").description("rename, retype or recolor a workflow state").argument("[id]").option("--team <key>", "team key (or HARNESS_TEAM)").option("--name <name>", "new name").option("--type <type>", "new type").option("--color <color>", "new swatch name").option("--json", "emit JSON").action(
|
|
1349
|
+
async (id, opts) => {
|
|
1350
|
+
await withConfig(opts.json === true, true, (ctx) => stateUpdate(ctx, id, opts));
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
state.command("order").description("reorder a team's states \u2014 pass EVERY state id, comma separated, in the order you want").argument("[ids]").option("--team <key>", "team key (or HARNESS_TEAM)").option("--json", "emit JSON").action(async (ids, opts) => {
|
|
1354
|
+
await withConfig(opts.json === true, true, (ctx) => stateOrder(ctx, ids, opts));
|
|
1355
|
+
});
|
|
1356
|
+
state.command("delete").description("delete a workflow state (refused while issues sit in it)").argument("[id]").option("--team <key>", "team key (or HARNESS_TEAM)").option("--yes", "confirm the deletion").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1357
|
+
await withConfig(opts.json === true, true, (ctx) => stateDelete(ctx, id, opts));
|
|
1358
|
+
});
|
|
1359
|
+
var member = program.command("member").description("the organization's tracker roster");
|
|
1360
|
+
member.command("list").description("who can reach this organization's tracker, and at what rank").option("--json", "emit JSON").action(async (opts) => {
|
|
1361
|
+
await withConfig(opts.json === true, false, (ctx) => memberList(ctx));
|
|
1362
|
+
});
|
|
1363
|
+
member.command("add").description("grant a rank to an existing account, minting the membership").option("--email <email>", "address of an existing account (required)").option("--role <role>", "admin | staff | collaborator (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1364
|
+
await withConfig(opts.json === true, true, (ctx) => memberAdd(ctx, opts));
|
|
1365
|
+
});
|
|
1366
|
+
member.command("set-role").description("change or revoke a member's rank; none revokes").argument("[user-id]").option("--role <role>", "admin | staff | collaborator | none (required)").option("--json", "emit JSON").action(async (userId, opts) => {
|
|
1367
|
+
await withConfig(opts.json === true, true, (ctx) => memberSetRole(ctx, userId, opts));
|
|
1368
|
+
});
|
|
1369
|
+
program.command("states").description("a team's workflow states (alias of `state list`)").option("--team <key>", "team key (or HARNESS_TEAM)").option("--json", "emit JSON").action(async (opts) => {
|
|
1370
|
+
await withConfig(
|
|
1371
|
+
opts.json === true,
|
|
1372
|
+
false,
|
|
1373
|
+
(ctx) => workflowStates(ctx, opts.team)
|
|
1374
|
+
);
|
|
1375
|
+
});
|
|
1115
1376
|
var user = program.command("user").description("users");
|
|
1116
1377
|
user.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1117
|
-
await withConfig(
|
|
1378
|
+
await withConfig(
|
|
1379
|
+
opts.json === true,
|
|
1380
|
+
false,
|
|
1381
|
+
(ctx) => userList(ctx)
|
|
1382
|
+
);
|
|
1118
1383
|
});
|
|
1119
1384
|
program.command("upload").description("upload a file; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1120
1385
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1121
|
-
if (path === void 0)
|
|
1386
|
+
if (path === void 0)
|
|
1387
|
+
throw new UsageError(
|
|
1388
|
+
"usage: harness upload <path> (png/jpg/webp/gif, up to 25 MB)"
|
|
1389
|
+
);
|
|
1122
1390
|
return upload(ctx, path);
|
|
1123
1391
|
});
|
|
1124
1392
|
});
|
|
1125
1393
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1126
|
-
const { startMcpServer } = await import("./server-
|
|
1394
|
+
const { startMcpServer } = await import("./server-ZRD4RTFU.js");
|
|
1127
1395
|
await startMcpServer();
|
|
1128
1396
|
});
|
|
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
|
-
|
|
1397
|
+
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(
|
|
1398
|
+
"--timeout <ms>",
|
|
1399
|
+
"how long to wait for the browser callback",
|
|
1400
|
+
integer
|
|
1401
|
+
).option("--json", "emit JSON").action(
|
|
1402
|
+
async (opts) => {
|
|
1403
|
+
const repoLookup = findRepoConfig();
|
|
1404
|
+
for (const warning of repoLookup.warnings)
|
|
1405
|
+
process.stderr.write(warning + "\n");
|
|
1406
|
+
const repoUrl = repoLookup.config?.url;
|
|
1407
|
+
const url = opts.url ?? blankToUndefined(process.env["HARNESS_API_URL"]) ?? repoUrl ?? storedLoginUrl() ?? DEFAULT_BASE_URL;
|
|
1408
|
+
if (url === repoUrl && opts.url === void 0 && blankToUndefined(process.env["HARNESS_API_URL"]) === void 0) {
|
|
1409
|
+
process.stderr.write(
|
|
1410
|
+
`harness: host ${url} comes from ${repoLookup.config?.path}
|
|
1411
|
+
`
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
const options = { noBrowser: !opts.browser };
|
|
1415
|
+
if (opts.timeout !== void 0) options.timeoutMs = opts.timeout;
|
|
1416
|
+
await finishAuth(await login(url, options), opts.json === true);
|
|
1137
1417
|
}
|
|
1138
|
-
|
|
1139
|
-
if (opts.timeout !== void 0) options.timeoutMs = opts.timeout;
|
|
1140
|
-
await finishAuth(await login(url, options), opts.json === true);
|
|
1141
|
-
});
|
|
1418
|
+
);
|
|
1142
1419
|
program.command("logout").description("revoke and forget the stored login").option("--json", "emit JSON").action(async (opts) => {
|
|
1143
1420
|
await finishAuth(await logout(), opts.json === true);
|
|
1144
1421
|
});
|
|
1145
1422
|
program.command("whoami").description("show the resolved host and credential source").option("--json", "emit JSON").action(async (opts) => {
|
|
1146
1423
|
const resolved = resolveConfig();
|
|
1147
|
-
for (const warning of resolved.warnings)
|
|
1424
|
+
for (const warning of resolved.warnings)
|
|
1425
|
+
process.stderr.write(warning + "\n");
|
|
1148
1426
|
if (resolved.kind === "error") {
|
|
1149
1427
|
process.stderr.write(`harness: ${resolved.message}
|
|
1150
1428
|
`);
|