@duyquangnvx/create-agent-cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/bin.js +1071 -544
- package/package.json +2 -2
- package/template/skills/{{binName}}/SKILL.md +3 -1
- package/template/src/cli.ts +22 -3
- package/template/test/cli.test.ts +40 -2
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs, parseEnv } from "node:util";
|
|
2
3
|
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
-
import { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { access, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { constants, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
6
|
+
import { constants as constants$1, homedir, tmpdir } from "node:os";
|
|
4
7
|
import { spawn } from "node:child_process";
|
|
5
|
-
import {
|
|
6
|
-
import { constants as constants$1, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
7
|
-
import { parseArgs, parseEnv } from "node:util";
|
|
8
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js
|
|
10
11
|
function getEnumValues(entries) {
|
|
@@ -2817,7 +2818,7 @@ function bucketFor(state, inst) {
|
|
|
2817
2818
|
return bucket;
|
|
2818
2819
|
}
|
|
2819
2820
|
let handoff;
|
|
2820
|
-
const open = [];
|
|
2821
|
+
const open$1 = [];
|
|
2821
2822
|
const memo = {
|
|
2822
2823
|
alloc(_inst, payload, empty) {
|
|
2823
2824
|
const bucket = handoff;
|
|
@@ -2828,7 +2829,7 @@ const memo = {
|
|
|
2828
2829
|
issues: null
|
|
2829
2830
|
};
|
|
2830
2831
|
bucket.set(payload.value, entry);
|
|
2831
|
-
open.push(entry);
|
|
2832
|
+
open$1.push(entry);
|
|
2832
2833
|
return empty;
|
|
2833
2834
|
},
|
|
2834
2835
|
guard(inst) {
|
|
@@ -2894,10 +2895,10 @@ const memo = {
|
|
|
2894
2895
|
return payload;
|
|
2895
2896
|
}
|
|
2896
2897
|
handoff = bucket;
|
|
2897
|
-
const depth = open.length;
|
|
2898
|
+
const depth = open$1.length;
|
|
2898
2899
|
const result = base(payload, ctx);
|
|
2899
2900
|
handoff = void 0;
|
|
2900
|
-
const entry = open.length > depth ? open.pop() : void 0;
|
|
2901
|
+
const entry = open$1.length > depth ? open$1.pop() : void 0;
|
|
2901
2902
|
if (result instanceof Promise) return result.then((r) => {
|
|
2902
2903
|
if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;
|
|
2903
2904
|
return r;
|
|
@@ -5581,6 +5582,54 @@ function untilAborted(signal) {
|
|
|
5581
5582
|
});
|
|
5582
5583
|
}
|
|
5583
5584
|
//#endregion
|
|
5585
|
+
//#region ../agent-cli/dist/stdin.js
|
|
5586
|
+
const STDIN_META = "stdin";
|
|
5587
|
+
function isStdinSchema(schema) {
|
|
5588
|
+
return globalRegistry.get(schema)?.[STDIN_META] === true;
|
|
5589
|
+
}
|
|
5590
|
+
function endOfInputKeys(platform) {
|
|
5591
|
+
return platform === "win32" ? "Ctrl-Z then Enter" : "Ctrl-D";
|
|
5592
|
+
}
|
|
5593
|
+
/** The progress line shown before reading a terminal, so a person isn't left facing silence. */
|
|
5594
|
+
function readingLine(name, platform) {
|
|
5595
|
+
return `reading <${name}> from stdin; end it with ${endOfInputKeys(platform)}`;
|
|
5596
|
+
}
|
|
5597
|
+
function isTerminal(source) {
|
|
5598
|
+
return typeof source !== "string" && "isTTY" in source && source.isTTY === true;
|
|
5599
|
+
}
|
|
5600
|
+
/** The whole of `source` as UTF-8; rejects with the signal's reason once it aborts. */
|
|
5601
|
+
function readStdin(source, signal) {
|
|
5602
|
+
if (typeof source === "string") return signal.aborted ? Promise.reject(signal.reason) : Promise.resolve(source);
|
|
5603
|
+
return new Promise((resolve, reject) => {
|
|
5604
|
+
const chunks = [];
|
|
5605
|
+
const onData = (chunk) => {
|
|
5606
|
+
chunks.push(chunk instanceof Uint8Array ? chunk : Buffer.from(String(chunk)));
|
|
5607
|
+
};
|
|
5608
|
+
const onEnd = () => {
|
|
5609
|
+
stop();
|
|
5610
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
5611
|
+
};
|
|
5612
|
+
const onError = (error) => {
|
|
5613
|
+
stop();
|
|
5614
|
+
reject(error);
|
|
5615
|
+
};
|
|
5616
|
+
const onAbort = () => {
|
|
5617
|
+
stop();
|
|
5618
|
+
reject(signal.reason);
|
|
5619
|
+
};
|
|
5620
|
+
const stop = () => {
|
|
5621
|
+
source.off("data", onData).off("end", onEnd).off("error", onError).pause();
|
|
5622
|
+
signal.removeEventListener("abort", onAbort);
|
|
5623
|
+
};
|
|
5624
|
+
if (signal.aborted) {
|
|
5625
|
+
reject(signal.reason);
|
|
5626
|
+
return;
|
|
5627
|
+
}
|
|
5628
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5629
|
+
source.on("data", onData).once("end", onEnd).once("error", onError);
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
//#endregion
|
|
5584
5633
|
//#region ../agent-cli/dist/input.js
|
|
5585
5634
|
function isWrapper(schema) {
|
|
5586
5635
|
return schema instanceof ZodOptional || schema instanceof ZodNullable || schema instanceof ZodDefault || schema instanceof ZodPrefault;
|
|
@@ -5597,11 +5646,13 @@ function describe(schema) {
|
|
|
5597
5646
|
let description = current.description;
|
|
5598
5647
|
let required = true;
|
|
5599
5648
|
let defaultValue;
|
|
5649
|
+
let stdin = isStdinSchema(current);
|
|
5600
5650
|
while (isWrapper(current)) {
|
|
5601
5651
|
if (current instanceof ZodDefault || current instanceof ZodPrefault) defaultValue = current.def.defaultValue;
|
|
5602
5652
|
required = false;
|
|
5603
5653
|
current = current.unwrap();
|
|
5604
5654
|
description ??= current.description;
|
|
5655
|
+
stdin ||= isStdinSchema(current);
|
|
5605
5656
|
}
|
|
5606
5657
|
const variadic = current instanceof ZodArray;
|
|
5607
5658
|
if (current instanceof ZodArray) current = current.element;
|
|
@@ -5611,10 +5662,12 @@ function describe(schema) {
|
|
|
5611
5662
|
defaultValue,
|
|
5612
5663
|
description,
|
|
5613
5664
|
choices: current instanceof ZodEnum ? current.options.map(String) : void 0,
|
|
5614
|
-
variadic
|
|
5665
|
+
variadic,
|
|
5666
|
+
stdin
|
|
5615
5667
|
};
|
|
5616
5668
|
}
|
|
5617
|
-
function argLabel(key, { variadic, required }) {
|
|
5669
|
+
function argLabel(key, { variadic, required, stdin }) {
|
|
5670
|
+
if (stdin) return required ? `<${key}|->` : `[${key}|-]`;
|
|
5618
5671
|
if (!variadic) return `<${key}>`;
|
|
5619
5672
|
return required ? `<${key}...>` : `[${key}...]`;
|
|
5620
5673
|
}
|
|
@@ -5635,7 +5688,8 @@ function optionField(key, schema) {
|
|
|
5635
5688
|
const name = kebab(key);
|
|
5636
5689
|
const isBoolean = described.type === "boolean";
|
|
5637
5690
|
const label = isBoolean && described.defaultValue === true ? `--no-${name}` : `--${name}`;
|
|
5638
|
-
const
|
|
5691
|
+
const value = `<${described.choices?.join("|") ?? "value"}>${described.variadic ? "..." : ""}`;
|
|
5692
|
+
const usage = isBoolean ? label : `${label} ${value}`;
|
|
5639
5693
|
return {
|
|
5640
5694
|
key,
|
|
5641
5695
|
name,
|
|
@@ -5651,17 +5705,32 @@ function fieldsOf$1(schema, toField) {
|
|
|
5651
5705
|
function argFields(schema) {
|
|
5652
5706
|
return fieldsOf$1(schema, argField);
|
|
5653
5707
|
}
|
|
5708
|
+
function keysOf(fields) {
|
|
5709
|
+
return fields.map(({ key }) => `'${key}'`).join(", ");
|
|
5710
|
+
}
|
|
5654
5711
|
/** Throws when an arg that takes every remaining operand is not the last arg. */
|
|
5655
5712
|
function checkArrayArgLast(name, args) {
|
|
5656
5713
|
const misplaced = argFields(args).slice(0, -1).filter((field) => field.variadic);
|
|
5657
|
-
if (misplaced.length > 0) {
|
|
5658
|
-
|
|
5659
|
-
|
|
5714
|
+
if (misplaced.length > 0) throw new Error(`Command '${name}' declares array arg ${keysOf(misplaced)} before its last arg; only the last arg may be an array`);
|
|
5715
|
+
}
|
|
5716
|
+
/** Throws unless at most one arg, and no option, is marked with `orStdin`. */
|
|
5717
|
+
function checkStdinFields(name, args, options) {
|
|
5718
|
+
const stdinArgs = argFields(args).filter((field) => field.stdin);
|
|
5719
|
+
if (stdinArgs.length > 1) throw new Error(`Command '${name}' marks args ${keysOf(stdinArgs)} with orStdin; only one arg may read stdin`);
|
|
5720
|
+
const stdinOptions = optionFields(options).filter((field) => field.stdin);
|
|
5721
|
+
if (stdinOptions.length > 0) {
|
|
5722
|
+
const noun = stdinOptions.length === 1 ? "option" : "options";
|
|
5723
|
+
throw new Error(`Command '${name}' marks ${noun} ${keysOf(stdinOptions)} with orStdin; only an arg may read stdin`);
|
|
5660
5724
|
}
|
|
5661
5725
|
}
|
|
5662
5726
|
function optionFields(schema) {
|
|
5663
5727
|
return fieldsOf$1(schema, optionField);
|
|
5664
5728
|
}
|
|
5729
|
+
/** Throws when an option is an array of booleans, which a switch cannot express. */
|
|
5730
|
+
function checkNoBooleanArrayOption(name, options) {
|
|
5731
|
+
const booleans = optionFields(options).filter((field) => field.variadic && field.type === "boolean");
|
|
5732
|
+
if (booleans.length > 0) throw new Error(`Command '${name}' declares boolean array option ${keysOf(booleans)}; a switch cannot repeat, use a boolean`);
|
|
5733
|
+
}
|
|
5665
5734
|
//#endregion
|
|
5666
5735
|
//#region ../agent-cli/dist/errors.js
|
|
5667
5736
|
var CliError = class extends Error {
|
|
@@ -5692,6 +5761,9 @@ function errnoOf(error) {
|
|
|
5692
5761
|
function isMissingFile(error) {
|
|
5693
5762
|
return errnoOf(error) === "ENOENT";
|
|
5694
5763
|
}
|
|
5764
|
+
function isNothingThere(error) {
|
|
5765
|
+
return isMissingFile(error) || errnoOf(error) === "ENOTDIR";
|
|
5766
|
+
}
|
|
5695
5767
|
function debugVariable(cliName) {
|
|
5696
5768
|
return `${cliName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_DEBUG`;
|
|
5697
5769
|
}
|
|
@@ -5756,6 +5828,89 @@ function formatRuntimeError(error, format) {
|
|
|
5756
5828
|
}
|
|
5757
5829
|
return errors.map((each) => errorLines(each, format)).join("");
|
|
5758
5830
|
}
|
|
5831
|
+
//#endregion
|
|
5832
|
+
//#region ../agent-cli/dist/tokenize.js
|
|
5833
|
+
const NEGATIVE_NUMBER = /^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/;
|
|
5834
|
+
function isOptionLike(word) {
|
|
5835
|
+
return word !== void 0 && word.length > 1 && word[0] === "-" && !NEGATIVE_NUMBER.test(word);
|
|
5836
|
+
}
|
|
5837
|
+
function parseArgsOptions(known) {
|
|
5838
|
+
return Object.fromEntries(known.map(({ flag, takes }) => [flag.slice(2), { type: takes === "value" ? "string" : "boolean" }]));
|
|
5839
|
+
}
|
|
5840
|
+
function flagItem(token, { flag, takes }, words) {
|
|
5841
|
+
if (takes !== "optional value" || token.inlineValue === true) return {
|
|
5842
|
+
kind: "flag",
|
|
5843
|
+
flag,
|
|
5844
|
+
value: token.value ?? true
|
|
5845
|
+
};
|
|
5846
|
+
const took = token.index + 1;
|
|
5847
|
+
const next = words[took];
|
|
5848
|
+
return next === void 0 || isOptionLike(next) ? {
|
|
5849
|
+
kind: "flag",
|
|
5850
|
+
flag,
|
|
5851
|
+
value: true
|
|
5852
|
+
} : {
|
|
5853
|
+
kind: "flag",
|
|
5854
|
+
flag,
|
|
5855
|
+
value: next,
|
|
5856
|
+
took
|
|
5857
|
+
};
|
|
5858
|
+
}
|
|
5859
|
+
function itemOf(token, words, byFlag) {
|
|
5860
|
+
const word = words[token.index] ?? "";
|
|
5861
|
+
if (token.kind === "option-terminator") return { kind: "end" };
|
|
5862
|
+
if (token.kind === "positional" || NEGATIVE_NUMBER.test(word)) return {
|
|
5863
|
+
kind: "operand",
|
|
5864
|
+
value: word
|
|
5865
|
+
};
|
|
5866
|
+
const flag = byFlag.get(token.rawName);
|
|
5867
|
+
return flag === void 0 || flag.takes === "nothing" && token.inlineValue === true ? {
|
|
5868
|
+
kind: "unknown option",
|
|
5869
|
+
word
|
|
5870
|
+
} : flagItem(token, flag, words);
|
|
5871
|
+
}
|
|
5872
|
+
/** Splits a Command's words into operands, unknown options and the values of known flags. */
|
|
5873
|
+
function tokenize(words, known) {
|
|
5874
|
+
const byFlag = new Map(known.map((flag) => [flag.flag, flag]));
|
|
5875
|
+
const { tokens } = parseArgs({
|
|
5876
|
+
args: [...words],
|
|
5877
|
+
options: parseArgsOptions(known),
|
|
5878
|
+
strict: false,
|
|
5879
|
+
allowPositionals: true,
|
|
5880
|
+
tokens: true
|
|
5881
|
+
});
|
|
5882
|
+
const operands = [];
|
|
5883
|
+
const unknownOptions = [];
|
|
5884
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
5885
|
+
const done = /* @__PURE__ */ new Set();
|
|
5886
|
+
let literal = false;
|
|
5887
|
+
let afterUnknownOption = false;
|
|
5888
|
+
for (const token of tokens) {
|
|
5889
|
+
if (done.has(token.index)) continue;
|
|
5890
|
+
done.add(token.index);
|
|
5891
|
+
const item = itemOf(token, words, byFlag);
|
|
5892
|
+
if (item.kind === "end") literal = true;
|
|
5893
|
+
else if (item.kind === "operand") {
|
|
5894
|
+
operands.push({
|
|
5895
|
+
value: item.value,
|
|
5896
|
+
afterUnknownOption: !literal && afterUnknownOption
|
|
5897
|
+
});
|
|
5898
|
+
afterUnknownOption = false;
|
|
5899
|
+
} else if (item.kind === "unknown option") {
|
|
5900
|
+
unknownOptions.push(item.word);
|
|
5901
|
+
afterUnknownOption = true;
|
|
5902
|
+
} else {
|
|
5903
|
+
occurrences.set(item.flag, [...occurrences.get(item.flag) ?? [], item.value]);
|
|
5904
|
+
if (item.took !== void 0) done.add(item.took);
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
return {
|
|
5908
|
+
operands,
|
|
5909
|
+
unknownOptions,
|
|
5910
|
+
values: new Map([...occurrences].map(([flag, all]) => [flag, all[all.length - 1] ?? true])),
|
|
5911
|
+
occurrences
|
|
5912
|
+
};
|
|
5913
|
+
}
|
|
5759
5914
|
const SHELL_SAFE = /^[\w@%+=:,./-]+$/;
|
|
5760
5915
|
function quoted$1(token) {
|
|
5761
5916
|
return SHELL_SAFE.test(token) ? token : `'${token.replaceAll("'", `'\\''`)}'`;
|
|
@@ -5763,10 +5918,6 @@ function quoted$1(token) {
|
|
|
5763
5918
|
function pasteable(cliName, tokens) {
|
|
5764
5919
|
return [cliName, ...tokens].map(quoted$1).join(" ");
|
|
5765
5920
|
}
|
|
5766
|
-
/** The caller's command line as typed, ready to paste. */
|
|
5767
|
-
function commandLineOf({ cliName, argv }) {
|
|
5768
|
-
return pasteable(cliName, argv);
|
|
5769
|
-
}
|
|
5770
5921
|
function split(argv) {
|
|
5771
5922
|
const end = argv.indexOf("--");
|
|
5772
5923
|
return end === -1 ? {
|
|
@@ -5777,19 +5928,29 @@ function split(argv) {
|
|
|
5777
5928
|
operands: argv.slice(end)
|
|
5778
5929
|
};
|
|
5779
5930
|
}
|
|
5780
|
-
function
|
|
5781
|
-
return
|
|
5931
|
+
function tookValue(takes, next) {
|
|
5932
|
+
return takes === "value" || takes === "optional value" && next !== void 0 && !isOptionLike(next);
|
|
5933
|
+
}
|
|
5934
|
+
function without(flag, takes, tokens) {
|
|
5935
|
+
const kept = [];
|
|
5936
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
5937
|
+
const token = tokens[index] ?? "";
|
|
5938
|
+
if (token === flag) {
|
|
5939
|
+
if (tookValue(takes, tokens[index + 1])) index += 1;
|
|
5940
|
+
} else if (!token.startsWith(`${flag}=`)) kept.push(token);
|
|
5941
|
+
}
|
|
5942
|
+
return kept;
|
|
5782
5943
|
}
|
|
5783
5944
|
/**
|
|
5784
|
-
* The caller's command line, ready to paste, with
|
|
5785
|
-
*
|
|
5945
|
+
* The caller's command line as typed, ready to paste, with each flag in `remove` dropped along with
|
|
5946
|
+
* its value and the words in `add` placed before any `--`.
|
|
5786
5947
|
*/
|
|
5787
|
-
function
|
|
5948
|
+
function commandLineOf({ cliName, argv, flags }, { add = [], remove = [] } = {}) {
|
|
5788
5949
|
const { options, operands } = split(argv);
|
|
5789
|
-
const
|
|
5950
|
+
const takes = new Map(flags.map((known) => [known.flag, known.takes]));
|
|
5790
5951
|
return pasteable(cliName, [
|
|
5791
|
-
...without(flag, options),
|
|
5792
|
-
...
|
|
5952
|
+
...remove.reduce((tokens, flag) => without(flag, takes.get(flag) ?? "nothing", tokens), options),
|
|
5953
|
+
...add,
|
|
5793
5954
|
...operands
|
|
5794
5955
|
]);
|
|
5795
5956
|
}
|
|
@@ -5942,7 +6103,10 @@ function renderRows(rows, fields) {
|
|
|
5942
6103
|
function printedPage(page, invocation) {
|
|
5943
6104
|
return page.nextCursor === void 0 ? { items: page.items } : {
|
|
5944
6105
|
items: page.items,
|
|
5945
|
-
next:
|
|
6106
|
+
next: commandLineOf(invocation, {
|
|
6107
|
+
remove: [PAGE_FLAGS.cursor.flag],
|
|
6108
|
+
add: [PAGE_FLAGS.cursor.flag, String(page.nextCursor)]
|
|
6109
|
+
})
|
|
5946
6110
|
};
|
|
5947
6111
|
}
|
|
5948
6112
|
/** The line that ends a rendered page, so the caller always knows whether and how to get more. */
|
|
@@ -6058,6 +6222,8 @@ function checkedCommand(command) {
|
|
|
6058
6222
|
checkNoStandardFlags(command);
|
|
6059
6223
|
checkForce(command);
|
|
6060
6224
|
checkArrayArgLast(command.name, command.args);
|
|
6225
|
+
checkStdinFields(command.name, command.args, command.options);
|
|
6226
|
+
checkNoBooleanArrayOption(command.name, command.options);
|
|
6061
6227
|
return command;
|
|
6062
6228
|
}
|
|
6063
6229
|
function defineCommandFor() {
|
|
@@ -6135,22 +6301,259 @@ function lastLines(output, count) {
|
|
|
6135
6301
|
return output.trim().split("\n").slice(-count).join("\n");
|
|
6136
6302
|
}
|
|
6137
6303
|
//#endregion
|
|
6304
|
+
//#region ../agent-cli/dist/tools.js
|
|
6305
|
+
const TOOL_MISSING = "TOOL_MISSING";
|
|
6306
|
+
const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
|
|
6307
|
+
function pathOf(env) {
|
|
6308
|
+
return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
|
|
6309
|
+
}
|
|
6310
|
+
function extensionsOf(env) {
|
|
6311
|
+
return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
|
|
6312
|
+
}
|
|
6313
|
+
async function isExecutable(file) {
|
|
6314
|
+
try {
|
|
6315
|
+
await access(file, constants.X_OK);
|
|
6316
|
+
return true;
|
|
6317
|
+
} catch (error) {
|
|
6318
|
+
if (isNothingThere(error) || errnoOf(error) === "EACCES") return false;
|
|
6319
|
+
throw error;
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
/** The file names `name` may have on `PATH`: itself when it already ends in one of the extensions. */
|
|
6323
|
+
function fileNamesOf(name, env) {
|
|
6324
|
+
const extensions = extensionsOf(env);
|
|
6325
|
+
const own = extname(name).toUpperCase();
|
|
6326
|
+
return own !== "" && extensions.some((extension) => extension.toUpperCase() === own) ? [name] : extensions.map((extension) => `${name}${extension}`);
|
|
6327
|
+
}
|
|
6328
|
+
/** Where `name` is found on `PATH` from `env`, or undefined. */
|
|
6329
|
+
async function findTool(name, env) {
|
|
6330
|
+
const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => fileNamesOf(name, env).map((file) => join(folder, file)));
|
|
6331
|
+
for (const file of candidates) if (await isExecutable(file)) return file;
|
|
6332
|
+
}
|
|
6333
|
+
/** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
|
|
6334
|
+
async function checkTools(tools, env) {
|
|
6335
|
+
const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
|
|
6336
|
+
const missing = tools.filter((_tool, index) => found[index] === void 0);
|
|
6337
|
+
if (missing.length === 0) return;
|
|
6338
|
+
const names = missing.map(({ name }) => name).join(", ");
|
|
6339
|
+
throw new CliError({
|
|
6340
|
+
code: TOOL_MISSING,
|
|
6341
|
+
message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
|
|
6342
|
+
hint: `install ${missing.length === 1 ? "it" : "them"}, then run the same command again: ${missing.map(({ install }) => install).join("; ")}`
|
|
6343
|
+
});
|
|
6344
|
+
}
|
|
6345
|
+
function toolsIn(node) {
|
|
6346
|
+
return isCommand(node) ? [...node.tools ?? []] : Object.values(node).flatMap(toolsIn);
|
|
6347
|
+
}
|
|
6348
|
+
/** One `doctor` row per distinct tool the CLI and its Commands declare, with its install command as the fix. */
|
|
6349
|
+
function toolRows(tools, commands) {
|
|
6350
|
+
return [...new Map([...tools, ...toolsIn(commands)].map((tool) => [tool.name, tool])).values()].map(({ name, install }) => ({
|
|
6351
|
+
name,
|
|
6352
|
+
run: async (ctx) => {
|
|
6353
|
+
const file = await findTool(name, ctx.env);
|
|
6354
|
+
return file === void 0 ? {
|
|
6355
|
+
state: "problem",
|
|
6356
|
+
message: `${name} is not on PATH`,
|
|
6357
|
+
fix: install
|
|
6358
|
+
} : {
|
|
6359
|
+
state: "ok",
|
|
6360
|
+
message: `found at ${file}`
|
|
6361
|
+
};
|
|
6362
|
+
}
|
|
6363
|
+
}));
|
|
6364
|
+
}
|
|
6365
|
+
//#endregion
|
|
6366
|
+
//#region ../agent-cli/dist/spawn.js
|
|
6367
|
+
const TOOL_FAILED = "TOOL_FAILED";
|
|
6368
|
+
const STDERR_TAIL_LINES$1 = 20;
|
|
6369
|
+
const SIGNAL_EXIT_BASE = 128;
|
|
6370
|
+
const BATCH_FILE = /\.(?:cmd|bat)$/i;
|
|
6371
|
+
const CMD_SHIM = /\.cmd$/i;
|
|
6372
|
+
const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
6373
|
+
const PATH_SEPARATOR = /[\\/]/;
|
|
6374
|
+
const TRAILING_CR = /\r$/;
|
|
6375
|
+
function escapedForCmd(text) {
|
|
6376
|
+
return text.replace(CMD_META, "^$1");
|
|
6377
|
+
}
|
|
6378
|
+
function batchArgument(arg, shim) {
|
|
6379
|
+
const escaped = escapedForCmd(`"${arg.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/, "$1$1")}"`);
|
|
6380
|
+
return shim ? escapedForCmd(escaped) : escaped;
|
|
6381
|
+
}
|
|
6382
|
+
function batchCommandLine(command, file, args) {
|
|
6383
|
+
const shim = CMD_SHIM.test(file);
|
|
6384
|
+
return [escapedForCmd(command), ...args.map((arg) => batchArgument(arg, shim))].join(" ");
|
|
6385
|
+
}
|
|
6386
|
+
async function windowsLaunch(command, args, env) {
|
|
6387
|
+
const file = PATH_SEPARATOR.test(command) ? command : await findTool(command, env);
|
|
6388
|
+
if (file === void 0 || !BATCH_FILE.test(file)) return {
|
|
6389
|
+
command,
|
|
6390
|
+
args,
|
|
6391
|
+
shell: false
|
|
6392
|
+
};
|
|
6393
|
+
return {
|
|
6394
|
+
command: batchCommandLine(command, file, args),
|
|
6395
|
+
args: [],
|
|
6396
|
+
shell: true
|
|
6397
|
+
};
|
|
6398
|
+
}
|
|
6399
|
+
function stop(child) {
|
|
6400
|
+
if (process.platform !== "win32" || child.pid === void 0) {
|
|
6401
|
+
child.kill();
|
|
6402
|
+
return Promise.resolve();
|
|
6403
|
+
}
|
|
6404
|
+
const pid = String(child.pid);
|
|
6405
|
+
return new Promise((resolve) => {
|
|
6406
|
+
const taskkill = spawn("taskkill", [
|
|
6407
|
+
"/pid",
|
|
6408
|
+
pid,
|
|
6409
|
+
"/t",
|
|
6410
|
+
"/f"
|
|
6411
|
+
], {
|
|
6412
|
+
stdio: "ignore",
|
|
6413
|
+
windowsHide: true
|
|
6414
|
+
});
|
|
6415
|
+
taskkill.once("error", () => {
|
|
6416
|
+
child.kill();
|
|
6417
|
+
resolve();
|
|
6418
|
+
});
|
|
6419
|
+
taskkill.once("exit", () => resolve());
|
|
6420
|
+
});
|
|
6421
|
+
}
|
|
6422
|
+
/** Calls `onLine` with each complete line pushed, and with the unterminated rest at the end. */
|
|
6423
|
+
function lineSplitter(onLine) {
|
|
6424
|
+
let rest = "";
|
|
6425
|
+
const emit = (line) => onLine?.(line.replace(TRAILING_CR, ""));
|
|
6426
|
+
return {
|
|
6427
|
+
push(text) {
|
|
6428
|
+
if (onLine === void 0) return;
|
|
6429
|
+
const lines = (rest + text).split("\n");
|
|
6430
|
+
rest = lines.pop() ?? "";
|
|
6431
|
+
for (const line of lines) emit(line);
|
|
6432
|
+
},
|
|
6433
|
+
end() {
|
|
6434
|
+
if (rest !== "") emit(rest);
|
|
6435
|
+
rest = "";
|
|
6436
|
+
}
|
|
6437
|
+
};
|
|
6438
|
+
}
|
|
6439
|
+
/**
|
|
6440
|
+
* Runs a child process to its end; it is killed on abort, and the promise settles only once it has
|
|
6441
|
+
* exited. A `detached` child leaves the terminal's process group on POSIX, and so its Ctrl-C.
|
|
6442
|
+
*/
|
|
6443
|
+
async function spawnChild(command, args, { cwd, env, signal, onLine, detached = false }) {
|
|
6444
|
+
signal.throwIfAborted();
|
|
6445
|
+
const launch = process.platform === "win32" ? await windowsLaunch(command, args, env) : {
|
|
6446
|
+
command,
|
|
6447
|
+
args,
|
|
6448
|
+
shell: false
|
|
6449
|
+
};
|
|
6450
|
+
signal.throwIfAborted();
|
|
6451
|
+
return new Promise((resolve, reject) => {
|
|
6452
|
+
const child = spawn(launch.command, launch.args, {
|
|
6453
|
+
cwd,
|
|
6454
|
+
env,
|
|
6455
|
+
shell: launch.shell,
|
|
6456
|
+
windowsHide: true,
|
|
6457
|
+
stdio: [
|
|
6458
|
+
"ignore",
|
|
6459
|
+
"pipe",
|
|
6460
|
+
"pipe"
|
|
6461
|
+
],
|
|
6462
|
+
detached: detached && process.platform !== "win32"
|
|
6463
|
+
});
|
|
6464
|
+
let stdout = "";
|
|
6465
|
+
let stderr = "";
|
|
6466
|
+
const stdoutLines = lineSplitter(onLine);
|
|
6467
|
+
const stderrLines = lineSplitter(onLine);
|
|
6468
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
6469
|
+
stdout += chunk;
|
|
6470
|
+
stdoutLines.push(chunk);
|
|
6471
|
+
});
|
|
6472
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
6473
|
+
stderr += chunk;
|
|
6474
|
+
stderrLines.push(chunk);
|
|
6475
|
+
});
|
|
6476
|
+
let stopped = Promise.resolve();
|
|
6477
|
+
const kill = () => {
|
|
6478
|
+
stopped = stop(child);
|
|
6479
|
+
};
|
|
6480
|
+
signal.addEventListener("abort", kill, { once: true });
|
|
6481
|
+
child.on("error", (error) => {
|
|
6482
|
+
signal.removeEventListener("abort", kill);
|
|
6483
|
+
reject(error);
|
|
6484
|
+
});
|
|
6485
|
+
child.on("close", (code, killedBy) => {
|
|
6486
|
+
signal.removeEventListener("abort", kill);
|
|
6487
|
+
if (signal.aborted) {
|
|
6488
|
+
stopped.then(() => reject(signal.reason));
|
|
6489
|
+
return;
|
|
6490
|
+
}
|
|
6491
|
+
stdoutLines.end();
|
|
6492
|
+
stderrLines.end();
|
|
6493
|
+
resolve({
|
|
6494
|
+
stdout,
|
|
6495
|
+
stderr,
|
|
6496
|
+
exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants$1.signals[killedBy])
|
|
6497
|
+
});
|
|
6498
|
+
});
|
|
6499
|
+
});
|
|
6500
|
+
}
|
|
6501
|
+
const realSpawn = (command, args, options) => spawnChild(command, args, options);
|
|
6502
|
+
function failedError(command, { exitCode, stderr }) {
|
|
6503
|
+
const tail = lastLines(stderr, STDERR_TAIL_LINES$1);
|
|
6504
|
+
return new CliError({
|
|
6505
|
+
code: TOOL_FAILED,
|
|
6506
|
+
message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
|
|
6507
|
+
hint: `the output of ${command} above says why it failed`
|
|
6508
|
+
});
|
|
6509
|
+
}
|
|
6510
|
+
function missingError(command, cause) {
|
|
6511
|
+
return new CliError({
|
|
6512
|
+
code: TOOL_MISSING,
|
|
6513
|
+
message: `${command} could not be started: it is not on PATH`,
|
|
6514
|
+
hint: `install ${command}, then run the same command again`
|
|
6515
|
+
}, { cause });
|
|
6516
|
+
}
|
|
6517
|
+
/** `ctx.spawn`: `spawn` bound to the run, its start failures classified by errno. */
|
|
6518
|
+
function contextSpawn(run, defaults, fileError) {
|
|
6519
|
+
return async (command, args = [], options = {}) => {
|
|
6520
|
+
let result;
|
|
6521
|
+
try {
|
|
6522
|
+
result = await run(command, args, {
|
|
6523
|
+
cwd: options.cwd ?? defaults.cwd,
|
|
6524
|
+
env: options.env ?? defaults.env,
|
|
6525
|
+
signal: defaults.signal,
|
|
6526
|
+
onLine: options.progress === true ? defaults.progress : void 0
|
|
6527
|
+
});
|
|
6528
|
+
} catch (error) {
|
|
6529
|
+
if (isMissingFile(error)) throw missingError(command, error);
|
|
6530
|
+
throw fileError(error, {
|
|
6531
|
+
path: command,
|
|
6532
|
+
access: "read"
|
|
6533
|
+
});
|
|
6534
|
+
}
|
|
6535
|
+
if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
|
|
6536
|
+
return result;
|
|
6537
|
+
};
|
|
6538
|
+
}
|
|
6539
|
+
//#endregion
|
|
6138
6540
|
//#region ../agent-cli/dist/npm.js
|
|
6139
6541
|
const REGISTRY_UNREACHABLE = "REGISTRY_UNREACHABLE";
|
|
6140
6542
|
const REGISTRY_AUTH = "REGISTRY_AUTH";
|
|
6141
6543
|
const INSTALL_FAILED = "INSTALL_FAILED";
|
|
6142
6544
|
const NPM_FAILED = "NPM_FAILED";
|
|
6143
6545
|
const NO_LATEST = "NO_LATEST";
|
|
6144
|
-
function npmContext(cli, install, ctx) {
|
|
6546
|
+
function npmContext(cli, install, ctx, node) {
|
|
6145
6547
|
return {
|
|
6146
6548
|
cliName: cli.name,
|
|
6147
6549
|
install,
|
|
6148
6550
|
env: ctx.env,
|
|
6149
|
-
signal: ctx.signal
|
|
6551
|
+
signal: ctx.signal,
|
|
6552
|
+
node
|
|
6150
6553
|
};
|
|
6151
6554
|
}
|
|
6152
6555
|
const NPMJS = "https://registry.npmjs.org/";
|
|
6153
|
-
const STDERR_TAIL_LINES
|
|
6556
|
+
const STDERR_TAIL_LINES = 10;
|
|
6154
6557
|
const RM_RETRIES = 3;
|
|
6155
6558
|
const NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
6156
6559
|
"ENOTFOUND",
|
|
@@ -6180,40 +6583,36 @@ function installCommandLine(install, version = "latest") {
|
|
|
6180
6583
|
...registryFlags(install)
|
|
6181
6584
|
].join(" ");
|
|
6182
6585
|
}
|
|
6586
|
+
function npmOf(ctx, windows) {
|
|
6587
|
+
const beside = ctx.node === void 0 ? void 0 : join(dirname(ctx.node), windows ? "npm.cmd" : "npm");
|
|
6588
|
+
if (beside === void 0 || !existsSync(beside)) return {
|
|
6589
|
+
path: "npm",
|
|
6590
|
+
env: ctx.env
|
|
6591
|
+
};
|
|
6592
|
+
if (windows) return {
|
|
6593
|
+
path: beside,
|
|
6594
|
+
env: ctx.env
|
|
6595
|
+
};
|
|
6596
|
+
const PATH = [dirname(beside), ctx.env.PATH].filter(Boolean).join(delimiter);
|
|
6597
|
+
return {
|
|
6598
|
+
path: beside,
|
|
6599
|
+
env: {
|
|
6600
|
+
...ctx.env,
|
|
6601
|
+
PATH
|
|
6602
|
+
}
|
|
6603
|
+
};
|
|
6604
|
+
}
|
|
6183
6605
|
function spawnNpm(args, ctx, signal) {
|
|
6184
|
-
const
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
windowsHide: true,
|
|
6191
|
-
stdio: [
|
|
6192
|
-
"ignore",
|
|
6193
|
-
"pipe",
|
|
6194
|
-
"pipe"
|
|
6195
|
-
],
|
|
6196
|
-
detached: signal === null && !windows,
|
|
6197
|
-
signal: signal ?? void 0
|
|
6198
|
-
});
|
|
6199
|
-
let stdout = "";
|
|
6200
|
-
let stderr = "";
|
|
6201
|
-
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
6202
|
-
stdout += chunk;
|
|
6203
|
-
});
|
|
6204
|
-
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
6205
|
-
stderr += chunk;
|
|
6206
|
-
});
|
|
6207
|
-
child.on("error", reject);
|
|
6208
|
-
child.on("close", (exitCode) => resolve({
|
|
6209
|
-
exitCode,
|
|
6210
|
-
stdout,
|
|
6211
|
-
stderr
|
|
6212
|
-
}));
|
|
6606
|
+
const npm = npmOf(ctx, process.platform === "win32");
|
|
6607
|
+
return spawnChild(npm.path, args, {
|
|
6608
|
+
cwd: process.cwd(),
|
|
6609
|
+
env: npm.env,
|
|
6610
|
+
signal: signal ?? new AbortController().signal,
|
|
6611
|
+
detached: signal === null
|
|
6213
6612
|
});
|
|
6214
6613
|
}
|
|
6215
6614
|
function tail$1(stderr) {
|
|
6216
|
-
const lines = lastLines(stderr, STDERR_TAIL_LINES
|
|
6615
|
+
const lines = lastLines(stderr, STDERR_TAIL_LINES);
|
|
6217
6616
|
return lines === "" ? "" : `\n${lines}`;
|
|
6218
6617
|
}
|
|
6219
6618
|
function failureOf(call, answer, ctx) {
|
|
@@ -6361,7 +6760,10 @@ const CONFIRMATION_REQUIRED = "CONFIRMATION_REQUIRED";
|
|
|
6361
6760
|
const CONFIRMATION_MISMATCH = "CONFIRMATION_MISMATCH";
|
|
6362
6761
|
function checkConfirmation({ invocation, commandLine, given }, expected) {
|
|
6363
6762
|
if (given === expected) return;
|
|
6364
|
-
const hint = `to confirm, run \`${
|
|
6763
|
+
const hint = `to confirm, run \`${commandLineOf(invocation, {
|
|
6764
|
+
remove: [CONFIRM_FLAG.flag],
|
|
6765
|
+
add: [CONFIRM_FLAG.flag, expected]
|
|
6766
|
+
})}\``;
|
|
6365
6767
|
if (given === void 0) throw new UsageError({
|
|
6366
6768
|
code: CONFIRMATION_REQUIRED,
|
|
6367
6769
|
message: `\`${commandLine}\` is destructive and needs ${CONFIRM_FLAG.flag}`,
|
|
@@ -6382,22 +6784,42 @@ function checkCost({ invocation, commandLine, yes }, cost) {
|
|
|
6382
6784
|
throw new CliError({
|
|
6383
6785
|
code: NOT_CONFIRMED,
|
|
6384
6786
|
message: `\`${commandLine}\` needs ${YES_FLAG.flag}: ${cost}`,
|
|
6385
|
-
hint: `to approve the cost, run \`${
|
|
6787
|
+
hint: `to approve the cost, run \`${commandLineOf(invocation, { add: [YES_FLAG.flag] })}\``
|
|
6386
6788
|
});
|
|
6387
6789
|
}
|
|
6388
6790
|
//#endregion
|
|
6389
6791
|
//#region ../agent-cli/dist/agent.js
|
|
6390
|
-
const
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
"
|
|
6395
|
-
"
|
|
6396
|
-
"
|
|
6792
|
+
const AGENT_NAME = /^[a-zA-Z0-9_-]+$/u;
|
|
6793
|
+
const isSet = (name) => (env) => (env[name] ?? "") !== "";
|
|
6794
|
+
const equals = (name, value) => (env) => env[name] === value;
|
|
6795
|
+
const MARKERS = [
|
|
6796
|
+
(env) => AGENT_NAME.test(env.AI_AGENT ?? ""),
|
|
6797
|
+
equals("AGENT", "amp"),
|
|
6798
|
+
isSet("CODEX_SANDBOX"),
|
|
6799
|
+
isSet("CODEX_CI"),
|
|
6800
|
+
isSet("CODEX_THREAD_ID"),
|
|
6801
|
+
isSet("GEMINI_CLI"),
|
|
6802
|
+
isSet("COPILOT_CLI"),
|
|
6803
|
+
isSet("OPENCODE"),
|
|
6804
|
+
isSet("ANTIGRAVITY_AGENT"),
|
|
6805
|
+
isSet("AUGMENT_AGENT"),
|
|
6806
|
+
isSet("REPL_ID"),
|
|
6807
|
+
isSet("CLAUDE_CODE_IS_COWORK"),
|
|
6808
|
+
isSet("CLAUDECODE"),
|
|
6809
|
+
isSet("CLAUDE_CODE"),
|
|
6810
|
+
isSet("CURSOR_TRACE_ID"),
|
|
6811
|
+
isSet("CURSOR_AGENT"),
|
|
6812
|
+
equals("CURSOR_EXTENSION_HOST_ROLE", "agent-exec"),
|
|
6813
|
+
equals("TERM_PROGRAM", "kiro"),
|
|
6814
|
+
(env) => ["/.pi/agent", "\\.pi\\agent"].some((dir) => (env.PATH ?? "").includes(dir)),
|
|
6815
|
+
isSet("GOOSE_PROVIDER")
|
|
6397
6816
|
];
|
|
6398
|
-
/**
|
|
6817
|
+
/**
|
|
6818
|
+
* Whether a Coding agent, not a person, is running the Agent CLI. Some Coding agents run commands
|
|
6819
|
+
* in a PTY, so a TTY alone doesn't mean a person is watching.
|
|
6820
|
+
*/
|
|
6399
6821
|
function isAgent(env) {
|
|
6400
|
-
return
|
|
6822
|
+
return MARKERS.some((marker) => marker(env));
|
|
6401
6823
|
}
|
|
6402
6824
|
//#endregion
|
|
6403
6825
|
//#region ../agent-cli/dist/errno.js
|
|
@@ -6405,26 +6827,38 @@ const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
|
6405
6827
|
const DISK_FULL = "DISK_FULL";
|
|
6406
6828
|
const NOT_FOUND = "NOT_FOUND";
|
|
6407
6829
|
const ALREADY_EXISTS = "ALREADY_EXISTS";
|
|
6408
|
-
|
|
6830
|
+
const NOT_A_FILE = "NOT_A_FILE";
|
|
6831
|
+
function permissionHint({ path, access, callerLine, env, platform }) {
|
|
6832
|
+
if (platform === "win32") return `a program may hold ${path} open: close it, then run the same command again`;
|
|
6409
6833
|
if (!isAgent(env)) return `check the permissions of ${path} and its folder`;
|
|
6410
6834
|
return `your sandbox may block it: ask your user to run \`! ${callerLine}\`, or ${access === "read" ? `to allow reading ${path} in the sandbox` : `to allow ${path} in the sandbox's write list`}`;
|
|
6411
6835
|
}
|
|
6412
|
-
|
|
6836
|
+
function fileErrorOf(scope) {
|
|
6837
|
+
return (error, at) => classifyErrno(error, {
|
|
6838
|
+
...at,
|
|
6839
|
+
...scope
|
|
6840
|
+
});
|
|
6841
|
+
}
|
|
6413
6842
|
function classifyErrno(error, context) {
|
|
6414
6843
|
const { path } = context;
|
|
6415
6844
|
switch (errnoOf(error)) {
|
|
6416
6845
|
case "EACCES":
|
|
6417
|
-
case "EPERM":
|
|
6846
|
+
case "EPERM":
|
|
6847
|
+
case "EROFS":
|
|
6848
|
+
case "EBUSY": return new CliError({
|
|
6418
6849
|
code: PERMISSION_DENIED,
|
|
6419
6850
|
message: `permission denied: ${path}`,
|
|
6420
6851
|
hint: permissionHint(context)
|
|
6421
6852
|
}, { cause: error });
|
|
6422
|
-
case "ENOSPC":
|
|
6853
|
+
case "ENOSPC":
|
|
6854
|
+
case "EDQUOT": return new CliError({
|
|
6423
6855
|
code: DISK_FULL,
|
|
6424
6856
|
message: `no space left on the disk holding ${path}`,
|
|
6425
6857
|
hint: "free space on that disk, then run the same command again"
|
|
6426
6858
|
}, { cause: error });
|
|
6427
|
-
case "ENOENT":
|
|
6859
|
+
case "ENOENT":
|
|
6860
|
+
case "ENOTDIR":
|
|
6861
|
+
case "ENAMETOOLONG": return new CliError({
|
|
6428
6862
|
code: NOT_FOUND,
|
|
6429
6863
|
message: `not found: ${path}`,
|
|
6430
6864
|
hint: `check that ${path} and its folder exist`
|
|
@@ -6434,6 +6868,11 @@ function classifyErrno(error, context) {
|
|
|
6434
6868
|
message: `already exists: ${path}`,
|
|
6435
6869
|
hint: `pass another path, or remove ${path}`
|
|
6436
6870
|
}, { cause: error });
|
|
6871
|
+
case "EISDIR": return new CliError({
|
|
6872
|
+
code: NOT_A_FILE,
|
|
6873
|
+
message: `not a file: ${path} is a folder`,
|
|
6874
|
+
hint: `pass the path of a file, not of the folder ${path}`
|
|
6875
|
+
}, { cause: error });
|
|
6437
6876
|
default: return error;
|
|
6438
6877
|
}
|
|
6439
6878
|
}
|
|
@@ -6522,7 +6961,7 @@ var RunControl = class {
|
|
|
6522
6961
|
else outer?.addEventListener("abort", this.#onOuterAbort, { once: true });
|
|
6523
6962
|
if (runDeadline !== void 0) {
|
|
6524
6963
|
const { ms, hint } = runDeadline;
|
|
6525
|
-
after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, hint)));
|
|
6964
|
+
after(clock, ms, this.#disposed.signal, () => this.#controller.abort(deadlineError(commandLine, ms, hint())));
|
|
6526
6965
|
}
|
|
6527
6966
|
}
|
|
6528
6967
|
deadline(ms) {
|
|
@@ -6554,7 +6993,7 @@ var RunControl = class {
|
|
|
6554
6993
|
return value;
|
|
6555
6994
|
} finally {
|
|
6556
6995
|
await this.#stepsDone();
|
|
6557
|
-
this
|
|
6996
|
+
this.close();
|
|
6558
6997
|
}
|
|
6559
6998
|
}
|
|
6560
6999
|
/**
|
|
@@ -6583,13 +7022,192 @@ var RunControl = class {
|
|
|
6583
7022
|
async #stepsDone() {
|
|
6584
7023
|
while (this.#steps.size > 0) await Promise.all(this.#steps.keys());
|
|
6585
7024
|
}
|
|
6586
|
-
|
|
7025
|
+
/** Stops the run's timers and its hold on the caller's signal, for a run that never settles. */
|
|
7026
|
+
close() {
|
|
6587
7027
|
this.#graceCancel.abort();
|
|
6588
7028
|
this.#disposed.abort();
|
|
6589
7029
|
this.#outer?.removeEventListener("abort", this.#onOuterAbort);
|
|
6590
7030
|
}
|
|
6591
7031
|
};
|
|
6592
7032
|
//#endregion
|
|
7033
|
+
//#region ../agent-cli/dist/operator.js
|
|
7034
|
+
const NEEDS_OPERATOR = "NEEDS_OPERATOR";
|
|
7035
|
+
/** Fails with `NEEDS_OPERATOR` when a Coding agent runs an Operator Command. */
|
|
7036
|
+
function checkOperator({ operator, commandLine }, env, callerLine) {
|
|
7037
|
+
if (!operator || !isAgent(env)) return;
|
|
7038
|
+
throw new CliError({
|
|
7039
|
+
code: NEEDS_OPERATOR,
|
|
7040
|
+
message: `\`${commandLine}\` must be run by a person at a terminal, not a Coding agent`,
|
|
7041
|
+
hint: `ask the operator to type: ! ${callerLine}`
|
|
7042
|
+
});
|
|
7043
|
+
}
|
|
7044
|
+
//#endregion
|
|
7045
|
+
//#region ../agent-cli/dist/files.js
|
|
7046
|
+
const TEMP_FILE = /^\..+\.agent-cli-(\d+)\.tmp$/;
|
|
7047
|
+
/** Where the Runtime writes `path` before moving it into place; a dead writer's is swept. */
|
|
7048
|
+
function tempPathOf(path) {
|
|
7049
|
+
return join(dirname(path), `.${basename(path)}.agent-cli-${process.pid}.tmp`);
|
|
7050
|
+
}
|
|
7051
|
+
function isAlive(pid) {
|
|
7052
|
+
try {
|
|
7053
|
+
process.kill(pid, 0);
|
|
7054
|
+
return true;
|
|
7055
|
+
} catch (error) {
|
|
7056
|
+
return errnoOf(error) === "EPERM";
|
|
7057
|
+
}
|
|
7058
|
+
}
|
|
7059
|
+
async function sweep(folder) {
|
|
7060
|
+
const dead = (await readdir(folder)).filter((name) => {
|
|
7061
|
+
const pid = TEMP_FILE.exec(name)?.[1];
|
|
7062
|
+
return pid !== void 0 && !isAlive(Number(pid));
|
|
7063
|
+
});
|
|
7064
|
+
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
7065
|
+
}
|
|
7066
|
+
/** The file a symlink at `path` points to, so a replacing write keeps the link. */
|
|
7067
|
+
async function targetOf$1(path) {
|
|
7068
|
+
try {
|
|
7069
|
+
return await realpath(path);
|
|
7070
|
+
} catch (error) {
|
|
7071
|
+
if (isMissingFile(error)) return path;
|
|
7072
|
+
throw error;
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
async function writeWithMode(path, data, mode) {
|
|
7076
|
+
if (mode === void 0) {
|
|
7077
|
+
await writeFile(path, data);
|
|
7078
|
+
return;
|
|
7079
|
+
}
|
|
7080
|
+
await writeFile(path, data, { mode });
|
|
7081
|
+
await chmod(path, mode);
|
|
7082
|
+
}
|
|
7083
|
+
/** What `link` answers on a file system without hard links, such as exFAT. */
|
|
7084
|
+
const NO_HARD_LINKS = /* @__PURE__ */ new Set([
|
|
7085
|
+
"EPERM",
|
|
7086
|
+
"ENOTSUP",
|
|
7087
|
+
"EXDEV"
|
|
7088
|
+
]);
|
|
7089
|
+
/** A link fails atomically when `path` is taken, so no reader ever sees half a file there. */
|
|
7090
|
+
async function linkNew(temp, path, data, mode) {
|
|
7091
|
+
try {
|
|
7092
|
+
await link(temp, path);
|
|
7093
|
+
} catch (error) {
|
|
7094
|
+
if (!NO_HARD_LINKS.has(errnoOf(error))) throw error;
|
|
7095
|
+
await createNew(path, data, mode);
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
/** A file this call created and could not finish is removed, so a rerun does not find half of it. */
|
|
7099
|
+
async function createNew(path, data, mode) {
|
|
7100
|
+
const handle = await open(path, "wx", mode);
|
|
7101
|
+
try {
|
|
7102
|
+
await handle.writeFile(data);
|
|
7103
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
7104
|
+
} catch (error) {
|
|
7105
|
+
await handle.close();
|
|
7106
|
+
await rm(path, { force: true });
|
|
7107
|
+
throw error;
|
|
7108
|
+
}
|
|
7109
|
+
await handle.close();
|
|
7110
|
+
}
|
|
7111
|
+
const RENAME_ATTEMPTS = 10;
|
|
7112
|
+
const RENAME_BACKOFF_MS = 10;
|
|
7113
|
+
const HELD_BY_ANOTHER_PROGRAM = /* @__PURE__ */ new Set(["EPERM", "EBUSY"]);
|
|
7114
|
+
async function renameRetrying(from, to) {
|
|
7115
|
+
for (let attempt = 1;; attempt++) try {
|
|
7116
|
+
await rename(from, to);
|
|
7117
|
+
return;
|
|
7118
|
+
} catch (error) {
|
|
7119
|
+
if (!(process.platform === "win32" && HELD_BY_ANOTHER_PROGRAM.has(errnoOf(error))) || attempt === RENAME_ATTEMPTS) throw error;
|
|
7120
|
+
await setTimeout$1(attempt * RENAME_BACKOFF_MS);
|
|
7121
|
+
}
|
|
7122
|
+
}
|
|
7123
|
+
/** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
|
|
7124
|
+
function realFiles() {
|
|
7125
|
+
const swept = /* @__PURE__ */ new Set();
|
|
7126
|
+
return {
|
|
7127
|
+
exists: async (path) => {
|
|
7128
|
+
try {
|
|
7129
|
+
await access(path);
|
|
7130
|
+
return true;
|
|
7131
|
+
} catch (error) {
|
|
7132
|
+
if (isNothingThere(error)) return false;
|
|
7133
|
+
throw error;
|
|
7134
|
+
}
|
|
7135
|
+
},
|
|
7136
|
+
write: async (path, data, options = {}) => {
|
|
7137
|
+
await mkdir(dirname(path), { recursive: true });
|
|
7138
|
+
const target = options.exclusive === true ? path : await targetOf$1(path);
|
|
7139
|
+
const folder = dirname(target);
|
|
7140
|
+
if (!swept.has(folder)) {
|
|
7141
|
+
await sweep(folder);
|
|
7142
|
+
swept.add(folder);
|
|
7143
|
+
}
|
|
7144
|
+
const temp = tempPathOf(target);
|
|
7145
|
+
try {
|
|
7146
|
+
await writeWithMode(temp, data, options.mode);
|
|
7147
|
+
if (options.exclusive === true) {
|
|
7148
|
+
await linkNew(temp, target, data, options.mode);
|
|
7149
|
+
await rm(temp);
|
|
7150
|
+
} else await renameRetrying(temp, target);
|
|
7151
|
+
} catch (error) {
|
|
7152
|
+
await rm(temp, { force: true });
|
|
7153
|
+
throw error;
|
|
7154
|
+
}
|
|
7155
|
+
},
|
|
7156
|
+
read: (path) => readFile(path),
|
|
7157
|
+
list: (folder) => readdir(folder),
|
|
7158
|
+
remove: (path) => rm(path, {
|
|
7159
|
+
recursive: true,
|
|
7160
|
+
force: true
|
|
7161
|
+
}),
|
|
7162
|
+
mkdir: async (path, options) => {
|
|
7163
|
+
if (options?.exclusive !== true) {
|
|
7164
|
+
await mkdir(path, { recursive: true });
|
|
7165
|
+
return;
|
|
7166
|
+
}
|
|
7167
|
+
await mkdir(dirname(path), { recursive: true });
|
|
7168
|
+
await mkdir(path);
|
|
7169
|
+
},
|
|
7170
|
+
stat: async (path) => {
|
|
7171
|
+
try {
|
|
7172
|
+
const { size, dev, ino } = await stat(path, { bigint: true });
|
|
7173
|
+
return {
|
|
7174
|
+
size: Number(size),
|
|
7175
|
+
id: `${dev}:${ino}`
|
|
7176
|
+
};
|
|
7177
|
+
} catch (error) {
|
|
7178
|
+
if (isNothingThere(error)) return void 0;
|
|
7179
|
+
throw error;
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
};
|
|
7183
|
+
}
|
|
7184
|
+
/**
|
|
7185
|
+
* `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
|
|
7186
|
+
* Runtime's temp files and sorts.
|
|
7187
|
+
*/
|
|
7188
|
+
function classifiedFiles(files, { cwd, fileError }) {
|
|
7189
|
+
async function at(access, path, work) {
|
|
7190
|
+
const target = resolve(cwd, path);
|
|
7191
|
+
try {
|
|
7192
|
+
return await work(target);
|
|
7193
|
+
} catch (error) {
|
|
7194
|
+
throw fileError(error, {
|
|
7195
|
+
path: target,
|
|
7196
|
+
access
|
|
7197
|
+
});
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
return {
|
|
7201
|
+
exists: (path) => at("read", path, (target) => files.exists(target)),
|
|
7202
|
+
write: (path, data, options) => at("write", path, (target) => files.write(target, data, options)),
|
|
7203
|
+
read: (path) => at("read", path, (target) => files.read(target)),
|
|
7204
|
+
list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
|
|
7205
|
+
remove: (path) => at("write", path, (target) => files.remove(target)),
|
|
7206
|
+
mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options)),
|
|
7207
|
+
stat: (path) => at("read", path, (target) => files.stat(target))
|
|
7208
|
+
};
|
|
7209
|
+
}
|
|
7210
|
+
//#endregion
|
|
6593
7211
|
//#region ../agent-cli/dist/outputs.js
|
|
6594
7212
|
const OUTPUT_EXISTS = "OUTPUT_EXISTS";
|
|
6595
7213
|
/** Refuses a run that would overwrite existing outputs unless the caller passed `--force`. */
|
|
@@ -6602,9 +7220,62 @@ async function checkOutputs({ invocation, commandLine, force, files }, outputs)
|
|
|
6602
7220
|
throw new CliError({
|
|
6603
7221
|
code: OUTPUT_EXISTS,
|
|
6604
7222
|
message: `\`${commandLine}\` would overwrite ${existing.join(", ")}`,
|
|
6605
|
-
hint: `to replace ${them}, run \`${
|
|
7223
|
+
hint: `to replace ${them}, run \`${commandLineOf(invocation, { add: [FORCE_FLAG.flag] })}\``
|
|
6606
7224
|
});
|
|
6607
7225
|
}
|
|
7226
|
+
/** Guards the writes to declared outputs of `files`, whose paths resolve against `cwd`. */
|
|
7227
|
+
function outputsGuard(files, cwd) {
|
|
7228
|
+
const unwritten = /* @__PURE__ */ new Set();
|
|
7229
|
+
let forceLine = "";
|
|
7230
|
+
const exclusiveWrite = async (path, data, options) => {
|
|
7231
|
+
const target = resolve(cwd, path);
|
|
7232
|
+
unwritten.delete(target);
|
|
7233
|
+
try {
|
|
7234
|
+
await files.write(path, data, {
|
|
7235
|
+
...options,
|
|
7236
|
+
exclusive: true
|
|
7237
|
+
});
|
|
7238
|
+
} catch (error) {
|
|
7239
|
+
unwritten.add(target);
|
|
7240
|
+
if (!(error instanceof CliError) || error.code !== "ALREADY_EXISTS") throw error;
|
|
7241
|
+
throw new CliError({
|
|
7242
|
+
code: ALREADY_EXISTS,
|
|
7243
|
+
message: error.message,
|
|
7244
|
+
hint: `to replace it, run \`${forceLine}\``
|
|
7245
|
+
}, { cause: error });
|
|
7246
|
+
}
|
|
7247
|
+
};
|
|
7248
|
+
return {
|
|
7249
|
+
files: {
|
|
7250
|
+
...files,
|
|
7251
|
+
write: (path, data, options) => unwritten.has(resolve(cwd, path)) ? exclusiveWrite(path, data, options) : files.write(path, data, options)
|
|
7252
|
+
},
|
|
7253
|
+
guard: (outputs, line) => {
|
|
7254
|
+
forceLine = line;
|
|
7255
|
+
for (const path of outputs) unwritten.add(resolve(cwd, path));
|
|
7256
|
+
}
|
|
7257
|
+
};
|
|
7258
|
+
}
|
|
7259
|
+
/**
|
|
7260
|
+
* Creates each output folder and a temp file in it, so a sandbox denial fails before `apply`
|
|
7261
|
+
* spends anything. The temp file's name is the Runtime's, so a crashed run's leftover is swept.
|
|
7262
|
+
*/
|
|
7263
|
+
async function probeOutputs({ files, cwd, fileError }, outputs) {
|
|
7264
|
+
const folders = new Set(outputs.map((path) => dirname(resolve(cwd, path))));
|
|
7265
|
+
await Promise.all([...folders].map(async (folder) => {
|
|
7266
|
+
const probe = tempPathOf(join(folder, "outputs-probe"));
|
|
7267
|
+
try {
|
|
7268
|
+
await files.mkdir(folder);
|
|
7269
|
+
await files.write(probe, "");
|
|
7270
|
+
await files.remove(probe);
|
|
7271
|
+
} catch (error) {
|
|
7272
|
+
throw fileError(error, {
|
|
7273
|
+
path: folder,
|
|
7274
|
+
access: "write"
|
|
7275
|
+
});
|
|
7276
|
+
}
|
|
7277
|
+
}));
|
|
7278
|
+
}
|
|
6608
7279
|
//#endregion
|
|
6609
7280
|
//#region ../agent-cli/dist/project.js
|
|
6610
7281
|
const PROJECT_INVALID = "PROJECT_INVALID";
|
|
@@ -6652,146 +7323,6 @@ function projectOf(projectFile, cwd) {
|
|
|
6652
7323
|
return projectFrom(projectFile, resolve(cwd));
|
|
6653
7324
|
}
|
|
6654
7325
|
//#endregion
|
|
6655
|
-
//#region ../agent-cli/dist/tools.js
|
|
6656
|
-
const TOOL_MISSING = "TOOL_MISSING";
|
|
6657
|
-
const WINDOWS_EXTENSIONS = ".COM;.EXE;.BAT;.CMD";
|
|
6658
|
-
const NOT_EXECUTABLE = /* @__PURE__ */ new Set([
|
|
6659
|
-
"ENOENT",
|
|
6660
|
-
"ENOTDIR",
|
|
6661
|
-
"EACCES"
|
|
6662
|
-
]);
|
|
6663
|
-
function pathOf(env) {
|
|
6664
|
-
return env[Object.keys(env).find((name) => name.toUpperCase() === "PATH") ?? "PATH"] ?? "";
|
|
6665
|
-
}
|
|
6666
|
-
function extensionsOf(env) {
|
|
6667
|
-
return process.platform === "win32" ? (env.PATHEXT ?? WINDOWS_EXTENSIONS).split(";") : [""];
|
|
6668
|
-
}
|
|
6669
|
-
async function isExecutable(file) {
|
|
6670
|
-
try {
|
|
6671
|
-
await access(file, constants$1.X_OK);
|
|
6672
|
-
return true;
|
|
6673
|
-
} catch (error) {
|
|
6674
|
-
if (NOT_EXECUTABLE.has(errnoOf(error) ?? "")) return false;
|
|
6675
|
-
throw error;
|
|
6676
|
-
}
|
|
6677
|
-
}
|
|
6678
|
-
/** Where `name` is found on `PATH` from `env`, or undefined. */
|
|
6679
|
-
async function findTool(name, env) {
|
|
6680
|
-
const candidates = pathOf(env).split(delimiter).filter((folder) => folder !== "").flatMap((folder) => extensionsOf(env).map((extension) => join(folder, `${name}${extension}`)));
|
|
6681
|
-
for (const file of candidates) if (await isExecutable(file)) return file;
|
|
6682
|
-
}
|
|
6683
|
-
/** Fails with `TOOL_MISSING`, naming every missing tool at once, unless all are on `PATH`. */
|
|
6684
|
-
async function checkTools(tools, env) {
|
|
6685
|
-
const found = await Promise.all(tools.map((tool) => findTool(tool.name, env)));
|
|
6686
|
-
const missing = tools.filter((_tool, index) => found[index] === void 0);
|
|
6687
|
-
if (missing.length === 0) return;
|
|
6688
|
-
const names = missing.map(({ name }) => name).join(", ");
|
|
6689
|
-
throw new CliError({
|
|
6690
|
-
code: TOOL_MISSING,
|
|
6691
|
-
message: `${missing.length === 1 ? "a tool it runs is" : "tools it runs are"} not on PATH: ${names}`,
|
|
6692
|
-
hint: `install ${missing.length === 1 ? "it" : "them"}, then run the same command again: ${missing.map(({ install }) => install).join("; ")}`
|
|
6693
|
-
});
|
|
6694
|
-
}
|
|
6695
|
-
function toolsIn(node) {
|
|
6696
|
-
return isCommand(node) ? [...node.tools ?? []] : Object.values(node).flatMap(toolsIn);
|
|
6697
|
-
}
|
|
6698
|
-
/** One `doctor` row per distinct tool the Commands declare, with its install command as the fix. */
|
|
6699
|
-
function toolRows(commands) {
|
|
6700
|
-
return [...new Map(toolsIn(commands).map((tool) => [tool.name, tool])).values()].map(({ name, install }) => ({
|
|
6701
|
-
name,
|
|
6702
|
-
run: async (ctx) => {
|
|
6703
|
-
const file = await findTool(name, ctx.env);
|
|
6704
|
-
return file === void 0 ? {
|
|
6705
|
-
state: "problem",
|
|
6706
|
-
message: `${name} is not on PATH`,
|
|
6707
|
-
fix: install
|
|
6708
|
-
} : {
|
|
6709
|
-
state: "ok",
|
|
6710
|
-
message: `found at ${file}`
|
|
6711
|
-
};
|
|
6712
|
-
}
|
|
6713
|
-
}));
|
|
6714
|
-
}
|
|
6715
|
-
//#endregion
|
|
6716
|
-
//#region ../agent-cli/dist/spawn.js
|
|
6717
|
-
const TOOL_FAILED = "TOOL_FAILED";
|
|
6718
|
-
const STDERR_TAIL_LINES = 20;
|
|
6719
|
-
const SIGNAL_EXIT_BASE = 128;
|
|
6720
|
-
const realSpawn = (command, args, { cwd, env, signal }) => new Promise((resolve, reject) => {
|
|
6721
|
-
signal.throwIfAborted();
|
|
6722
|
-
const child = spawn(command, args, {
|
|
6723
|
-
cwd,
|
|
6724
|
-
env,
|
|
6725
|
-
stdio: [
|
|
6726
|
-
"ignore",
|
|
6727
|
-
"pipe",
|
|
6728
|
-
"pipe"
|
|
6729
|
-
]
|
|
6730
|
-
});
|
|
6731
|
-
const stdout = [];
|
|
6732
|
-
const stderr = [];
|
|
6733
|
-
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
6734
|
-
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
6735
|
-
const kill = () => {
|
|
6736
|
-
child.kill();
|
|
6737
|
-
};
|
|
6738
|
-
signal.addEventListener("abort", kill, { once: true });
|
|
6739
|
-
child.on("error", (error) => {
|
|
6740
|
-
signal.removeEventListener("abort", kill);
|
|
6741
|
-
reject(error);
|
|
6742
|
-
});
|
|
6743
|
-
child.on("close", (code, killedBy) => {
|
|
6744
|
-
signal.removeEventListener("abort", kill);
|
|
6745
|
-
if (signal.aborted) {
|
|
6746
|
-
reject(signal.reason);
|
|
6747
|
-
return;
|
|
6748
|
-
}
|
|
6749
|
-
resolve({
|
|
6750
|
-
stdout: Buffer.concat(stdout).toString(),
|
|
6751
|
-
stderr: Buffer.concat(stderr).toString(),
|
|
6752
|
-
exitCode: code ?? SIGNAL_EXIT_BASE + (killedBy === null ? 0 : constants.signals[killedBy])
|
|
6753
|
-
});
|
|
6754
|
-
});
|
|
6755
|
-
});
|
|
6756
|
-
function failedError(command, { exitCode, stderr }) {
|
|
6757
|
-
const tail = lastLines(stderr, STDERR_TAIL_LINES);
|
|
6758
|
-
return new CliError({
|
|
6759
|
-
code: TOOL_FAILED,
|
|
6760
|
-
message: tail === "" ? `${command} exited with ${exitCode} and wrote nothing to stderr` : `${command} exited with ${exitCode}; its stderr ends:\n${tail}`,
|
|
6761
|
-
hint: `the output of ${command} above says why it failed`
|
|
6762
|
-
});
|
|
6763
|
-
}
|
|
6764
|
-
function missingError(command, cause) {
|
|
6765
|
-
return new CliError({
|
|
6766
|
-
code: TOOL_MISSING,
|
|
6767
|
-
message: `${command} could not be started: it is not on PATH`,
|
|
6768
|
-
hint: `install ${command}, then run the same command again`
|
|
6769
|
-
}, { cause });
|
|
6770
|
-
}
|
|
6771
|
-
/** `ctx.spawn`: `spawn` bound to the run, its start failures classified by errno. */
|
|
6772
|
-
function contextSpawn(run, defaults, callerLine) {
|
|
6773
|
-
return async (command, args = [], options = {}) => {
|
|
6774
|
-
let result;
|
|
6775
|
-
try {
|
|
6776
|
-
result = await run(command, args, {
|
|
6777
|
-
cwd: options.cwd ?? defaults.cwd,
|
|
6778
|
-
env: options.env ?? defaults.env,
|
|
6779
|
-
signal: defaults.signal
|
|
6780
|
-
});
|
|
6781
|
-
} catch (error) {
|
|
6782
|
-
if (isMissingFile(error)) throw missingError(command, error);
|
|
6783
|
-
throw classifyErrno(error, {
|
|
6784
|
-
path: command,
|
|
6785
|
-
access: "read",
|
|
6786
|
-
callerLine,
|
|
6787
|
-
env: defaults.env
|
|
6788
|
-
});
|
|
6789
|
-
}
|
|
6790
|
-
if (options.check === true && result.exitCode !== 0) throw failedError(command, result);
|
|
6791
|
-
return result;
|
|
6792
|
-
};
|
|
6793
|
-
}
|
|
6794
|
-
//#endregion
|
|
6795
7326
|
//#region ../agent-cli/dist/exit-classes.js
|
|
6796
7327
|
const SIGNAL_EXIT_CODES = {
|
|
6797
7328
|
SIGINT: 130,
|
|
@@ -6814,6 +7345,7 @@ function runtimeCodes() {
|
|
|
6814
7345
|
[CONFIRMATION_REQUIRED]: "caller",
|
|
6815
7346
|
[CONFIRMATION_MISMATCH]: "caller",
|
|
6816
7347
|
[NOT_CONFIRMED]: "operator",
|
|
7348
|
+
[NEEDS_OPERATOR]: "operator",
|
|
6817
7349
|
[OUTPUT_EXISTS]: "caller",
|
|
6818
7350
|
[INTERRUPTED]: "interrupted",
|
|
6819
7351
|
[DEADLINE_EXCEEDED]: "wait",
|
|
@@ -6822,6 +7354,7 @@ function runtimeCodes() {
|
|
|
6822
7354
|
[DISK_FULL]: "operator",
|
|
6823
7355
|
[NOT_FOUND]: "caller",
|
|
6824
7356
|
[ALREADY_EXISTS]: "caller",
|
|
7357
|
+
[NOT_A_FILE]: "caller",
|
|
6825
7358
|
[TOOL_FAILED]: "operator",
|
|
6826
7359
|
[TOOL_MISSING]: "operator",
|
|
6827
7360
|
[PROJECT_INVALID]: "operator",
|
|
@@ -6885,6 +7418,53 @@ function exitCodeOf(error, errors, stop) {
|
|
|
6885
7418
|
return exitClass === "interrupted" ? SIGNAL_EXIT_CODES[stopSignalOf(stop?.reason)] : EXIT_CODES[exitClass];
|
|
6886
7419
|
}
|
|
6887
7420
|
//#endregion
|
|
7421
|
+
//#region ../agent-cli/dist/command-spec.js
|
|
7422
|
+
/** The group's Commands and groups that help and suggestions may name: hidden Commands left out. */
|
|
7423
|
+
function visibleCommands({ commands }) {
|
|
7424
|
+
return Object.fromEntries(Object.entries(commands).filter(([, node]) => node.kind === "group" || !node.hidden));
|
|
7425
|
+
}
|
|
7426
|
+
function declaredOf(command) {
|
|
7427
|
+
if (!hasPlan(command)) return {
|
|
7428
|
+
outputs: false,
|
|
7429
|
+
force: void 0,
|
|
7430
|
+
cost: false
|
|
7431
|
+
};
|
|
7432
|
+
return {
|
|
7433
|
+
outputs: command.outputs !== void 0,
|
|
7434
|
+
force: command.force,
|
|
7435
|
+
cost: command.cost !== void 0
|
|
7436
|
+
};
|
|
7437
|
+
}
|
|
7438
|
+
function commandSpec(commandLine, command) {
|
|
7439
|
+
const list = command.rows === void 0 ? void 0 : listSpec(command.rows);
|
|
7440
|
+
return {
|
|
7441
|
+
kind: "command",
|
|
7442
|
+
commandLine,
|
|
7443
|
+
command,
|
|
7444
|
+
description: command.description,
|
|
7445
|
+
help: command.help,
|
|
7446
|
+
hidden: command.hidden === true,
|
|
7447
|
+
operator: command.operator === true,
|
|
7448
|
+
effect: command.effect,
|
|
7449
|
+
examples: command.examples,
|
|
7450
|
+
args: argFields(command.args),
|
|
7451
|
+
options: optionFields(command.options),
|
|
7452
|
+
standardFlags: standardFlagsFor(command.effect, declaredOf(command), list),
|
|
7453
|
+
list,
|
|
7454
|
+
output: list?.output ?? command.output
|
|
7455
|
+
};
|
|
7456
|
+
}
|
|
7457
|
+
function groupSpec(commandLine, commands) {
|
|
7458
|
+
return {
|
|
7459
|
+
kind: "group",
|
|
7460
|
+
commandLine,
|
|
7461
|
+
commands: Object.fromEntries(Object.entries(commands).map(([key, node]) => [key, specOf(`${commandLine} ${key}`, node)]))
|
|
7462
|
+
};
|
|
7463
|
+
}
|
|
7464
|
+
function specOf(commandLine, node) {
|
|
7465
|
+
return isCommand(node) ? commandSpec(commandLine, node) : groupSpec(commandLine, node);
|
|
7466
|
+
}
|
|
7467
|
+
//#endregion
|
|
6888
7468
|
//#region ../agent-cli/dist/help.js
|
|
6889
7469
|
function table(rows) {
|
|
6890
7470
|
const width = Math.max(...rows.map(([left]) => left.length));
|
|
@@ -6898,10 +7478,11 @@ function section(title, rows, style) {
|
|
|
6898
7478
|
];
|
|
6899
7479
|
}
|
|
6900
7480
|
function summaryOf(node) {
|
|
6901
|
-
|
|
7481
|
+
if (node.kind === "group") return `group: ${Object.keys(visibleCommands(node)).join(", ")}`;
|
|
7482
|
+
return node.operator ? `${node.description} (operator only)` : node.description;
|
|
6902
7483
|
}
|
|
6903
|
-
function commandRows(
|
|
6904
|
-
return Object.entries(
|
|
7484
|
+
function commandRows(group) {
|
|
7485
|
+
return Object.entries(visibleCommands(group)).map(([key, node]) => [key, summaryOf(node)]);
|
|
6905
7486
|
}
|
|
6906
7487
|
function lines(...parts) {
|
|
6907
7488
|
return `${parts.join("\n")}\n`;
|
|
@@ -6929,11 +7510,11 @@ function exitCodeRows(cli) {
|
|
|
6929
7510
|
}
|
|
6930
7511
|
function firstExample(node) {
|
|
6931
7512
|
if (node.kind === "command") return node.examples.slice(0, 1);
|
|
6932
|
-
return Object.values(node
|
|
7513
|
+
return Object.values(visibleCommands(node)).flatMap(firstExample).slice(0, 1);
|
|
6933
7514
|
}
|
|
6934
7515
|
function rootExamples(root) {
|
|
6935
7516
|
if (root.kind === "command") return [...root.examples];
|
|
6936
|
-
const { doctor, ...others } = root
|
|
7517
|
+
const { doctor, ...others } = visibleCommands(root);
|
|
6937
7518
|
return [...Object.values(others), ...doctor === void 0 ? [] : [doctor]].flatMap(firstExample);
|
|
6938
7519
|
}
|
|
6939
7520
|
function examplesSection(examples, style) {
|
|
@@ -6952,6 +7533,7 @@ const EFFECT_NOTES = {
|
|
|
6952
7533
|
destructive: "changes something hard to undo; show the Plan with --dry-run, run with --confirm",
|
|
6953
7534
|
serve: "changes nothing; prints its data at once, then runs until Ctrl-C, which exits 0"
|
|
6954
7535
|
};
|
|
7536
|
+
const OPERATOR_NOTE = "only a person at a terminal runs it; a Coding agent asks the operator to type it after `!`";
|
|
6955
7537
|
function choicesOf(field) {
|
|
6956
7538
|
return field.choices === void 0 ? [] : [`one of: ${field.choices.join(", ")}`];
|
|
6957
7539
|
}
|
|
@@ -6981,8 +7563,10 @@ function commandLines(spec, style, { examples }) {
|
|
|
6981
7563
|
...examples ? examplesSection(spec.examples, style) : [],
|
|
6982
7564
|
...section("Arguments", spec.args.map(argRow), style),
|
|
6983
7565
|
...section("Options", spec.options.map(optionRow), style),
|
|
7566
|
+
...spec.help === void 0 ? [] : ["", spec.help],
|
|
6984
7567
|
"",
|
|
6985
7568
|
`${style.bold("Effect:")} ${spec.effect} (${EFFECT_NOTES[spec.effect]})`,
|
|
7569
|
+
...spec.operator ? [`${style.bold("Operator Command:")} ${OPERATOR_NOTE}`] : [],
|
|
6986
7570
|
...section("Standard flags", spec.standardFlags.map(flagRow), style)
|
|
6987
7571
|
];
|
|
6988
7572
|
}
|
|
@@ -7004,7 +7588,9 @@ function commandData(spec) {
|
|
|
7004
7588
|
return {
|
|
7005
7589
|
name: spec.commandLine,
|
|
7006
7590
|
description: spec.description,
|
|
7591
|
+
help: spec.help ?? null,
|
|
7007
7592
|
effect: spec.effect,
|
|
7593
|
+
operator: spec.operator,
|
|
7008
7594
|
examples: spec.examples,
|
|
7009
7595
|
args: inputSchema(spec.command.args ?? object({})),
|
|
7010
7596
|
options: optionsSchema(spec.options),
|
|
@@ -7022,7 +7608,7 @@ function nodeData(node) {
|
|
|
7022
7608
|
function groupData(group) {
|
|
7023
7609
|
return {
|
|
7024
7610
|
name: group.commandLine,
|
|
7025
|
-
commands: Object.values(group
|
|
7611
|
+
commands: Object.values(visibleCommands(group)).map(nodeData)
|
|
7026
7612
|
};
|
|
7027
7613
|
}
|
|
7028
7614
|
function printed$1(data) {
|
|
@@ -7064,6 +7650,10 @@ function printed(value, render, { json, invocation, style, fields }) {
|
|
|
7064
7650
|
items: rows
|
|
7065
7651
|
})}\n` : `${renderRows(rows, fields)}${pageFooter(page)}`;
|
|
7066
7652
|
}
|
|
7653
|
+
/** A progress message as stderr carries it: one JSON object per line under `--json` (ADR 0006). */
|
|
7654
|
+
function progressLine(message, json) {
|
|
7655
|
+
return json ? `${JSON.stringify({ progress: message })}\n` : `${message}\n`;
|
|
7656
|
+
}
|
|
7067
7657
|
function errorText(error, format) {
|
|
7068
7658
|
const text = formatRuntimeError(error, {
|
|
7069
7659
|
...format,
|
|
@@ -7150,12 +7740,51 @@ function versionResult(cli, packageRoot) {
|
|
|
7150
7740
|
};
|
|
7151
7741
|
}
|
|
7152
7742
|
//#endregion
|
|
7743
|
+
//#region ../agent-cli/dist/skill-source.js
|
|
7744
|
+
/** The branch a release moves to the released commit, which holds the skill of that version. */
|
|
7745
|
+
const RELEASE_BRANCH = "release";
|
|
7746
|
+
const GITHUB_SHORTHAND = /^[\w.-]+\/[\w.-]+$/;
|
|
7747
|
+
const GIT_URL_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
7748
|
+
"https:",
|
|
7749
|
+
"http:",
|
|
7750
|
+
"ssh:",
|
|
7751
|
+
"git:"
|
|
7752
|
+
]);
|
|
7753
|
+
const SCP_STYLE = /^[\w.-]+@[\w.-]+:[\w./-]+$/;
|
|
7754
|
+
const manifestSchema = object({ repository: union([string(), object({ url: string() })]).optional() });
|
|
7755
|
+
function sourceFromRepository(repository) {
|
|
7756
|
+
if (GITHUB_SHORTHAND.test(repository)) return repository;
|
|
7757
|
+
const url = repository.replace(/^git\+/, "");
|
|
7758
|
+
if (!(URL.canParse(url) && GIT_URL_PROTOCOLS.has(new URL(url).protocol)) && !SCP_STYLE.test(url)) return void 0;
|
|
7759
|
+
return `${url.endsWith(".git") ? url : `${url}.git`}#${RELEASE_BRANCH}`;
|
|
7760
|
+
}
|
|
7761
|
+
async function repositoryAt(packageRoot) {
|
|
7762
|
+
let manifest;
|
|
7763
|
+
try {
|
|
7764
|
+
manifest = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
7765
|
+
} catch {
|
|
7766
|
+
return;
|
|
7767
|
+
}
|
|
7768
|
+
const parsed = manifestSchema.safeParse(manifest);
|
|
7769
|
+
const repository = parsed.success ? parsed.data.repository : void 0;
|
|
7770
|
+
return typeof repository === "object" ? repository.url : repository;
|
|
7771
|
+
}
|
|
7772
|
+
/** What `npx skills add` installs the CLI's skill from: the declared source, else the package's repository. */
|
|
7773
|
+
async function skillSource(declared, packageRoot) {
|
|
7774
|
+
if (declared !== void 0) return declared.source;
|
|
7775
|
+
const repository = await repositoryAt(packageRoot);
|
|
7776
|
+
return repository === void 0 ? void 0 : sourceFromRepository(repository);
|
|
7777
|
+
}
|
|
7778
|
+
//#endregion
|
|
7153
7779
|
//#region ../agent-cli/dist/skills.js
|
|
7154
7780
|
/** Where the skills CLI (`npx skills add`) puts a skill, under a project and under home. */
|
|
7155
7781
|
const SKILL_DIRS = [[".claude", "skills"], [".agents", "skills"]];
|
|
7156
7782
|
function skillUpdateCommand(cliName) {
|
|
7157
7783
|
return `npx -y skills update ${cliName}`;
|
|
7158
7784
|
}
|
|
7785
|
+
function skillAddCommand(source, cliName) {
|
|
7786
|
+
return `npx -y skills add ${source} --skill ${cliName} -g`;
|
|
7787
|
+
}
|
|
7159
7788
|
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
7160
7789
|
const METADATA_VERSION = /^metadata:[ \t]*\r?\n(?:[ \t]+.*\r?\n)*?[ \t]+version:[ \t]*["']?([^"'\s]+)["']?[ \t]*$/m;
|
|
7161
7790
|
/** The version a SKILL.md's frontmatter `metadata.version` states. */
|
|
@@ -7260,7 +7889,7 @@ function projectRow(projectFile) {
|
|
|
7260
7889
|
}
|
|
7261
7890
|
};
|
|
7262
7891
|
}
|
|
7263
|
-
function installRow(cli, bin) {
|
|
7892
|
+
function installRow(cli, { bin, node }) {
|
|
7264
7893
|
return {
|
|
7265
7894
|
name: "install",
|
|
7266
7895
|
advisory: true,
|
|
@@ -7268,7 +7897,7 @@ function installRow(cli, bin) {
|
|
|
7268
7897
|
const { install } = cli;
|
|
7269
7898
|
if (install === void 0) return ok(`${cli.name} declares no install, so it has no \`update\``);
|
|
7270
7899
|
try {
|
|
7271
|
-
return ok(`a global npm install under ${(await globalInstallOf(bin, npmContext(cli, install, ctx))).prefix}`);
|
|
7900
|
+
return ok(`a global npm install under ${(await globalInstallOf(bin, npmContext(cli, install, ctx, node))).prefix}`);
|
|
7272
7901
|
} catch (error) {
|
|
7273
7902
|
if (error instanceof CliError && error.code === "NOT_GLOBAL_INSTALL") return {
|
|
7274
7903
|
state: "problem",
|
|
@@ -7280,14 +7909,14 @@ function installRow(cli, bin) {
|
|
|
7280
7909
|
}
|
|
7281
7910
|
};
|
|
7282
7911
|
}
|
|
7283
|
-
function registryRow(cli) {
|
|
7912
|
+
function registryRow(cli, node) {
|
|
7284
7913
|
return {
|
|
7285
7914
|
name: "registry",
|
|
7286
7915
|
advisory: true,
|
|
7287
7916
|
run: async (ctx) => {
|
|
7288
7917
|
const { install } = cli;
|
|
7289
7918
|
if (install === void 0) return ok(`${cli.name} declares no registry to compare this build with`);
|
|
7290
|
-
const latest = await latestVersion(npmContext(cli, install, ctx));
|
|
7919
|
+
const latest = await latestVersion(npmContext(cli, install, ctx, node));
|
|
7291
7920
|
const standing = standingOf(cli.version, latest);
|
|
7292
7921
|
const head = `${install.registry} has ${latest} as latest: this build`;
|
|
7293
7922
|
if (standing === "behind") return {
|
|
@@ -7318,7 +7947,15 @@ function skillRow(cli) {
|
|
|
7318
7947
|
name: "skill",
|
|
7319
7948
|
run: async (ctx) => {
|
|
7320
7949
|
const copies = await skillCopies(cli.name, cli.version, ctx.cwd, ctx.env);
|
|
7321
|
-
if (copies.length === 0)
|
|
7950
|
+
if (copies.length === 0) {
|
|
7951
|
+
const message = `no copy of the ${cli.name} skill is installed`;
|
|
7952
|
+
const source = await skillSource(cli.skill, ctx.packageRoot);
|
|
7953
|
+
return source === void 0 ? ok(message) : {
|
|
7954
|
+
state: "ok",
|
|
7955
|
+
message,
|
|
7956
|
+
fix: skillAddCommand(source, cli.name)
|
|
7957
|
+
};
|
|
7958
|
+
}
|
|
7322
7959
|
const fix = skillFix(cli, copies);
|
|
7323
7960
|
const message = copies.map(describeCopy).join("; ");
|
|
7324
7961
|
return fix === void 0 ? ok(message) : {
|
|
@@ -7330,15 +7967,15 @@ function skillRow(cli) {
|
|
|
7330
7967
|
};
|
|
7331
7968
|
}
|
|
7332
7969
|
/** Every row `doctor` reports, in order: the fixed rows around the CLI's own and its tools'. */
|
|
7333
|
-
function doctorChecks(cli,
|
|
7970
|
+
function doctorChecks(cli, launch) {
|
|
7334
7971
|
return [
|
|
7335
7972
|
buildRow(cli),
|
|
7336
7973
|
nodeRow(cli),
|
|
7337
7974
|
...cli.project === void 0 ? [] : [projectRow(cli.project)],
|
|
7338
7975
|
...cli.doctor ?? [],
|
|
7339
|
-
...toolRows(cli.commands),
|
|
7340
|
-
installRow(cli,
|
|
7341
|
-
registryRow(cli),
|
|
7976
|
+
...toolRows(cli.tools ?? [], cli.commands),
|
|
7977
|
+
installRow(cli, launch),
|
|
7978
|
+
registryRow(cli, launch.node),
|
|
7342
7979
|
skillRow(cli)
|
|
7343
7980
|
];
|
|
7344
7981
|
}
|
|
@@ -7351,9 +7988,14 @@ const MIN_LABEL_WIDTH = 8;
|
|
|
7351
7988
|
const reportSchema = object({
|
|
7352
7989
|
rows: array(object({
|
|
7353
7990
|
check: string(),
|
|
7354
|
-
state: _enum([
|
|
7991
|
+
state: _enum([
|
|
7992
|
+
"ok",
|
|
7993
|
+
"problem",
|
|
7994
|
+
"skipped"
|
|
7995
|
+
]),
|
|
7355
7996
|
message: string(),
|
|
7356
|
-
fix: string().optional()
|
|
7997
|
+
fix: string().optional(),
|
|
7998
|
+
code: string().optional()
|
|
7357
7999
|
})),
|
|
7358
8000
|
next: array(string())
|
|
7359
8001
|
});
|
|
@@ -7364,7 +8006,8 @@ function problemOf(check, error, { signal, deadline, ms }) {
|
|
|
7364
8006
|
};
|
|
7365
8007
|
if (error instanceof CliError) return {
|
|
7366
8008
|
state: "problem",
|
|
7367
|
-
message: `${error.message}; ${error.hint}
|
|
8009
|
+
message: `${error.message}; ${error.hint}`,
|
|
8010
|
+
code: error.code
|
|
7368
8011
|
};
|
|
7369
8012
|
return {
|
|
7370
8013
|
state: "problem",
|
|
@@ -7381,6 +8024,7 @@ async function rowOf(check, ctx) {
|
|
|
7381
8024
|
try {
|
|
7382
8025
|
finding = await Promise.race([Promise.resolve().then(() => check.run(withSignal(ctx, signal))), untilAborted(signal)]);
|
|
7383
8026
|
} catch (error) {
|
|
8027
|
+
if (ctx.signal.aborted) return void 0;
|
|
7384
8028
|
finding = problemOf(check, error, {
|
|
7385
8029
|
signal: ctx.signal,
|
|
7386
8030
|
deadline: signal,
|
|
@@ -7406,15 +8050,16 @@ function render({ rows, next }, style) {
|
|
|
7406
8050
|
return `${check.padEnd(width)}${label}${" ".repeat(stateWidth - state.length)}${message}`;
|
|
7407
8051
|
}), ...next.map((fix) => `${"next".padEnd(width)}${fix}`)].join("\n");
|
|
7408
8052
|
}
|
|
7409
|
-
function failure(cliName, problems
|
|
7410
|
-
|
|
7411
|
-
code: DOCTOR_PROBLEMS,
|
|
7412
|
-
message: `${cliName} doctor found
|
|
7413
|
-
hint:
|
|
7414
|
-
});
|
|
8053
|
+
function failure(cliName, problems) {
|
|
8054
|
+
const errors = problems.map(({ check, message, fix, code }) => new CliError({
|
|
8055
|
+
code: code ?? "DOCTOR_PROBLEMS",
|
|
8056
|
+
message: `${cliName} doctor found a problem in ${check}: ${message}`,
|
|
8057
|
+
hint: fix === void 0 ? "the row says what is wrong" : `run ${fix}, then run doctor again`
|
|
8058
|
+
}));
|
|
8059
|
+
return new AggregateError(errors);
|
|
7415
8060
|
}
|
|
7416
8061
|
/** The built-in `doctor`: the fixed rows around the CLI's own, each under its own deadline. */
|
|
7417
|
-
function doctorCommand(cli,
|
|
8062
|
+
function doctorCommand(cli, launch) {
|
|
7418
8063
|
return defineCommand$1({
|
|
7419
8064
|
name: "doctor",
|
|
7420
8065
|
description: "check the setup and say what fixes each problem",
|
|
@@ -7428,17 +8073,20 @@ function doctorCommand(cli, bin) {
|
|
|
7428
8073
|
effect: "read",
|
|
7429
8074
|
output: reportSchema,
|
|
7430
8075
|
run: async (_input, ctx) => {
|
|
7431
|
-
const checked = await Promise.all(doctorChecks(cli,
|
|
7432
|
-
ctx.signal.throwIfAborted();
|
|
8076
|
+
const checked = (await Promise.all(doctorChecks(cli, launch).map((check) => rowOf(check, ctx)))).filter((row) => row !== void 0);
|
|
7433
8077
|
const rows = checked.map(({ row }) => row);
|
|
7434
8078
|
const report = {
|
|
7435
8079
|
rows,
|
|
7436
8080
|
next: nextOf(rows)
|
|
7437
8081
|
};
|
|
7438
|
-
|
|
8082
|
+
if (ctx.signal.aborted) {
|
|
8083
|
+
ctx.deliver(report);
|
|
8084
|
+
ctx.signal.throwIfAborted();
|
|
8085
|
+
}
|
|
8086
|
+
const problems = checked.filter(({ row, advisory }) => row.state === "problem" && !advisory).map(({ row }) => row);
|
|
7439
8087
|
if (problems.length > 0) {
|
|
7440
8088
|
ctx.deliver(report);
|
|
7441
|
-
throw failure(cli.name, problems
|
|
8089
|
+
throw failure(cli.name, problems);
|
|
7442
8090
|
}
|
|
7443
8091
|
return report;
|
|
7444
8092
|
},
|
|
@@ -7517,7 +8165,7 @@ function interruptedAfterInstall(plan) {
|
|
|
7517
8165
|
});
|
|
7518
8166
|
}
|
|
7519
8167
|
/** The built-in `update`, for a CLI installed globally with npm. */
|
|
7520
|
-
function updateCommand(cli, install, bin) {
|
|
8168
|
+
function updateCommand(cli, install, { bin, node }) {
|
|
7521
8169
|
return defineCommand$1({
|
|
7522
8170
|
name: "update",
|
|
7523
8171
|
description: "install the latest version of this CLI from its registry",
|
|
@@ -7531,7 +8179,7 @@ function updateCommand(cli, install, bin) {
|
|
|
7531
8179
|
effect: "write",
|
|
7532
8180
|
output: updateSchema,
|
|
7533
8181
|
plan: async (_input, ctx) => {
|
|
7534
|
-
const npm = npmContext(cli, install, ctx);
|
|
8182
|
+
const npm = npmContext(cli, install, ctx, node);
|
|
7535
8183
|
const copy = await globalInstallOf(bin, npm);
|
|
7536
8184
|
return {
|
|
7537
8185
|
package: install.package,
|
|
@@ -7552,7 +8200,7 @@ function updateCommand(cli, install, bin) {
|
|
|
7552
8200
|
};
|
|
7553
8201
|
ctx.signal.throwIfAborted();
|
|
7554
8202
|
ctx.progress(`installing ${plan.package}@${latest} with the npm under ${prefix}…`);
|
|
7555
|
-
await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, npmContext(cli, install, ctx)));
|
|
8203
|
+
await ctx.runToEnd(`installing ${plan.package}@${latest}`, () => installGlobally(latest, npmContext(cli, install, ctx, node)));
|
|
7556
8204
|
const update = {
|
|
7557
8205
|
from,
|
|
7558
8206
|
latest,
|
|
@@ -7576,10 +8224,10 @@ function updateCommand(cli, install, bin) {
|
|
|
7576
8224
|
//#endregion
|
|
7577
8225
|
//#region ../agent-cli/dist/built-ins.js
|
|
7578
8226
|
/** The Commands the Runtime adds to a multi-Command CLI: `update` only when it declares an install. */
|
|
7579
|
-
function builtInCommands(cli,
|
|
8227
|
+
function builtInCommands(cli, launch) {
|
|
7580
8228
|
return {
|
|
7581
|
-
doctor: doctorCommand(cli,
|
|
7582
|
-
...cli.install === void 0 ? {} : { update: updateCommand(cli, cli.install,
|
|
8229
|
+
doctor: doctorCommand(cli, launch),
|
|
8230
|
+
...cli.install === void 0 ? {} : { update: updateCommand(cli, cli.install, launch) }
|
|
7583
8231
|
};
|
|
7584
8232
|
}
|
|
7585
8233
|
//#endregion
|
|
@@ -7592,7 +8240,10 @@ function defineCli$1(cli) {
|
|
|
7592
8240
|
checkOwnCodes(cli);
|
|
7593
8241
|
if (cli.commands === void 0) return cli;
|
|
7594
8242
|
const { commands } = cli;
|
|
7595
|
-
const taken = Object.keys(builtInCommands(cli,
|
|
8243
|
+
const taken = Object.keys(builtInCommands(cli, {
|
|
8244
|
+
bin: void 0,
|
|
8245
|
+
node: void 0
|
|
8246
|
+
})).filter((name) => commands[name] !== void 0);
|
|
7596
8247
|
if (taken.length > 0) throw new Error(`${cli.name} defines ${taken.join(", ")}, a built-in Command it cannot replace`);
|
|
7597
8248
|
return cli;
|
|
7598
8249
|
}
|
|
@@ -7620,46 +8271,6 @@ function cliError(_table, code, init, options) {
|
|
|
7620
8271
|
}, options);
|
|
7621
8272
|
}
|
|
7622
8273
|
//#endregion
|
|
7623
|
-
//#region ../agent-cli/dist/command-spec.js
|
|
7624
|
-
function declaredOf(command) {
|
|
7625
|
-
if (!hasPlan(command)) return {
|
|
7626
|
-
outputs: false,
|
|
7627
|
-
force: void 0,
|
|
7628
|
-
cost: false
|
|
7629
|
-
};
|
|
7630
|
-
return {
|
|
7631
|
-
outputs: command.outputs !== void 0,
|
|
7632
|
-
force: command.force,
|
|
7633
|
-
cost: command.cost !== void 0
|
|
7634
|
-
};
|
|
7635
|
-
}
|
|
7636
|
-
function commandSpec(commandLine, command) {
|
|
7637
|
-
const list = command.rows === void 0 ? void 0 : listSpec(command.rows);
|
|
7638
|
-
return {
|
|
7639
|
-
kind: "command",
|
|
7640
|
-
commandLine,
|
|
7641
|
-
command,
|
|
7642
|
-
description: command.description,
|
|
7643
|
-
effect: command.effect,
|
|
7644
|
-
examples: command.examples,
|
|
7645
|
-
args: argFields(command.args),
|
|
7646
|
-
options: optionFields(command.options),
|
|
7647
|
-
standardFlags: standardFlagsFor(command.effect, declaredOf(command), list),
|
|
7648
|
-
list,
|
|
7649
|
-
output: list?.output ?? command.output
|
|
7650
|
-
};
|
|
7651
|
-
}
|
|
7652
|
-
function groupSpec(commandLine, commands) {
|
|
7653
|
-
return {
|
|
7654
|
-
kind: "group",
|
|
7655
|
-
commandLine,
|
|
7656
|
-
commands: Object.fromEntries(Object.entries(commands).map(([key, node]) => [key, specOf(`${commandLine} ${key}`, node)]))
|
|
7657
|
-
};
|
|
7658
|
-
}
|
|
7659
|
-
function specOf(commandLine, node) {
|
|
7660
|
-
return isCommand(node) ? commandSpec(commandLine, node) : groupSpec(commandLine, node);
|
|
7661
|
-
}
|
|
7662
|
-
//#endregion
|
|
7663
8274
|
//#region ../agent-cli/dist/execute.js
|
|
7664
8275
|
function outcomeOf(value, render) {
|
|
7665
8276
|
return {
|
|
@@ -7692,7 +8303,7 @@ function dryRunOutcome(command, plan) {
|
|
|
7692
8303
|
].join("\n")
|
|
7693
8304
|
};
|
|
7694
8305
|
}
|
|
7695
|
-
async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started }) {
|
|
8306
|
+
async function execute(command, input, ctx, { dryRun, confirm, yes, page, invocation, commandLine, started, files, guardOutputs }) {
|
|
7696
8307
|
await checkTools(command.tools ?? [], ctx.env);
|
|
7697
8308
|
if (command.rows !== void 0) return outcomeOf(await command.run({
|
|
7698
8309
|
...input,
|
|
@@ -7705,12 +8316,13 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
|
|
|
7705
8316
|
}
|
|
7706
8317
|
const plan = await command.plan(input, ctx);
|
|
7707
8318
|
if (dryRun) return dryRunOutcome(command, plan);
|
|
8319
|
+
const outputs = command.outputs?.(plan);
|
|
7708
8320
|
await checkOutputs({
|
|
7709
8321
|
invocation,
|
|
7710
8322
|
commandLine,
|
|
7711
8323
|
force: ctx.force,
|
|
7712
8324
|
files: ctx.files
|
|
7713
|
-
},
|
|
8325
|
+
}, outputs);
|
|
7714
8326
|
checkCost({
|
|
7715
8327
|
invocation,
|
|
7716
8328
|
commandLine,
|
|
@@ -7721,100 +8333,15 @@ async function execute(command, input, ctx, { dryRun, confirm, yes, page, invoca
|
|
|
7721
8333
|
commandLine,
|
|
7722
8334
|
given: confirm
|
|
7723
8335
|
}, command.confirm(plan));
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
}
|
|
7732
|
-
function isAlive(pid) {
|
|
7733
|
-
try {
|
|
7734
|
-
process.kill(pid, 0);
|
|
7735
|
-
return true;
|
|
7736
|
-
} catch (error) {
|
|
7737
|
-
return errnoOf(error) === "EPERM";
|
|
7738
|
-
}
|
|
7739
|
-
}
|
|
7740
|
-
async function sweep(folder) {
|
|
7741
|
-
const dead = (await readdir(folder)).filter((name) => {
|
|
7742
|
-
const pid = TEMP_FILE.exec(name)?.[1];
|
|
7743
|
-
return pid !== void 0 && !isAlive(Number(pid));
|
|
7744
|
-
});
|
|
7745
|
-
await Promise.all(dead.map((name) => rm(join(folder, name), { force: true })));
|
|
7746
|
-
}
|
|
7747
|
-
/** Real files for one run: the first write to each folder sweeps the temp files of dead writers. */
|
|
7748
|
-
function realFiles() {
|
|
7749
|
-
const swept = /* @__PURE__ */ new Set();
|
|
7750
|
-
return {
|
|
7751
|
-
exists: async (path) => {
|
|
7752
|
-
try {
|
|
7753
|
-
await access(path);
|
|
7754
|
-
return true;
|
|
7755
|
-
} catch (error) {
|
|
7756
|
-
if (isMissingFile(error)) return false;
|
|
7757
|
-
throw error;
|
|
7758
|
-
}
|
|
7759
|
-
},
|
|
7760
|
-
write: async (path, data) => {
|
|
7761
|
-
const folder = dirname(path);
|
|
7762
|
-
await mkdir(folder, { recursive: true });
|
|
7763
|
-
if (!swept.has(folder)) {
|
|
7764
|
-
await sweep(folder);
|
|
7765
|
-
swept.add(folder);
|
|
7766
|
-
}
|
|
7767
|
-
const temp = tempPathOf(path);
|
|
7768
|
-
try {
|
|
7769
|
-
await writeFile(temp, data);
|
|
7770
|
-
await rename(temp, path);
|
|
7771
|
-
} catch (error) {
|
|
7772
|
-
await rm(temp, { force: true });
|
|
7773
|
-
throw error;
|
|
7774
|
-
}
|
|
7775
|
-
},
|
|
7776
|
-
read: (path) => readFile(path),
|
|
7777
|
-
list: (folder) => readdir(folder),
|
|
7778
|
-
remove: (path) => rm(path, {
|
|
7779
|
-
recursive: true,
|
|
7780
|
-
force: true
|
|
7781
|
-
}),
|
|
7782
|
-
mkdir: async (path, options) => {
|
|
7783
|
-
if (options?.exclusive !== true) {
|
|
7784
|
-
await mkdir(path, { recursive: true });
|
|
7785
|
-
return;
|
|
7786
|
-
}
|
|
7787
|
-
await mkdir(dirname(path), { recursive: true });
|
|
7788
|
-
await mkdir(path);
|
|
7789
|
-
}
|
|
7790
|
-
};
|
|
7791
|
-
}
|
|
7792
|
-
/**
|
|
7793
|
-
* `files` resolving paths against `cwd`, its failures classified by errno; `list` drops the
|
|
7794
|
-
* Runtime's temp files and sorts.
|
|
7795
|
-
*/
|
|
7796
|
-
function classifiedFiles(files, { cwd, env, callerLine }) {
|
|
7797
|
-
async function at(access, path, work) {
|
|
7798
|
-
const target = resolve(cwd, path);
|
|
7799
|
-
try {
|
|
7800
|
-
return await work(target);
|
|
7801
|
-
} catch (error) {
|
|
7802
|
-
throw classifyErrno(error, {
|
|
7803
|
-
path: target,
|
|
7804
|
-
access,
|
|
7805
|
-
callerLine,
|
|
7806
|
-
env
|
|
7807
|
-
});
|
|
7808
|
-
}
|
|
8336
|
+
if (outputs !== void 0) {
|
|
8337
|
+
await probeOutputs({
|
|
8338
|
+
files,
|
|
8339
|
+
cwd: ctx.cwd,
|
|
8340
|
+
fileError: ctx.fileError
|
|
8341
|
+
}, outputs);
|
|
8342
|
+
if (!ctx.force) guardOutputs(outputs, ctx.commandLine({ add: [FORCE_FLAG.flag] }));
|
|
7809
8343
|
}
|
|
7810
|
-
return
|
|
7811
|
-
exists: (path) => at("read", path, (target) => files.exists(target)),
|
|
7812
|
-
write: (path, data) => at("write", path, (target) => files.write(target, data)),
|
|
7813
|
-
read: (path) => at("read", path, (target) => files.read(target)),
|
|
7814
|
-
list: (folder) => at("read", folder, async (target) => (await files.list(target)).filter((name) => !TEMP_FILE.test(name)).sort()),
|
|
7815
|
-
remove: (path) => at("write", path, (target) => files.remove(target)),
|
|
7816
|
-
mkdir: (path, options) => at("write", path, (target) => files.mkdir(target, options))
|
|
7817
|
-
};
|
|
8344
|
+
return outcomeOf(await command.apply(plan, ctx), (data, style) => command.render(data, style));
|
|
7818
8345
|
}
|
|
7819
8346
|
//#endregion
|
|
7820
8347
|
//#region ../agent-cli/dist/package-root.js
|
|
@@ -7873,88 +8400,6 @@ function closestFlag(flag, flags) {
|
|
|
7873
8400
|
return closest.length === 1 ? closest[0] : void 0;
|
|
7874
8401
|
}
|
|
7875
8402
|
//#endregion
|
|
7876
|
-
//#region ../agent-cli/dist/tokenize.js
|
|
7877
|
-
const NEGATIVE_NUMBER = /^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/;
|
|
7878
|
-
function isOptionLike(word) {
|
|
7879
|
-
return word !== void 0 && word.length > 1 && word[0] === "-" && !NEGATIVE_NUMBER.test(word);
|
|
7880
|
-
}
|
|
7881
|
-
function parseArgsOptions(known) {
|
|
7882
|
-
return Object.fromEntries(known.map(({ flag, takes }) => [flag.slice(2), { type: takes === "value" ? "string" : "boolean" }]));
|
|
7883
|
-
}
|
|
7884
|
-
function flagItem(token, { flag, takes }, words) {
|
|
7885
|
-
if (takes !== "optional value" || token.inlineValue === true) return {
|
|
7886
|
-
kind: "flag",
|
|
7887
|
-
flag,
|
|
7888
|
-
value: token.value ?? true
|
|
7889
|
-
};
|
|
7890
|
-
const took = token.index + 1;
|
|
7891
|
-
const next = words[took];
|
|
7892
|
-
return next === void 0 || isOptionLike(next) ? {
|
|
7893
|
-
kind: "flag",
|
|
7894
|
-
flag,
|
|
7895
|
-
value: true
|
|
7896
|
-
} : {
|
|
7897
|
-
kind: "flag",
|
|
7898
|
-
flag,
|
|
7899
|
-
value: next,
|
|
7900
|
-
took
|
|
7901
|
-
};
|
|
7902
|
-
}
|
|
7903
|
-
function itemOf(token, words, byFlag) {
|
|
7904
|
-
const word = words[token.index] ?? "";
|
|
7905
|
-
if (token.kind === "option-terminator") return { kind: "end" };
|
|
7906
|
-
if (token.kind === "positional" || NEGATIVE_NUMBER.test(word)) return {
|
|
7907
|
-
kind: "operand",
|
|
7908
|
-
value: word
|
|
7909
|
-
};
|
|
7910
|
-
const flag = byFlag.get(token.rawName);
|
|
7911
|
-
return flag === void 0 || flag.takes === "nothing" && token.inlineValue === true ? {
|
|
7912
|
-
kind: "unknown option",
|
|
7913
|
-
word
|
|
7914
|
-
} : flagItem(token, flag, words);
|
|
7915
|
-
}
|
|
7916
|
-
/** Splits a Command's words into operands, unknown options and the values of known flags. */
|
|
7917
|
-
function tokenize(words, known) {
|
|
7918
|
-
const byFlag = new Map(known.map((flag) => [flag.flag, flag]));
|
|
7919
|
-
const { tokens } = parseArgs({
|
|
7920
|
-
args: [...words],
|
|
7921
|
-
options: parseArgsOptions(known),
|
|
7922
|
-
strict: false,
|
|
7923
|
-
allowPositionals: true,
|
|
7924
|
-
tokens: true
|
|
7925
|
-
});
|
|
7926
|
-
const operands = [];
|
|
7927
|
-
const unknownOptions = [];
|
|
7928
|
-
const values = /* @__PURE__ */ new Map();
|
|
7929
|
-
const done = /* @__PURE__ */ new Set();
|
|
7930
|
-
let literal = false;
|
|
7931
|
-
let afterUnknownOption = false;
|
|
7932
|
-
for (const token of tokens) {
|
|
7933
|
-
if (done.has(token.index)) continue;
|
|
7934
|
-
done.add(token.index);
|
|
7935
|
-
const item = itemOf(token, words, byFlag);
|
|
7936
|
-
if (item.kind === "end") literal = true;
|
|
7937
|
-
else if (item.kind === "operand") {
|
|
7938
|
-
operands.push({
|
|
7939
|
-
value: item.value,
|
|
7940
|
-
afterUnknownOption: !literal && afterUnknownOption
|
|
7941
|
-
});
|
|
7942
|
-
afterUnknownOption = false;
|
|
7943
|
-
} else if (item.kind === "unknown option") {
|
|
7944
|
-
unknownOptions.push(item.word);
|
|
7945
|
-
afterUnknownOption = true;
|
|
7946
|
-
} else {
|
|
7947
|
-
values.set(item.flag, item.value);
|
|
7948
|
-
if (item.took !== void 0) done.add(item.took);
|
|
7949
|
-
}
|
|
7950
|
-
}
|
|
7951
|
-
return {
|
|
7952
|
-
operands,
|
|
7953
|
-
unknownOptions,
|
|
7954
|
-
values
|
|
7955
|
-
};
|
|
7956
|
-
}
|
|
7957
|
-
//#endregion
|
|
7958
8403
|
//#region ../agent-cli/dist/parse-input.js
|
|
7959
8404
|
const NO_INPUT = object({});
|
|
7960
8405
|
function knownFlags(spec) {
|
|
@@ -7982,7 +8427,9 @@ function unknownOptionsOf(words, candidates) {
|
|
|
7982
8427
|
};
|
|
7983
8428
|
});
|
|
7984
8429
|
}
|
|
7985
|
-
function optionValue(field,
|
|
8430
|
+
function optionValue(field, all) {
|
|
8431
|
+
if (field.variadic) return variadicValue(field, (all ?? []).filter((value) => value !== true));
|
|
8432
|
+
const raw = all?.[all.length - 1];
|
|
7986
8433
|
return field.type === "boolean" && raw === true && field.label === `--no-${field.name}` ? false : raw;
|
|
7987
8434
|
}
|
|
7988
8435
|
function converted(field, raw) {
|
|
@@ -7998,7 +8445,7 @@ function parse(schema, values) {
|
|
|
7998
8445
|
const input = Object.fromEntries([...values].filter(([, raw]) => raw !== void 0).map(([field, raw]) => [field.key, converted(field, raw)]));
|
|
7999
8446
|
return schema.safeParse(input);
|
|
8000
8447
|
}
|
|
8001
|
-
function issueLines(result, values, howToPass, reported) {
|
|
8448
|
+
function issueLines(result, values, { howToPass, arrayIssueLine, reported = [], fromStdin }) {
|
|
8002
8449
|
if (result.success) return [];
|
|
8003
8450
|
return result.error.issues.flatMap((issue) => {
|
|
8004
8451
|
const field = [...values.keys()].find((candidate) => candidate.key === issue.path[0]);
|
|
@@ -8006,9 +8453,15 @@ function issueLines(result, values, howToPass, reported) {
|
|
|
8006
8453
|
if (reported.includes(field)) return [];
|
|
8007
8454
|
const raw = values.get(field);
|
|
8008
8455
|
if (raw === void 0) return [` ${field.label}: required, ${howToPass(field)}`];
|
|
8009
|
-
|
|
8456
|
+
if (field === fromStdin) return [` ${field.label} from stdin: ${issue.message}`];
|
|
8457
|
+
return Array.isArray(raw) ? [arrayIssueLine(field, raw, issue)] : [` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`];
|
|
8010
8458
|
});
|
|
8011
8459
|
}
|
|
8460
|
+
function flagValuesIssueLine(field, raw, issue) {
|
|
8461
|
+
const [, element] = issue.path;
|
|
8462
|
+
if (typeof element === "number") return ` ${field.label} ${JSON.stringify(raw[element])}: ${issue.message}`;
|
|
8463
|
+
return raw.length === 0 ? ` ${field.label}: ${issue.message}, pass ${field.usage}` : ` ${field.label} ${JSON.stringify(raw)}: ${issue.message}`;
|
|
8464
|
+
}
|
|
8012
8465
|
function operandsIssueLine(field, raw, issue, first) {
|
|
8013
8466
|
const [, element] = issue.path;
|
|
8014
8467
|
if (typeof element === "number") return ` ${field.label} ${JSON.stringify(raw[element])} (argument ${first + element}): ${issue.message}`;
|
|
@@ -8030,16 +8483,36 @@ function invalidInput(commandLine, invocation, unknown, otherLines) {
|
|
|
8030
8483
|
hint: correctable ? `did you mean \`${commandLineRenaming(invocation, renames)}\`?` : `run \`${commandLine} --help\` to see the expected input`
|
|
8031
8484
|
});
|
|
8032
8485
|
}
|
|
8033
|
-
function
|
|
8486
|
+
async function stdinTextOf(spec, operands, read) {
|
|
8487
|
+
const index = spec.args.findIndex((field) => field.stdin);
|
|
8488
|
+
const field = spec.args[index];
|
|
8489
|
+
if (field === void 0 || operands[index]?.value !== "-") return void 0;
|
|
8490
|
+
return {
|
|
8491
|
+
field,
|
|
8492
|
+
text: await read(field)
|
|
8493
|
+
};
|
|
8494
|
+
}
|
|
8495
|
+
async function parseInput(spec, words, invocation, readStdin) {
|
|
8034
8496
|
const { command, args: argFields, options: optionFields } = spec;
|
|
8035
8497
|
const pageFields = spec.list?.pageFields ?? [];
|
|
8036
8498
|
const known = knownFlags(spec);
|
|
8037
|
-
const { operands, unknownOptions, values } = tokenize(words, known);
|
|
8499
|
+
const { operands, unknownOptions, values, occurrences } = tokenize(words, known);
|
|
8500
|
+
const stdin = await stdinTextOf(spec, operands, readStdin);
|
|
8038
8501
|
if (values.get(CONFIRM_FLAG.flag) === true) throw invalidInputError(spec.commandLine, `option '${CONFIRM_FLAG.usage}' argument missing`);
|
|
8039
|
-
const missing = [...optionFields, ...pageFields].filter((field) => field.type !== "boolean" && values.get(field.label) === true);
|
|
8040
|
-
const
|
|
8502
|
+
const missing = [...optionFields, ...pageFields].filter((field) => field.type !== "boolean" && (field.variadic ? occurrences.get(field.label)?.includes(true) : values.get(field.label) === true));
|
|
8503
|
+
const argValue = (field, index) => {
|
|
8504
|
+
if (field === stdin?.field) return stdin.text;
|
|
8505
|
+
if (!field.variadic) return operands[index]?.value;
|
|
8506
|
+
return variadicValue(field, operands.slice(index).filter((operand) => !operand.afterUnknownOption).map((operand) => operand.value));
|
|
8507
|
+
};
|
|
8508
|
+
const argValues = new Map(argFields.map((field, index) => [field, argValue(field, index)]));
|
|
8041
8509
|
const extraOperands = argFields.some((field) => field.variadic) ? [] : operands.slice(argFields.length);
|
|
8042
|
-
const valuesOf = (fields) => new Map(fields.map((field) => [field, missing.includes(field) ? void 0 : optionValue(field,
|
|
8510
|
+
const valuesOf = (fields) => new Map(fields.map((field) => [field, missing.includes(field) ? void 0 : optionValue(field, occurrences.get(field.label))]));
|
|
8511
|
+
const flagWording = {
|
|
8512
|
+
howToPass: (field) => `pass ${field.usage}`,
|
|
8513
|
+
arrayIssueLine: flagValuesIssueLine,
|
|
8514
|
+
reported: missing
|
|
8515
|
+
};
|
|
8043
8516
|
const optionValues = valuesOf(optionFields);
|
|
8044
8517
|
const pageValues = valuesOf(pageFields);
|
|
8045
8518
|
const args = parse(command.args ?? NO_INPUT, argValues);
|
|
@@ -8049,9 +8522,13 @@ function parseInput(spec, words, invocation) {
|
|
|
8049
8522
|
const otherLines = [
|
|
8050
8523
|
...missing.map((field) => ` ${field.label}: missing value, pass ${field.usage}`),
|
|
8051
8524
|
...extraOperands.filter((operand) => !operand.afterUnknownOption).map((operand) => ` unexpected argument ${JSON.stringify(operand.value)}`),
|
|
8052
|
-
...issueLines(args, argValues,
|
|
8053
|
-
|
|
8054
|
-
|
|
8525
|
+
...issueLines(args, argValues, {
|
|
8526
|
+
howToPass: (field) => `pass it as argument ${argFields.indexOf(field) + 1}`,
|
|
8527
|
+
arrayIssueLine: (field, raw, issue) => operandsIssueLine(field, raw, issue, argFields.indexOf(field) + 1),
|
|
8528
|
+
fromStdin: stdin?.field
|
|
8529
|
+
}),
|
|
8530
|
+
...issueLines(options, optionValues, flagWording),
|
|
8531
|
+
...issueLines(page, pageValues, flagWording)
|
|
8055
8532
|
];
|
|
8056
8533
|
if (unknown.length === 0 && otherLines.length === 0 && args.success && options.success && page.success) return {
|
|
8057
8534
|
input: {
|
|
@@ -8065,9 +8542,12 @@ function parseInput(spec, words, invocation) {
|
|
|
8065
8542
|
if (only !== void 0 && unknown.length === 1 && otherLines.length === 0) throw unknownOptionError(spec.commandLine, invocation, only.flag, only.suggestion === void 0 ? [] : [only.suggestion]);
|
|
8066
8543
|
throw invalidInput(spec.commandLine, invocation, unknown, otherLines);
|
|
8067
8544
|
}
|
|
8068
|
-
/**
|
|
8069
|
-
|
|
8070
|
-
|
|
8545
|
+
/**
|
|
8546
|
+
* The validated input and Standard flag values of the routed Command, or one usage error; the arg
|
|
8547
|
+
* given `-` takes its text from `readStdin`.
|
|
8548
|
+
*/
|
|
8549
|
+
async function parseCommand({ spec, words, json }, invocation, readStdin) {
|
|
8550
|
+
const { input, page, values } = await parseInput(spec, words, invocation, readStdin);
|
|
8071
8551
|
const confirm = values.get(CONFIRM_FLAG.flag);
|
|
8072
8552
|
return {
|
|
8073
8553
|
input,
|
|
@@ -8138,7 +8618,7 @@ function walk(node, words, json, invocation) {
|
|
|
8138
8618
|
return unknownCommand(node, first, json);
|
|
8139
8619
|
}
|
|
8140
8620
|
function unknownCommand(group, name, json) {
|
|
8141
|
-
const suggestions = closestNames(name, Object.keys(group
|
|
8621
|
+
const suggestions = closestNames(name, Object.keys(visibleCommands(group)));
|
|
8142
8622
|
return {
|
|
8143
8623
|
kind: "usage error",
|
|
8144
8624
|
error: unknownCommandError(group.commandLine, name, suggestions),
|
|
@@ -8163,30 +8643,28 @@ function dotenvPath(cliName, env) {
|
|
|
8163
8643
|
const config = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
|
|
8164
8644
|
return join(config, cliName, ".env");
|
|
8165
8645
|
}
|
|
8166
|
-
function readDotenv(path,
|
|
8646
|
+
function readDotenv(path, fileError) {
|
|
8167
8647
|
let text;
|
|
8168
8648
|
try {
|
|
8169
8649
|
text = readFileSync(path, "utf8");
|
|
8170
8650
|
} catch (error) {
|
|
8171
8651
|
if (isMissingFile(error)) return {};
|
|
8172
|
-
throw
|
|
8652
|
+
throw fileError(error, {
|
|
8173
8653
|
path,
|
|
8174
|
-
access: "read"
|
|
8175
|
-
callerLine,
|
|
8176
|
-
env
|
|
8654
|
+
access: "read"
|
|
8177
8655
|
});
|
|
8178
8656
|
}
|
|
8179
8657
|
return parseEnv(text);
|
|
8180
8658
|
}
|
|
8181
8659
|
/** Looks `name` up in the env, then in `$XDG_CONFIG_HOME/<cli>/.env`; an empty value counts as unset. */
|
|
8182
|
-
function secretOf({ cliName, env,
|
|
8660
|
+
function secretOf({ cliName, env, fileError }, name) {
|
|
8183
8661
|
const fromEnv = env[name];
|
|
8184
8662
|
if (fromEnv) return {
|
|
8185
8663
|
value: fromEnv,
|
|
8186
8664
|
origin: { kind: "env" }
|
|
8187
8665
|
};
|
|
8188
8666
|
const path = dotenvPath(cliName, env);
|
|
8189
|
-
const fromFile = readDotenv(path,
|
|
8667
|
+
const fromFile = readDotenv(path, fileError)[name];
|
|
8190
8668
|
return fromFile ? {
|
|
8191
8669
|
value: fromFile,
|
|
8192
8670
|
origin: {
|
|
@@ -8220,11 +8698,9 @@ async function prepare(folder, cutoff, target) {
|
|
|
8220
8698
|
await mkdir(folder, { recursive: true });
|
|
8221
8699
|
await pruneOld(folder, cutoff);
|
|
8222
8700
|
} catch (error) {
|
|
8223
|
-
throw
|
|
8701
|
+
throw target.fileError(error, {
|
|
8224
8702
|
path: folder,
|
|
8225
|
-
access: "write"
|
|
8226
|
-
callerLine: target.callerLine,
|
|
8227
|
-
env: target.env
|
|
8703
|
+
access: "write"
|
|
8228
8704
|
});
|
|
8229
8705
|
}
|
|
8230
8706
|
}
|
|
@@ -8280,22 +8756,22 @@ function styleFor(env, stdoutIsTTY) {
|
|
|
8280
8756
|
}
|
|
8281
8757
|
//#endregion
|
|
8282
8758
|
//#region ../agent-cli/dist/run-cli.js
|
|
8283
|
-
|
|
8759
|
+
function stdinNeverEndedHint(callerLine) {
|
|
8760
|
+
return `stdin never ended: pipe the text into \`${callerLine}\`, or redirect a file into it`;
|
|
8761
|
+
}
|
|
8762
|
+
async function runCommand(routed, { base, writeStdout, signal, format: baseFormat, ports, clock, files, spawn, projectFile, stdin, platform }) {
|
|
8284
8763
|
const { command, commandLine } = routed.spec;
|
|
8764
|
+
const invocation = {
|
|
8765
|
+
...baseFormat.invocation,
|
|
8766
|
+
flags: knownFlags(routed.spec)
|
|
8767
|
+
};
|
|
8285
8768
|
const format = {
|
|
8286
8769
|
...baseFormat,
|
|
8287
|
-
json: routed.json
|
|
8770
|
+
json: routed.json,
|
|
8771
|
+
invocation
|
|
8288
8772
|
};
|
|
8289
|
-
const
|
|
8290
|
-
let
|
|
8291
|
-
let project;
|
|
8292
|
-
try {
|
|
8293
|
-
parsed = parseCommand(routed, invocation);
|
|
8294
|
-
project = projectFile === void 0 ? void 0 : projectOf(projectFile, base.cwd);
|
|
8295
|
-
} catch (error) {
|
|
8296
|
-
return errorResult(error, format);
|
|
8297
|
-
}
|
|
8298
|
-
const { input, flags } = parsed;
|
|
8773
|
+
const typedLine = (change) => commandLineOf(invocation, change);
|
|
8774
|
+
let resumeHint;
|
|
8299
8775
|
const control = new RunControl({
|
|
8300
8776
|
commandLine,
|
|
8301
8777
|
outer: signal,
|
|
@@ -8303,41 +8779,52 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
|
|
|
8303
8779
|
progress: base.progress,
|
|
8304
8780
|
runDeadline: command.deadline === void 0 ? void 0 : {
|
|
8305
8781
|
ms: command.deadline.ms,
|
|
8306
|
-
hint:
|
|
8782
|
+
hint: () => resumeHint ?? stdinNeverEndedHint(typedLine())
|
|
8307
8783
|
}
|
|
8308
8784
|
});
|
|
8785
|
+
let parsed;
|
|
8786
|
+
let project;
|
|
8787
|
+
try {
|
|
8788
|
+
checkOperator(routed.spec, base.env, typedLine());
|
|
8789
|
+
parsed = await parseCommand(routed, invocation, (field) => {
|
|
8790
|
+
if (isTerminal(stdin)) base.progress(readingLine(field.name, platform));
|
|
8791
|
+
return readStdin(stdin, control.signal);
|
|
8792
|
+
});
|
|
8793
|
+
project = projectFile === void 0 ? void 0 : projectOf(projectFile, base.cwd);
|
|
8794
|
+
} catch (error) {
|
|
8795
|
+
control.close();
|
|
8796
|
+
return errorResult(error, format);
|
|
8797
|
+
}
|
|
8798
|
+
const { input, flags } = parsed;
|
|
8799
|
+
resumeHint = command.deadline?.resume(input, typedLine);
|
|
8309
8800
|
let delivered;
|
|
8310
|
-
const
|
|
8311
|
-
const
|
|
8801
|
+
const { fileError } = base;
|
|
8802
|
+
const outputs = outputsGuard(classifiedFiles(files, {
|
|
8312
8803
|
cwd: base.cwd,
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
});
|
|
8804
|
+
fileError
|
|
8805
|
+
}), base.cwd);
|
|
8316
8806
|
const spill = spillTo({
|
|
8317
8807
|
cliName: format.cliName,
|
|
8318
8808
|
commandName: command.name,
|
|
8319
8809
|
env: base.env,
|
|
8320
8810
|
clock,
|
|
8321
|
-
files:
|
|
8322
|
-
|
|
8811
|
+
files: outputs.files,
|
|
8812
|
+
fileError
|
|
8323
8813
|
});
|
|
8324
8814
|
const portsBase = {
|
|
8325
|
-
|
|
8326
|
-
env: base.env,
|
|
8327
|
-
packageRoot: base.packageRoot,
|
|
8328
|
-
secret: base.secret,
|
|
8815
|
+
...base,
|
|
8329
8816
|
signal: control.signal,
|
|
8330
8817
|
clock,
|
|
8331
|
-
files:
|
|
8818
|
+
files: outputs.files,
|
|
8332
8819
|
spawn: contextSpawn(spawn, {
|
|
8333
8820
|
cwd: base.cwd,
|
|
8334
8821
|
env: base.env,
|
|
8335
|
-
signal: control.signal
|
|
8336
|
-
|
|
8822
|
+
signal: control.signal,
|
|
8823
|
+
progress: base.progress
|
|
8824
|
+
}, fileError)
|
|
8337
8825
|
};
|
|
8338
8826
|
const builtPorts = once(() => ports(portsBase));
|
|
8339
8827
|
const ctx = {
|
|
8340
|
-
...base,
|
|
8341
8828
|
...portsBase,
|
|
8342
8829
|
deadline: (ms) => control.deadline(ms),
|
|
8343
8830
|
runToEnd: (name, work) => control.runToEnd(name, work),
|
|
@@ -8346,7 +8833,8 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
|
|
|
8346
8833
|
},
|
|
8347
8834
|
project,
|
|
8348
8835
|
force: flags.force,
|
|
8349
|
-
|
|
8836
|
+
commandLine: typedLine,
|
|
8837
|
+
poll: (check, { intervalMs, signal: stop }) => poll(clock, check, intervalMs, stop === void 0 ? control.signal : AbortSignal.any([control.signal, stop])),
|
|
8350
8838
|
deliver: (data) => {
|
|
8351
8839
|
delivered = deliveredOutcome(command, data);
|
|
8352
8840
|
}
|
|
@@ -8366,7 +8854,9 @@ async function runCommand(routed, { base, writeStdout, signal, format: baseForma
|
|
|
8366
8854
|
started: async (outcome) => {
|
|
8367
8855
|
writeStdout(await startedOutput(outcome, format, spill));
|
|
8368
8856
|
served = true;
|
|
8369
|
-
}
|
|
8857
|
+
},
|
|
8858
|
+
files,
|
|
8859
|
+
guardOutputs: outputs.guard
|
|
8370
8860
|
}))
|
|
8371
8861
|
};
|
|
8372
8862
|
} catch (error) {
|
|
@@ -8401,29 +8891,59 @@ function once(build) {
|
|
|
8401
8891
|
throw outcome.error;
|
|
8402
8892
|
};
|
|
8403
8893
|
}
|
|
8894
|
+
function selfOf(name, bin) {
|
|
8895
|
+
return bin === void 0 ? [name] : [
|
|
8896
|
+
process.execPath,
|
|
8897
|
+
...process.execArgv,
|
|
8898
|
+
bin
|
|
8899
|
+
];
|
|
8900
|
+
}
|
|
8404
8901
|
function portsOf(cli, replacement) {
|
|
8405
8902
|
if (replacement !== void 0) return () => replacement;
|
|
8406
8903
|
return cli.ports ?? (() => ({}));
|
|
8407
8904
|
}
|
|
8408
|
-
|
|
8905
|
+
function stopLine(notice, json) {
|
|
8906
|
+
return json ? progressLine(notice, true) : `\n${notice}\n`;
|
|
8907
|
+
}
|
|
8908
|
+
/** Calls `onStop` if `signal` aborts while `work` runs. */
|
|
8909
|
+
async function whileStopping(signal, onStop, work) {
|
|
8910
|
+
if (signal === void 0 || onStop === void 0) return work();
|
|
8911
|
+
signal.addEventListener("abort", onStop, { once: true });
|
|
8912
|
+
try {
|
|
8913
|
+
return await work();
|
|
8914
|
+
} finally {
|
|
8915
|
+
signal.removeEventListener("abort", onStop);
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
async function runWithProgress(cli, argv, options, write, stopNotice) {
|
|
8409
8919
|
const format = {
|
|
8410
8920
|
cliName: cli.name,
|
|
8411
8921
|
env: options.env,
|
|
8412
8922
|
style: styleFor(options.env, options.stdoutIsTTY === true),
|
|
8413
8923
|
invocation: {
|
|
8414
8924
|
cliName: cli.name,
|
|
8415
|
-
argv
|
|
8925
|
+
argv,
|
|
8926
|
+
flags: []
|
|
8416
8927
|
},
|
|
8417
8928
|
json: false,
|
|
8418
8929
|
errors: cli.errors ?? {},
|
|
8419
8930
|
stop: options.signal
|
|
8420
8931
|
};
|
|
8421
|
-
const builtIns = cli.commands === void 0 ? {} : builtInCommands(cli,
|
|
8932
|
+
const builtIns = cli.commands === void 0 ? {} : builtInCommands(cli, {
|
|
8933
|
+
bin: options.bin,
|
|
8934
|
+
node: options.node
|
|
8935
|
+
});
|
|
8422
8936
|
const root = cli.commands === void 0 ? commandSpec(cli.name, cli.command) : groupSpec(cli.name, {
|
|
8423
8937
|
...cli.commands,
|
|
8424
8938
|
...builtIns
|
|
8425
8939
|
});
|
|
8426
8940
|
const routed = route(root, format.invocation);
|
|
8941
|
+
const platform = options.platform ?? process.platform;
|
|
8942
|
+
const fileError = fileErrorOf({
|
|
8943
|
+
callerLine: commandLineOf(format.invocation),
|
|
8944
|
+
env: options.env,
|
|
8945
|
+
platform
|
|
8946
|
+
});
|
|
8427
8947
|
const misused = (node) => isAgent(options.env) ? {
|
|
8428
8948
|
...format,
|
|
8429
8949
|
usageHelp: textHelp(node, root, cli, format.style)
|
|
@@ -8438,17 +8958,21 @@ async function runWithProgress(cli, argv, options, write) {
|
|
|
8438
8958
|
...misused(routed.node),
|
|
8439
8959
|
json: routed.json
|
|
8440
8960
|
});
|
|
8441
|
-
case "command": return runCommand(routed, {
|
|
8961
|
+
case "command": return whileStopping(options.signal, stopNotice === void 0 ? void 0 : () => write.stderr(stopLine(stopNotice, routed.json)), () => runCommand(routed, {
|
|
8442
8962
|
base: {
|
|
8443
8963
|
cwd: options.cwd,
|
|
8444
8964
|
env: options.env,
|
|
8445
8965
|
packageRoot: packageRootOf(options.bin, options.cwd),
|
|
8446
|
-
progress: (message) => write.stderr(
|
|
8966
|
+
progress: (message) => write.stderr(progressLine(message, routed.json)),
|
|
8447
8967
|
secret: (name) => secretOf({
|
|
8448
8968
|
cliName: cli.name,
|
|
8449
8969
|
env: options.env,
|
|
8450
|
-
|
|
8451
|
-
}, name)
|
|
8970
|
+
fileError
|
|
8971
|
+
}, name),
|
|
8972
|
+
fileError,
|
|
8973
|
+
version: cli.version,
|
|
8974
|
+
build: cli.build,
|
|
8975
|
+
self: selfOf(cli.name, options.bin)
|
|
8452
8976
|
},
|
|
8453
8977
|
writeStdout: (text) => write.stdout(text),
|
|
8454
8978
|
signal: options.signal,
|
|
@@ -8457,8 +8981,10 @@ async function runWithProgress(cli, argv, options, write) {
|
|
|
8457
8981
|
clock: options.clock ?? realClock,
|
|
8458
8982
|
files: options.files ?? realFiles(),
|
|
8459
8983
|
spawn: options.spawn ?? realSpawn,
|
|
8460
|
-
projectFile: Object.values(builtIns).includes(routed.spec.command) ? void 0 : cli.project
|
|
8461
|
-
|
|
8984
|
+
projectFile: Object.values(builtIns).includes(routed.spec.command) ? void 0 : cli.project,
|
|
8985
|
+
stdin: options.stdin ?? "",
|
|
8986
|
+
platform
|
|
8987
|
+
}));
|
|
8462
8988
|
}
|
|
8463
8989
|
}
|
|
8464
8990
|
//#endregion
|
|
@@ -8470,7 +8996,6 @@ async function runProcess(cli) {
|
|
|
8470
8996
|
const stop = new AbortController();
|
|
8471
8997
|
const onSignal = (signal) => {
|
|
8472
8998
|
if (stop.signal.aborted) process.exit(SIGNAL_EXIT_CODES[signal]);
|
|
8473
|
-
process.stderr.write("\nstopping… (Ctrl-C again to quit now)\n");
|
|
8474
8999
|
stop.abort(signal);
|
|
8475
9000
|
};
|
|
8476
9001
|
process.on("SIGINT", onSignal);
|
|
@@ -8481,11 +9006,13 @@ async function runProcess(cli) {
|
|
|
8481
9006
|
cwd: process.cwd(),
|
|
8482
9007
|
stdoutIsTTY: process.stdout.isTTY === true,
|
|
8483
9008
|
signal: stop.signal,
|
|
8484
|
-
bin: process.argv[1]
|
|
9009
|
+
bin: process.argv[1],
|
|
9010
|
+
node: process.execPath,
|
|
9011
|
+
stdin: process.stdin
|
|
8485
9012
|
}, {
|
|
8486
9013
|
stdout: (text) => process.stdout.write(text),
|
|
8487
9014
|
stderr: (text) => process.stderr.write(text)
|
|
8488
|
-
});
|
|
9015
|
+
}, "stopping… (Ctrl-C again to quit now)");
|
|
8489
9016
|
await written(process.stdout, result.stdout);
|
|
8490
9017
|
await written(process.stderr, result.stderr);
|
|
8491
9018
|
process.exitCode = result.exitCode;
|
|
@@ -8498,9 +9025,9 @@ async function runProcess(cli) {
|
|
|
8498
9025
|
//#endregion
|
|
8499
9026
|
//#region src/build.ts
|
|
8500
9027
|
const build = {
|
|
8501
|
-
"sha": "
|
|
9028
|
+
"sha": "c325cae",
|
|
8502
9029
|
"dirty": false,
|
|
8503
|
-
"builtAt": "2026-10-
|
|
9030
|
+
"builtAt": "2026-10-19T14:33:32.022Z"
|
|
8504
9031
|
};
|
|
8505
9032
|
//#endregion
|
|
8506
9033
|
//#region src/steps.ts
|
|
@@ -8629,7 +9156,7 @@ async function writeTemplate(root, files, values) {
|
|
|
8629
9156
|
}
|
|
8630
9157
|
//#endregion
|
|
8631
9158
|
//#region src/version.ts
|
|
8632
|
-
const version = "0.
|
|
9159
|
+
const version = "0.5.0";
|
|
8633
9160
|
//#endregion
|
|
8634
9161
|
//#region src/create.ts
|
|
8635
9162
|
const { defineCommand } = agentCli();
|