@allocator-one/harness-cli 1.5.0 → 1.5.2
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
CHANGED
|
@@ -73,6 +73,33 @@ Auth resolution is identical to the CLI. Without a credential, tools return
|
|
|
73
73
|
an error telling the agent to run `harness login` in a terminal — the
|
|
74
74
|
server never opens a browser itself.
|
|
75
75
|
|
|
76
|
+
### Text from a file, never from a shell argument
|
|
77
|
+
|
|
78
|
+
Every free-text option has a `--<field>-file` companion, and `-` means stdin:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
harness create --title "…" --description-file ./body.md
|
|
82
|
+
harness update IO-123 --notes-file ./notes.md --acceptance-file ./ac.md
|
|
83
|
+
harness comment IO-123 --body-file ./comment.md
|
|
84
|
+
git log -1 --format=%B | harness close IO-123 --reason-file -
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`create` and `update` cover `--title`, `--description`, `--design`, `--notes`
|
|
88
|
+
and `--acceptance` (plus `--append-notes` on `update`); `close` takes
|
|
89
|
+
`--reason-file`, `comment` takes `--body-file`.
|
|
90
|
+
|
|
91
|
+
Use these whenever the text is not something you authored inline. Prose that
|
|
92
|
+
reaches a shell argument has to survive quoting, and quoting can be defeated —
|
|
93
|
+
a `"` closes the string, `$(…)` substitutes, and a heredoc ends at whatever
|
|
94
|
+
flush-left delimiter line the text happens to contain. Handing over a path
|
|
95
|
+
removes the question, the way `git commit -F` does. This matters most for
|
|
96
|
+
agents relaying issue text they did not write.
|
|
97
|
+
|
|
98
|
+
Passing both an inline value and its `-file` for the same field is a usage
|
|
99
|
+
error rather than a silent precedence, and only one field per invocation may
|
|
100
|
+
read stdin. Both are checked before anything is read, so a rejected command
|
|
101
|
+
never consumes your pipe. One trailing newline is dropped, like `$(cat file)`.
|
|
102
|
+
|
|
76
103
|
## Repo config
|
|
77
104
|
|
|
78
105
|
A committed `.harness-cli/config.json`, discovered git-style by walking up
|
|
@@ -514,12 +514,19 @@ function pageCount(meta) {
|
|
|
514
514
|
function dedupe(records) {
|
|
515
515
|
const seen = /* @__PURE__ */ new Set();
|
|
516
516
|
return records.filter((record) => {
|
|
517
|
-
const
|
|
517
|
+
const id = record["id"];
|
|
518
|
+
const key = id === void 0 || id === null ? `struct:${stableKey(record)}` : `id:${stableKey(id)}`;
|
|
518
519
|
if (seen.has(key)) return false;
|
|
519
520
|
seen.add(key);
|
|
520
521
|
return true;
|
|
521
522
|
});
|
|
522
523
|
}
|
|
524
|
+
function stableKey(value) {
|
|
525
|
+
return JSON.stringify(
|
|
526
|
+
value,
|
|
527
|
+
(_field, nested) => nested !== null && typeof nested === "object" && !Array.isArray(nested) ? Object.fromEntries(Object.entries(nested).sort(([a], [b]) => a < b ? -1 : 1)) : nested
|
|
528
|
+
);
|
|
529
|
+
}
|
|
523
530
|
|
|
524
531
|
// src/commands/issues.ts
|
|
525
532
|
var PRIORITY_NAMES = { urgent: 1, high: 2, medium: 3, low: 4, none: 0 };
|
package/dist/cli.js
CHANGED
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
version,
|
|
71
71
|
workflowStates,
|
|
72
72
|
writeCredentials
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-CIW4OU5G.js";
|
|
74
74
|
|
|
75
75
|
// src/cli.ts
|
|
76
76
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -383,6 +383,113 @@ function defaultOpenBrowser(url) {
|
|
|
383
383
|
}
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
+
// src/core/textinput.ts
|
|
387
|
+
import { readFileSync, statSync } from "fs";
|
|
388
|
+
import { isatty } from "tty";
|
|
389
|
+
var STDIN_PATH = "-";
|
|
390
|
+
var MAX_TEXT_BYTES = 1e6;
|
|
391
|
+
function flagOf(name) {
|
|
392
|
+
return name.replace(/[A-Z]/g, (upper) => `-${upper.toLowerCase()}`);
|
|
393
|
+
}
|
|
394
|
+
function resolveTextSources(sources) {
|
|
395
|
+
const resolved = {};
|
|
396
|
+
const toRead = [];
|
|
397
|
+
let stdinClaimedBy;
|
|
398
|
+
for (const { name, inline, file } of sources) {
|
|
399
|
+
if (inline !== void 0 && file !== void 0) {
|
|
400
|
+
throw new UsageError(
|
|
401
|
+
`--${flagOf(name)} and --${flagOf(name)}-file both given \u2014 pass one; there is no precedence between them`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
if (file === void 0) {
|
|
405
|
+
if (inline !== void 0) resolved[name] = inline;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (file === STDIN_PATH) {
|
|
409
|
+
if (isatty(0)) {
|
|
410
|
+
throw new UsageError(
|
|
411
|
+
`--${flagOf(name)}-file - reads stdin, but stdin is a terminal \u2014 pipe something in or pass a path`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (stdinClaimedBy !== void 0) {
|
|
415
|
+
throw new UsageError(
|
|
416
|
+
`--${flagOf(name)}-file and --${flagOf(stdinClaimedBy)}-file both read stdin \u2014 there is only one stream, so only one may be "-"`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
stdinClaimedBy = name;
|
|
420
|
+
}
|
|
421
|
+
if (file !== STDIN_PATH) checkReadable(file, name);
|
|
422
|
+
toRead.push({ name, file });
|
|
423
|
+
}
|
|
424
|
+
for (const { name, file } of [...toRead].sort(byStdinLast)) {
|
|
425
|
+
resolved[name] = stripOneTrailingNewline(readTextFile(file, name));
|
|
426
|
+
}
|
|
427
|
+
return resolved;
|
|
428
|
+
}
|
|
429
|
+
function readTextFile(path, name) {
|
|
430
|
+
if (path === STDIN_PATH) {
|
|
431
|
+
let piped;
|
|
432
|
+
try {
|
|
433
|
+
piped = readFileSync(0);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw new UsageError(`--${flagOf(name)}-file -: cannot read stdin (${describe(error)})`);
|
|
436
|
+
}
|
|
437
|
+
if (piped.length > MAX_TEXT_BYTES) {
|
|
438
|
+
throw new UsageError(oversize(`--${flagOf(name)}-file -`));
|
|
439
|
+
}
|
|
440
|
+
return decodeUtf8(piped, `--${flagOf(name)}-file -`);
|
|
441
|
+
}
|
|
442
|
+
const flag = `--${flagOf(name)}-file ${path}`;
|
|
443
|
+
let bytes;
|
|
444
|
+
try {
|
|
445
|
+
bytes = readFileSync(path);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
throw new UsageError(`${flag}: ${describe(error)}`);
|
|
448
|
+
}
|
|
449
|
+
if (bytes.length > MAX_TEXT_BYTES) throw new UsageError(oversize(flag));
|
|
450
|
+
return decodeUtf8(bytes, flag);
|
|
451
|
+
}
|
|
452
|
+
function decodeUtf8(bytes, flag) {
|
|
453
|
+
try {
|
|
454
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
455
|
+
} catch {
|
|
456
|
+
throw new UsageError(`${flag}: not valid UTF-8 \u2014 convert it first`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function oversize(what) {
|
|
460
|
+
return `${what}: over ${MAX_TEXT_BYTES / 1e6} MB \u2014 that is not prose; check the path`;
|
|
461
|
+
}
|
|
462
|
+
function checkReadable(path, name) {
|
|
463
|
+
const flag = `--${flagOf(name)}-file ${path}`;
|
|
464
|
+
let stats2;
|
|
465
|
+
try {
|
|
466
|
+
stats2 = statSync(path);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw new UsageError(`${flag}: ${describe(error)}`);
|
|
469
|
+
}
|
|
470
|
+
if (!stats2.isFile()) {
|
|
471
|
+
throw new UsageError(
|
|
472
|
+
`${flag}: ${stats2.isDirectory() ? "is a directory" : "not a regular file"}`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
if (stats2.size > MAX_TEXT_BYTES) throw new UsageError(oversize(flag));
|
|
476
|
+
}
|
|
477
|
+
function byStdinLast(a, b) {
|
|
478
|
+
return Number(a.file === STDIN_PATH) - Number(b.file === STDIN_PATH);
|
|
479
|
+
}
|
|
480
|
+
function stripOneTrailingNewline(text) {
|
|
481
|
+
if (text.endsWith("\r\n")) return text.slice(0, -2);
|
|
482
|
+
if (text.endsWith("\n")) return text.slice(0, -1);
|
|
483
|
+
return text;
|
|
484
|
+
}
|
|
485
|
+
function describe(error) {
|
|
486
|
+
const code = error?.code;
|
|
487
|
+
if (code === "ENOENT") return "no such file";
|
|
488
|
+
if (code === "EISDIR") return "is a directory";
|
|
489
|
+
if (code === "EACCES") return "permission denied";
|
|
490
|
+
return error instanceof Error ? error.message : String(error);
|
|
491
|
+
}
|
|
492
|
+
|
|
386
493
|
// src/core/stdio.ts
|
|
387
494
|
var BROKEN_PIPE_CODES = /* @__PURE__ */ new Set(["EPIPE", "ERR_STREAM_DESTROYED"]);
|
|
388
495
|
function guardBrokenPipes(streams = [process.stdout, process.stderr]) {
|
|
@@ -401,11 +508,11 @@ import {
|
|
|
401
508
|
existsSync,
|
|
402
509
|
linkSync,
|
|
403
510
|
mkdirSync,
|
|
404
|
-
readFileSync,
|
|
511
|
+
readFileSync as readFileSync2,
|
|
405
512
|
realpathSync,
|
|
406
513
|
renameSync,
|
|
407
514
|
rmSync,
|
|
408
|
-
statSync,
|
|
515
|
+
statSync as statSync2,
|
|
409
516
|
writeFileSync
|
|
410
517
|
} from "fs";
|
|
411
518
|
import { basename, dirname, join as join2, parse, sep } from "path";
|
|
@@ -448,7 +555,7 @@ function findPackageRoot(from) {
|
|
|
448
555
|
}
|
|
449
556
|
function packageNameAt(path) {
|
|
450
557
|
try {
|
|
451
|
-
const parsed = JSON.parse(
|
|
558
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
452
559
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
453
560
|
const name = parsed["name"];
|
|
454
561
|
return typeof name === "string" ? name : null;
|
|
@@ -459,7 +566,7 @@ function packageNameAt(path) {
|
|
|
459
566
|
function versionAt(packageRoot) {
|
|
460
567
|
try {
|
|
461
568
|
const parsed = JSON.parse(
|
|
462
|
-
|
|
569
|
+
readFileSync2(join2(packageRoot, "package.json"), "utf8")
|
|
463
570
|
);
|
|
464
571
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
465
572
|
const version2 = parsed["version"];
|
|
@@ -524,7 +631,7 @@ function stampPath(env = process.env) {
|
|
|
524
631
|
}
|
|
525
632
|
function readStamp(env = process.env) {
|
|
526
633
|
try {
|
|
527
|
-
const parsed = JSON.parse(
|
|
634
|
+
const parsed = JSON.parse(readFileSync2(stampPath(env), "utf8"));
|
|
528
635
|
if (typeof parsed !== "object" || parsed === null) return null;
|
|
529
636
|
const record = parsed;
|
|
530
637
|
const checkedAt = record["checkedAt"];
|
|
@@ -829,8 +936,8 @@ function releaseLock(lock) {
|
|
|
829
936
|
}
|
|
830
937
|
function readHolder(path) {
|
|
831
938
|
try {
|
|
832
|
-
const mtimeMs =
|
|
833
|
-
const pid = Number.parseInt(
|
|
939
|
+
const mtimeMs = statSync2(path).mtimeMs;
|
|
940
|
+
const pid = Number.parseInt(readFileSync2(path, "utf8").trim(), 10);
|
|
834
941
|
return { pid: Number.isInteger(pid) && pid > 0 ? pid : null, mtimeMs };
|
|
835
942
|
} catch {
|
|
836
943
|
return null;
|
|
@@ -1081,17 +1188,43 @@ program.command("version").description("print CLI and server API versions").opti
|
|
|
1081
1188
|
);
|
|
1082
1189
|
await exitWith(0);
|
|
1083
1190
|
});
|
|
1084
|
-
|
|
1191
|
+
function issueTextSources(opts) {
|
|
1192
|
+
return [
|
|
1193
|
+
{ name: "title", inline: opts.title, file: opts.titleFile },
|
|
1194
|
+
{ name: "description", inline: opts.description, file: opts.descriptionFile },
|
|
1195
|
+
{ name: "design", inline: opts.design, file: opts.designFile },
|
|
1196
|
+
{ name: "notes", inline: opts.notes, file: opts.notesFile },
|
|
1197
|
+
{ name: "acceptance", inline: opts.acceptance, file: opts.acceptanceFile }
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
function requireNonBlankInlineTitle(title) {
|
|
1201
|
+
if (title !== void 0 && title.trim() === "") {
|
|
1202
|
+
throw new UsageError("--title is empty \u2014 an issue needs a title");
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function requireNonBlankTitle(text, titleFile) {
|
|
1206
|
+
if ((text["title"] ?? "").trim() !== "") return;
|
|
1207
|
+
throw new UsageError(
|
|
1208
|
+
titleFile === void 0 ? "--title is empty \u2014 an issue needs a title" : `--title-file ${titleFile} produced an empty title`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
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("--title-file <path>", 'read the title from a file ("-" for stdin)').option("--description-file <path>", 'read the description from a file ("-" for stdin)').option("--design-file <path>", 'read the design notes from a file ("-" for stdin)').option("--notes-file <path>", 'read the notes from a file ("-" for stdin)').option("--acceptance-file <path>", 'read the acceptance criteria from a file ("-" for stdin)').option(
|
|
1085
1212
|
"--type <type>",
|
|
1086
1213
|
"task | bug | feature | epic (applied as a type/ label)"
|
|
1087
1214
|
).option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", "YYYY-MM-DD").option("--parent <id>", "parent epic").option("--assignee <who>", "me, a user id, an email, or a full name").option("--team <key>", "team key (or HARNESS_TEAM)").option("--label <name>", "label to apply (repeatable)", collect, []).option("--json", "emit JSON").action(async (opts) => {
|
|
1088
|
-
await withConfig(
|
|
1089
|
-
opts.
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1215
|
+
await withConfig(opts.json === true, true, (ctx) => {
|
|
1216
|
+
if (opts.title === void 0 && opts.titleFile === void 0) {
|
|
1217
|
+
throw new UsageError(
|
|
1218
|
+
"usage: harness create --title <title> | --title-file <path> [--team <key>] \u2026"
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
requireNonBlankInlineTitle(opts.title);
|
|
1222
|
+
const text = resolveTextSources(issueTextSources(opts));
|
|
1223
|
+
requireNonBlankTitle(text, opts.titleFile);
|
|
1224
|
+
return create(ctx, { ...opts, ...text });
|
|
1225
|
+
});
|
|
1093
1226
|
});
|
|
1094
|
-
program.command("update").description("update an issue; --claim takes it atomically").argument("[id]").option("--claim", "claim atomically and move to the first started state").option("--state <name>", "workflow state by name or type").option("--title <text>").option("--description <text>").option("--design <text>").option("--notes <text>", "replace the notes").option("--append-notes <text>", "append to the notes").option("--acceptance <text>").option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", 'YYYY-MM-DD; "" clears').option("--parent <id>").option(
|
|
1227
|
+
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("--title-file <path>", 'read the title from a file ("-" for stdin)').option("--description-file <path>", 'read the description from a file ("-" for stdin)').option("--design-file <path>", 'read the design notes from a file ("-" for stdin)').option("--notes-file <path>", 'replace the notes from a file ("-" for stdin)').option("--append-notes-file <path>", 'append to the notes from a file ("-" for stdin)').option("--acceptance-file <path>", 'read the acceptance criteria from a file ("-" for stdin)').option("--priority <name>", "urgent | high | medium | low | none").option("--due-date <date>", 'YYYY-MM-DD; "" clears').option("--parent <id>").option(
|
|
1095
1228
|
"--assignee <who>",
|
|
1096
1229
|
'me, a user id, an email, or a full name; "" unassigns'
|
|
1097
1230
|
).option("--json", "emit JSON").action(
|
|
@@ -1103,20 +1236,34 @@ program.command("update").description("update an issue; --claim takes it atomica
|
|
|
1103
1236
|
);
|
|
1104
1237
|
}
|
|
1105
1238
|
requireIdentifiers2([id]);
|
|
1106
|
-
|
|
1239
|
+
requireNonBlankInlineTitle(opts.title);
|
|
1240
|
+
const text = resolveTextSources([
|
|
1241
|
+
...issueTextSources(opts),
|
|
1242
|
+
{ name: "appendNotes", inline: opts.appendNotes, file: opts.appendNotesFile }
|
|
1243
|
+
]);
|
|
1244
|
+
if (Object.hasOwn(text, "title")) requireNonBlankTitle(text, opts.titleFile);
|
|
1245
|
+
return update(ctx, id, { ...opts, ...text });
|
|
1107
1246
|
});
|
|
1108
1247
|
}
|
|
1109
1248
|
);
|
|
1110
|
-
program.command("close").description("close issues; the response lists newly unblocked issues").argument("[ids...]").option("--reason <text>", "why it is done (e.g. the commit hash)").option("--cancel", "close as canceled instead of completed").option("--json", "emit JSON").action(
|
|
1249
|
+
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("--reason-file <path>", 'read the reason from a file ("-" for stdin)').option("--cancel", "close as canceled instead of completed").option("--json", "emit JSON").action(
|
|
1111
1250
|
async (ids, opts) => {
|
|
1112
1251
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1113
1252
|
if (ids.length === 0) {
|
|
1114
1253
|
throw new UsageError(
|
|
1115
|
-
"usage: harness close <id> [<id>\u2026] [--reason <text>] [--cancel]"
|
|
1254
|
+
"usage: harness close <id> [<id>\u2026] [--reason <text> | --reason-file <path>] [--cancel]"
|
|
1116
1255
|
);
|
|
1117
1256
|
}
|
|
1118
1257
|
requireIdentifiers2(ids);
|
|
1119
|
-
|
|
1258
|
+
const text = resolveTextSources([
|
|
1259
|
+
{ name: "reason", inline: opts.reason, file: opts.reasonFile }
|
|
1260
|
+
]);
|
|
1261
|
+
if (Object.hasOwn(text, "reason") && (text["reason"] ?? "").trim() === "") {
|
|
1262
|
+
throw new UsageError(
|
|
1263
|
+
opts.reasonFile === void 0 ? "--reason is empty \u2014 omit it, or give a reason" : `--reason-file ${opts.reasonFile} produced an empty reason`
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
return close(ctx, ids, { ...opts, ...text });
|
|
1120
1267
|
});
|
|
1121
1268
|
}
|
|
1122
1269
|
);
|
|
@@ -1151,17 +1298,31 @@ program.command("dep").description("blocking dependencies: dep add|rm <issue> <b
|
|
|
1151
1298
|
});
|
|
1152
1299
|
}
|
|
1153
1300
|
);
|
|
1154
|
-
program.command("comment").description("comment on an issue").argument("[id]").argument("[text...]").option("--mention <name>", "notify a person (repeatable)", collect, []).option("--json", "emit JSON").action(
|
|
1301
|
+
program.command("comment").description("comment on an issue").argument("[id]").argument("[text...]").option("--body-file <path>", 'read the comment body from a file ("-" for stdin)').option("--mention <name>", "notify a person (repeatable)", collect, []).option("--json", "emit JSON").action(
|
|
1155
1302
|
async (id, text, opts) => {
|
|
1156
1303
|
await withConfig(opts.json === true, true, (ctx) => {
|
|
1157
|
-
if (id === void 0 || text.length === 0) {
|
|
1304
|
+
if (id === void 0 || opts.bodyFile === void 0 && text.length === 0) {
|
|
1158
1305
|
throw new UsageError(
|
|
1159
|
-
"usage: harness comment <id> <text> [--mention NAME]\u2026"
|
|
1306
|
+
"usage: harness comment <id> <text> [--mention NAME]\u2026 | harness comment <id> --body-file <path>"
|
|
1160
1307
|
);
|
|
1161
1308
|
}
|
|
1162
1309
|
requireIdentifiers2([id]);
|
|
1310
|
+
if (opts.bodyFile !== void 0 && text.length > 0) {
|
|
1311
|
+
throw new UsageError(
|
|
1312
|
+
"comment text and --body-file both given \u2014 pass one; there is no precedence between them"
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
const fromFile = resolveTextSources([
|
|
1316
|
+
{ name: "body", inline: void 0, file: opts.bodyFile }
|
|
1317
|
+
]).body;
|
|
1318
|
+
const body = fromFile === void 0 ? text : [fromFile];
|
|
1319
|
+
if (body.join(" ").trim() === "") {
|
|
1320
|
+
throw new UsageError(
|
|
1321
|
+
opts.bodyFile === void 0 ? "the comment text is empty \u2014 nothing to post" : `--body-file ${opts.bodyFile} produced an empty comment \u2014 nothing to post`
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1163
1324
|
const mentions = opts.mention.filter((name) => name.trim() !== "");
|
|
1164
|
-
return comment(ctx, id,
|
|
1325
|
+
return comment(ctx, id, body, mentions);
|
|
1165
1326
|
});
|
|
1166
1327
|
}
|
|
1167
1328
|
);
|
|
@@ -1391,7 +1552,7 @@ program.command("upload").description("upload a file; prints markdown to referen
|
|
|
1391
1552
|
});
|
|
1392
1553
|
});
|
|
1393
1554
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
1394
|
-
const { startMcpServer } = await import("./server-
|
|
1555
|
+
const { startMcpServer } = await import("./server-J2V5JXFY.js");
|
|
1395
1556
|
await startMcpServer();
|
|
1396
1557
|
});
|
|
1397
1558
|
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(
|