@allocator-one/harness-cli 1.2.1 → 1.4.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/README.md +5 -6
- package/dist/{chunk-JMGBMBN6.js → chunk-MF7MQ2L6.js} +11 -9
- package/dist/cli.js +438 -4
- package/dist/{server-H3U2Y6R7.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]);
|
|
@@ -1477,12 +1478,12 @@ function resolveConfig(env = process.env, credentialsPath, repo) {
|
|
|
1477
1478
|
};
|
|
1478
1479
|
}
|
|
1479
1480
|
function resolveCacheDir(env = process.env) {
|
|
1480
|
-
return absolute(env["HARNESS_CACHE_DIR"]) ?? join4(
|
|
1481
|
+
return absolute(env["HARNESS_CACHE_DIR"]) ?? join4(resolveCacheHome(env), "harness", "attachments");
|
|
1481
1482
|
}
|
|
1482
1483
|
function absolute(value) {
|
|
1483
1484
|
return value !== void 0 && value !== "" && isAbsolute3(value) ? value : null;
|
|
1484
1485
|
}
|
|
1485
|
-
function
|
|
1486
|
+
function resolveCacheHome(env = process.env) {
|
|
1486
1487
|
return absolute(env["XDG_CACHE_HOME"]) ?? join4(homedir3() || tmpdir(), ".cache");
|
|
1487
1488
|
}
|
|
1488
1489
|
function normalize(url) {
|
|
@@ -1595,6 +1596,7 @@ export {
|
|
|
1595
1596
|
trustHost,
|
|
1596
1597
|
DEFAULT_BASE_URL,
|
|
1597
1598
|
resolveConfig,
|
|
1599
|
+
resolveCacheHome,
|
|
1598
1600
|
validateBaseUrl,
|
|
1599
1601
|
loginHelp
|
|
1600
1602
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CLI_VERSION,
|
|
3
4
|
Client,
|
|
4
5
|
CommandError,
|
|
5
6
|
DEFAULT_BASE_URL,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
readCredentials,
|
|
44
45
|
ready,
|
|
45
46
|
requireIdentifiers,
|
|
47
|
+
resolveCacheHome,
|
|
46
48
|
resolveConfig,
|
|
47
49
|
search,
|
|
48
50
|
show,
|
|
@@ -57,7 +59,7 @@ import {
|
|
|
57
59
|
version,
|
|
58
60
|
workflowStates,
|
|
59
61
|
writeCredentials
|
|
60
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-MF7MQ2L6.js";
|
|
61
63
|
|
|
62
64
|
// src/cli.ts
|
|
63
65
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -370,6 +372,426 @@ function defaultOpenBrowser(url) {
|
|
|
370
372
|
}
|
|
371
373
|
}
|
|
372
374
|
|
|
375
|
+
// src/core/upgrade.ts
|
|
376
|
+
import { spawn, spawnSync } from "child_process";
|
|
377
|
+
import {
|
|
378
|
+
accessSync as accessSync2,
|
|
379
|
+
constants,
|
|
380
|
+
existsSync,
|
|
381
|
+
linkSync,
|
|
382
|
+
mkdirSync,
|
|
383
|
+
readFileSync,
|
|
384
|
+
realpathSync,
|
|
385
|
+
renameSync,
|
|
386
|
+
rmSync,
|
|
387
|
+
statSync,
|
|
388
|
+
writeFileSync
|
|
389
|
+
} from "fs";
|
|
390
|
+
import { basename, dirname, join as join2, parse, sep } from "path";
|
|
391
|
+
import { fileURLToPath } from "url";
|
|
392
|
+
var PACKAGE_NAME = "@allocator-one/harness-cli";
|
|
393
|
+
var UPDATE_CHECK_COMMAND = "__update-check";
|
|
394
|
+
var REEXEC_SENTINEL = "HARNESS_UPGRADED";
|
|
395
|
+
var DEFAULT_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
396
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
397
|
+
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
398
|
+
var NPM_ROOT_TIMEOUT_MS = 15e3;
|
|
399
|
+
var INSTALL_TIMEOUT_MS = 12e4;
|
|
400
|
+
var LOCK_STALE_MS = NPM_ROOT_TIMEOUT_MS + INSTALL_TIMEOUT_MS + 6e4;
|
|
401
|
+
function detectInstall(from = fileURLToPath(import.meta.url)) {
|
|
402
|
+
const unknown = { kind: "unknown", packageRoot: null, nodeModules: null };
|
|
403
|
+
if (from.split(sep).includes("_npx")) return { ...unknown, kind: "npx" };
|
|
404
|
+
const packageRoot = findPackageRoot(from);
|
|
405
|
+
if (packageRoot === null) return unknown;
|
|
406
|
+
if (existsSync(join2(packageRoot, ".git"))) {
|
|
407
|
+
return { kind: "checkout", packageRoot, nodeModules: null };
|
|
408
|
+
}
|
|
409
|
+
const nodeModules = enclosingNodeModules(packageRoot);
|
|
410
|
+
if (nodeModules === null) return { ...unknown, packageRoot };
|
|
411
|
+
const kind = existsSync(join2(nodeModules, ".bin")) ? "local" : "global";
|
|
412
|
+
return { kind, packageRoot, nodeModules };
|
|
413
|
+
}
|
|
414
|
+
function findPackageRoot(from) {
|
|
415
|
+
let dir = dirname(from);
|
|
416
|
+
const { root } = parse(dir);
|
|
417
|
+
for (; ; ) {
|
|
418
|
+
if (packageNameAt(join2(dir, "package.json")) === PACKAGE_NAME) return dir;
|
|
419
|
+
if (dir === root) return null;
|
|
420
|
+
dir = dirname(dir);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function packageNameAt(path) {
|
|
424
|
+
try {
|
|
425
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
426
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
427
|
+
const name = parsed["name"];
|
|
428
|
+
return typeof name === "string" ? name : null;
|
|
429
|
+
} catch {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function versionAt(packageRoot) {
|
|
434
|
+
try {
|
|
435
|
+
const parsed = JSON.parse(readFileSync(join2(packageRoot, "package.json"), "utf8"));
|
|
436
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
437
|
+
const version2 = parsed["version"];
|
|
438
|
+
return typeof version2 === "string" ? version2 : null;
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function enclosingNodeModules(packageRoot) {
|
|
444
|
+
let dir = dirname(packageRoot);
|
|
445
|
+
const { root } = parse(dir);
|
|
446
|
+
for (; ; ) {
|
|
447
|
+
if (basename(dir) === "node_modules") return dir;
|
|
448
|
+
if (dir === root) return null;
|
|
449
|
+
dir = dirname(dir);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
var SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
453
|
+
function parseVersion(value) {
|
|
454
|
+
const match = SEMVER.exec(value.trim());
|
|
455
|
+
if (match === null) return null;
|
|
456
|
+
return {
|
|
457
|
+
parts: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
458
|
+
pre: match[4] ?? null
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function isNewer(candidate, current) {
|
|
462
|
+
const a = parseVersion(candidate);
|
|
463
|
+
const b = parseVersion(current);
|
|
464
|
+
if (a === null || b === null) return false;
|
|
465
|
+
for (let index = 0; index < 3; index++) {
|
|
466
|
+
if (a.parts[index] !== b.parts[index]) return a.parts[index] > b.parts[index];
|
|
467
|
+
}
|
|
468
|
+
if (a.pre === b.pre) return false;
|
|
469
|
+
if (a.pre === null) return true;
|
|
470
|
+
if (b.pre === null) return false;
|
|
471
|
+
return comparePrerelease(a.pre, b.pre) > 0;
|
|
472
|
+
}
|
|
473
|
+
function comparePrerelease(left, right) {
|
|
474
|
+
const a = left.split(".");
|
|
475
|
+
const b = right.split(".");
|
|
476
|
+
for (let index = 0; index < Math.max(a.length, b.length); index++) {
|
|
477
|
+
const x = a[index];
|
|
478
|
+
const y = b[index];
|
|
479
|
+
if (x === void 0) return -1;
|
|
480
|
+
if (y === void 0) return 1;
|
|
481
|
+
const xNumeric = /^\d+$/.test(x);
|
|
482
|
+
const yNumeric = /^\d+$/.test(y);
|
|
483
|
+
if (xNumeric && yNumeric) {
|
|
484
|
+
if (Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1;
|
|
485
|
+
} else if (xNumeric !== yNumeric) {
|
|
486
|
+
return xNumeric ? -1 : 1;
|
|
487
|
+
} else if (x !== y) {
|
|
488
|
+
return x < y ? -1 : 1;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return 0;
|
|
492
|
+
}
|
|
493
|
+
function stampPath(env = process.env) {
|
|
494
|
+
return join2(resolveCacheHome(env), "harness", "update-check.json");
|
|
495
|
+
}
|
|
496
|
+
function readStamp(env = process.env) {
|
|
497
|
+
try {
|
|
498
|
+
const parsed = JSON.parse(readFileSync(stampPath(env), "utf8"));
|
|
499
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
500
|
+
const record = parsed;
|
|
501
|
+
const checkedAt = record["checkedAt"];
|
|
502
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt)) return null;
|
|
503
|
+
const latest = record["latest"];
|
|
504
|
+
return { checkedAt, latest: typeof latest === "string" ? latest : null };
|
|
505
|
+
} catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function writeStamp(stamp, env = process.env) {
|
|
510
|
+
const path = stampPath(env);
|
|
511
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
512
|
+
const latest = stamp.latest ?? readStamp(env)?.latest ?? null;
|
|
513
|
+
try {
|
|
514
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
515
|
+
writeFileSync(temporary, JSON.stringify({ ...stamp, latest }) + "\n");
|
|
516
|
+
renameSync(temporary, path);
|
|
517
|
+
} catch {
|
|
518
|
+
try {
|
|
519
|
+
rmSync(temporary, { force: true });
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function updateCheck(options = {}) {
|
|
525
|
+
const env = options.env ?? process.env;
|
|
526
|
+
const now = options.now ?? Date.now();
|
|
527
|
+
if (!checksEnabled(env)) return null;
|
|
528
|
+
const install = options.install ?? detectInstall();
|
|
529
|
+
if (install.kind !== "global" && install.kind !== "local") return null;
|
|
530
|
+
const stamp = readStamp(env);
|
|
531
|
+
if (stamp === null || now - stamp.checkedAt >= CHECK_INTERVAL_MS) {
|
|
532
|
+
writeStamp({ checkedAt: now, latest: stamp?.latest ?? null }, env);
|
|
533
|
+
(options.refresh ?? spawnRefresh)();
|
|
534
|
+
}
|
|
535
|
+
if (stamp !== null && stamp.latest !== null && isNewer(stamp.latest, CLI_VERSION)) {
|
|
536
|
+
return `harness: ${stamp.latest} is available (you have ${CLI_VERSION}) \u2014 ${upgradeHint(install)}
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
function upgradeHint(install) {
|
|
542
|
+
return install.kind === "local" ? `npm i ${PACKAGE_NAME}@latest` : `npm i -g ${PACKAGE_NAME}@latest`;
|
|
543
|
+
}
|
|
544
|
+
function spawnRefresh() {
|
|
545
|
+
const self = process.argv[1];
|
|
546
|
+
if (self === void 0 || self === "") return;
|
|
547
|
+
try {
|
|
548
|
+
const child = spawn(process.execPath, [self, UPDATE_CHECK_COMMAND], {
|
|
549
|
+
detached: true,
|
|
550
|
+
stdio: "ignore"
|
|
551
|
+
});
|
|
552
|
+
child.on("error", () => {
|
|
553
|
+
});
|
|
554
|
+
child.unref();
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function performUpdateCheck(env = process.env, now = Date.now()) {
|
|
559
|
+
if (!checksEnabled(env)) return;
|
|
560
|
+
writeStamp({ checkedAt: now, latest: await fetchLatestVersion(env) }, env);
|
|
561
|
+
}
|
|
562
|
+
async function fetchLatestVersion(env) {
|
|
563
|
+
const configured = env["HARNESS_REGISTRY_URL"];
|
|
564
|
+
const url = configured !== void 0 && configured.trim() !== "" ? configured.trim() : DEFAULT_REGISTRY_URL;
|
|
565
|
+
try {
|
|
566
|
+
const response = await fetch(url, {
|
|
567
|
+
headers: { accept: "application/json" },
|
|
568
|
+
signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS)
|
|
569
|
+
});
|
|
570
|
+
if (!response.ok) return null;
|
|
571
|
+
const parsed = await response.json();
|
|
572
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
573
|
+
const version2 = parsed["version"];
|
|
574
|
+
if (typeof version2 !== "string" || parseVersion(version2) === null) return null;
|
|
575
|
+
return version2;
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function selfHeal(options = {}) {
|
|
581
|
+
const env = options.env ?? process.env;
|
|
582
|
+
const argv2 = options.argv ?? process.argv;
|
|
583
|
+
if (truthy(env[REEXEC_SENTINEL])) {
|
|
584
|
+
return { kind: "skipped", reason: "already upgraded once for this command" };
|
|
585
|
+
}
|
|
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" };
|
|
588
|
+
const install = options.install ?? detectInstall();
|
|
589
|
+
if (install.kind !== "global" || install.packageRoot === null) {
|
|
590
|
+
return { kind: "skipped", reason: describeInstall(install.kind) };
|
|
591
|
+
}
|
|
592
|
+
const packageRoot = install.packageRoot;
|
|
593
|
+
const installed = versionAt(packageRoot);
|
|
594
|
+
if (installed !== null && isNewer(installed, CLI_VERSION)) {
|
|
595
|
+
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
596
|
+
}
|
|
597
|
+
const lock = takeLock(env);
|
|
598
|
+
if (lock === null) return { kind: "skipped", reason: "another harness process is already upgrading" };
|
|
599
|
+
try {
|
|
600
|
+
const stillHeld = () => readHolder(lock.path)?.pid === lock.pid;
|
|
601
|
+
const result = (options.runInstall ?? npmInstallGlobal)(install, stillHeld);
|
|
602
|
+
if (!result.ok) return { kind: "failed", reason: result.detail };
|
|
603
|
+
const after = versionAt(packageRoot);
|
|
604
|
+
if (after !== null && after === installed) {
|
|
605
|
+
return { kind: "failed", reason: `still ${after} after upgrading \u2014 no newer release is published` };
|
|
606
|
+
}
|
|
607
|
+
if (after !== null && installed !== null && !isNewer(after, installed)) {
|
|
608
|
+
return { kind: "failed", reason: `npm left ${after} installed, which is not newer than ${installed}` };
|
|
609
|
+
}
|
|
610
|
+
} finally {
|
|
611
|
+
releaseLock(lock);
|
|
612
|
+
}
|
|
613
|
+
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
614
|
+
}
|
|
615
|
+
function reexec(options, env, argv2) {
|
|
616
|
+
return (options.reexec ?? reexecSelf)({ ...env, [REEXEC_SENTINEL]: "1" }, argv2);
|
|
617
|
+
}
|
|
618
|
+
function describeInstall(kind) {
|
|
619
|
+
switch (kind) {
|
|
620
|
+
case "checkout":
|
|
621
|
+
return "this is a git checkout, not an npm install";
|
|
622
|
+
case "local":
|
|
623
|
+
return "this copy is a project dependency, not a global install";
|
|
624
|
+
case "npx":
|
|
625
|
+
return "npx runs a throwaway copy";
|
|
626
|
+
default:
|
|
627
|
+
return "cannot tell how this CLI was installed";
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function npmInstallGlobal(install, stillHeld) {
|
|
631
|
+
const globalRoot = npmGlobalRoot();
|
|
632
|
+
if (globalRoot === null) return { ok: false, detail: "npm did not say where its global root is" };
|
|
633
|
+
if (globalRoot.includes("***")) {
|
|
634
|
+
return { ok: false, detail: "npm redacted its own global root, so this copy's location cannot be confirmed" };
|
|
635
|
+
}
|
|
636
|
+
if (install.nodeModules === null || !samePath(globalRoot, install.nodeModules)) {
|
|
637
|
+
return {
|
|
638
|
+
ok: false,
|
|
639
|
+
detail: `this copy lives in ${install.nodeModules ?? "an unrecognized place"}, not npm's global root (${globalRoot})`
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
accessSync2(globalRoot, constants.W_OK);
|
|
644
|
+
} catch {
|
|
645
|
+
return { ok: false, detail: `${globalRoot} is not writable \u2014 upgrade it yourself` };
|
|
646
|
+
}
|
|
647
|
+
if (!stillHeld()) {
|
|
648
|
+
return { ok: false, detail: "lost the upgrade lock while preparing \u2014 another process took over" };
|
|
649
|
+
}
|
|
650
|
+
process.stderr.write(`harness: upgrading ${PACKAGE_NAME} \u2026
|
|
651
|
+
`);
|
|
652
|
+
const args = [
|
|
653
|
+
"install",
|
|
654
|
+
"--global",
|
|
655
|
+
"--ignore-scripts",
|
|
656
|
+
"--no-audit",
|
|
657
|
+
"--no-fund",
|
|
658
|
+
"--no-update-notifier",
|
|
659
|
+
"--loglevel=error",
|
|
660
|
+
`${PACKAGE_NAME}@latest`
|
|
661
|
+
];
|
|
662
|
+
const result = spawnSync(npmCommand(), args, {
|
|
663
|
+
// npm's diagnostics belong on stderr with ours; stdout stays the
|
|
664
|
+
// command's own, so its summary is dropped rather than piped into jq.
|
|
665
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
666
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
667
|
+
...npmSpawnOptions()
|
|
668
|
+
});
|
|
669
|
+
if (result.error !== void 0) return { ok: false, detail: result.error.message };
|
|
670
|
+
if (result.status !== 0) {
|
|
671
|
+
return { ok: false, detail: `npm install exited ${result.status ?? `on ${String(result.signal)}`}` };
|
|
672
|
+
}
|
|
673
|
+
return { ok: true, detail: "" };
|
|
674
|
+
}
|
|
675
|
+
function npmGlobalRoot() {
|
|
676
|
+
try {
|
|
677
|
+
const result = spawnSync(npmCommand(), ["root", "--global"], {
|
|
678
|
+
encoding: "utf8",
|
|
679
|
+
timeout: NPM_ROOT_TIMEOUT_MS,
|
|
680
|
+
...npmSpawnOptions()
|
|
681
|
+
});
|
|
682
|
+
if (result.status !== 0 || typeof result.stdout !== "string") return null;
|
|
683
|
+
const path = result.stdout.trim();
|
|
684
|
+
return path === "" ? null : path;
|
|
685
|
+
} catch {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function npmCommand() {
|
|
690
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
691
|
+
}
|
|
692
|
+
function npmSpawnOptions() {
|
|
693
|
+
return process.platform === "win32" ? { shell: true } : {};
|
|
694
|
+
}
|
|
695
|
+
function samePath(left, right) {
|
|
696
|
+
const resolve = (path) => {
|
|
697
|
+
try {
|
|
698
|
+
return realpathSync(path);
|
|
699
|
+
} catch {
|
|
700
|
+
return path;
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
return resolve(left) === resolve(right);
|
|
704
|
+
}
|
|
705
|
+
function reexecSelf(env, argv2) {
|
|
706
|
+
const self = argv2[1];
|
|
707
|
+
if (self === void 0) return 1;
|
|
708
|
+
const result = spawnSync(process.execPath, [self, ...argv2.slice(2)], {
|
|
709
|
+
stdio: "inherit",
|
|
710
|
+
env
|
|
711
|
+
});
|
|
712
|
+
if (result.error !== void 0) {
|
|
713
|
+
process.stderr.write(`harness: upgraded, but re-running failed: ${result.error.message}
|
|
714
|
+
`);
|
|
715
|
+
return 1;
|
|
716
|
+
}
|
|
717
|
+
return result.status ?? 1;
|
|
718
|
+
}
|
|
719
|
+
function takeLock(env) {
|
|
720
|
+
const path = join2(resolveCacheHome(env), "harness", "upgrade.lock");
|
|
721
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
722
|
+
try {
|
|
723
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
724
|
+
writeFileSync(temporary, `${process.pid}
|
|
725
|
+
`);
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
731
|
+
try {
|
|
732
|
+
linkSync(temporary, path);
|
|
733
|
+
return { path, pid: process.pid };
|
|
734
|
+
} catch {
|
|
735
|
+
const holder = readHolder(path);
|
|
736
|
+
if (holder === null) continue;
|
|
737
|
+
const aged = Date.now() - holder.mtimeMs > LOCK_STALE_MS;
|
|
738
|
+
const abandoned = holder.pid === null ? aged : !running(holder.pid) || aged;
|
|
739
|
+
if (!abandoned) return null;
|
|
740
|
+
try {
|
|
741
|
+
rmSync(path, { force: true });
|
|
742
|
+
} catch {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return null;
|
|
748
|
+
} finally {
|
|
749
|
+
try {
|
|
750
|
+
rmSync(temporary, { force: true });
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function releaseLock(lock) {
|
|
756
|
+
try {
|
|
757
|
+
if (readHolder(lock.path)?.pid !== lock.pid) return;
|
|
758
|
+
rmSync(lock.path, { force: true });
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function readHolder(path) {
|
|
763
|
+
try {
|
|
764
|
+
const mtimeMs = statSync(path).mtimeMs;
|
|
765
|
+
const pid = Number.parseInt(readFileSync(path, "utf8").trim(), 10);
|
|
766
|
+
return { pid: Number.isInteger(pid) && pid > 0 ? pid : null, mtimeMs };
|
|
767
|
+
} catch {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function running(pid) {
|
|
772
|
+
if (pid === null) return false;
|
|
773
|
+
try {
|
|
774
|
+
process.kill(pid, 0);
|
|
775
|
+
return true;
|
|
776
|
+
} catch (error) {
|
|
777
|
+
return error.code === "EPERM";
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function truthy(value) {
|
|
781
|
+
if (value === void 0) return false;
|
|
782
|
+
const normalized = value.trim().toLowerCase();
|
|
783
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
|
|
784
|
+
}
|
|
785
|
+
function checksEnabled(env) {
|
|
786
|
+
return !truthy(env["CI"]) && !truthy(env["HARNESS_NO_UPDATE_CHECK"]);
|
|
787
|
+
}
|
|
788
|
+
function autoUpgradeEnabled(env) {
|
|
789
|
+
if (!checksEnabled(env)) return false;
|
|
790
|
+
const explicit = env["HARNESS_AUTO_UPGRADE"];
|
|
791
|
+
if (explicit !== void 0 && explicit.trim() !== "") return truthy(explicit);
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
|
|
373
795
|
// src/cli.ts
|
|
374
796
|
function promptLine(question) {
|
|
375
797
|
const readline = createInterface2({ input: process.stdin, output: process.stderr });
|
|
@@ -474,7 +896,10 @@ async function fail(error, hostNotice) {
|
|
|
474
896
|
return exitWith(3);
|
|
475
897
|
}
|
|
476
898
|
if (error instanceof UpgradeRequiredError) {
|
|
477
|
-
|
|
899
|
+
const healed = selfHeal();
|
|
900
|
+
if (healed.kind === "upgraded") return exitWith(healed.code);
|
|
901
|
+
const why = healed.kind === "failed" ? ` (tried to upgrade: ${clean(healed.reason)})` : "";
|
|
902
|
+
process.stderr.write(hostNotice + `harness: ${clean(error.message)} (HTTP 426)${why}
|
|
478
903
|
`);
|
|
479
904
|
return exitWith(1);
|
|
480
905
|
}
|
|
@@ -510,7 +935,7 @@ program.command("blocked").description("issues waiting on a blocker").option("--
|
|
|
510
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) => {
|
|
511
936
|
await withConfig(opts.json === true, false, (ctx) => list(ctx, opts));
|
|
512
937
|
});
|
|
513
|
-
program.command("search").description("
|
|
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) => {
|
|
514
939
|
await withConfig(opts.json === true, false, (ctx) => search(ctx, query, opts));
|
|
515
940
|
});
|
|
516
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) => {
|
|
@@ -698,7 +1123,7 @@ program.command("upload").description("upload a file; prints markdown to referen
|
|
|
698
1123
|
});
|
|
699
1124
|
});
|
|
700
1125
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
701
|
-
const { startMcpServer } = await import("./server-
|
|
1126
|
+
const { startMcpServer } = await import("./server-LJXJLWNK.js");
|
|
702
1127
|
await startMcpServer();
|
|
703
1128
|
});
|
|
704
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) => {
|
|
@@ -749,6 +1174,15 @@ async function finishAuth(result, json2) {
|
|
|
749
1174
|
}
|
|
750
1175
|
var argv = process.argv.slice();
|
|
751
1176
|
if (argv[2] === "--version" || argv[2] === "-V") argv[2] = "version";
|
|
1177
|
+
if (argv[2] === UPDATE_CHECK_COMMAND) {
|
|
1178
|
+
await performUpdateCheck();
|
|
1179
|
+
await exitWith(0);
|
|
1180
|
+
}
|
|
1181
|
+
try {
|
|
1182
|
+
const notice = updateCheck();
|
|
1183
|
+
if (notice !== null) process.stderr.write(notice);
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
752
1186
|
if (argv.length <= 2) {
|
|
753
1187
|
program.outputHelp({ error: true });
|
|
754
1188
|
await exitWith(2);
|
|
@@ -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
|
+
}
|