@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/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-MF7MQ2L6.js";
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 state = generateState();
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, state, options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
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, state, timeoutMs) {
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") === state && code !== null && code !== "") {
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 = { kind: "unknown", packageRoot: null, nodeModules: null };
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(readFileSync(join2(packageRoot, "package.json"), "utf8"));
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]) return 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)) return null;
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) return 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 { kind: "skipped", reason: "already upgraded once for this command" };
615
+ return {
616
+ kind: "skipped",
617
+ reason: "already upgraded once for this command"
618
+ };
585
619
  }
586
- if (!autoUpgradeEnabled(env)) return { kind: "skipped", reason: "automatic upgrades are off" };
587
- if (argv2[2] === "mcp") return { kind: "skipped", reason: "an MCP session must not be replaced mid-stream" };
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) return { kind: "skipped", reason: "another harness process is already upgrading" };
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 { kind: "failed", reason: `still ${after} after upgrading \u2014 no newer release is published` };
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 { kind: "failed", reason: `npm left ${after} installed, which is not newer than ${installed}` };
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)({ ...env, [REEXEC_SENTINEL]: "1" }, argv2);
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) return { ok: false, detail: "npm did not say where its global root is" };
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 { ok: false, detail: "npm redacted its own global root, so this copy's location cannot be confirmed" };
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 { ok: false, detail: `${globalRoot} is not writable \u2014 upgrade it yourself` };
701
+ return {
702
+ ok: false,
703
+ detail: `${globalRoot} is not writable \u2014 upgrade it yourself`
704
+ };
646
705
  }
647
706
  if (!stillHeld()) {
648
- return { ok: false, detail: "lost the upgrade lock while preparing \u2014 another process took over" };
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
- `${PACKAGE_NAME}@latest`
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) return { ok: false, detail: result.error.message };
731
+ if (result.error !== void 0)
732
+ return { ok: false, detail: result.error.message };
670
733
  if (result.status !== 0) {
671
- return { ok: false, detail: `npm install exited ${result.status ?? `on ${String(result.signal)}`}` };
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(`harness: upgraded, but re-running failed: ${result.error.message}
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({ input: process.stdin, output: process.stderr });
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(() => process.exit(code));
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)) throw new InvalidArgumentError("expected a non-negative integer");
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(`harness: still not signed in to ${resolved.baseUrl} after logging in
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(hostNotice + `harness: cannot reach the API: ${clean(error.message)}
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(hostNotice + `harness: ${clean(error.message)} (HTTP 426)${why}
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)) return "";
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(opts.json === true, opts.claim === true, (ctx) => ready(ctx, opts));
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(opts.json === true, false, (ctx) => blocked(ctx, opts));
934
- });
935
- program.command("list").description("list issues with filters").option("--status <status>", "open | in_progress | closed").option("--assignee <who>", 'me, a user id, an email, or a full name; "" for unassigned').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) => {
936
- await withConfig(opts.json === true, false, (ctx) => list(ctx, opts));
1019
+ await withConfig(
1020
+ opts.json === true,
1021
+ false,
1022
+ (ctx) => blocked(ctx, opts)
1023
+ );
937
1024
  });
