@allocator-one/harness-cli 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -6
- package/dist/{chunk-3WR6YFU6.js → chunk-MF7MQ2L6.js} +8 -7
- package/dist/cli.js +386 -159
- package/dist/{server-TK2KFAOQ.js → server-LJXJLWNK.js} +15 -1
- package/package.json +1 -1
- package/spec/openapi.json +67 -18
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
CLI and MCP server for [Harness](https://harness.infra.one), allocator.one's
|
|
4
4
|
issue tracker. A standalone Node client — no backend checkout, no Erlang
|
|
5
|
-
toolchain
|
|
6
|
-
|
|
5
|
+
toolchain, and no local issue state: issues live server-side, so there is
|
|
6
|
+
nothing to commit or sync.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
@@ -27,8 +27,8 @@ Requires Node >= 20.
|
|
|
27
27
|
No setup needed: commands target `https://harness.infra.one` and start the
|
|
28
28
|
browser login the first time. One login covers every shell, worktree, and
|
|
29
29
|
agent slot on a machine — credentials are stored at
|
|
30
|
-
`~/.config/harness/credentials.json` (0600),
|
|
31
|
-
|
|
30
|
+
`~/.config/harness/credentials.json` (0600) as a single `{url, token}` pair,
|
|
31
|
+
and a token is only ever sent to the host that minted it.
|
|
32
32
|
|
|
33
33
|
- `harness login` — browser flow (PKCE loopback); `--no-browser` prints the
|
|
34
34
|
URL and prompts for a pasted token instead.
|
|
@@ -88,8 +88,7 @@ from the working directory, gives every clone of a repo its defaults:
|
|
|
88
88
|
| `url` | Target host. `HARNESS_API_URL` wins. Tokens stay host-bound: a stored login only serves the host that minted it, an env `HARNESS_API_TOKEN` refuses to pair with a repo-named host (set `HARNESS_API_URL` explicitly), and before the interactive auto-login opens a browser at a host named only by the repo, the CLI asks once per host (remembered in `~/.config/harness/trusted-hosts.json`). |
|
|
89
89
|
| `org` | Reserved for Harness org-scoping; parsed today, `url` wins meanwhile. |
|
|
90
90
|
|
|
91
|
-
This is config, not state — issues stay server-side.
|
|
92
|
-
read this file (TS-CLI-only).
|
|
91
|
+
This is config, not state — issues stay server-side.
|
|
93
92
|
|
|
94
93
|
## Development
|
|
95
94
|
|
|
@@ -557,13 +557,14 @@ async function list(ctx, opts) {
|
|
|
557
557
|
}
|
|
558
558
|
async function search(ctx, queryWords, opts) {
|
|
559
559
|
if (queryWords.length === 0) throw new UsageError("usage: harness search <query>");
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
};
|
|
565
|
-
const
|
|
566
|
-
|
|
560
|
+
const deprecation = opts.page !== void 0 || opts.all === true ? "--page/--all are ignored: search returns one ranked list, capped at 20" : void 0;
|
|
561
|
+
let params = { q: queryWords.join(" ") };
|
|
562
|
+
if (opts.limit !== void 0) params = { ...params, limit: opts.limit };
|
|
563
|
+
const body = await ctx.client.get("/api/v1/issues/search", params);
|
|
564
|
+
const meta = body.meta ?? {};
|
|
565
|
+
const degraded = meta["mode"] === "lexical" ? "semantic search unavailable \u2014 showing lexical matches only" : void 0;
|
|
566
|
+
const notice = [deprecation, degraded].filter(Boolean).join("; ") || void 0;
|
|
567
|
+
return { json: body.data, human: issuesTable(body.data), notice };
|
|
567
568
|
}
|
|
568
569
|
async function show(ctx, identifier, opts) {
|
|
569
570
|
requireIdentifiers([identifier]);
|
package/dist/cli.js
CHANGED
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
version,
|
|
60
60
|
workflowStates,
|
|
61
61
|
writeCredentials
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-MF7MQ2L6.js";
|
|
63
63
|
|
|
64
64
|
// src/cli.ts
|
|
65
65
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -372,6 +372,16 @@ function defaultOpenBrowser(url) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// src/core/stdio.ts
|
|
376
|
+
var BROKEN_PIPE_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED"]);
|
|
377
|
+
function guardBrokenPipes(streams = [process.stdout, process.stderr]) {
|
|
378
|
+
for (const stream of streams) {
|
|
379
|
+
stream.on("error", (error) => {
|
|
380
|
+
if (!BROKEN_PIPE_CODES.has(error.code ?? "")) throw error;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
375
385
|
// src/core/upgrade.ts
|
|
376
386
|
import { spawn, spawnSync } from "child_process";
|
|
377
387
|
import {
|
|
@@ -390,6 +400,7 @@ import {
|
|
|
390
400
|
import { basename, dirname, join as join2, parse, sep } from "path";
|
|
391
401
|
import { fileURLToPath } from "url";
|
|
392
402
|
var PACKAGE_NAME = "@allocator-one/harness-cli";
|
|
403
|
+
var UPGRADE_INSTALL_SPEC = `${PACKAGE_NAME}@latest`;
|
|
393
404
|
var UPDATE_CHECK_COMMAND = "__update-check";
|
|
394
405
|
var REEXEC_SENTINEL = "HARNESS_UPGRADED";
|
|
395
406
|
var DEFAULT_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
@@ -399,7 +410,11 @@ var NPM_ROOT_TIMEOUT_MS = 15e3;
|
|
|
399
410
|
var INSTALL_TIMEOUT_MS = 12e4;
|
|
400
411
|
var LOCK_STALE_MS = NPM_ROOT_TIMEOUT_MS + INSTALL_TIMEOUT_MS + 6e4;
|
|
401
412
|
function detectInstall(from = fileURLToPath(import.meta.url)) {
|
|
402
|
-
const unknown = {
|
|
413
|
+
const unknown = {
|
|
414
|
+
kind: "unknown",
|
|
415
|
+
packageRoot: null,
|
|
416
|
+
nodeModules: null
|
|
417
|
+
};
|
|
403
418
|
if (from.split(sep).includes("_npx")) return { ...unknown, kind: "npx" };
|
|
404
419
|
const packageRoot = findPackageRoot(from);
|
|
405
420
|
if (packageRoot === null) return unknown;
|
|
@@ -432,7 +447,9 @@ function packageNameAt(path) {
|
|
|
432
447
|
}
|
|
433
448
|
function versionAt(packageRoot) {
|
|
434
449
|
try {
|
|
435
|
-
const parsed = JSON.parse(
|
|
450
|
+
const parsed = JSON.parse(
|
|
451
|
+
readFileSync(join2(packageRoot, "package.json"), "utf8")
|
|
452
|
+
);
|
|
436
453
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
437
454
|
const version2 = parsed["version"];
|
|
438
455
|
return typeof version2 === "string" ? version2 : null;
|
|
@@ -463,7 +480,8 @@ function isNewer(candidate, current) {
|
|
|
463
480
|
const b = parseVersion(current);
|
|
464
481
|
if (a === null || b === null) return false;
|
|
465
482
|
for (let index = 0; index < 3; index++) {
|
|
466
|
-
if (a.parts[index] !== b.parts[index])
|
|
483
|
+
if (a.parts[index] !== b.parts[index])
|
|
484
|
+
return a.parts[index] > b.parts[index];
|
|
467
485
|
}
|
|
468
486
|
if (a.pre === b.pre) return false;
|
|
469
487
|
if (a.pre === null) return true;
|
|
@@ -499,7 +517,8 @@ function readStamp(env = process.env) {
|
|
|
499
517
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
500
518
|
const record = parsed;
|
|
501
519
|
const checkedAt = record["checkedAt"];
|
|
502
|
-
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))
|
|
520
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt))
|
|
521
|
+
return null;
|
|
503
522
|
const latest = record["latest"];
|
|
504
523
|
return { checkedAt, latest: typeof latest === "string" ? latest : null };
|
|
505
524
|
} catch {
|
|
@@ -571,7 +590,8 @@ async function fetchLatestVersion(env) {
|
|
|
571
590
|
const parsed = await response.json();
|
|
572
591
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
573
592
|
const version2 = parsed["version"];
|
|
574
|
-
if (typeof version2 !== "string" || parseVersion(version2) === null)
|
|
593
|
+
if (typeof version2 !== "string" || parseVersion(version2) === null)
|
|
594
|
+
return null;
|
|
575
595
|
return version2;
|
|
576
596
|
} catch {
|
|
577
597
|
return null;
|
|
@@ -581,10 +601,18 @@ function selfHeal(options = {}) {
|
|
|
581
601
|
const env = options.env ?? process.env;
|
|
582
602
|
const argv2 = options.argv ?? process.argv;
|
|
583
603
|
if (truthy(env[REEXEC_SENTINEL])) {
|
|
584
|
-
return {
|
|
604
|
+
return {
|
|
605
|
+
kind: "skipped",
|
|
606
|
+
reason: "already upgraded once for this command"
|
|
607
|
+
};
|
|
585
608
|
}
|
|
586
|
-
if (!autoUpgradeEnabled(env))
|
|
587
|
-
|
|
609
|
+
if (!autoUpgradeEnabled(env))
|
|
610
|
+
return { kind: "skipped", reason: "automatic upgrades are off" };
|
|
611
|
+
if (argv2[2] === "mcp")
|
|
612
|
+
return {
|
|
613
|
+
kind: "skipped",
|
|
614
|
+
reason: "an MCP session must not be replaced mid-stream"
|
|
615
|
+
};
|
|
588
616
|
const install = options.install ?? detectInstall();
|
|
589
617
|
if (install.kind !== "global" || install.packageRoot === null) {
|
|
590
618
|
return { kind: "skipped", reason: describeInstall(install.kind) };
|
|
@@ -595,17 +623,27 @@ function selfHeal(options = {}) {
|
|
|
595
623
|
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
596
624
|
}
|
|
597
625
|
const lock = takeLock(env);
|
|
598
|
-
if (lock === null)
|
|
626
|
+
if (lock === null)
|
|
627
|
+
return {
|
|
628
|
+
kind: "skipped",
|
|
629
|
+
reason: "another harness process is already upgrading"
|
|
630
|
+
};
|
|
599
631
|
try {
|
|
600
632
|
const stillHeld = () => readHolder(lock.path)?.pid === lock.pid;
|
|
601
633
|
const result = (options.runInstall ?? npmInstallGlobal)(install, stillHeld);
|
|
602
634
|
if (!result.ok) return { kind: "failed", reason: result.detail };
|
|
603
635
|
const after = versionAt(packageRoot);
|
|
604
636
|
if (after !== null && after === installed) {
|
|
605
|
-
return {
|
|
637
|
+
return {
|
|
638
|
+
kind: "failed",
|
|
639
|
+
reason: `still ${after} after upgrading \u2014 no newer release is published`
|
|
640
|
+
};
|
|
606
641
|
}
|
|
607
642
|
if (after !== null && installed !== null && !isNewer(after, installed)) {
|
|
608
|
-
return {
|
|
643
|
+
return {
|
|
644
|
+
kind: "failed",
|
|
645
|
+
reason: `npm left ${after} installed, which is not newer than ${installed}`
|
|
646
|
+
};
|
|
609
647
|
}
|
|
610
648
|
} finally {
|
|
611
649
|
releaseLock(lock);
|
|
@@ -613,7 +651,10 @@ function selfHeal(options = {}) {
|
|
|
613
651
|
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
614
652
|
}
|
|
615
653
|
function reexec(options, env, argv2) {
|
|
616
|
-
return (options.reexec ?? reexecSelf)(
|
|
654
|
+
return (options.reexec ?? reexecSelf)(
|
|
655
|
+
{ ...env, [REEXEC_SENTINEL]: "1" },
|
|
656
|
+
argv2
|
|
657
|
+
);
|
|
617
658
|
}
|
|
618
659
|
function describeInstall(kind) {
|
|
619
660
|
switch (kind) {
|
|
@@ -629,9 +670,13 @@ function describeInstall(kind) {
|
|
|
629
670
|
}
|
|
630
671
|
function npmInstallGlobal(install, stillHeld) {
|
|
631
672
|
const globalRoot = npmGlobalRoot();
|
|
632
|
-
if (globalRoot === null)
|
|
673
|
+
if (globalRoot === null)
|
|
674
|
+
return { ok: false, detail: "npm did not say where its global root is" };
|
|
633
675
|
if (globalRoot.includes("***")) {
|
|
634
|
-
return {
|
|
676
|
+
return {
|
|
677
|
+
ok: false,
|
|
678
|
+
detail: "npm redacted its own global root, so this copy's location cannot be confirmed"
|
|
679
|
+
};
|
|
635
680
|
}
|
|
636
681
|
if (install.nodeModules === null || !samePath(globalRoot, install.nodeModules)) {
|
|
637
682
|
return {
|
|
@@ -642,10 +687,16 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
642
687
|
try {
|
|
643
688
|
accessSync2(globalRoot, constants.W_OK);
|
|
644
689
|
} catch {
|
|
645
|
-
return {
|
|
690
|
+
return {
|
|
691
|
+
ok: false,
|
|
692
|
+
detail: `${globalRoot} is not writable \u2014 upgrade it yourself`
|
|
693
|
+
};
|
|
646
694
|
}
|
|
647
695
|
if (!stillHeld()) {
|
|
648
|
-
return {
|
|
696
|
+
return {
|
|
697
|
+
ok: false,
|
|
698
|
+
detail: "lost the upgrade lock while preparing \u2014 another process took over"
|
|
699
|
+
};
|
|
649
700
|
}
|
|
650
701
|
process.stderr.write(`harness: upgrading ${PACKAGE_NAME} \u2026
|
|
651
702
|
`);
|
|
@@ -657,7 +708,7 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
657
708
|
"--no-fund",
|
|
658
709
|
"--no-update-notifier",
|
|
659
710
|
"--loglevel=error",
|
|
660
|
-
|
|
711
|
+
UPGRADE_INSTALL_SPEC
|
|
661
712
|
];
|
|
662
713
|
const result = spawnSync(npmCommand(), args, {
|
|
663
714
|
// npm's diagnostics belong on stderr with ours; stdout stays the
|
|
@@ -666,9 +717,13 @@ function npmInstallGlobal(install, stillHeld) {
|
|
|
666
717
|
timeout: INSTALL_TIMEOUT_MS,
|
|
667
718
|
...npmSpawnOptions()
|
|
668
719
|
});
|
|
669
|
-
if (result.error !== void 0)
|
|
720
|
+
if (result.error !== void 0)
|
|
721
|
+
return { ok: false, detail: result.error.message };
|
|
670
722
|
if (result.status !== 0) {
|
|
671
|
-
return {
|
|
723
|
+
return {
|
|
724
|
+
ok: false,
|
|
725
|
+
detail: `npm install exited ${result.status ?? `on ${String(result.signal)}`}`
|
|
726
|
+
};
|
|
672
727
|
}
|
|
673
728
|
return { ok: true, detail: "" };
|
|
674
729
|
}
|
|
@@ -710,8 +765,10 @@ function reexecSelf(env, argv2) {
|
|
|
710
765
|
env
|
|
711
766
|
});
|
|
712
767
|
if (result.error !== void 0) {
|
|
713
|
-
process.stderr.write(
|
|
714
|
-
`
|
|
768
|
+
process.stderr.write(
|
|
769
|
+
`harness: upgraded, but re-running failed: ${result.error.message}
|
|
770
|
+
`
|
|
771
|
+
);
|
|
715
772
|
return 1;
|
|
716
773
|
}
|
|
717
774
|
return result.status ?? 1;
|
|
@@ -794,7 +851,10 @@ function autoUpgradeEnabled(env) {
|
|
|
794
851
|
|
|
795
852
|
// src/cli.ts
|
|
796
853
|
function promptLine(question) {
|
|
797
|
-
const readline = createInterface2({
|
|
854
|
+
const readline = createInterface2({
|
|
855
|
+
input: process.stdin,
|
|
856
|
+
output: process.stderr
|
|
857
|
+
});
|
|
798
858
|
return new Promise((resolve) => {
|
|
799
859
|
readline.question(question, (answer) => {
|
|
800
860
|
readline.close();
|
|
@@ -804,15 +864,19 @@ function promptLine(question) {
|
|
|
804
864
|
}
|
|
805
865
|
function exitWith(code) {
|
|
806
866
|
const drain = (stream) => new Promise((resolve) => stream.write("", () => resolve()));
|
|
807
|
-
return Promise.all([drain(process.stdout), drain(process.stderr)]).then(
|
|
867
|
+
return Promise.all([drain(process.stdout), drain(process.stderr)]).then(
|
|
868
|
+
() => process.exit(code)
|
|
869
|
+
);
|
|
808
870
|
}
|
|
871
|
+
guardBrokenPipes();
|
|
809
872
|
var program = new Command();
|
|
810
873
|
program.name("harness").description("issue tracking for humans and agents (harness.infra.one)").exitOverride().configureOutput({
|
|
811
874
|
writeOut: (text) => process.stdout.write(text),
|
|
812
875
|
writeErr: (text) => process.stderr.write(text)
|
|
813
876
|
});
|
|
814
877
|
function integer(value) {
|
|
815
|
-
if (!/^\d+$/.test(value))
|
|
878
|
+
if (!/^\d+$/.test(value))
|
|
879
|
+
throw new InvalidArgumentError("expected a non-negative integer");
|
|
816
880
|
return Number.parseInt(value, 10);
|
|
817
881
|
}
|
|
818
882
|
function collect(value, previous) {
|
|
@@ -858,8 +922,10 @@ async function withConfig(jsonFlag, mutating, run) {
|
|
|
858
922
|
process.stderr.write(loginResult.human);
|
|
859
923
|
const again = resolveConfig();
|
|
860
924
|
if (again.kind !== "ok") {
|
|
861
|
-
process.stderr.write(
|
|
862
|
-
`
|
|
925
|
+
process.stderr.write(
|
|
926
|
+
`harness: still not signed in to ${resolved.baseUrl} after logging in
|
|
927
|
+
`
|
|
928
|
+
);
|
|
863
929
|
return exitWith(1);
|
|
864
930
|
}
|
|
865
931
|
return execute(again.config, jsonFlag, mutating, run);
|
|
@@ -891,16 +957,20 @@ async function fail(error, hostNotice) {
|
|
|
891
957
|
return exitWith(1);
|
|
892
958
|
}
|
|
893
959
|
if (error instanceof TransportError) {
|
|
894
|
-
process.stderr.write(
|
|
895
|
-
`)
|
|
960
|
+
process.stderr.write(
|
|
961
|
+
hostNotice + `harness: cannot reach the API: ${clean(error.message)}
|
|
962
|
+
`
|
|
963
|
+
);
|
|
896
964
|
return exitWith(3);
|
|
897
965
|
}
|
|
898
966
|
if (error instanceof UpgradeRequiredError) {
|
|
899
967
|
const healed = selfHeal();
|
|
900
968
|
if (healed.kind === "upgraded") return exitWith(healed.code);
|
|
901
969
|
const why = healed.kind === "failed" ? ` (tried to upgrade: ${clean(healed.reason)})` : "";
|
|
902
|
-
process.stderr.write(
|
|
903
|
-
`)
|
|
970
|
+
process.stderr.write(
|
|
971
|
+
hostNotice + `harness: ${clean(error.message)} (HTTP 426)${why}
|
|
972
|
+
`
|
|
973
|
+
);
|
|
904
974
|
return exitWith(1);
|
|
905
975
|
}
|
|
906
976
|
if (error instanceof HttpError) {
|
|
@@ -919,7 +989,8 @@ async function fail(error, hostNotice) {
|
|
|
919
989
|
}
|
|
920
990
|
function errorDetails(body) {
|
|
921
991
|
const details = body["details"];
|
|
922
|
-
if (typeof details !== "object" || details === null || Array.isArray(details))
|
|
992
|
+
if (typeof details !== "object" || details === null || Array.isArray(details))
|
|
993
|
+
return "";
|
|
923
994
|
return Object.entries(details).map(([field, messages]) => {
|
|
924
995
|
const text = Array.isArray(messages) ? messages.map(String).join("; ") : String(messages);
|
|
925
996
|
return `
|
|
@@ -927,24 +998,53 @@ function errorDetails(body) {
|
|
|
927
998
|
}).join("");
|
|
928
999
|
}
|
|
929
1000
|
program.command("ready").description("issues ready to work, highest priority first").option("--claim", "atomically claim the first claimable issue").option("--team <key>", "team filter").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
930
|
-
await withConfig(
|
|
1001
|
+
await withConfig(
|
|
1002
|
+
opts.json === true,
|
|
1003
|
+
opts.claim === true,
|
|
1004
|
+
(ctx) => ready(ctx, opts)
|
|
1005
|
+
);
|
|
931
1006
|
});
|
|
932
1007
|
program.command("blocked").description("issues waiting on a blocker").option("--team <key>", "team filter").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
933
|
-
await withConfig(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1008
|
+
await withConfig(
|
|
1009
|
+
opts.json === true,
|
|
1010
|
+
false,
|
|
1011
|
+
(ctx) => blocked(ctx, opts)
|
|
1012
|
+
);
|
|
937
1013
|
});
|
|
938
|
-
program.command("
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
await withConfig(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1014
|
+
program.command("list").description("list issues with filters").option("--status <status>", "open | in_progress | closed").option(
|
|
1015
|
+
"--assignee <who>",
|
|
1016
|
+
'me, a user id, an email, or a full name; "" for unassigned'
|
|
1017
|
+
).option("--priority <name>", "urgent | high | medium | low | none").option("--label <name>", "label filter").option("--type <type>", "task | bug | feature | epic").option("--search <query>", "full-text search").option("--team <key>", "team filter").option("--parent <id>", "children of an epic").option("--scope <scope>", "all | archived").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
1018
|
+
await withConfig(
|
|
1019
|
+
opts.json === true,
|
|
1020
|
+
false,
|
|
1021
|
+
(ctx) => list(ctx, opts)
|
|
1022
|
+
);
|
|
947
1023
|
});
|
|
1024
|
+
program.command("search").description(
|
|
1025
|
+
"search by meaning \u2014 ranked, exact identifier first; use list --search for paginated filtering"
|
|
1026
|
+
).argument("[query...]").option("--limit <n>", "maximum results (default 10, capped at 20)", integer).option(
|
|
1027
|
+
"--page <n>",
|
|
1028
|
+
"(deprecated, ignored \u2014 results are ranked and capped)",
|
|
1029
|
+
integer
|
|
1030
|
+
).option("--all", "(deprecated, ignored \u2014 results are ranked and capped)").option("--json", "emit JSON").action(
|
|
1031
|
+
async (query, opts) => {
|
|
1032
|
+
await withConfig(
|
|
1033
|
+
opts.json === true,
|
|
1034
|
+
false,
|
|
1035
|
+
(ctx) => search(ctx, query, opts)
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
1039
|
+
program.command("show").description("full issue detail; downloads attached images locally").argument("[id]").option("--no-images", "skip attachment downloads").option("--json", "emit JSON").action(
|
|
1040
|
+
async (id, opts) => {
|
|
1041
|
+
await withConfig(opts.json === true, false, (ctx) => {
|
|
1042
|
+
if (id === void 0) throw new UsageError("usage: harness show <id>");
|
|
1043
|
+
requireIdentifiers2([id]);
|
|
1044
|
+
return show(ctx, id, opts);
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
);
|
|
948
1048
|
program.command("stats").description("per-team and total counts").option("--json", "emit JSON").action(async (opts) => {
|
|
949
1049
|
await withConfig(opts.json === true, false, (ctx) => stats(ctx));
|
|
950
1050
|
});
|
|
@@ -956,195 +1056,322 @@ program.command("version").description("print CLI and server API versions").opti
|
|
|
956
1056
|
let config = null;
|
|
957
1057
|
if (resolved.kind === "ok") config = resolved.config;
|
|
958
1058
|
else if (resolved.kind === "needs-login") {
|
|
959
|
-
config = {
|
|
1059
|
+
config = {
|
|
1060
|
+
baseUrl: resolved.baseUrl,
|
|
1061
|
+
token: "",
|
|
1062
|
+
actor: null,
|
|
1063
|
+
team: null,
|
|
1064
|
+
cacheDir: ""
|
|
1065
|
+
};
|
|
960
1066
|
}
|
|
961
1067
|
const output = await version(config);
|
|
962
|
-
process.stdout.write(
|
|
1068
|
+
process.stdout.write(
|
|
1069
|
+
opts.json === true ? json(output.json) : output.human
|
|
1070
|
+
);
|
|
963
1071
|
await exitWith(0);
|
|
964
1072
|
});
|
|
965
|
-
program.command("create").description("create an issue").option("--title <text>", "issue title (required)").option("--description <text>", "problem statement").option("--design <text>", "design notes").option("--notes <text>", "working notes").option("--acceptance <text>", "acceptance criteria").option(
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
await withConfig(
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
return update(ctx, id, opts);
|
|
975
|
-
});
|
|
976
|
-
});
|
|
977
|
-
program.command("close").description("close issues; the response lists newly unblocked issues").argument("[ids...]").option("--reason <text>", "why it is done (e.g. the commit hash)").option("--cancel", "close as canceled instead of completed").option("--json", "emit JSON").action(async (ids, opts) => {
|
|
978
|
-
await withConfig(opts.json === true, true, (ctx) => {
|
|
979
|
-
if (ids.length === 0) {
|
|
980
|
-
throw new UsageError("usage: harness close <id> [<id>\u2026] [--reason <text>] [--cancel]");
|
|
981
|
-
}
|
|
982
|
-
requireIdentifiers2(ids);
|
|
983
|
-
return close(ctx, ids, opts);
|
|
984
|
-
});
|
|
985
|
-
});
|
|
986
|
-
program.command("archive").description("archive issues; --restore brings them back").argument("[ids...]").option("--restore", "restore instead of archive").option("--json", "emit JSON").action(async (ids, opts) => {
|
|
987
|
-
await withConfig(opts.json === true, true, (ctx) => {
|
|
988
|
-
if (ids.length === 0) throw new UsageError("usage: harness archive <id> [<id>\u2026] [--restore]");
|
|
989
|
-
requireIdentifiers2(ids);
|
|
990
|
-
return archive(ctx, ids, opts);
|
|
991
|
-
});
|
|
1073
|
+
program.command("create").description("create an issue").option("--title <text>", "issue title (required)").option("--description <text>", "problem statement").option("--design <text>", "design notes").option("--notes <text>", "working notes").option("--acceptance <text>", "acceptance criteria").option(
|
|
1074
|
+
"--type <type>",
|
|
1075
|
+
"task | bug | feature | epic (applied as a type/ label)"
|
|
1076
|
+
).option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", "YYYY-MM-DD").option("--parent <id>", "parent epic").option("--assignee <who>", "me, a user id, an email, or a full name").option("--team <key>", "team key (or HARNESS_TEAM)").option("--label <name>", "label to apply (repeatable)", collect, []).option("--json", "emit JSON").action(async (opts) => {
|
|
1077
|
+
await withConfig(
|
|
1078
|
+
opts.json === true,
|
|
1079
|
+
true,
|
|
1080
|
+
(ctx) => create(ctx, opts)
|
|
1081
|
+
);
|
|
992
1082
|
});
|
|
1083
|
+
program.command("update").description("update an issue; --claim takes it atomically").argument("[id]").option("--claim", "claim atomically and move to the first started state").option("--state <name>", "workflow state by name or type").option("--title <text>").option("--description <text>").option("--design <text>").option("--notes <text>", "replace the notes").option("--append-notes <text>", "append to the notes").option("--acceptance <text>").option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", 'YYYY-MM-DD; "" clears').option("--parent <id>").option(
|
|
1084
|
+
"--assignee <who>",
|
|
1085
|
+
'me, a user id, an email, or a full name; "" unassigns'
|
|
1086
|
+
).option("--json", "emit JSON").action(
|
|
1087
|
+
async (id, opts) => {
|
|
1088
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1089
|
+
if (id === void 0) {
|
|
1090
|
+
throw new UsageError(
|
|
1091
|
+
"usage: harness update <id> --claim | --title \u2026 | --state <name> | \u2026"
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
requireIdentifiers2([id]);
|
|
1095
|
+
return update(ctx, id, opts);
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1099
|
+
program.command("close").description("close issues; the response lists newly unblocked issues").argument("[ids...]").option("--reason <text>", "why it is done (e.g. the commit hash)").option("--cancel", "close as canceled instead of completed").option("--json", "emit JSON").action(
|
|
1100
|
+
async (ids, opts) => {
|
|
1101
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1102
|
+
if (ids.length === 0) {
|
|
1103
|
+
throw new UsageError(
|
|
1104
|
+
"usage: harness close <id> [<id>\u2026] [--reason <text>] [--cancel]"
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
requireIdentifiers2(ids);
|
|
1108
|
+
return close(ctx, ids, opts);
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
program.command("archive").description("archive issues; --restore brings them back").argument("[ids...]").option("--restore", "restore instead of archive").option("--json", "emit JSON").action(
|
|
1113
|
+
async (ids, opts) => {
|
|
1114
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1115
|
+
if (ids.length === 0)
|
|
1116
|
+
throw new UsageError(
|
|
1117
|
+
"usage: harness archive <id> [<id>\u2026] [--restore]"
|
|
1118
|
+
);
|
|
1119
|
+
requireIdentifiers2(ids);
|
|
1120
|
+
return archive(ctx, ids, opts);
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
);
|
|
993
1124
|
program.command("comments").description("an issue's full comment thread, untruncated").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
994
1125
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
995
|
-
if (id === void 0)
|
|
1126
|
+
if (id === void 0)
|
|
1127
|
+
throw new UsageError("usage: harness comments <id>");
|
|
996
1128
|
requireIdentifiers2([id]);
|
|
997
1129
|
return comments(ctx, id);
|
|
998
1130
|
});
|
|
999
1131
|
});
|
|
1000
|
-
program.command("dep").description("blocking dependencies: dep add|rm <issue> <blocker>").argument("[verb]").argument("[issue]").argument("[blocker]").option("--json", "emit JSON").action(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1132
|
+
program.command("dep").description("blocking dependencies: dep add|rm <issue> <blocker>").argument("[verb]").argument("[issue]").argument("[blocker]").option("--json", "emit JSON").action(
|
|
1133
|
+
async (verb, issue, blocker, opts) => {
|
|
1134
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1135
|
+
if (verb === void 0 || issue === void 0 || blocker === void 0) {
|
|
1136
|
+
throw new UsageError("usage: harness dep add|rm <issue> <blocker>");
|
|
1137
|
+
}
|
|
1138
|
+
requireIdentifiers2([issue, blocker]);
|
|
1139
|
+
return dep(ctx, verb, issue, blocker);
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
program.command("comment").description("comment on an issue").argument("[id]").argument("[text...]").option("--mention <name>", "notify a person (repeatable)", collect, []).option("--json", "emit JSON").action(
|
|
1144
|
+
async (id, text, opts) => {
|
|
1145
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1146
|
+
if (id === void 0 || text.length === 0) {
|
|
1147
|
+
throw new UsageError(
|
|
1148
|
+
"usage: harness comment <id> <text> [--mention NAME]\u2026"
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
requireIdentifiers2([id]);
|
|
1152
|
+
const mentions = opts.mention.filter((name) => name.trim() !== "");
|
|
1153
|
+
return comment(ctx, id, text, mentions);
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1019
1157
|
program.command("attach-image").description("upload an image; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1020
1158
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1021
1159
|
if (path === void 0) {
|
|
1022
|
-
throw new UsageError(
|
|
1160
|
+
throw new UsageError(
|
|
1161
|
+
"usage: harness attach-image <path> (one png, jpg, webp or gif, up to 25 MB)"
|
|
1162
|
+
);
|
|
1023
1163
|
}
|
|
1024
1164
|
return attachImage(ctx, path);
|
|
1025
1165
|
});
|
|
1026
1166
|
});
|
|
1027
1167
|
var project = program.command("project").description("projects");
|
|
1028
1168
|
project.command("list").option("--search <query>", "full-text search").option("--state <state>", "project state filter").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
1029
|
-
await withConfig(
|
|
1169
|
+
await withConfig(
|
|
1170
|
+
opts.json === true,
|
|
1171
|
+
false,
|
|
1172
|
+
(ctx) => projectList(ctx, opts)
|
|
1173
|
+
);
|
|
1030
1174
|
});
|
|
1031
1175
|
project.command("show").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1032
1176
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1033
|
-
if (id === void 0)
|
|
1177
|
+
if (id === void 0)
|
|
1178
|
+
throw new UsageError("usage: harness project show <id>");
|
|
1034
1179
|
return projectShow(ctx, id);
|
|
1035
1180
|
});
|
|
1036
1181
|
});
|
|
1037
1182
|
project.command("create").option("--name <name>", "project name (required)").option("--description <text>").option("--summary <text>").option("--state <state>").option("--lead <user-id>", "lead by user id").option("--start-date <date>", "YYYY-MM-DD").option("--target-date <date>", "YYYY-MM-DD").option("--json", "emit JSON").action(async (opts) => {
|
|
1038
|
-
await withConfig(
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
return projectUpdate(ctx, id, opts);
|
|
1044
|
-
});
|
|
1183
|
+
await withConfig(
|
|
1184
|
+
opts.json === true,
|
|
1185
|
+
true,
|
|
1186
|
+
(ctx) => projectCreate(ctx, opts)
|
|
1187
|
+
);
|
|
1045
1188
|
});
|
|
1189
|
+
project.command("update").argument("[id]").option("--name <name>").option("--description <text>").option("--summary <text>").option("--state <state>").option("--lead <user-id>", "lead by user id").option("--start-date <date>", 'YYYY-MM-DD; "" clears').option("--target-date <date>", 'YYYY-MM-DD; "" clears').option("--json", "emit JSON").action(
|
|
1190
|
+
async (id, opts) => {
|
|
1191
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1192
|
+
if (id === void 0)
|
|
1193
|
+
throw new UsageError(
|
|
1194
|
+
"usage: harness project update <id> --name \u2026 | --state \u2026 | \u2026"
|
|
1195
|
+
);
|
|
1196
|
+
return projectUpdate(ctx, id, opts);
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
1046
1200
|
var milestone = program.command("milestone").description("project milestones");
|
|
1047
1201
|
milestone.command("list").option("--project <id>", "project id (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1048
|
-
await withConfig(
|
|
1202
|
+
await withConfig(
|
|
1203
|
+
opts.json === true,
|
|
1204
|
+
false,
|
|
1205
|
+
(ctx) => milestoneList(ctx, opts.project)
|
|
1206
|
+
);
|
|
1049
1207
|
});
|
|
1050
1208
|
milestone.command("create").option("--project <id>", "project id (required)").option("--name <name>", "milestone name (required)").option("--description <text>").option("--target-date <date>", "YYYY-MM-DD").option("--json", "emit JSON").action(async (opts) => {
|
|
1051
|
-
await withConfig(
|
|
1209
|
+
await withConfig(
|
|
1210
|
+
opts.json === true,
|
|
1211
|
+
true,
|
|
1212
|
+
(ctx) => milestoneCreate(ctx, opts)
|
|
1213
|
+
);
|
|
1052
1214
|
});
|
|
1053
1215
|
var doc = program.command("doc").description("documents");
|
|
1054
1216
|
doc.command("list").option("--search <query>", "full-text search").option("--limit <n>", "page size", integer).option("--page <n>", "page number", integer).option("--all", "follow every page").option("--json", "emit JSON").action(async (opts) => {
|
|
1055
|
-
await withConfig(
|
|
1217
|
+
await withConfig(
|
|
1218
|
+
opts.json === true,
|
|
1219
|
+
false,
|
|
1220
|
+
(ctx) => docList(ctx, opts)
|
|
1221
|
+
);
|
|
1056
1222
|
});
|
|
1057
1223
|
doc.command("show").argument("[id]").option("--json", "emit JSON").action(async (id, opts) => {
|
|
1058
1224
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1059
|
-
if (id === void 0)
|
|
1225
|
+
if (id === void 0)
|
|
1226
|
+
throw new UsageError("usage: harness doc show <id>");
|
|
1060
1227
|
return docShow(ctx, id);
|
|
1061
1228
|
});
|
|
1062
1229
|
});
|
|
1063
1230
|
doc.command("create").option("--title <title>", "document title (required)").option("--content <text>").option("--project <id>", "attach to a project").option("--json", "emit JSON").action(async (opts) => {
|
|
1064
|
-
await withConfig(
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
return docUpdate(ctx, id, opts);
|
|
1070
|
-
});
|
|
1231
|
+
await withConfig(
|
|
1232
|
+
opts.json === true,
|
|
1233
|
+
true,
|
|
1234
|
+
(ctx) => docCreate(ctx, opts)
|
|
1235
|
+
);
|
|
1071
1236
|
});
|
|
1237
|
+
doc.command("update").argument("[id]").option("--title <title>").option("--content <text>").option("--project <id>").option("--json", "emit JSON").action(
|
|
1238
|
+
async (id, opts) => {
|
|
1239
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1240
|
+
if (id === void 0)
|
|
1241
|
+
throw new UsageError(
|
|
1242
|
+
"usage: harness doc update <id> --title \u2026 | --content \u2026 | --project \u2026"
|
|
1243
|
+
);
|
|
1244
|
+
return docUpdate(ctx, id, opts);
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
);
|
|
1072
1248
|
var link = program.command("link").description("URLs attached to an issue (distinct from dep relations)");
|
|
1073
1249
|
link.command("list").argument("[issue]").option("--json", "emit JSON").action(async (issue, opts) => {
|
|
1074
1250
|
await withConfig(opts.json === true, false, (ctx) => {
|
|
1075
|
-
if (issue === void 0)
|
|
1251
|
+
if (issue === void 0)
|
|
1252
|
+
throw new UsageError("usage: harness link list <issue>");
|
|
1076
1253
|
requireIdentifiers2([issue]);
|
|
1077
1254
|
return linkList(ctx, issue);
|
|
1078
1255
|
});
|
|
1079
1256
|
});
|
|
1080
|
-
link.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1257
|
+
link.command("add").argument("[issue]").argument("[url]").option("--title <title>", "link title").option("--json", "emit JSON").action(
|
|
1258
|
+
async (issue, url, opts) => {
|
|
1259
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1260
|
+
if (issue === void 0 || url === void 0) {
|
|
1261
|
+
throw new UsageError(
|
|
1262
|
+
"usage: harness link add <issue> <url> [--title <title>]"
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
requireIdentifiers2([issue]);
|
|
1266
|
+
return linkAdd(ctx, issue, url, opts.title);
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
);
|
|
1270
|
+
link.command("rm").argument("[issue]").argument("[id-or-url]").option("--json", "emit JSON").action(
|
|
1271
|
+
async (issue, idOrUrl, opts) => {
|
|
1272
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1273
|
+
if (issue === void 0 || idOrUrl === void 0) {
|
|
1274
|
+
throw new UsageError(
|
|
1275
|
+
"usage: harness link rm <issue> <link-id-or-url>"
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
requireIdentifiers2([issue]);
|
|
1279
|
+
return linkRm(ctx, issue, idOrUrl);
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
);
|
|
1098
1283
|
var label = program.command("label").description("labels");
|
|
1099
1284
|
label.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1100
|
-
await withConfig(
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1285
|
+
await withConfig(
|
|
1286
|
+
opts.json === true,
|
|
1287
|
+
false,
|
|
1288
|
+
(ctx) => labelList(ctx)
|
|
1289
|
+
);
|
|
1104
1290
|
});
|
|
1291
|
+
label.command("create").argument("[name]").option("--color <color>", "label color").option("--json", "emit JSON").action(
|
|
1292
|
+
async (name, opts) => {
|
|
1293
|
+
await withConfig(
|
|
1294
|
+
opts.json === true,
|
|
1295
|
+
true,
|
|
1296
|
+
(ctx) => labelCreate(ctx, name, opts.color)
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
);
|
|
1105
1300
|
var team = program.command("team").description("teams");
|
|
1106
1301
|
team.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1107
|
-
await withConfig(
|
|
1302
|
+
await withConfig(
|
|
1303
|
+
opts.json === true,
|
|
1304
|
+
false,
|
|
1305
|
+
(ctx) => teamList(ctx)
|
|
1306
|
+
);
|
|
1108
1307
|
});
|
|
1109
|
-
team.command("create").description("create a team, seeded with the default workflow states").option(
|
|
1110
|
-
|
|
1308
|
+
team.command("create").description("create a team, seeded with the default workflow states").option(
|
|
1309
|
+
"--key <key>",
|
|
1310
|
+
"identifier prefix for the team's issues, e.g. IOS (required)"
|
|
1311
|
+
).option("--name <name>", "display name (required)").option("--json", "emit JSON").action(async (opts) => {
|
|
1312
|
+
await withConfig(
|
|
1313
|
+
opts.json === true,
|
|
1314
|
+
true,
|
|
1315
|
+
(ctx) => teamCreate(ctx, opts)
|
|
1316
|
+
);
|
|
1111
1317
|
});
|
|
1112
1318
|
program.command("states").description("a team's workflow states").option("--team <key>", "team key (or HARNESS_TEAM)").option("--json", "emit JSON").action(async (opts) => {
|
|
1113
|
-
await withConfig(
|
|
1319
|
+
await withConfig(
|
|
1320
|
+
opts.json === true,
|
|
1321
|
+
false,
|
|
1322
|
+
(ctx) => workflowStates(ctx, opts.team)
|
|
1323
|
+
);
|
|
1114
1324
|
});
|
|
1115
1325
|
var user = program.command("user").description("users");
|
|
1116
1326
|
user.command("list").option("--json", "emit JSON").action(async (opts) => {
|
|
1117
|
-
await withConfig(
|
|
1327
|
+
await withConfig(
|
|
1328
|
+
opts.json === true,
|
|
1329
|
+
false,
|
|
1330
|
+
(ctx) => userList(ctx)
|
|
1331
|
+
);
|
|
1118
1332
|
});
|
|
1119
1333
|
program.command("upload").description("upload a file; prints markdown to reference it").argument("[path]").option("--json", "emit JSON").action(async (path, opts) => {
|
|
1120
1334
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1121
|
-
if (path === void 0)
|
|
1335
|
+
if (path === void 0)
|
|
1336
|
+
throw new UsageError(
|
|
1337
|
+
"usage: harness upload <path> (png/jpg/webp/gif, up to 25 MB)"
|
|
1338
|
+
);
|
|
1122
1339
|
return upload(ctx, path);
|
|
1123
1340
|
});
|
|
1124
1341
|
});
|
|
1125
1342
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1126
|
-
const { startMcpServer } = await import("./server-
|
|
1343
|
+
const { startMcpServer } = await import("./server-LJXJLWNK.js");
|
|
1127
1344
|
await startMcpServer();
|
|
1128
1345
|
});
|
|
1129
|
-
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option(
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1346
|
+
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option(
|
|
1347
|
+
"--timeout <ms>",
|
|
1348
|
+
"how long to wait for the browser callback",
|
|
1349
|
+
integer
|
|
1350
|
+
).option("--json", "emit JSON").action(
|
|
1351
|
+
async (opts) => {
|
|
1352
|
+
const repoLookup = findRepoConfig();
|
|
1353
|
+
for (const warning of repoLookup.warnings)
|
|
1354
|
+
process.stderr.write(warning + "\n");
|
|
1355
|
+
const repoUrl = repoLookup.config?.url;
|
|
1356
|
+
const url = opts.url ?? blankToUndefined(process.env["HARNESS_API_URL"]) ?? repoUrl ?? storedLoginUrl() ?? DEFAULT_BASE_URL;
|
|
1357
|
+
if (url === repoUrl && opts.url === void 0 && blankToUndefined(process.env["HARNESS_API_URL"]) === void 0) {
|
|
1358
|
+
process.stderr.write(
|
|
1359
|
+
`harness: host ${url} comes from ${repoLookup.config?.path}
|
|
1360
|
+
`
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
const options = { noBrowser: !opts.browser };
|
|
1364
|
+
if (opts.timeout !== void 0) options.timeoutMs = opts.timeout;
|
|
1365
|
+
await finishAuth(await login(url, options), opts.json === true);
|
|
1137
1366
|
}
|
|
1138
|
-
|
|
1139
|
-
if (opts.timeout !== void 0) options.timeoutMs = opts.timeout;
|
|
1140
|
-
await finishAuth(await login(url, options), opts.json === true);
|
|
1141
|
-
});
|
|
1367
|
+
);
|
|
1142
1368
|
program.command("logout").description("revoke and forget the stored login").option("--json", "emit JSON").action(async (opts) => {
|
|
1143
1369
|
await finishAuth(await logout(), opts.json === true);
|
|
1144
1370
|
});
|
|
1145
1371
|
program.command("whoami").description("show the resolved host and credential source").option("--json", "emit JSON").action(async (opts) => {
|
|
1146
1372
|
const resolved = resolveConfig();
|
|
1147
|
-
for (const warning of resolved.warnings)
|
|
1373
|
+
for (const warning of resolved.warnings)
|
|
1374
|
+
process.stderr.write(warning + "\n");
|
|
1148
1375
|
if (resolved.kind === "error") {
|
|
1149
1376
|
process.stderr.write(`harness: ${resolved.message}
|
|
1150
1377
|
`);
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
projectUpdate,
|
|
31
31
|
ready,
|
|
32
32
|
resolveConfig,
|
|
33
|
+
search,
|
|
33
34
|
show,
|
|
34
35
|
stats,
|
|
35
36
|
teamCreate,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
upload,
|
|
39
40
|
userList,
|
|
40
41
|
workflowStates
|
|
41
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-MF7MQ2L6.js";
|
|
42
43
|
|
|
43
44
|
// src/mcp/server.ts
|
|
44
45
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -116,6 +117,19 @@ var TOOLS = [
|
|
|
116
117
|
return list(ctx, args);
|
|
117
118
|
}
|
|
118
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: "issue_search",
|
|
122
|
+
description: "Search issues by meaning: ranked results across a lexical and a semantic signal, so describing a problem in your own words finds issues that share none of them. Pass the question as asked \u2014 plain language works better than keywords. An exact identifier always ranks first. Returns at most 20 results, no pagination; use issue_list with search for paginated lexical filtering.",
|
|
123
|
+
readOnly: true,
|
|
124
|
+
shape: {
|
|
125
|
+
query: z.string().describe("the question or phrase to search for"),
|
|
126
|
+
limit: z.number().int().optional().describe("maximum results (default 10, capped at 20)")
|
|
127
|
+
},
|
|
128
|
+
handler: (ctx, args) => {
|
|
129
|
+
const limit = args["limit"] === void 0 ? {} : { limit: args["limit"] };
|
|
130
|
+
return search(ctx, [String(args["query"])], limit);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
119
133
|
{
|
|
120
134
|
name: "issue_show",
|
|
121
135
|
description: "Full issue detail: fields, relations, children, comments. images=true downloads attached images to the local cache and includes their paths as local_images.",
|
package/package.json
CHANGED
package/spec/openapi.json
CHANGED
|
@@ -517,6 +517,55 @@
|
|
|
517
517
|
"tags": []
|
|
518
518
|
}
|
|
519
519
|
},
|
|
520
|
+
"/api/v1/issues/search": {
|
|
521
|
+
"get": {
|
|
522
|
+
"callbacks": {},
|
|
523
|
+
"deprecated": false,
|
|
524
|
+
"description": "Requires the `harness:read` scope. Ranked hybrid search: an exact identifier always comes first, then results ordered by relevance across a lexical and a semantic (embedding) signal — a query can match issues that share none of its words. Returns at most 20 results; there is no pagination. `meta.mode` is `hybrid` when the semantic side ran and `lexical` when it degraded (embeddings disabled or the provider unavailable). Unarchived issues only. For paginated, filterable listings use `GET /api/v1/issues` instead.",
|
|
525
|
+
"parameters": [
|
|
526
|
+
{
|
|
527
|
+
"description": "The question or phrase to search for — plain language works better than keywords",
|
|
528
|
+
"in": "query",
|
|
529
|
+
"name": "q",
|
|
530
|
+
"required": false,
|
|
531
|
+
"schema": {
|
|
532
|
+
"type": "string"
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
"description": "Maximum results (default 10, capped at 20)",
|
|
537
|
+
"in": "query",
|
|
538
|
+
"name": "limit",
|
|
539
|
+
"required": false,
|
|
540
|
+
"schema": {
|
|
541
|
+
"type": "integer"
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
],
|
|
545
|
+
"responses": {
|
|
546
|
+
"200": {
|
|
547
|
+
"description": "Success"
|
|
548
|
+
},
|
|
549
|
+
"400": {
|
|
550
|
+
"description": "Bad request"
|
|
551
|
+
},
|
|
552
|
+
"401": {
|
|
553
|
+
"description": "Missing or invalid token"
|
|
554
|
+
},
|
|
555
|
+
"403": {
|
|
556
|
+
"description": "Token not permitted (org-scoped, non-admin, or missing scope)"
|
|
557
|
+
},
|
|
558
|
+
"404": {
|
|
559
|
+
"description": "Not found"
|
|
560
|
+
},
|
|
561
|
+
"422": {
|
|
562
|
+
"description": "Validation error"
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
"summary": "Search issues by meaning",
|
|
566
|
+
"tags": []
|
|
567
|
+
}
|
|
568
|
+
},
|
|
520
569
|
"/api/v1/issues/{identifier}": {
|
|
521
570
|
"get": {
|
|
522
571
|
"callbacks": {},
|
|
@@ -524,7 +573,7 @@
|
|
|
524
573
|
"description": "Requires the `harness:read` scope. The payload always carries `blocked` and `blocked_by` (identifiers of open blockers); `include` embeds relations (direction-aware), children, and recent comments in one response.",
|
|
525
574
|
"parameters": [
|
|
526
575
|
{
|
|
527
|
-
"description": "Issue identifier, e.g.
|
|
576
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
528
577
|
"in": "path",
|
|
529
578
|
"name": "identifier",
|
|
530
579
|
"required": true,
|
|
@@ -571,7 +620,7 @@
|
|
|
571
620
|
"description": "Requires the `harness:write` scope.",
|
|
572
621
|
"parameters": [
|
|
573
622
|
{
|
|
574
|
-
"description": "Issue identifier, e.g.
|
|
623
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
575
624
|
"in": "path",
|
|
576
625
|
"name": "identifier",
|
|
577
626
|
"required": true,
|
|
@@ -656,7 +705,7 @@
|
|
|
656
705
|
"description": "Requires the `harness:write` scope.",
|
|
657
706
|
"parameters": [
|
|
658
707
|
{
|
|
659
|
-
"description": "Issue identifier, e.g.
|
|
708
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
660
709
|
"in": "path",
|
|
661
710
|
"name": "identifier",
|
|
662
711
|
"required": true,
|
|
@@ -711,7 +760,7 @@
|
|
|
711
760
|
"description": "Requires the `harness:write` scope. Atomic compare-and-swap: assigns the caller and moves the issue to the team's first started-type state. Idempotent for the current claimant.",
|
|
712
761
|
"parameters": [
|
|
713
762
|
{
|
|
714
|
-
"description": "Issue identifier, e.g.
|
|
763
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
715
764
|
"in": "path",
|
|
716
765
|
"name": "identifier",
|
|
717
766
|
"required": true,
|
|
@@ -754,7 +803,7 @@
|
|
|
754
803
|
"description": "Requires the `harness:write` scope. Moves the issue to the team's first completed-type state (canceled-type with `cancel`), posting `reason` as a comment first. The response's `unblocked` lists identifiers of issues that became ready because this one closed.",
|
|
755
804
|
"parameters": [
|
|
756
805
|
{
|
|
757
|
-
"description": "Issue identifier, e.g.
|
|
806
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
758
807
|
"in": "path",
|
|
759
808
|
"name": "identifier",
|
|
760
809
|
"required": true,
|
|
@@ -818,7 +867,7 @@
|
|
|
818
867
|
"description": "Requires the `harness:read` scope.",
|
|
819
868
|
"parameters": [
|
|
820
869
|
{
|
|
821
|
-
"description": "Issue identifier, e.g.
|
|
870
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
822
871
|
"in": "path",
|
|
823
872
|
"name": "identifier",
|
|
824
873
|
"required": true,
|
|
@@ -856,7 +905,7 @@
|
|
|
856
905
|
"description": "Requires the `harness:write` scope.",
|
|
857
906
|
"parameters": [
|
|
858
907
|
{
|
|
859
|
-
"description": "Issue identifier, e.g.
|
|
908
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
860
909
|
"in": "path",
|
|
861
910
|
"name": "identifier",
|
|
862
911
|
"required": true,
|
|
@@ -915,7 +964,7 @@
|
|
|
915
964
|
"description": "Requires the `harness:write` scope.",
|
|
916
965
|
"parameters": [
|
|
917
966
|
{
|
|
918
|
-
"description": "Issue identifier, e.g.
|
|
967
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
919
968
|
"in": "path",
|
|
920
969
|
"name": "identifier",
|
|
921
970
|
"required": true,
|
|
@@ -973,7 +1022,7 @@
|
|
|
973
1022
|
"description": "Requires the `harness:read` scope.",
|
|
974
1023
|
"parameters": [
|
|
975
1024
|
{
|
|
976
|
-
"description": "Issue identifier, e.g.
|
|
1025
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
977
1026
|
"in": "path",
|
|
978
1027
|
"name": "identifier",
|
|
979
1028
|
"required": true,
|
|
@@ -1011,7 +1060,7 @@
|
|
|
1011
1060
|
"description": "Requires the `harness:write` scope.",
|
|
1012
1061
|
"parameters": [
|
|
1013
1062
|
{
|
|
1014
|
-
"description": "Issue identifier, e.g.
|
|
1063
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1015
1064
|
"in": "path",
|
|
1016
1065
|
"name": "identifier",
|
|
1017
1066
|
"required": true,
|
|
@@ -1070,7 +1119,7 @@
|
|
|
1070
1119
|
"description": "Requires the `harness:write` scope.",
|
|
1071
1120
|
"parameters": [
|
|
1072
1121
|
{
|
|
1073
|
-
"description": "Issue identifier, e.g.
|
|
1122
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1074
1123
|
"in": "path",
|
|
1075
1124
|
"name": "identifier",
|
|
1076
1125
|
"required": true,
|
|
@@ -1119,7 +1168,7 @@
|
|
|
1119
1168
|
"description": "Requires the `harness:read` scope.",
|
|
1120
1169
|
"parameters": [
|
|
1121
1170
|
{
|
|
1122
|
-
"description": "Issue identifier, e.g.
|
|
1171
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1123
1172
|
"in": "path",
|
|
1124
1173
|
"name": "identifier",
|
|
1125
1174
|
"required": true,
|
|
@@ -1157,7 +1206,7 @@
|
|
|
1157
1206
|
"description": "Requires the `harness:write` scope.",
|
|
1158
1207
|
"parameters": [
|
|
1159
1208
|
{
|
|
1160
|
-
"description": "Issue identifier, e.g.
|
|
1209
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1161
1210
|
"in": "path",
|
|
1162
1211
|
"name": "identifier",
|
|
1163
1212
|
"required": true,
|
|
@@ -1225,7 +1274,7 @@
|
|
|
1225
1274
|
"description": "Requires the `harness:write` scope.",
|
|
1226
1275
|
"parameters": [
|
|
1227
1276
|
{
|
|
1228
|
-
"description": "Issue identifier, e.g.
|
|
1277
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1229
1278
|
"in": "path",
|
|
1230
1279
|
"name": "identifier",
|
|
1231
1280
|
"required": true,
|
|
@@ -1274,7 +1323,7 @@
|
|
|
1274
1323
|
"description": "Requires the `harness:write` scope.",
|
|
1275
1324
|
"parameters": [
|
|
1276
1325
|
{
|
|
1277
|
-
"description": "Issue identifier, e.g.
|
|
1326
|
+
"description": "Issue identifier, e.g. ALL-4901 or CLI-12 (issues minted before team-prefixed numbering carry IO-)",
|
|
1278
1327
|
"in": "path",
|
|
1279
1328
|
"name": "identifier",
|
|
1280
1329
|
"required": true,
|
|
@@ -1870,7 +1919,7 @@
|
|
|
1870
1919
|
"post": {
|
|
1871
1920
|
"callbacks": {},
|
|
1872
1921
|
"deprecated": false,
|
|
1873
|
-
"description": "Requires the `harness:write` scope. Creates a team seeded with the default workflow states (Backlog, Todo, In Progress, Done, Canceled), so it is immediately usable for the full issue lifecycle. The key is upcased
|
|
1922
|
+
"description": "Requires the `harness:write` scope. Creates a team seeded with the default workflow states (Backlog, Todo, In Progress, Done, Canceled), so it is immediately usable for the full issue lifecycle. The key is upcased and must match `^[A-Z][A-Z0-9]*$`; numbering starts above any issues that already carry the key as their prefix.",
|
|
1874
1923
|
"parameters": [],
|
|
1875
1924
|
"requestBody": {
|
|
1876
1925
|
"content": {
|
|
@@ -1878,7 +1927,7 @@
|
|
|
1878
1927
|
"schema": {
|
|
1879
1928
|
"properties": {
|
|
1880
1929
|
"key": {
|
|
1881
|
-
"description": "Identifier prefix for
|
|
1930
|
+
"description": "Identifier prefix for the team's issues (e.g. ALL in ALL-4901). Upcased; create-only.",
|
|
1882
1931
|
"type": "string"
|
|
1883
1932
|
},
|
|
1884
1933
|
"name": {
|
|
@@ -2143,4 +2192,4 @@
|
|
|
2143
2192
|
],
|
|
2144
2193
|
"servers": [],
|
|
2145
2194
|
"tags": []
|
|
2146
|
-
}
|
|
2195
|
+
}
|