@3minapi/cli 0.0.0-stage → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/dist/bin.js +54 -0
- package/dist/cli.js +241 -0
- package/dist/commands/auth.js +573 -0
- package/dist/commands/common.js +48 -0
- package/dist/commands/dashboard.js +298 -0
- package/dist/commands/delete.js +167 -0
- package/dist/commands/doctor.js +326 -0
- package/dist/commands/guide.js +84 -0
- package/dist/commands/index.js +28 -0
- package/dist/commands/init.js +248 -0
- package/dist/commands/logs.js +154 -0
- package/dist/commands/new.js +109 -0
- package/dist/commands/paths.js +64 -0
- package/dist/commands/reload.js +32 -0
- package/dist/commands/results.js +445 -0
- package/dist/commands/start.js +443 -0
- package/dist/commands/stop.js +68 -0
- package/dist/commands/tasks.js +93 -0
- package/dist/commands/worker.js +178 -0
- package/dist/core/api.js +132 -0
- package/dist/core/config.js +188 -0
- package/dist/core/control-db.js +462 -0
- package/dist/core/credentials.js +75 -0
- package/dist/core/domain/constants.js +121 -0
- package/dist/core/domain/contract.js +102 -0
- package/dist/core/domain/errors.js +57 -0
- package/dist/core/domain/index.js +3 -0
- package/dist/core/domain/types.js +1 -0
- package/dist/core/error-report.js +103 -0
- package/dist/core/host/detach.js +49 -0
- package/dist/core/host/live-connection.js +270 -0
- package/dist/core/host/live-receiver.js +264 -0
- package/dist/core/host/module.js +1 -0
- package/dist/core/host/poller.js +113 -0
- package/dist/core/host/pull-step.js +54 -0
- package/dist/core/host/receiver.js +1 -0
- package/dist/core/host/reload.js +34 -0
- package/dist/core/host/runner-lock.js +245 -0
- package/dist/core/host/session.js +84 -0
- package/dist/core/host/usage.js +20 -0
- package/dist/core/log-read.js +59 -0
- package/dist/core/logger.js +206 -0
- package/dist/core/names.js +27 -0
- package/dist/core/network.js +10 -0
- package/dist/core/open-browser.js +26 -0
- package/dist/core/output.js +68 -0
- package/dist/core/paths.js +26 -0
- package/dist/core/project.js +85 -0
- package/dist/core/prompt.js +140 -0
- package/dist/core/redact.js +45 -0
- package/dist/core/secure-fs.js +50 -0
- package/dist/core/text-file.js +12 -0
- package/dist/core/text.js +27 -0
- package/dist/core/time.js +84 -0
- package/dist/core/update-notice.js +142 -0
- package/dist/core/version.js +16 -0
- package/dist/core/warnings.js +10 -0
- package/dist/help.js +144 -0
- package/dist/modules/dashboard/assets.js +411 -0
- package/dist/modules/dashboard/data.js +565 -0
- package/dist/modules/dashboard/guard.js +75 -0
- package/dist/modules/dashboard/lock.js +93 -0
- package/dist/modules/dashboard/server.js +295 -0
- package/dist/modules/dashboard/views/html.js +114 -0
- package/dist/modules/dashboard/views/layout.js +59 -0
- package/dist/modules/dashboard/views/logs.js +122 -0
- package/dist/modules/dashboard/views/overview.js +316 -0
- package/dist/modules/dashboard/views/runs.js +486 -0
- package/dist/modules/dashboard/views/workers.js +157 -0
- package/dist/modules/workers/builtin/host-identity.js +59 -0
- package/dist/modules/workers/builtin/results-worker.js +37 -0
- package/dist/modules/workers/local-server.js +244 -0
- package/dist/modules/workers/module.js +98 -0
- package/dist/modules/workers/reload-result.js +16 -0
- package/dist/modules/workers/repeat-scheduler.js +165 -0
- package/dist/modules/workers/results/missing-paths.js +66 -0
- package/dist/modules/workers/results/query.js +175 -0
- package/dist/modules/workers/results/reader-input.js +134 -0
- package/dist/modules/workers/results/reader.js +430 -0
- package/dist/modules/workers/results/results-child.js +64 -0
- package/dist/modules/workers/results/shape.js +212 -0
- package/dist/modules/workers/results/store.js +336 -0
- package/dist/modules/workers/results/substitute.js +30 -0
- package/dist/modules/workers/runtime/env.js +43 -0
- package/dist/modules/workers/runtime/error-chain.js +109 -0
- package/dist/modules/workers/runtime/executor.js +289 -0
- package/dist/modules/workers/runtime/inspect.js +53 -0
- package/dist/modules/workers/runtime/protocol.js +1 -0
- package/dist/modules/workers/runtime/resolve.js +30 -0
- package/dist/modules/workers/runtime/schema.js +85 -0
- package/dist/modules/workers/runtime/thread.js +117 -0
- package/dist/modules/workers/start.js +265 -0
- package/dist/modules/workers/sync.js +241 -0
- package/dist/modules/workers/task-runner.js +892 -0
- package/dist/modules/workers/template.js +100 -0
- package/dist/modules/workers/worker-set.js +122 -0
- package/dist/modules/workers/workers.js +66 -0
- package/package.json +48 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maverick Works
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,89 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 3minapi
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run your own machine as a [3Min API](https://3minapi.com) host. An AI you work with (ChatGPT, Claude and other
|
|
4
|
+
MCP clients) creates tasks; this CLI fetches them and runs them with worker files you write — plain JavaScript or
|
|
5
|
+
TypeScript functions on your PC.
|
|
6
|
+
|
|
7
|
+
- Node.js **24.12 or newer**, no runtime dependencies, no install scripts
|
|
8
|
+
- Works on macOS, Linux and Windows
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install -g @3minapi/cli
|
|
14
|
+
3minapi --version
|
|
15
|
+
3minapi doctor # checks Node.js, config, network and proxy settings
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`npx @3minapi/cli <command>` runs it without installing. On Windows PowerShell, if scripts are disabled, type
|
|
19
|
+
`3minapi.cmd` in place of `3minapi`.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
1. **Create a host token** on the dashboard: [3minapi.com/dashboard/tasks/local](https://3minapi.com/dashboard/tasks/local).
|
|
24
|
+
It is shown once.
|
|
25
|
+
2. **Log in** — the prompt is masked; the token is saved only on this machine.
|
|
26
|
+
```sh
|
|
27
|
+
3minapi login
|
|
28
|
+
```
|
|
29
|
+
3. **Create the project** (default `~/3minapi`) and a worker:
|
|
30
|
+
```sh
|
|
31
|
+
3minapi init
|
|
32
|
+
3minapi worker new hello
|
|
33
|
+
3minapi worker run hello --input '{"message":"hi"}' # runs locally, no API calls
|
|
34
|
+
```
|
|
35
|
+
4. **Start the host** — registers the workers and runs tasks until stopped:
|
|
36
|
+
```sh
|
|
37
|
+
3minapi start # or: 3minapi start --detach, then 3minapi stop
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Ask your AI to create a task for the `hello` worker; `3minapi status` and `3minapi logs` show what happened.
|
|
41
|
+
|
|
42
|
+
## A worker
|
|
43
|
+
|
|
44
|
+
One file in `workers/` is one worker. The AI reads `description`; `handler` gets the input the AI sent.
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
export const description = 'Says hello. input: { message: string }';
|
|
48
|
+
|
|
49
|
+
export async function handler(input, ctx) {
|
|
50
|
+
ctx.log(`got ${input.message}`);
|
|
51
|
+
return { reply: `hello, ${input.message}` };
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Returning completes the task with that value; throwing fails it with the error message. `ctx.signal` aborts on
|
|
56
|
+
cancel or timeout. `3minapi worker new` writes a template that documents every option.
|
|
57
|
+
|
|
58
|
+
## Using it from an AI coding agent
|
|
59
|
+
|
|
60
|
+
`3minapi init` writes `AGENTS.md` (and a `CLAUDE.md` that imports it) into the project, so Claude Code, Codex and
|
|
61
|
+
other agents know which files to edit, which commands to run and which files never to touch. The same text is
|
|
62
|
+
printed by:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
3minapi guide
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Every command takes `--json` for machine-readable output. Log in yourself — never paste the host token into a chat.
|
|
69
|
+
|
|
70
|
+
## Update
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
3minapi stop # if start is running
|
|
74
|
+
npm install -g @3minapi/cli@latest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The CLI tells you when a newer version is recommended, and stops with exit code 6 when it is too old to connect.
|
|
78
|
+
|
|
79
|
+
## Where releases come from
|
|
80
|
+
|
|
81
|
+
Releases are built and published by GitHub Actions from the public repository
|
|
82
|
+
[maverick-works-dev/3minapi-cli](https://github.com/maverick-works-dev/3minapi-cli), with npm provenance. The
|
|
83
|
+
**Provenance** section on the npm package page links each version to the commit and workflow run that built it.
|
|
84
|
+
|
|
85
|
+
## Links
|
|
86
|
+
|
|
87
|
+
- Guide: [3minapi.com](https://3minapi.com)
|
|
88
|
+
- Security policy: [SECURITY.md](SECURITY.md)
|
|
89
|
+
- License: [MIT](LICENSE)
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { EXIT_GRACE_MS, MIN_NODE_VERSION } from "./core/domain/constants.js";
|
|
3
|
+
import { raiseConnectAttemptTimeout } from "./core/network.js";
|
|
4
|
+
import { satisfiesMinNode } from "./core/version.js";
|
|
5
|
+
import { suppressSqliteExperimentalWarning } from "./core/warnings.js";
|
|
6
|
+
if (!satisfiesMinNode(process.versions.node)) {
|
|
7
|
+
const message = `3minapi requires Node.js ${MIN_NODE_VERSION} or later (you have ${process.versions.node}).\n` +
|
|
8
|
+
'Install the current Node.js 24 LTS from https://nodejs.org and try again.';
|
|
9
|
+
process.stderr.write(`error: ${message}\n`);
|
|
10
|
+
if (process.argv.includes('--json'))
|
|
11
|
+
process.stdout.write(JSON.stringify({ error: { kind: 'config', message, exitCode: 3 } }, null, 2) + '\n');
|
|
12
|
+
process.exit(3);
|
|
13
|
+
}
|
|
14
|
+
suppressSqliteExperimentalWarning();
|
|
15
|
+
raiseConnectAttemptTimeout({ execArgv: process.execArgv, nodeOptions: process.env.NODE_OPTIONS });
|
|
16
|
+
const interrupt = new AbortController();
|
|
17
|
+
let claimed = false;
|
|
18
|
+
const onSignal = () => {
|
|
19
|
+
if (claimed && !interrupt.signal.aborted)
|
|
20
|
+
interrupt.abort();
|
|
21
|
+
else
|
|
22
|
+
process.exit(130);
|
|
23
|
+
};
|
|
24
|
+
process.on('SIGINT', onSignal);
|
|
25
|
+
process.on('SIGTERM', onSignal);
|
|
26
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
27
|
+
stream.on('error', (err) => {
|
|
28
|
+
if (err.code === 'EPIPE')
|
|
29
|
+
process.exit(0);
|
|
30
|
+
throw err;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const { main } = await import("./cli.js");
|
|
34
|
+
process.exitCode = await main(process.argv.slice(2), {
|
|
35
|
+
stdout: process.stdout,
|
|
36
|
+
stderr: process.stderr,
|
|
37
|
+
stdin: process.stdin,
|
|
38
|
+
env: process.env,
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
platform: process.platform,
|
|
41
|
+
home: (await import('node:os')).homedir(),
|
|
42
|
+
signal: interrupt.signal,
|
|
43
|
+
claimInterrupt: () => {
|
|
44
|
+
claimed = true;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const exitWhenFlushed = () => {
|
|
48
|
+
if (process.stdout.writableLength > 0 || process.stderr.writableLength > 0) {
|
|
49
|
+
setTimeout(exitWhenFlushed, EXIT_GRACE_MS).unref();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
process.exit();
|
|
53
|
+
};
|
|
54
|
+
setTimeout(exitWhenFlushed, EXIT_GRACE_MS).unref();
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { commands } from "./commands/index.js";
|
|
4
|
+
import { loadConfig } from "./core/config.js";
|
|
5
|
+
import { describeError, errorDocument } from "./core/error-report.js";
|
|
6
|
+
import { BIN, CliError, ExitCode, UsageError } from "./core/domain/index.js";
|
|
7
|
+
import { Output } from "./core/output.js";
|
|
8
|
+
import { resolveDirs } from "./core/paths.js";
|
|
9
|
+
import { resolveProject, writeProjectRecord } from "./core/project.js";
|
|
10
|
+
import { GLOBAL_OPTIONS, commandHelp, commandHelpJson, groupHelp, groupHelpJson, rootHelp, rootHelpJson, suggest } from "./help.js";
|
|
11
|
+
export const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
12
|
+
export async function main(argv, io) {
|
|
13
|
+
const out = new Output({
|
|
14
|
+
stdout: io.stdout,
|
|
15
|
+
stderr: io.stderr,
|
|
16
|
+
env: io.env,
|
|
17
|
+
json: argv.includes('--json'),
|
|
18
|
+
color: argv.includes('--no-color') ? false : argv.includes('--color') ? true : undefined
|
|
19
|
+
});
|
|
20
|
+
try {
|
|
21
|
+
return await dispatch(argv, io, out);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const report = describeError(err);
|
|
25
|
+
if (report.kind === 'bug') {
|
|
26
|
+
out.error(`unexpected error: ${report.stack}`);
|
|
27
|
+
out.info('This is a bug in the CLI. Please report it with the output above.');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
out.error(report.message);
|
|
31
|
+
}
|
|
32
|
+
out.errorResult(errorDocument(report));
|
|
33
|
+
return report.exitCode;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function dispatch(argv, io, out) {
|
|
37
|
+
const index = commandIndex(argv);
|
|
38
|
+
const name = index === -1 ? undefined : argv[index];
|
|
39
|
+
let rest = index === -1 ? argv : [...argv.slice(0, index), ...argv.slice(index + 1)];
|
|
40
|
+
const showRoot = () => out.result(rootHelpJson(commands, VERSION), rootHelp(commands, VERSION));
|
|
41
|
+
const showCommand = (c) => c.subcommands
|
|
42
|
+
? out.result(groupHelpJson(c), groupHelp(c))
|
|
43
|
+
: out.result(commandHelpJson(c), commandHelp(c));
|
|
44
|
+
if (name === undefined) {
|
|
45
|
+
const { values } = parse(rest, GLOBAL_OPTIONS, false, null);
|
|
46
|
+
if (values.version) {
|
|
47
|
+
out.result({ version: VERSION }, VERSION);
|
|
48
|
+
return ExitCode.Ok;
|
|
49
|
+
}
|
|
50
|
+
showRoot();
|
|
51
|
+
return ExitCode.Ok;
|
|
52
|
+
}
|
|
53
|
+
if (name === 'help') {
|
|
54
|
+
const { positionals } = parse(rest, GLOBAL_OPTIONS, true, null);
|
|
55
|
+
if (positionals.length === 0) {
|
|
56
|
+
showRoot();
|
|
57
|
+
return ExitCode.Ok;
|
|
58
|
+
}
|
|
59
|
+
let target = findCommand(positionals[0]);
|
|
60
|
+
for (const p of positionals.slice(1)) {
|
|
61
|
+
if (!target.subcommands)
|
|
62
|
+
break;
|
|
63
|
+
target = findSub(target, p);
|
|
64
|
+
}
|
|
65
|
+
showCommand(target);
|
|
66
|
+
return ExitCode.Ok;
|
|
67
|
+
}
|
|
68
|
+
let cmd = findCommand(name);
|
|
69
|
+
while (cmd.subcommands) {
|
|
70
|
+
const i = commandIndex(rest);
|
|
71
|
+
if (i === -1) {
|
|
72
|
+
showCommand(cmd);
|
|
73
|
+
return ExitCode.Ok;
|
|
74
|
+
}
|
|
75
|
+
const sub = findSub(cmd, rest[i]);
|
|
76
|
+
rest = [...rest.slice(0, i), ...rest.slice(i + 1)];
|
|
77
|
+
cmd = sub;
|
|
78
|
+
}
|
|
79
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
80
|
+
showCommand(cmd);
|
|
81
|
+
return ExitCode.Ok;
|
|
82
|
+
}
|
|
83
|
+
const { values, positionals } = parse(rest, { ...GLOBAL_OPTIONS, ...cmd.options }, true, cmd.name);
|
|
84
|
+
if (values.version) {
|
|
85
|
+
out.result({ version: VERSION }, VERSION);
|
|
86
|
+
return ExitCode.Ok;
|
|
87
|
+
}
|
|
88
|
+
if (positionals.length < cmd.args.min || positionals.length > cmd.args.max) {
|
|
89
|
+
throw new UsageError(`usage: ${BIN} ${cmd.name} ${cmd.usage}\nRun '${BIN} help ${cmd.name}' for details.`);
|
|
90
|
+
}
|
|
91
|
+
const dirs = resolveDirs({ platform: io.platform, env: io.env, home: io.home });
|
|
92
|
+
let resolved;
|
|
93
|
+
let configWarned = false;
|
|
94
|
+
const ctx = {
|
|
95
|
+
positionals,
|
|
96
|
+
values,
|
|
97
|
+
out,
|
|
98
|
+
stdin: io.stdin,
|
|
99
|
+
env: io.env,
|
|
100
|
+
cwd: io.cwd,
|
|
101
|
+
home: io.home,
|
|
102
|
+
platform: io.platform,
|
|
103
|
+
dirs,
|
|
104
|
+
version: VERSION,
|
|
105
|
+
now: io.now ?? Date.now,
|
|
106
|
+
signal: io.signal ?? new AbortController().signal,
|
|
107
|
+
claimInterrupt: () => io.claimInterrupt?.(),
|
|
108
|
+
chdir: io.chdir ?? ((dir) => process.chdir(dir)),
|
|
109
|
+
loadConfig: () => {
|
|
110
|
+
const config = loadConfig({
|
|
111
|
+
cwd: io.cwd,
|
|
112
|
+
env: io.env,
|
|
113
|
+
configDir: dirs.config,
|
|
114
|
+
configFile: values.config,
|
|
115
|
+
...(values.config === undefined ? { projectFile: hostProject() } : {}),
|
|
116
|
+
flags: configFlags(values, cmd.name)
|
|
117
|
+
});
|
|
118
|
+
if (!configWarned && cmd.name !== 'doctor') {
|
|
119
|
+
configWarned = true;
|
|
120
|
+
for (const w of config.warnings)
|
|
121
|
+
out.warn(w);
|
|
122
|
+
}
|
|
123
|
+
return config;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
return cmd.run(ctx);
|
|
127
|
+
function hostProject() {
|
|
128
|
+
if (resolved !== undefined)
|
|
129
|
+
return resolved;
|
|
130
|
+
const r = resolveProject({
|
|
131
|
+
cwd: io.cwd,
|
|
132
|
+
dataDir: dirs.data,
|
|
133
|
+
platform: io.platform,
|
|
134
|
+
home: io.home
|
|
135
|
+
});
|
|
136
|
+
if (r.record !== null) {
|
|
137
|
+
try {
|
|
138
|
+
writeProjectRecord(dirs.data, r.record, (io.now ?? Date.now)());
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
out.warn(`could not record this host's project: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const notice of r.notices)
|
|
145
|
+
out.warn(notice);
|
|
146
|
+
resolved = r.projectFile;
|
|
147
|
+
return resolved;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function configFlags(values, command) {
|
|
151
|
+
const flags = {};
|
|
152
|
+
if (typeof values['api-url'] === 'string')
|
|
153
|
+
flags.apiUrl = values['api-url'];
|
|
154
|
+
if (command === 'start' && typeof values.port === 'string')
|
|
155
|
+
flags.localServerPort = values.port;
|
|
156
|
+
if (command === 'start' && typeof values.mode === 'string')
|
|
157
|
+
flags.receiveMode = values.mode;
|
|
158
|
+
return flags;
|
|
159
|
+
}
|
|
160
|
+
function commandIndex(argv) {
|
|
161
|
+
for (let i = 0; i < argv.length; i++) {
|
|
162
|
+
const arg = argv[i];
|
|
163
|
+
if (arg === '--')
|
|
164
|
+
return i + 1 < argv.length ? i + 1 : -1;
|
|
165
|
+
if (!arg.startsWith('-') || arg === '-')
|
|
166
|
+
return i;
|
|
167
|
+
const long = arg.startsWith('--') ? arg.slice(2) : undefined;
|
|
168
|
+
if (long !== undefined && !long.includes('=') && GLOBAL_OPTIONS[long]?.type === 'string')
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
return -1;
|
|
172
|
+
}
|
|
173
|
+
function findCommand(name) {
|
|
174
|
+
const cmd = commands.find((c) => c.name === name);
|
|
175
|
+
if (cmd)
|
|
176
|
+
return cmd;
|
|
177
|
+
const guess = suggest(name, commands.map((c) => c.name));
|
|
178
|
+
throw new UsageError(`unknown command "${name}".` +
|
|
179
|
+
(guess ? ` Did you mean "${guess}"?` : '') +
|
|
180
|
+
`\nRun '${BIN} --help' to see all commands.`);
|
|
181
|
+
}
|
|
182
|
+
function findSub(group, name) {
|
|
183
|
+
const subs = group.subcommands ?? [];
|
|
184
|
+
const short = (c) => c.name.slice(group.name.length + 1);
|
|
185
|
+
const sub = subs.find((c) => short(c) === name);
|
|
186
|
+
if (sub)
|
|
187
|
+
return sub;
|
|
188
|
+
const guess = suggest(name, subs.map(short));
|
|
189
|
+
throw new UsageError(`unknown command "${group.name} ${name}".` +
|
|
190
|
+
(guess ? ` Did you mean "${group.name} ${guess}"?` : '') +
|
|
191
|
+
`\nRun '${BIN} ${group.name} --help' to see its commands.`);
|
|
192
|
+
}
|
|
193
|
+
function parse(args, options, allowPositionals, command) {
|
|
194
|
+
const config = Object.fromEntries(Object.entries(options).map(([k, { description: _d, valueName: _v, negated: _n, ...spec }]) => [
|
|
195
|
+
k,
|
|
196
|
+
spec
|
|
197
|
+
]));
|
|
198
|
+
let parsed;
|
|
199
|
+
try {
|
|
200
|
+
parsed = parseArgs({
|
|
201
|
+
args,
|
|
202
|
+
options: config,
|
|
203
|
+
strict: true,
|
|
204
|
+
allowPositionals,
|
|
205
|
+
allowNegative: true,
|
|
206
|
+
tokens: true
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
if (err instanceof TypeError &&
|
|
211
|
+
'code' in err &&
|
|
212
|
+
String(err.code).startsWith('ERR_PARSE_ARGS')) {
|
|
213
|
+
throw new UsageError(parseErrorMessage(err, String(err.code), options, command));
|
|
214
|
+
}
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
for (const token of parsed.tokens) {
|
|
218
|
+
if (token.kind !== 'option' || !token.rawName.startsWith('--no-'))
|
|
219
|
+
continue;
|
|
220
|
+
const spec = options[token.name];
|
|
221
|
+
if (spec?.negated || token.name === 'color')
|
|
222
|
+
continue;
|
|
223
|
+
throw new UsageError(parseErrorMessage(new Error(`Unknown option '${token.rawName}'`), 'ERR_PARSE_ARGS_UNKNOWN_OPTION', options, command));
|
|
224
|
+
}
|
|
225
|
+
return { values: parsed.values, positionals: parsed.positionals };
|
|
226
|
+
}
|
|
227
|
+
function parseErrorMessage(err, code, options, command) {
|
|
228
|
+
const help = command ? `Run '${BIN} help ${command}' for its options.` : `Run '${BIN} --help'.`;
|
|
229
|
+
const quoted = /'([^']+)'/.exec(err.message)?.[1];
|
|
230
|
+
if (code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION' && quoted) {
|
|
231
|
+
const names = Object.entries(options).flatMap(([name, spec]) => [
|
|
232
|
+
`--${name}`,
|
|
233
|
+
...(spec.short ? [`-${spec.short}`] : []),
|
|
234
|
+
...(spec.negated || name === 'color' ? [`--no-${name}`] : [])
|
|
235
|
+
]);
|
|
236
|
+
const guess = suggest(quoted, names);
|
|
237
|
+
return `unknown option "${quoted}".${guess ? ` Did you mean "${guess}"?` : ''}\n${help}`;
|
|
238
|
+
}
|
|
239
|
+
const first = err.message.split(/\.\s/)[0].replace(/\.$/, '');
|
|
240
|
+
return `${first}.\n${help}`;
|
|
241
|
+
}
|