938
- program.command("search").description("search by meaning \u2014 ranked, exact identifier first; use list --search for paginated filtering").argument("[query...]").option("--limit <n>", "maximum results (default 10, capped at 20)", integer).option("--page <n>", "(deprecated, ignored \u2014 results are ranked and capped)", integer).option("--all", "(deprecated, ignored \u2014 results are ranked and capped)").option("--json", "emit JSON").action(async (query, opts) => {
939
- await withConfig(opts.json === true, false, (ctx) => search(ctx, query, opts));
940
- });
941
- program.command("show").description("full issue detail; downloads attached images locally").argument("[id]").option("--no-images", "skip attachment downloads").option("--json", "emit JSON").action(async (id, opts) => {
942
- await withConfig(opts.json === true, false, (ctx) => {
943
- if (id === void 0) throw new UsageError("usage: harness show <id>");
944
- requireIdentifiers2([id]);
945
- return show(ctx, id, opts);
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 = { baseUrl: resolved.baseUrl, token: "", actor: null, team: null, cacheDir: "" };
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(opts.json === true ? json(output.json) : output.human);
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("--type <type>", "task | bug | feature | epic (applied as a type/ label)").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) => {
966
- await withConfig(opts.json === true, true, (ctx) => create(ctx, opts));
967
- });
968
- 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("--assignee <who>", 'me, a user id, an email, or a full name; "" unassigns').option("--json", "emit JSON").action(async (id, opts) => {
969
- await withConfig(opts.json === true, true, (ctx) => {
970
- if (id === void 0) {
971
- throw new UsageError("usage: harness update <id> --claim | --title \u2026 | --state <name> | \u2026");
972
- }
973
- requireIdentifiers2([id]);
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) throw new UsageError("usage: harness comments <id>");
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(async (verb, issue, blocker, opts) => {
1001
- await withConfig(opts.json === true, true, (ctx) => {
1002
- if (verb === void 0 || issue === void 0 || blocker === void 0) {
1003
- throw new UsageError("usage: harness dep add|rm <issue> <blocker>");
1004
- }
1005
- requireIdentifiers2([issue, blocker]);
1006
- return dep(ctx, verb, issue, blocker);
1007
- });
1008
- });
1009
- 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(async (id, text, opts) => {
1010
- await withConfig(opts.json === true, true, (ctx) => {
1011
- if (id === void 0 || text.length === 0) {
1012
- throw new UsageError("usage: harness comment <id> <text> [--mention NAME]\u2026");
1013
- }
1014
- requireIdentifiers2([id]);
1015
- const mentions = opts.mention.filter((name) => name.trim() !== "");
1016
- return comment(ctx, id, text, mentions);
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("usage: harness attach-image <path> (one png, jpg, webp or gif, up to 25 MB)");
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(opts.json === true, false, (ctx) => projectList(ctx, opts));
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) throw new UsageError("usage: harness project show <id>");
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(opts.json === true, true, (ctx) => projectCreate(ctx, opts));
1039
- });
1040
- 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(async (id, opts) => {
1041
- await withConfig(opts.json === true, true, (ctx) => {
1042
- if (id === void 0) throw new UsageError("usage: harness project update <id> --name \u2026 | --state \u2026 | \u2026");
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(opts.json === true, false, (ctx) => milestoneList(ctx, opts.project));
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(opts.json === true, true, (ctx) => milestoneCreate(ctx, opts));
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(opts.json === true, false, (ctx) => docList(ctx, opts));
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) throw new UsageError("usage: harness doc show <id>");
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(opts.json === true, true, (ctx) => docCreate(ctx, opts));
1065
- });
1066
- doc.command("update").argument("[id]").option("--title <title>").option("--content <text>").option("--project <id>").option("--json", "emit JSON").action(async (id, opts) => {
1067
- await withConfig(opts.json === true, true, (ctx) => {
1068
- if (id === void 0) throw new UsageError("usage: harness doc update <id> --title \u2026 | --content \u2026 | --project \u2026");
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) throw new UsageError("usage: harness link list <issue>");
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(async (issue, url, opts) => {
1081
- await withConfig(opts.json === true, true, (ctx) => {
1082
- if (issue === void 0 || url === void 0) {
1083
- throw new UsageError("usage: harness link add <issue> <url> [--title <title>]");
1084
- }
1085
- requireIdentifiers2([issue]);
1086
- return linkAdd(ctx, issue, url, opts.title);
1087
- });
1088
- });
1089
- link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(async (issue, idOrUrl, opts) => {
1090
- await withConfig(opts.json === true, true, (ctx) => {
1091
- if (issue === void 0 || idOrUrl === void 0) {
1092
- throw new UsageError("usage: harness link rm <issue> <link-id-or-url>");
1093
- }
1094
- requireIdentifiers2([issue]);
1095
- return linkRm(ctx, issue, idOrUrl);
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(opts.json === true, false, (ctx) => labelList(ctx));
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("create").argument("[name]").option("--color <color>", "label color").option("--json", "emit JSON").action(async (name, opts) => {
1103
- await withConfig(opts.json === true, true, (ctx) => labelCreate(ctx, name, opts.color));
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(opts.json === true, false, (ctx) => teamList(ctx));
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("create").description("create a team, seeded with the default workflow states").option("--key <key>", "identifier prefix for the team's issues, e.g. IOS (required)").option("--name <name>", "display name (required)").option("--json", "emit JSON").action(async (opts) => {
1110
- await withConfig(opts.json === true, true, (ctx) => teamCreate(ctx, opts));
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("states").description("a team's workflow states").option("--team <key>", "team key (or HARNESS_TEAM)").option("--json", "emit JSON").action(async (opts) => {
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(opts.json === true, false, (ctx) => userList(ctx));
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) throw new UsageError("usage: harness upload <path> (png/jpg/webp/gif, up to 25 MB)");
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-LJXJLWNK.js");
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("--timeout <ms>", "how long to wait for the browser callback", integer).option("--json", "emit JSON").action(async (opts) => {
1130
- const repoLookup = findRepoConfig();
1131
- for (const warning of repoLookup.warnings) process.stderr.write(warning + "\n");
1132
- const repoUrl = repoLookup.config?.url;
1133
- const url = opts.url ?? blankToUndefined(process.env["HARNESS_API_URL"]) ?? repoUrl ?? storedLoginUrl() ?? DEFAULT_BASE_URL;
1134
- if (url === repoUrl && opts.url === void 0 && blankToUndefined(process.env["HARNESS_API_URL"]) === void 0) {
1135
- process.stderr.write(`harness: host ${url} comes from ${repoLookup.config?.path}
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
- const options = { noBrowser: !opts.browser };
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) process.stderr.write(warning + "\n");
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
  `);