ainize 0.1.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/LICENSE +21 -0
- package/README.md +225 -0
- package/dist/bin.d.ts +10 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +711 -0
- package/dist/bin.js.map +1 -0
- package/dist/client.d.ts +38 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +164 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/auth.d.ts +30 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +216 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/branch.d.ts +326 -0
- package/dist/commands/branch.d.ts.map +1 -0
- package/dist/commands/branch.js +467 -0
- package/dist/commands/branch.js.map +1 -0
- package/dist/commands/chain.d.ts +43 -0
- package/dist/commands/chain.d.ts.map +1 -0
- package/dist/commands/chain.js +224 -0
- package/dist/commands/chain.js.map +1 -0
- package/dist/commands/chat.d.ts +157 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +395 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/dataset.d.ts +51 -0
- package/dist/commands/dataset.d.ts.map +1 -0
- package/dist/commands/dataset.js +157 -0
- package/dist/commands/dataset.js.map +1 -0
- package/dist/commands/drive.d.ts +32 -0
- package/dist/commands/drive.d.ts.map +1 -0
- package/dist/commands/drive.js +68 -0
- package/dist/commands/drive.js.map +1 -0
- package/dist/commands/huggingface-dataset.d.ts +63 -0
- package/dist/commands/huggingface-dataset.d.ts.map +1 -0
- package/dist/commands/huggingface-dataset.js +254 -0
- package/dist/commands/huggingface-dataset.js.map +1 -0
- package/dist/commands/init.d.ts +107 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +538 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/ledger.d.ts +43 -0
- package/dist/commands/ledger.d.ts.map +1 -0
- package/dist/commands/ledger.js +125 -0
- package/dist/commands/ledger.js.map +1 -0
- package/dist/commands/node.d.ts +250 -0
- package/dist/commands/node.d.ts.map +1 -0
- package/dist/commands/node.js +651 -0
- package/dist/commands/node.js.map +1 -0
- package/dist/commands/patch.d.ts +852 -0
- package/dist/commands/patch.d.ts.map +1 -0
- package/dist/commands/patch.js +1527 -0
- package/dist/commands/patch.js.map +1 -0
- package/dist/commands/peers.d.ts +86 -0
- package/dist/commands/peers.d.ts.map +1 -0
- package/dist/commands/peers.js +143 -0
- package/dist/commands/peers.js.map +1 -0
- package/dist/commands/teach-dataset.d.ts +381 -0
- package/dist/commands/teach-dataset.d.ts.map +1 -0
- package/dist/commands/teach-dataset.js +780 -0
- package/dist/commands/teach-dataset.js.map +1 -0
- package/dist/commands/teach.d.ts +351 -0
- package/dist/commands/teach.d.ts.map +1 -0
- package/dist/commands/teach.js +435 -0
- package/dist/commands/teach.js.map +1 -0
- package/dist/context.d.ts +55 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +102 -0
- package/dist/context.js.map +1 -0
- package/dist/ens.d.ts +50 -0
- package/dist/ens.d.ts.map +1 -0
- package/dist/ens.js +155 -0
- package/dist/ens.js.map +1 -0
- package/dist/help.d.ts +57 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +227 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +104 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +383 -0
- package/dist/output.js.map +1 -0
- package/dist/pid.d.ts +5 -0
- package/dist/pid.d.ts.map +1 -0
- package/dist/pid.js +25 -0
- package/dist/pid.js.map +1 -0
- package/dist/quiet.d.ts +2 -0
- package/dist/quiet.d.ts.map +1 -0
- package/dist/quiet.js +12 -0
- package/dist/quiet.js.map +1 -0
- package/package.json +55 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI context: where the node config lives (AINIZE_HOME), which node URL to talk to,
|
|
3
|
+
* the stored operator bearer token (cli.json) and output flags.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { basename, join } from 'node:path';
|
|
7
|
+
import { DEFAULT_HOME, configPath, loadConfig } from '@ainize/core';
|
|
8
|
+
/**
|
|
9
|
+
* Name the binary was invoked as. There is one: `ainize` (AI + -ize, "ainize your knowledge", like the 2019
|
|
10
|
+
* `ainize` CLI that ainized GitHub repos). The historical `ngram` alias was withdrawn when the packages moved
|
|
11
|
+
* to the `@ainize` scope — item 110 counted fourteen hints that told you to run a name the product no longer
|
|
12
|
+
* uses, starting with the first line `ainize init` prints.
|
|
13
|
+
*/
|
|
14
|
+
export const PROG_NAMES = ['ainize'];
|
|
15
|
+
export function progName(argv1 = process.argv[1]) {
|
|
16
|
+
const base = basename(argv1 ?? '').replace(/\.(c|m)?js$/, '');
|
|
17
|
+
return PROG_NAMES.includes(base) ? base : 'ainize';
|
|
18
|
+
}
|
|
19
|
+
export const PROG = progName();
|
|
20
|
+
export class CliError extends Error {
|
|
21
|
+
exitCode;
|
|
22
|
+
/** The node's JSON error body, when there was one — e.g. the per-line report a refused dataset upload carries. */
|
|
23
|
+
details;
|
|
24
|
+
constructor(message, exitCode = 1, details) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.exitCode = exitCode;
|
|
27
|
+
this.details = details;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function statePath(home) { return join(home, 'cli.json'); }
|
|
31
|
+
export function readState(home) {
|
|
32
|
+
const p = statePath(home);
|
|
33
|
+
if (!existsSync(p))
|
|
34
|
+
return {};
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function writeState(home, state) {
|
|
43
|
+
mkdirSync(home, { recursive: true });
|
|
44
|
+
writeFileSync(statePath(home), JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
|
45
|
+
}
|
|
46
|
+
export function resolveHome(explicit) {
|
|
47
|
+
return explicit ?? process.env.AINIZE_HOME ?? DEFAULT_HOME;
|
|
48
|
+
}
|
|
49
|
+
/** A TCP port — what config.json's `port` and AINIZE_PORT must hold before a URL can be built from them. */
|
|
50
|
+
const isPort = (v) => Number.isInteger(v) && v >= 1 && v <= 65535;
|
|
51
|
+
/** Build a context from global flags. Node URL precedence: --node > AINIZE_NODE_URL > cli.json > AINIZE_PORT > config port > default. */
|
|
52
|
+
export function buildContext(opts = {}) {
|
|
53
|
+
const home = resolveHome(opts.home);
|
|
54
|
+
const cfg = loadConfig(home);
|
|
55
|
+
const state = readState(home);
|
|
56
|
+
const envPort = process.env.AINIZE_PORT;
|
|
57
|
+
if (envPort !== undefined && !isPort(Number(envPort)))
|
|
58
|
+
throw new CliError(`AINIZE_PORT must be a port number (1–65535) — got ${JSON.stringify(envPort)}`, 2);
|
|
59
|
+
const port = envPort !== undefined ? Number(envPort) : cfg?.port ?? 3402;
|
|
60
|
+
const url = opts.node ?? process.env.AINIZE_NODE_URL ?? state.nodeUrl ?? `http://localhost:${port}`;
|
|
61
|
+
const nodeSource = opts.node ? 'flag'
|
|
62
|
+
: process.env.AINIZE_NODE_URL ? 'env'
|
|
63
|
+
: state.nodeUrl ? 'state'
|
|
64
|
+
: envPort !== undefined ? 'env'
|
|
65
|
+
: cfg ? 'config'
|
|
66
|
+
: 'default';
|
|
67
|
+
const nodeUrl = url.replace(/\/+$/, '');
|
|
68
|
+
// A config.json whose `port` is not a number used to make every command — `config set port …`, the fix, included —
|
|
69
|
+
// die with "--node must be a full URL", blaming a flag nobody passed (item 123). The URL is only checked when
|
|
70
|
+
// someone typed it; a broken config is named by the commands that actually need a node (requireNodeTarget).
|
|
71
|
+
let nodeUrlProblem = null;
|
|
72
|
+
if (nodeSource === 'config' && !isPort(cfg.port)) {
|
|
73
|
+
nodeUrlProblem = `port in ${configPath(home)} is ${JSON.stringify(cfg.port)}, not a port number — fix it with \`${PROG} config set port <1-65535>\``;
|
|
74
|
+
}
|
|
75
|
+
else if (nodeSource === 'flag' || nodeSource === 'env' || nodeSource === 'state') {
|
|
76
|
+
// `--node localhost:3402` is the standard typo, and a scheme-less URL used to be reported as a dead node while
|
|
77
|
+
// the node was serving happily on that very port (item 116).
|
|
78
|
+
let parsed = null;
|
|
79
|
+
try {
|
|
80
|
+
parsed = new URL(nodeUrl);
|
|
81
|
+
}
|
|
82
|
+
catch { /* not a URL at all */ }
|
|
83
|
+
// note `new URL('localhost:3402')` parses — with protocol "localhost:" — so the scheme has to be checked too
|
|
84
|
+
if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
|
|
85
|
+
const where = nodeSource === 'flag' ? '--node' : process.env.AINIZE_NODE_URL ? 'AINIZE_NODE_URL' : `the node URL in ${statePath(home)}`;
|
|
86
|
+
throw new CliError(`${where} must be a full URL — did you mean http://${nodeUrl.replace(/^\w+:\/\//, '').replace(/^\/+/, '')}?`, 2);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { home, nodeUrl, nodeSource, nodeUrlProblem, token: process.env.AINIZE_TOKEN ?? state.token ?? null, json: !!opts.json, quiet: !!opts.quiet, cfg };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Refuse to send a request nobody aimed: with no config in this home and no `--node`, every read command used to
|
|
93
|
+
* report whatever answered port 3402 — a colleague's node, or the demo cluster — as yours (item 101).
|
|
94
|
+
*/
|
|
95
|
+
export function requireNodeTarget(ctx) {
|
|
96
|
+
if (ctx.nodeUrlProblem)
|
|
97
|
+
throw new CliError(ctx.nodeUrlProblem, 2);
|
|
98
|
+
if (ctx.cfg || ctx.nodeSource !== 'default')
|
|
99
|
+
return;
|
|
100
|
+
throw new CliError(`no node configured in ${ctx.home} — run \`${PROG} init\` to create one, or pass --node <url> to talk to an existing node`, 2);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/ens.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Where the answer came from, so a caller can say it rather than imply it. */
|
|
2
|
+
export type ResolveSource = 'names-file' | 'config' | 'on-chain';
|
|
3
|
+
export interface ResolvedName {
|
|
4
|
+
name: string;
|
|
5
|
+
/** The node that holds the knowledge — what `--node` would have been. */
|
|
6
|
+
node: string;
|
|
7
|
+
/** The knowledge id on that node — what `use <id>` would have been. */
|
|
8
|
+
patch: string;
|
|
9
|
+
source: ResolveSource;
|
|
10
|
+
/** Where exactly, so an operator can go and edit it. */
|
|
11
|
+
where: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const NODE_KEY = "ainize.node";
|
|
14
|
+
export declare const PATCH_KEY = "ainize.patch";
|
|
15
|
+
/** A name this CLI will try to resolve rather than treat as a subcommand or an id. */
|
|
16
|
+
export declare function looksLikeEnsName(s: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* ENSIP-1 namehash. Reverse label order, each step hashing the parent node with the label hash.
|
|
19
|
+
* `keccak` comes from ain-util, which this workspace already depends on — node's `sha3-256` is NOT keccak256
|
|
20
|
+
* (different padding) and using it here would produce plausible, wrong nodes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function namehash(name: string): string;
|
|
23
|
+
export interface ResolveOptions {
|
|
24
|
+
/** JSON-RPC endpoint. `--rpc`, then ENS_RPC_URL, then `ens.rpc` in the node config. */
|
|
25
|
+
rpc?: string;
|
|
26
|
+
/** ENS registry address. Never defaulted — see the note at the top of this file. */
|
|
27
|
+
registry?: string;
|
|
28
|
+
/** Explicit names file, overriding the search order. */
|
|
29
|
+
namesFile?: string;
|
|
30
|
+
/** `ens` block from the node config, when there is one. */
|
|
31
|
+
config?: {
|
|
32
|
+
rpc?: string;
|
|
33
|
+
registry?: string;
|
|
34
|
+
names?: Record<string, {
|
|
35
|
+
node: string;
|
|
36
|
+
patch: string;
|
|
37
|
+
}>;
|
|
38
|
+
} | null;
|
|
39
|
+
home?: string;
|
|
40
|
+
}
|
|
41
|
+
/** The places a names file may live, in the order they are consulted. */
|
|
42
|
+
export declare function namesFileCandidates(opts?: ResolveOptions): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Resolve `name` to the node and knowledge id it points at.
|
|
45
|
+
*
|
|
46
|
+
* Throws with BOTH paths named when neither works, because "cannot resolve" without saying what was tried is
|
|
47
|
+
* the error message that sends someone to the wrong place.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveName(name: string, opts?: ResolveOptions): Promise<ResolvedName>;
|
|
50
|
+
//# sourceMappingURL=ens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ens.d.ts","sourceRoot":"","sources":["../src/ens.ts"],"names":[],"mappings":"AAmCA,+EAA+E;AAC/E,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,CAAC;IACtB,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,QAAQ,gBAAgB,CAAC;AACtC,eAAO,MAAM,SAAS,iBAAiB,CAAC;AAExC,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEnD;AAID;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU7C;AAkCD,MAAM,WAAW,cAAc;IAC7B,uFAAuF;IACvF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7G,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAYD,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,cAAmB,GAAG,MAAM,EAAE,CAMvE;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsChG"}
|
package/dist/ens.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve an ENS name to the knowledge it points at.
|
|
3
|
+
*
|
|
4
|
+
* `ainize patch vaults.defi.engram.eth` replaces three commands and a copied id:
|
|
5
|
+
*
|
|
6
|
+
* ainize patch ls --node http://their-node:3402 --status LISTED -q "<topic>"
|
|
7
|
+
* ainize login && ainize use <id>
|
|
8
|
+
*
|
|
9
|
+
* The reason a name can do that is that it carries BOTH halves of what those commands supply by hand — which
|
|
10
|
+
* node to talk to and which knowledge to ask for. `ainize.node` and `ainize.patch` are ENSIP-5 text records,
|
|
11
|
+
* so a publisher sets them once and every buyer's command line collapses to the name.
|
|
12
|
+
*
|
|
13
|
+
* TWO RESOLUTION PATHS, and the honest status of each:
|
|
14
|
+
*
|
|
15
|
+
* local A names file — `ens.names` in the node config, or ~/.ainize/names.json, or --names <file>.
|
|
16
|
+
* Verified: it is a file this CLI reads and this repository tests. It is also what makes the
|
|
17
|
+
* command work offline, in a demo, and before any name is registered.
|
|
18
|
+
*
|
|
19
|
+
* on-chain registry.resolver(namehash) then resolver.text(namehash, key), over JSON-RPC with no new
|
|
20
|
+
* dependency. `text(bytes32,string)` is ENSIP-5 and stable; the REGISTRY address is not something
|
|
21
|
+
* this file guesses — it must be given with --registry or `ens.registry`, because ENSv2's own
|
|
22
|
+
* documentation says its contracts "are not yet final and may change prior to mainnet
|
|
23
|
+
* deployment", and a hard-coded address that silently resolves against the wrong registry is the
|
|
24
|
+
* kind of confident wrong answer this project has spent its time removing.
|
|
25
|
+
* NOT YET EXERCISED against a live ENSv2 deployment. It is written from the ENSIP-5 interface and
|
|
26
|
+
* it will say so if it fails.
|
|
27
|
+
*
|
|
28
|
+
* Local wins when both are available, deliberately: a name you have pinned locally is a name you have
|
|
29
|
+
* checked, and a lookup that silently prefers the network can change under you between two runs.
|
|
30
|
+
*/
|
|
31
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { homedir } from 'node:os';
|
|
34
|
+
import { keccak } from '@ainblockchain/ain-util';
|
|
35
|
+
export const NODE_KEY = 'ainize.node';
|
|
36
|
+
export const PATCH_KEY = 'ainize.patch';
|
|
37
|
+
/** A name this CLI will try to resolve rather than treat as a subcommand or an id. */
|
|
38
|
+
export function looksLikeEnsName(s) {
|
|
39
|
+
return /^[a-z0-9-]+(\.[a-z0-9-]+)+$/i.test(s) && /\.(eth|test)$/i.test(s);
|
|
40
|
+
}
|
|
41
|
+
const hex = (b) => Buffer.from(b).toString('hex');
|
|
42
|
+
/**
|
|
43
|
+
* ENSIP-1 namehash. Reverse label order, each step hashing the parent node with the label hash.
|
|
44
|
+
* `keccak` comes from ain-util, which this workspace already depends on — node's `sha3-256` is NOT keccak256
|
|
45
|
+
* (different padding) and using it here would produce plausible, wrong nodes.
|
|
46
|
+
*/
|
|
47
|
+
export function namehash(name) {
|
|
48
|
+
let node = Buffer.alloc(32);
|
|
49
|
+
if (name) {
|
|
50
|
+
for (const label of name.toLowerCase().split('.').reverse()) {
|
|
51
|
+
if (!label)
|
|
52
|
+
continue;
|
|
53
|
+
const labelHash = Buffer.from(keccak(Buffer.from(label, 'utf8')));
|
|
54
|
+
node = Buffer.from(keccak(Buffer.concat([node, labelHash])));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return `0x${node.toString('hex')}`;
|
|
58
|
+
}
|
|
59
|
+
const selector = (sig) => `0x${hex(keccak(Buffer.from(sig, 'utf8'))).slice(0, 8)}`;
|
|
60
|
+
const padWord = (h) => h.replace(/^0x/, '').padStart(64, '0');
|
|
61
|
+
/** ABI-encode `text(bytes32 node, string key)` — one static word, then an offset and the string tail. */
|
|
62
|
+
function encodeText(node, key) {
|
|
63
|
+
const keyBytes = Buffer.from(key, 'utf8');
|
|
64
|
+
const len = keyBytes.length.toString(16).padStart(64, '0');
|
|
65
|
+
const body = Buffer.concat([keyBytes, Buffer.alloc((32 - (keyBytes.length % 32)) % 32)]).toString('hex');
|
|
66
|
+
return selector('text(bytes32,string)') + padWord(node) + (64).toString(16).padStart(64, '0') + len + body;
|
|
67
|
+
}
|
|
68
|
+
/** Decode a single ABI-encoded string return value. Returns '' for an empty or absent record. */
|
|
69
|
+
function decodeString(data) {
|
|
70
|
+
const raw = data.replace(/^0x/, '');
|
|
71
|
+
if (raw.length < 128)
|
|
72
|
+
return '';
|
|
73
|
+
const len = parseInt(raw.slice(64, 128), 16);
|
|
74
|
+
if (!Number.isFinite(len) || len === 0)
|
|
75
|
+
return '';
|
|
76
|
+
return Buffer.from(raw.slice(128, 128 + len * 2), 'hex').toString('utf8');
|
|
77
|
+
}
|
|
78
|
+
async function ethCall(rpc, to, data) {
|
|
79
|
+
const r = await fetch(rpc, {
|
|
80
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
81
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_call', params: [{ to, data }, 'latest'] }),
|
|
82
|
+
signal: AbortSignal.timeout(20_000),
|
|
83
|
+
});
|
|
84
|
+
if (!r.ok)
|
|
85
|
+
throw new Error(`RPC ${r.status}`);
|
|
86
|
+
const j = await r.json();
|
|
87
|
+
if (j.error)
|
|
88
|
+
throw new Error(j.error.message);
|
|
89
|
+
return j.result ?? '0x';
|
|
90
|
+
}
|
|
91
|
+
function readNamesFile(path) {
|
|
92
|
+
if (!existsSync(path))
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
const j = JSON.parse(readFileSync(path, 'utf8'));
|
|
96
|
+
return ('names' in j && j.names ? j.names : j);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** The places a names file may live, in the order they are consulted. */
|
|
103
|
+
export function namesFileCandidates(opts = {}) {
|
|
104
|
+
const out = [];
|
|
105
|
+
if (opts.namesFile)
|
|
106
|
+
out.push(opts.namesFile);
|
|
107
|
+
if (opts.home)
|
|
108
|
+
out.push(join(opts.home, 'names.json'));
|
|
109
|
+
out.push(join(homedir(), '.ainize', 'names.json'));
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Resolve `name` to the node and knowledge id it points at.
|
|
114
|
+
*
|
|
115
|
+
* Throws with BOTH paths named when neither works, because "cannot resolve" without saying what was tried is
|
|
116
|
+
* the error message that sends someone to the wrong place.
|
|
117
|
+
*/
|
|
118
|
+
export async function resolveName(name, opts = {}) {
|
|
119
|
+
const tried = [];
|
|
120
|
+
// 1. Local, in order: an explicit file, this home's names.json, ~/.ainize/names.json, then node config.
|
|
121
|
+
for (const path of namesFileCandidates(opts)) {
|
|
122
|
+
const names = readNamesFile(path);
|
|
123
|
+
tried.push(path);
|
|
124
|
+
const hit = names?.[name.toLowerCase()];
|
|
125
|
+
if (hit?.node && hit?.patch)
|
|
126
|
+
return { name, node: hit.node, patch: hit.patch, source: 'names-file', where: path };
|
|
127
|
+
}
|
|
128
|
+
const fromCfg = opts.config?.names?.[name.toLowerCase()];
|
|
129
|
+
if (fromCfg?.node && fromCfg?.patch)
|
|
130
|
+
return { name, node: fromCfg.node, patch: fromCfg.patch, source: 'config', where: 'config.json ens.names' };
|
|
131
|
+
tried.push('config.json ens.names');
|
|
132
|
+
// 2. On-chain. Both the RPC and the registry must be supplied; neither is guessed.
|
|
133
|
+
const rpc = opts.rpc ?? process.env.ENS_RPC_URL ?? opts.config?.rpc;
|
|
134
|
+
const registry = opts.registry ?? process.env.ENS_REGISTRY ?? opts.config?.registry;
|
|
135
|
+
if (rpc && registry) {
|
|
136
|
+
const node = namehash(name);
|
|
137
|
+
const resolverWord = await ethCall(rpc, registry, selector('resolver(bytes32)') + padWord(node));
|
|
138
|
+
const resolver = `0x${resolverWord.replace(/^0x/, '').slice(-40)}`;
|
|
139
|
+
if (/^0x0{40}$/.test(resolver))
|
|
140
|
+
throw new Error(`${name} has no resolver on the registry at ${registry}`);
|
|
141
|
+
const [nodeUrl, patchId] = await Promise.all([
|
|
142
|
+
ethCall(rpc, resolver, encodeText(node, NODE_KEY)).then(decodeString),
|
|
143
|
+
ethCall(rpc, resolver, encodeText(node, PATCH_KEY)).then(decodeString),
|
|
144
|
+
]);
|
|
145
|
+
if (!nodeUrl || !patchId) {
|
|
146
|
+
throw new Error(`${name} resolves, but its ${!nodeUrl ? NODE_KEY : PATCH_KEY} text record is empty — the publisher has not pointed it at a knowledge yet`);
|
|
147
|
+
}
|
|
148
|
+
return { name, node: nodeUrl, patch: patchId, source: 'on-chain', where: `${resolver} via ${rpc}` };
|
|
149
|
+
}
|
|
150
|
+
tried.push(rpc ? 'on-chain (no registry address given)' : 'on-chain (no RPC given)');
|
|
151
|
+
throw new Error(`cannot resolve ${name}. Tried: ${tried.join(', ')}.\n` +
|
|
152
|
+
`Either add it to a names file — {"${name.toLowerCase()}": {"node": "http://host:3402", "patch": "<id>"}} — ` +
|
|
153
|
+
`or give both --rpc and --registry (ENSv2's contracts are not final, so the registry address is never assumed).`);
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=ens.js.map
|
package/dist/ens.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ens.js","sourceRoot":"","sources":["../src/ens.ts"],"names":[],"mappings":"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"}
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the CLI says when the command line itself is wrong (item 103).
|
|
3
|
+
*
|
|
4
|
+
* yargs' own answers are written for whoever wrote the parser: `Not enough non-option arguments: got 0, need at
|
|
5
|
+
* least 1` for `ainize publish`, `Unknown argument: statsu` for a one-character typo. Everything needed to answer
|
|
6
|
+
* properly is already in the command's own help — its usage line, its positionals, its options, its examples — so
|
|
7
|
+
* this module reads that help back and turns the parser's complaint into the sentence the person needed.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here is a second copy of the command tree: the facts come from the help text yargs prints for the level
|
|
10
|
+
* that failed, which is generated from the declarations in bin.ts.
|
|
11
|
+
*/
|
|
12
|
+
/** One command's help, as read back from the block yargs printed for it. */
|
|
13
|
+
export interface HelpFacts {
|
|
14
|
+
/** the first line of the usage block: `ainize patch publish <file>` */
|
|
15
|
+
usage: string;
|
|
16
|
+
/** the command words in front of the first positional: `ainize patch publish` */
|
|
17
|
+
path: string;
|
|
18
|
+
/** every subcommand registered at this level, by its own word (`ls`, `get`, `publish`) */
|
|
19
|
+
commands: string[];
|
|
20
|
+
/** every option this level accepts, with dashes (`--price`, `-y`) */
|
|
21
|
+
options: string[];
|
|
22
|
+
/** each positional and what it is for */
|
|
23
|
+
positionals: {
|
|
24
|
+
name: string;
|
|
25
|
+
describe: string | null;
|
|
26
|
+
}[];
|
|
27
|
+
/** the option describes, so a missing `--benchmark` can be named in the command's own words */
|
|
28
|
+
describes: Record<string, string>;
|
|
29
|
+
/** the first `.example()`, command and description */
|
|
30
|
+
example: {
|
|
31
|
+
cmd: string;
|
|
32
|
+
desc: string;
|
|
33
|
+
} | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read a printed help block back into the facts an error message needs.
|
|
37
|
+
*
|
|
38
|
+
* yargs lays every block out the same way: two spaces, the name, two or more spaces, the description, and a type
|
|
39
|
+
* column at the end. A description too long for the terminal wraps onto lines indented past the name column, so
|
|
40
|
+
* those are folded back onto the entry they belong to instead of being read as another entry.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readHelp(text: string): HelpFacts;
|
|
43
|
+
/** Levenshtein distance, for "did you mean". */
|
|
44
|
+
export declare function distance(a: string, b: string): number;
|
|
45
|
+
/** The closest candidate within a small edit distance, or null when nothing is close enough to suggest. */
|
|
46
|
+
export declare function nearest(word: string, candidates: string[]): string | null;
|
|
47
|
+
export interface UsageError {
|
|
48
|
+
message: string;
|
|
49
|
+
hints: string[];
|
|
50
|
+
command: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Turn a yargs failure into the answer the person needed: what is missing or misspelled, in the command's own
|
|
54
|
+
* words, with its usage line and its first worked example underneath.
|
|
55
|
+
*/
|
|
56
|
+
export declare function explainUsageError(msg: string | null, err: Error | undefined, help: string, argv: string[]): UsageError;
|
|
57
|
+
//# sourceMappingURL=help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yCAAyC;IACzC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;IACzD,+FAA+F;IAC/F,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,sDAAsD;IACtD,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC/C;AAoBD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CA6DhD;AAED,gDAAgD;AAChD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAYrD;AAED,2GAA2G;AAC3G,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAQzE;AAED,MAAM,WAAW,UAAU;IAAG,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAIjF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA8EtH"}
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the CLI says when the command line itself is wrong (item 103).
|
|
3
|
+
*
|
|
4
|
+
* yargs' own answers are written for whoever wrote the parser: `Not enough non-option arguments: got 0, need at
|
|
5
|
+
* least 1` for `ainize publish`, `Unknown argument: statsu` for a one-character typo. Everything needed to answer
|
|
6
|
+
* properly is already in the command's own help — its usage line, its positionals, its options, its examples — so
|
|
7
|
+
* this module reads that help back and turns the parser's complaint into the sentence the person needed.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here is a second copy of the command tree: the facts come from the help text yargs prints for the level
|
|
10
|
+
* that failed, which is generated from the declarations in bin.ts.
|
|
11
|
+
*/
|
|
12
|
+
const BLOCK = /^(Commands|Options|Positionals|Examples):$/;
|
|
13
|
+
/** The `[string] [required] [default: 3]` column yargs prints after a description — a type, not a sentence. */
|
|
14
|
+
const TYPES = /(\s*\[(?:string|number|boolean|array|count|required|deprecated|hidden|default:[^\]]*|choices:[^\]]*)\])+$/;
|
|
15
|
+
/** `ainize patch publish <file>` → `ainize patch publish` (stop at the first `<positional>` or `[option]`). */
|
|
16
|
+
function pathOf(usage) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const tok of usage.split(/\s+/)) {
|
|
19
|
+
if (tok.startsWith('<') || tok.startsWith('['))
|
|
20
|
+
break;
|
|
21
|
+
out.push(tok);
|
|
22
|
+
}
|
|
23
|
+
return out.join(' ');
|
|
24
|
+
}
|
|
25
|
+
/** `ainize patch get <id>` in a Commands: block → `get`: the word that names the subcommand at this level. */
|
|
26
|
+
function commandWord(line, depth) {
|
|
27
|
+
const toks = line.trim().split(/\s+/);
|
|
28
|
+
const word = toks[depth];
|
|
29
|
+
return word && /^[a-z0-9][\w:-]*$/i.test(word) ? word : null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read a printed help block back into the facts an error message needs.
|
|
33
|
+
*
|
|
34
|
+
* yargs lays every block out the same way: two spaces, the name, two or more spaces, the description, and a type
|
|
35
|
+
* column at the end. A description too long for the terminal wraps onto lines indented past the name column, so
|
|
36
|
+
* those are folded back onto the entry they belong to instead of being read as another entry.
|
|
37
|
+
*/
|
|
38
|
+
export function readHelp(text) {
|
|
39
|
+
const lines = text.split('\n');
|
|
40
|
+
const usage = lines.find((l) => l.trim())?.trim() ?? '';
|
|
41
|
+
const path = pathOf(usage);
|
|
42
|
+
const depth = path.split(' ').filter(Boolean).length;
|
|
43
|
+
const facts = { usage, path, commands: [], options: [], positionals: [], describes: {}, example: null };
|
|
44
|
+
// the indented lines under each `Commands:` / `Options:` / `Positionals:` / `Examples:` heading
|
|
45
|
+
const blocks = new Map();
|
|
46
|
+
let cur = null;
|
|
47
|
+
for (const raw of lines) {
|
|
48
|
+
const line = raw.replace(/\s+$/, '');
|
|
49
|
+
const head = BLOCK.exec(line.trim());
|
|
50
|
+
if (head) {
|
|
51
|
+
cur = [];
|
|
52
|
+
blocks.set(head[1], cur);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!line.trim())
|
|
56
|
+
continue;
|
|
57
|
+
if (!line.startsWith(' ')) {
|
|
58
|
+
cur = null;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
cur?.push(line);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Where an entry starts, per block: an option always begins with a dash (` -h, --help` sits two columns left of
|
|
65
|
+
* ` --price`, so an indent rule would fold every long option into the short-alias one above it); a command
|
|
66
|
+
* and an example always begin with the program name; a positional is at the block's own left edge. Everything
|
|
67
|
+
* indented past that is a description too long for the terminal, and is folded back into the entry it belongs to.
|
|
68
|
+
*/
|
|
69
|
+
const prog = path.split(' ')[0];
|
|
70
|
+
const entriesOf = (block) => {
|
|
71
|
+
const src = blocks.get(block) ?? [];
|
|
72
|
+
if (!src.length)
|
|
73
|
+
return [];
|
|
74
|
+
const left = Math.min(...src.map((l) => l.length - l.trimStart().length));
|
|
75
|
+
const isEntry = (line) => {
|
|
76
|
+
const body = line.trim();
|
|
77
|
+
if (block === 'Options')
|
|
78
|
+
return /^-{1,2}[A-Za-z0-9]/.test(body);
|
|
79
|
+
if (block === 'Commands' || block === 'Examples')
|
|
80
|
+
return body.split(/\s+/)[0] === prog;
|
|
81
|
+
return line.length - line.trimStart().length <= left;
|
|
82
|
+
};
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const line of src) {
|
|
85
|
+
const body = line.trim();
|
|
86
|
+
const more = body.replace(TYPES, '').trim();
|
|
87
|
+
if (!isEntry(line) && out.length) {
|
|
88
|
+
const prev = out[out.length - 1];
|
|
89
|
+
// a wrapped description when there is one, otherwise the rest of a long command
|
|
90
|
+
if (prev.desc)
|
|
91
|
+
prev.desc = `${prev.desc} ${more}`.trim();
|
|
92
|
+
else
|
|
93
|
+
prev.name = `${prev.name} ${more}`.trim();
|
|
94
|
+
prev.raw = `${prev.raw} ${more}`.trim();
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const at = body.search(/\s{2,}/);
|
|
98
|
+
out.push({ name: (at === -1 ? body : body.slice(0, at)).trim(), desc: (at === -1 ? '' : body.slice(at)).replace(TYPES, '').trim(), raw: more });
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
};
|
|
102
|
+
for (const e of entriesOf('Commands')) {
|
|
103
|
+
const w = commandWord(e.raw, depth);
|
|
104
|
+
if (w && !facts.commands.includes(w))
|
|
105
|
+
facts.commands.push(w);
|
|
106
|
+
}
|
|
107
|
+
for (const e of entriesOf('Options')) {
|
|
108
|
+
for (const n of e.name.split(/,\s*/))
|
|
109
|
+
if (n.startsWith('-')) {
|
|
110
|
+
facts.options.push(n);
|
|
111
|
+
if (e.desc)
|
|
112
|
+
facts.describes[n] = e.desc;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const e of entriesOf('Positionals'))
|
|
116
|
+
facts.positionals.push({ name: e.name, describe: e.desc || null });
|
|
117
|
+
const ex = entriesOf('Examples')[0];
|
|
118
|
+
if (ex)
|
|
119
|
+
facts.example = { cmd: ex.name, desc: ex.desc };
|
|
120
|
+
return facts;
|
|
121
|
+
}
|
|
122
|
+
/** Levenshtein distance, for "did you mean". */
|
|
123
|
+
export function distance(a, b) {
|
|
124
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
125
|
+
for (let i = 1; i <= a.length; i++) {
|
|
126
|
+
let diag = prev[0];
|
|
127
|
+
prev[0] = i;
|
|
128
|
+
for (let j = 1; j <= b.length; j++) {
|
|
129
|
+
const t = prev[j];
|
|
130
|
+
prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
131
|
+
diag = t;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return prev[b.length];
|
|
135
|
+
}
|
|
136
|
+
/** The closest candidate within a small edit distance, or null when nothing is close enough to suggest. */
|
|
137
|
+
export function nearest(word, candidates) {
|
|
138
|
+
let best = null;
|
|
139
|
+
let bestD = Infinity;
|
|
140
|
+
for (const cand of candidates) {
|
|
141
|
+
const d = distance(word.toLowerCase(), cand.toLowerCase());
|
|
142
|
+
if (d < bestD && d <= Math.max(2, Math.floor(cand.length / 3))) {
|
|
143
|
+
best = cand;
|
|
144
|
+
bestD = d;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return best;
|
|
148
|
+
}
|
|
149
|
+
const list = (xs) => (xs.length < 2 ? xs.join('') : `${xs.slice(0, -1).join(', ')} and ${xs[xs.length - 1]}`);
|
|
150
|
+
/**
|
|
151
|
+
* Turn a yargs failure into the answer the person needed: what is missing or misspelled, in the command's own
|
|
152
|
+
* words, with its usage line and its first worked example underneath.
|
|
153
|
+
*/
|
|
154
|
+
export function explainUsageError(msg, err, help, argv) {
|
|
155
|
+
const f = readHelp(help);
|
|
156
|
+
const cmd = f.path || argv.filter((a) => !a.startsWith('-'))[0] || '';
|
|
157
|
+
const seeHelp = f.path ? `try \`${f.path} --help\`` : 'try `--help`';
|
|
158
|
+
const example = f.example ? [`example:`, ` ${f.example.cmd}${f.example.desc ? ` # ${f.example.desc}` : ''}`] : [];
|
|
159
|
+
const usageLine = f.usage ? [`usage: ${f.usage}`] : [];
|
|
160
|
+
// A `.check()` or a coercion threw: it already speaks the user's language.
|
|
161
|
+
if (err?.message)
|
|
162
|
+
return { message: err.message, hints: [seeHelp], command: cmd };
|
|
163
|
+
const m = msg ?? 'invalid command line';
|
|
164
|
+
// The word the user typed where a subcommand belongs — the one yargs' own "Did you mean" never names.
|
|
165
|
+
const typed = argv.filter((a) => !a.startsWith('-'));
|
|
166
|
+
const depth = f.path.split(' ').filter(Boolean).length - 1; // minus the program name
|
|
167
|
+
const typo = typed[depth] ?? typed[typed.length - 1] ?? null;
|
|
168
|
+
const recommend = /^Did you mean (.+)\?$/.exec(m);
|
|
169
|
+
if (recommend) {
|
|
170
|
+
const suggestion = recommend[1].replace(/\?$/, '');
|
|
171
|
+
return {
|
|
172
|
+
message: typo ? `unknown command "${typo}" — did you mean \`${f.path} ${suggestion}\`?` : `did you mean \`${f.path} ${suggestion}\`?`,
|
|
173
|
+
hints: [`\`${f.path} --help\` lists every command here.`],
|
|
174
|
+
command: cmd,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const unknown = /^Unknown arguments?: (.+)$/.exec(m);
|
|
178
|
+
if (unknown) {
|
|
179
|
+
const names = unknown[1].split(/,\s*/).map((s) => s.trim()).filter(Boolean);
|
|
180
|
+
const flags = names.filter((n) => argv.some((a) => a === `--${n}` || a === `-${n}` || a.startsWith(`--${n}=`)));
|
|
181
|
+
const words = names.filter((n) => !flags.includes(n));
|
|
182
|
+
const parts = [];
|
|
183
|
+
const hints = [];
|
|
184
|
+
for (const w of words) {
|
|
185
|
+
const near = nearest(w, f.commands);
|
|
186
|
+
parts.push(`unknown command "${w}"${near ? ` — did you mean \`${f.path} ${near}\`?` : ''}`);
|
|
187
|
+
}
|
|
188
|
+
for (const flag of flags) {
|
|
189
|
+
const near = nearest(`--${flag}`, f.options);
|
|
190
|
+
parts.push(`unknown option "--${flag}"${near ? ` — did you mean \`${near}\`?` : ''}`);
|
|
191
|
+
}
|
|
192
|
+
if (words.length && !words.some((w) => nearest(w, f.commands)))
|
|
193
|
+
hints.push(`\`${f.path} --help\` lists every command here.`);
|
|
194
|
+
if (!parts.length)
|
|
195
|
+
parts.push(m);
|
|
196
|
+
hints.push(seeHelp);
|
|
197
|
+
return { message: parts.join('; '), hints, command: cmd };
|
|
198
|
+
}
|
|
199
|
+
if (/^Not enough non-option arguments/.test(m)) {
|
|
200
|
+
const need = f.positionals.filter((p) => p.describe !== null);
|
|
201
|
+
const what = need.length ? list(need.map((p) => `<${p.name}>`)) : 'an argument';
|
|
202
|
+
const said = need.map((p) => `${p.name}: ${p.describe}`);
|
|
203
|
+
return { message: `\`${f.path}\` needs ${what}`, hints: [...said, ...usageLine, ...example, seeHelp], command: cmd };
|
|
204
|
+
}
|
|
205
|
+
const missing = /^Missing required arguments?: (.+)$/.exec(m);
|
|
206
|
+
if (missing) {
|
|
207
|
+
const names = missing[1].split(/,\s*/).map((s) => s.trim()).filter(Boolean);
|
|
208
|
+
const said = names.map((n) => (f.describes[`--${n}`] ? `--${n}: ${f.describes[`--${n}`]}` : null)).filter((x) => !!x);
|
|
209
|
+
return { message: `\`${f.path}\` needs ${list(names.map((n) => `--${n}`))}`, hints: [...said, ...usageLine, ...example, seeHelp], command: cmd };
|
|
210
|
+
}
|
|
211
|
+
// demandCommand: "Specify a command…" / "Subcommand is required (ls|add|rm)."
|
|
212
|
+
if (/command is required|Specify a command|Give a dataset file/i.test(m)) {
|
|
213
|
+
// `ainize --hepl` reaches demandCommand before strict() ever looks at the flag, and used to be reported as a
|
|
214
|
+
// missing command. An option that no longer exists is an option, and is named as one.
|
|
215
|
+
const bad = argv.filter((a) => /^--?[A-Za-z]/.test(a)).map((a) => a.split('=')[0])
|
|
216
|
+
.filter((a) => !f.options.includes(a) && !f.options.includes(a.replace(/^--no-/, '--')));
|
|
217
|
+
if (bad.length) {
|
|
218
|
+
const near = nearest(bad[0], f.options);
|
|
219
|
+
return { message: `unknown option "${bad[0]}"${near ? ` — did you mean \`${near}\`?` : ''}`, hints: [seeHelp], command: cmd };
|
|
220
|
+
}
|
|
221
|
+
// the demandCommand message already names them on most groups; only add the list when it does not
|
|
222
|
+
const hints = f.commands.length && !/\(.+\|.+\)/.test(m) ? [`commands here: ${f.commands.join(', ')}`, seeHelp] : [seeHelp];
|
|
223
|
+
return { message: m, hints, command: cmd };
|
|
224
|
+
}
|
|
225
|
+
return { message: m, hints: [seeHelp], command: cmd };
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=help.js.map
|