@steerable/agent-cli 0.6.74 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/approve.d.ts +17 -5
- package/dist/approve.js +35 -16
- package/dist/bin.js +6 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +129 -26
- package/dist/copy.d.ts +15 -0
- package/dist/copy.js +16 -0
- package/dist/help.d.ts +11 -0
- package/dist/help.js +35 -5
- package/dist/node-version.d.ts +17 -0
- package/dist/node-version.js +40 -0
- package/dist/project-binding.d.ts +35 -0
- package/dist/project-binding.js +151 -0
- package/dist/rules.d.ts +8 -0
- package/dist/rules.js +12 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.js +272 -14
- package/dist/tui/commands.js +1 -0
- package/dist/tui/run.d.ts +4 -0
- package/dist/tui/run.js +17 -2
- package/dist/tui/screen.d.ts +8 -0
- package/dist/tui/screen.js +20 -1
- package/dist/tui/session.d.ts +21 -0
- package/dist/tui/session.js +293 -19
- package/dist/tui/transcript.d.ts +4 -0
- package/dist/tui/transcript.js +6 -1
- package/dist/tui/view.js +5 -3
- package/package.json +9 -5
package/dist/approve.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type { ApprovalDecisionKind } from '@steerable/agent-client';
|
|
2
2
|
export type ApprovePolicy = 'deny' | 'allow-read' | 'allow-all';
|
|
3
|
+
export type ApprovalSurface = 'run' | 'tui';
|
|
4
|
+
export interface ApprovalSpec {
|
|
5
|
+
policy: ApprovePolicy | 'ask';
|
|
6
|
+
answerAsk: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveApprovalRequest(raw: string | undefined, surface: ApprovalSurface): ApprovalSpec | null;
|
|
3
9
|
export declare function parseApprovePolicy(raw: string | undefined): ApprovePolicy | null;
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
/**
|
|
11
|
+
* `mode` is the declared tool mode. A missing mode is not treated as read.
|
|
12
|
+
* `prompt` means the TUI should show the card.
|
|
13
|
+
*/
|
|
14
|
+
export declare function decisionFor(spec: ApprovalSpec, prompt: {
|
|
15
|
+
mode?: string;
|
|
16
|
+
}, surface: ApprovalSurface): ApprovalDecisionKind | 'prompt';
|
|
17
|
+
export declare function unattendedAskText(): string;
|
|
18
|
+
export declare function autoAnswer(questions: {
|
|
19
|
+
id: string;
|
|
20
|
+
}[], text?: string): Record<string, string>;
|
|
9
21
|
export declare function denied(kind: ApprovalDecisionKind): boolean;
|
package/dist/approve.js
CHANGED
|
@@ -1,25 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function parseApprovePolicy(raw) {
|
|
8
|
-
if (raw === undefined || raw === 'deny' || raw === 'allow-read' || raw === 'allow-all') {
|
|
9
|
-
return raw ?? 'deny';
|
|
1
|
+
import { cliText } from './copy.js';
|
|
2
|
+
export function resolveApprovalRequest(raw, surface) {
|
|
3
|
+
if (raw === undefined) {
|
|
4
|
+
return surface === 'tui'
|
|
5
|
+
? { policy: 'ask', answerAsk: false }
|
|
6
|
+
: { policy: 'deny', answerAsk: false };
|
|
10
7
|
}
|
|
11
|
-
|
|
8
|
+
if (raw !== 'deny' && raw !== 'allow-read' && raw !== 'allow-all')
|
|
9
|
+
return null;
|
|
10
|
+
return { policy: raw, answerAsk: raw === 'allow-all' };
|
|
11
|
+
}
|
|
12
|
+
export function parseApprovePolicy(raw) {
|
|
13
|
+
const spec = resolveApprovalRequest(raw, 'run');
|
|
14
|
+
if (!spec || spec.policy === 'ask')
|
|
15
|
+
return null;
|
|
16
|
+
return spec.policy;
|
|
12
17
|
}
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* `mode` is the declared tool mode. A missing mode is not treated as read.
|
|
20
|
+
* `prompt` means the TUI should show the card.
|
|
21
|
+
*/
|
|
22
|
+
export function decisionFor(spec, prompt, surface) {
|
|
23
|
+
if (spec.policy === 'allow-all')
|
|
16
24
|
return 'allow_once';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (policy === 'allow-read' &&
|
|
25
|
+
if (spec.policy === 'ask')
|
|
26
|
+
return 'prompt';
|
|
27
|
+
if (spec.policy === 'allow-read' && prompt.mode === 'read')
|
|
20
28
|
return 'allow_once';
|
|
29
|
+
if (spec.policy === 'allow-read' && surface === 'tui')
|
|
30
|
+
return 'prompt';
|
|
21
31
|
return 'deny_once';
|
|
22
32
|
}
|
|
33
|
+
export function unattendedAskText() {
|
|
34
|
+
return cliText('ask.unattended');
|
|
35
|
+
}
|
|
36
|
+
export function autoAnswer(questions, text = unattendedAskText()) {
|
|
37
|
+
const answers = {};
|
|
38
|
+
for (const question of questions)
|
|
39
|
+
answers[question.id] = text;
|
|
40
|
+
return answers;
|
|
41
|
+
}
|
|
23
42
|
export function denied(kind) {
|
|
24
43
|
return kind === 'deny_once' || kind === 'deny_for_session' || kind === 'deny_always' || kind === 'abort';
|
|
25
44
|
}
|
package/dist/bin.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
process.
|
|
2
|
+
import { ensureSupportedNode } from './node-version.js';
|
|
3
|
+
const status = ensureSupportedNode(process.versions.node, process.stderr, 'steerable-cli');
|
|
4
|
+
if (status !== 0)
|
|
5
|
+
process.exit(status);
|
|
6
|
+
const { createCli } = await import('./cli.js');
|
|
7
|
+
process.exit(await createCli({ installSignals: true, commandName: 'steerable-cli' }));
|
package/dist/cli.d.ts
CHANGED
|
@@ -10,5 +10,7 @@ export interface CliOptions {
|
|
|
10
10
|
createClient?: (options: LocalClientOptions) => Promise<AgentClient>;
|
|
11
11
|
installSignals?: boolean;
|
|
12
12
|
commands?: readonly CliCommandSpec[];
|
|
13
|
+
/** Bin name written in place of `<product>` in help and protected-directory text. */
|
|
14
|
+
commandName?: string;
|
|
13
15
|
}
|
|
14
16
|
export declare function createCli(options?: CliOptions): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,11 @@ import { restoreLogConsole, silenceLogConsole } from '@steerable/agent-shell/log
|
|
|
5
5
|
import { collectPackCliCommands, } from '@steerable/agent-shell/host/pack-cli';
|
|
6
6
|
import { getUserDataDir } from '@steerable/agent-shell/runtime';
|
|
7
7
|
import { applyHostRuntimeEnv, createLocalClient, listBusyChatIds, } from '@steerable/agent-client';
|
|
8
|
-
import {
|
|
8
|
+
import { nodeDoctorLine } from './node-version.js';
|
|
9
|
+
import { parseApprovePolicy, resolveApprovalRequest } from './approve.js';
|
|
10
|
+
import { defaultProjectFs, projectStatusLabel, protectedTuiMessage, resolveProjectRequest, } from './project-binding.js';
|
|
9
11
|
import { chatCommand, configCommand, mcpCommand, skillsCommand } from './commands.js';
|
|
10
|
-
import { chatHelp, configHelp, doctorHelp, mcpHelp, rootHelp, runHelp, skillsHelp, tuiHelp } from './help.js';
|
|
12
|
+
import { chatHelp, configHelp, doctorHelp, mcpHelp, renderCommandName, rootHelp, runHelp, skillsHelp, tuiHelp } from './help.js';
|
|
11
13
|
import { runTurn } from './run.js';
|
|
12
14
|
import { runTui } from './tui/run.js';
|
|
13
15
|
export async function createCli(options = {}) {
|
|
@@ -47,6 +49,9 @@ async function runCli(options) {
|
|
|
47
49
|
json: { type: 'boolean' },
|
|
48
50
|
'stream-json': { type: 'boolean' },
|
|
49
51
|
cwd: { type: 'string' },
|
|
52
|
+
project: { type: 'string' },
|
|
53
|
+
'no-project': { type: 'boolean', default: false },
|
|
54
|
+
trust: { type: 'boolean', default: false },
|
|
50
55
|
timeout: { type: 'string' },
|
|
51
56
|
'data-dir': { type: 'string' },
|
|
52
57
|
agent: { type: 'string' },
|
|
@@ -56,12 +61,12 @@ async function runCli(options) {
|
|
|
56
61
|
});
|
|
57
62
|
}
|
|
58
63
|
catch (error) {
|
|
59
|
-
stderr.write(`${error instanceof Error ? error.message : String(error)}\n${rootHelp(packCommands)}\n`);
|
|
64
|
+
stderr.write(`${error instanceof Error ? error.message : String(error)}\n${present(options, rootHelp(packCommands))}\n`);
|
|
60
65
|
return 2;
|
|
61
66
|
}
|
|
62
67
|
const command = parsed.positionals[0];
|
|
63
68
|
if (parsed.values.help && !command) {
|
|
64
|
-
stdout.write(`${rootHelp(packCommands)}\n`);
|
|
69
|
+
stdout.write(`${present(options, rootHelp(packCommands))}\n`);
|
|
65
70
|
return 0;
|
|
66
71
|
}
|
|
67
72
|
if (!command)
|
|
@@ -69,13 +74,13 @@ async function runCli(options) {
|
|
|
69
74
|
if (command === 'run')
|
|
70
75
|
return runCommand(parsed, options, stdout, stderr);
|
|
71
76
|
if (command === 'chat')
|
|
72
|
-
return dispatch(parsed, options, stdout, stderr, chatHelp(), chatCommand);
|
|
77
|
+
return dispatch(parsed, options, stdout, stderr, present(options, chatHelp()), chatCommand);
|
|
73
78
|
if (command === 'skills')
|
|
74
|
-
return dispatch(parsed, options, stdout, stderr, skillsHelp(), skillsCommand);
|
|
79
|
+
return dispatch(parsed, options, stdout, stderr, present(options, skillsHelp()), skillsCommand);
|
|
75
80
|
if (command === 'mcp')
|
|
76
|
-
return dispatch(parsed, options, stdout, stderr, mcpHelp(), mcpCommand);
|
|
81
|
+
return dispatch(parsed, options, stdout, stderr, present(options, mcpHelp()), mcpCommand);
|
|
77
82
|
if (command === 'config')
|
|
78
|
-
return dispatch(parsed, options, stdout, stderr, configHelp(), configCommand);
|
|
83
|
+
return dispatch(parsed, options, stdout, stderr, present(options, configHelp()), configCommand);
|
|
79
84
|
if (command === 'doctor')
|
|
80
85
|
return doctorCommand(parsed, options, stdout, stderr);
|
|
81
86
|
if (command === 'tui')
|
|
@@ -83,56 +88,68 @@ async function runCli(options) {
|
|
|
83
88
|
const pack = packCommands.find((entry) => entry.name === command);
|
|
84
89
|
if (pack)
|
|
85
90
|
return runPackCommand(pack, parsed, options, stdout, stderr);
|
|
86
|
-
stderr.write(`unknown command: ${command}\n${rootHelp(packCommands)}\n`);
|
|
91
|
+
stderr.write(`unknown command: ${command}\n${present(options, rootHelp(packCommands))}\n`);
|
|
87
92
|
return 2;
|
|
88
93
|
}
|
|
89
94
|
async function runPackCommand(command, parsed, options, stdout, stderr) {
|
|
90
95
|
if (parsed.values.help) {
|
|
91
|
-
stdout.write(`<product> ${command.name}\n${command.summary}\n`);
|
|
96
|
+
stdout.write(present(options, `<product> ${command.name}\n${command.summary}\n`));
|
|
92
97
|
return 0;
|
|
93
98
|
}
|
|
94
99
|
return withClient(parsed, options, async (client) => command.run(parsed.positionals.slice(1), {
|
|
95
|
-
write: (text) => stdout.write(text),
|
|
96
|
-
writeError: (text) => stderr.write(text),
|
|
100
|
+
write: (text) => stdout.write(present(options, text)),
|
|
101
|
+
writeError: (text) => stderr.write(present(options, text)),
|
|
97
102
|
request: (method, requestPath, body) => client.request(method, requestPath, body),
|
|
98
103
|
}), stderr);
|
|
99
104
|
}
|
|
100
105
|
async function runCommand(parsed, options, stdout, stderr) {
|
|
101
106
|
if (parsed.values.help) {
|
|
102
|
-
stdout.write(`${runHelp()}\n`);
|
|
107
|
+
stdout.write(`${present(options, runHelp())}\n`);
|
|
103
108
|
return 0;
|
|
104
109
|
}
|
|
105
110
|
const policy = parseApprovePolicy(parsed.values.approve);
|
|
106
111
|
if (!policy) {
|
|
107
|
-
stderr.write(`unknown --approve value\n${runHelp()}\n`);
|
|
112
|
+
stderr.write(`unknown --approve value\n${present(options, runHelp())}\n`);
|
|
108
113
|
return 2;
|
|
109
114
|
}
|
|
110
115
|
let timeoutMs;
|
|
111
116
|
if (typeof parsed.values.timeout === 'string') {
|
|
112
117
|
const parsedTimeout = parseTimeout(parsed.values.timeout);
|
|
113
118
|
if (parsedTimeout === null) {
|
|
114
|
-
stderr.write(`invalid --timeout\n${runHelp()}\n`);
|
|
119
|
+
stderr.write(`invalid --timeout\n${present(options, runHelp())}\n`);
|
|
115
120
|
return 2;
|
|
116
121
|
}
|
|
117
122
|
timeoutMs = parsedTimeout;
|
|
118
123
|
}
|
|
119
124
|
const task = await readTask(parsed.positionals.slice(1), options);
|
|
120
125
|
if (!task.trim()) {
|
|
121
|
-
stderr.write(`missing task\n${runHelp()}\n`);
|
|
126
|
+
stderr.write(`missing task\n${present(options, runHelp())}\n`);
|
|
122
127
|
return 2;
|
|
123
128
|
}
|
|
124
129
|
const request = {
|
|
125
130
|
task: task.trim(),
|
|
126
131
|
...(typeof parsed.values.chat === 'string' ? { chatId: parsed.values.chat } : {}),
|
|
127
132
|
...(typeof parsed.values.agent === 'string' ? { agentId: parsed.values.agent } : {}),
|
|
128
|
-
...(typeof parsed.values.cwd === 'string' ? { cwd: parsed.values.cwd } : {}),
|
|
129
133
|
files: parsed.values.file ?? [],
|
|
130
134
|
policy,
|
|
131
135
|
json: parsed.values.json === true,
|
|
132
136
|
streamJson: parsed.values['stream-json'] === true,
|
|
133
137
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
134
138
|
};
|
|
135
|
-
|
|
139
|
+
const spec = projectSpec(parsed);
|
|
140
|
+
if (spec.kind === 'error') {
|
|
141
|
+
stderr.write(`${present(options, spec.message)}\n`);
|
|
142
|
+
return spec.code;
|
|
143
|
+
}
|
|
144
|
+
return withClient(parsed, options, async (client, signal) => {
|
|
145
|
+
const bound = await bindAndTrust(client, parsed, spec, stderr);
|
|
146
|
+
if (typeof bound === 'number')
|
|
147
|
+
return bound;
|
|
148
|
+
return runTurn(client, {
|
|
149
|
+
...request,
|
|
150
|
+
...(bound ? { projectId: bound.id } : {}),
|
|
151
|
+
}, stdout, stderr, signal);
|
|
152
|
+
});
|
|
136
153
|
}
|
|
137
154
|
function commandIO(parsed) {
|
|
138
155
|
return {
|
|
@@ -147,29 +164,54 @@ async function dispatch(parsed, options, stdout, stderr, help, command) {
|
|
|
147
164
|
}
|
|
148
165
|
async function tuiCommand(parsed, options, stdout, stderr) {
|
|
149
166
|
if (parsed.values.help) {
|
|
150
|
-
stdout.write(`${tuiHelp()}\n`);
|
|
167
|
+
stdout.write(`${present(options, tuiHelp())}\n`);
|
|
151
168
|
return 0;
|
|
152
169
|
}
|
|
170
|
+
const approval = resolveApprovalRequest(typeof parsed.values.approve === 'string' ? parsed.values.approve : undefined, 'tui');
|
|
171
|
+
if (!approval) {
|
|
172
|
+
stderr.write(`unknown --approve value\n${present(options, tuiHelp())}\n`);
|
|
173
|
+
return 2;
|
|
174
|
+
}
|
|
153
175
|
const tty = options.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
154
176
|
if (!tty) {
|
|
155
|
-
stderr.write(`tui needs a terminal. Use run for piped input.\n${tuiHelp()}\n`);
|
|
177
|
+
stderr.write(`tui needs a terminal. Use run for piped input.\n${present(options, tuiHelp())}\n`);
|
|
156
178
|
return 2;
|
|
157
179
|
}
|
|
180
|
+
const spec = projectSpec(parsed);
|
|
181
|
+
if (spec.kind === 'error') {
|
|
182
|
+
if (spec.reason === 'protected' && spec.protectedKind) {
|
|
183
|
+
stderr.write(`${present(options, protectedTuiMessage(spec.protectedKind))}\n`);
|
|
184
|
+
stderr.write('q 退出\n');
|
|
185
|
+
await waitForQuit(options.stdin ?? process.stdin);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
stderr.write(`${present(options, spec.message)}\n`);
|
|
189
|
+
}
|
|
190
|
+
return spec.code;
|
|
191
|
+
}
|
|
158
192
|
if ((options.stdout ?? process.stdout) === process.stdout)
|
|
159
193
|
silenceLogConsole('all');
|
|
160
|
-
return withClient(parsed, options, async (client) =>
|
|
161
|
-
client,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
194
|
+
return withClient(parsed, options, async (client) => {
|
|
195
|
+
const bound = await bindAndTrust(client, parsed, spec, stderr);
|
|
196
|
+
if (typeof bound === 'number')
|
|
197
|
+
return bound;
|
|
198
|
+
return runTui({
|
|
199
|
+
client,
|
|
200
|
+
product: getBrand().displayName,
|
|
201
|
+
dataDir: dataDirFrom(parsed, options.env ?? process.env),
|
|
202
|
+
...(bound ? { projectId: bound.id, projectLabel: bound.label } : { projectLabel: projectStatusLabel(null) }),
|
|
203
|
+
approvalPolicy: approval,
|
|
204
|
+
});
|
|
205
|
+
}, stderr);
|
|
165
206
|
}
|
|
166
207
|
async function doctorCommand(parsed, options, stdout, stderr) {
|
|
167
208
|
if (parsed.values.help) {
|
|
168
|
-
stdout.write(`${doctorHelp()}\n`);
|
|
209
|
+
stdout.write(`${present(options, doctorHelp())}\n`);
|
|
169
210
|
return 0;
|
|
170
211
|
}
|
|
171
212
|
const dataDir = dataDirFrom(parsed, options.env ?? process.env);
|
|
172
213
|
const report = applyHostRuntimeEnv(dataDir, options.env ?? process.env);
|
|
214
|
+
stdout.write(nodeDoctorLine(process.versions.node));
|
|
173
215
|
stdout.write(`data dir: ${dataDir}\n`);
|
|
174
216
|
stdout.write(`log file: ${path.join(dataDir, 'logs', 'main.log')}\n`);
|
|
175
217
|
stdout.write(`runtime file: ${report.fileFound ? report.filePath : 'missing'}\n`);
|
|
@@ -230,6 +272,64 @@ async function withClient(parsed, options, body, stderr = options.stderr ?? proc
|
|
|
230
272
|
await client?.close();
|
|
231
273
|
}
|
|
232
274
|
}
|
|
275
|
+
function projectSpec(parsed) {
|
|
276
|
+
const fs = defaultProjectFs();
|
|
277
|
+
return resolveProjectRequest({
|
|
278
|
+
cwd: process.cwd(),
|
|
279
|
+
...(typeof parsed.values.project === 'string' ? { projectArg: parsed.values.project } : {}),
|
|
280
|
+
...(typeof parsed.values.cwd === 'string' ? { cwdArg: parsed.values.cwd } : {}),
|
|
281
|
+
noProject: parsed.values['no-project'] === true,
|
|
282
|
+
...fs,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async function bindProject(client, spec, stderr) {
|
|
286
|
+
if (spec.kind === 'none')
|
|
287
|
+
return null;
|
|
288
|
+
if (spec.kind === 'error')
|
|
289
|
+
return spec.code;
|
|
290
|
+
const response = await client.request('POST', '/api/v2/projects/resolve', {
|
|
291
|
+
folderPath: spec.dir,
|
|
292
|
+
origin: 'cli',
|
|
293
|
+
});
|
|
294
|
+
const data = response.data;
|
|
295
|
+
if (response.status === 403) {
|
|
296
|
+
stderr.write('该产品未启用项目模式,请使用 --no-project。\n');
|
|
297
|
+
return 2;
|
|
298
|
+
}
|
|
299
|
+
const project = data.project;
|
|
300
|
+
if (response.status !== 200 || !project?.id || !project.name || !project.folderPath) {
|
|
301
|
+
stderr.write(`${data.error ?? `failed to bind project (${response.status})`}\n`);
|
|
302
|
+
return 2;
|
|
303
|
+
}
|
|
304
|
+
if (data.created)
|
|
305
|
+
stderr.write(`已绑定项目 ${project.name}:${project.folderPath}\n`);
|
|
306
|
+
return {
|
|
307
|
+
id: project.id,
|
|
308
|
+
label: projectStatusLabel({ name: project.name, folderPath: project.folderPath }),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async function bindAndTrust(client, parsed, spec, stderr) {
|
|
312
|
+
if (parsed.values.trust === true && spec.kind === 'none') {
|
|
313
|
+
stderr.write('--trust 需要绑定项目,不能与 --no-project 同用。\n');
|
|
314
|
+
return 2;
|
|
315
|
+
}
|
|
316
|
+
const bound = await bindProject(client, spec, stderr);
|
|
317
|
+
if (typeof bound === 'number' || !bound || parsed.values.trust !== true)
|
|
318
|
+
return bound;
|
|
319
|
+
const trusted = await client.request('PUT', `/api/v2/projects/${encodeURIComponent(bound.id)}/trust`, { trusted: true });
|
|
320
|
+
if (trusted.status !== 200) {
|
|
321
|
+
const data = trusted.data;
|
|
322
|
+
stderr.write(`${data.error ?? `failed to trust project (${trusted.status})`}\n`);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
return bound;
|
|
326
|
+
}
|
|
327
|
+
async function waitForQuit(stdin) {
|
|
328
|
+
for await (const chunk of stdin) {
|
|
329
|
+
if (String(chunk).includes('q'))
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
233
333
|
function dataDirFrom(parsed, env) {
|
|
234
334
|
if (typeof parsed.values['data-dir'] === 'string')
|
|
235
335
|
return parsed.values['data-dir'];
|
|
@@ -237,6 +337,9 @@ function dataDirFrom(parsed, env) {
|
|
|
237
337
|
return env.DEEPPATH_USER_DATA_DIR;
|
|
238
338
|
return getUserDataDir();
|
|
239
339
|
}
|
|
340
|
+
function present(options, text) {
|
|
341
|
+
return renderCommandName(text, options.commandName);
|
|
342
|
+
}
|
|
240
343
|
function parseTimeout(raw) {
|
|
241
344
|
const match = /^(\d+)(ms|s|m)?$/.exec(raw.trim());
|
|
242
345
|
if (!match)
|
package/dist/copy.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command-line copy. Product packs do not override these strings.
|
|
3
|
+
*/
|
|
4
|
+
export declare const cliCopy: {
|
|
5
|
+
readonly 'ask.unattended': "无人可答,请按最合理的默认继续。";
|
|
6
|
+
readonly 'cli.approveAllWarning': "警告:--approve allow-all 会放行本次运行中的全部审批";
|
|
7
|
+
readonly 'tui.approvalClosed': "审批已关闭";
|
|
8
|
+
readonly 'tui.approvalRead': "只读自动放行";
|
|
9
|
+
readonly 'tui.toolAuto': "自动";
|
|
10
|
+
readonly 'tui.toolAutoAnswer': "自动回答";
|
|
11
|
+
readonly 'tui.toolSessionAllow': "本会话已允许";
|
|
12
|
+
};
|
|
13
|
+
export type CliCopyKey = keyof typeof cliCopy;
|
|
14
|
+
/** @param key Dictionary key. @returns The command-line string for that key. */
|
|
15
|
+
export declare function cliText(key: CliCopyKey): string;
|
package/dist/copy.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command-line copy. Product packs do not override these strings.
|
|
3
|
+
*/
|
|
4
|
+
export const cliCopy = {
|
|
5
|
+
'ask.unattended': '无人可答,请按最合理的默认继续。',
|
|
6
|
+
'cli.approveAllWarning': '警告:--approve allow-all 会放行本次运行中的全部审批',
|
|
7
|
+
'tui.approvalClosed': '审批已关闭',
|
|
8
|
+
'tui.approvalRead': '只读自动放行',
|
|
9
|
+
'tui.toolAuto': '自动',
|
|
10
|
+
'tui.toolAutoAnswer': '自动回答',
|
|
11
|
+
'tui.toolSessionAllow': '本会话已允许',
|
|
12
|
+
};
|
|
13
|
+
/** @param key Dictionary key. @returns The command-line string for that key. */
|
|
14
|
+
export function cliText(key) {
|
|
15
|
+
return cliCopy[key];
|
|
16
|
+
}
|
package/dist/help.d.ts
CHANGED
|
@@ -9,3 +9,14 @@ export declare function mcpHelp(): string;
|
|
|
9
9
|
export declare function configHelp(): string;
|
|
10
10
|
export declare function tuiHelp(): string;
|
|
11
11
|
export declare function doctorHelp(): string;
|
|
12
|
+
/** Placeholder left in help text until a product bin supplies its command name. */
|
|
13
|
+
export declare const COMMAND_NAME_TOKEN = "<product>";
|
|
14
|
+
/**
|
|
15
|
+
* Replaces {@link COMMAND_NAME_TOKEN} with the running bin name.
|
|
16
|
+
* A blank name, the token itself, or a name that is not a command token is left unchanged.
|
|
17
|
+
*
|
|
18
|
+
* @param text Help or protected-directory text.
|
|
19
|
+
* @param commandName Bin name such as `aroli` or `steerable-cli`.
|
|
20
|
+
* @returns Text with the placeholder replaced when the name is usable.
|
|
21
|
+
*/
|
|
22
|
+
export declare function renderCommandName(text: string, commandName: string | undefined): string;
|
package/dist/help.js
CHANGED
|
@@ -19,8 +19,8 @@ export function rootHelp(commands = []) {
|
|
|
19
19
|
return `
|
|
20
20
|
Usage:
|
|
21
21
|
<product> run "<task>" [--chat <id>] [--approve deny|allow-read|allow-all]
|
|
22
|
-
[--json | --stream-json] [--
|
|
23
|
-
[--data-dir <dir>] [--agent <id>] [--file <path>...]
|
|
22
|
+
[--json | --stream-json] [--project <dir> | --cwd <dir> | --no-project] [--trust]
|
|
23
|
+
[--timeout <dur>] [--data-dir <dir>] [--agent <id>] [--file <path>...]
|
|
24
24
|
<product> chat list | show <id> | rm <id> | export <id> [--format md|json]
|
|
25
25
|
<product> skills list
|
|
26
26
|
<product> mcp list | add <name> -- <cmd...> | rm <name>
|
|
@@ -35,9 +35,15 @@ export function runHelp() {
|
|
|
35
35
|
return `
|
|
36
36
|
Usage:
|
|
37
37
|
<product> run "<task>" [--chat <id>] [--approve deny|allow-read|allow-all]
|
|
38
|
-
[--json | --stream-json] [--
|
|
38
|
+
[--json | --stream-json] [--project <dir> | --cwd <dir> | --no-project] [--trust]
|
|
39
|
+
[--data-dir <dir>]
|
|
39
40
|
|
|
40
41
|
--approve deny is the default. A denied approval exits 3. A busy chat exits 6.
|
|
42
|
+
run binds the current directory as a project. --project and --cwd name that directory.
|
|
43
|
+
--no-project keeps a workspace per chat.
|
|
44
|
+
--trust marks that project trusted and loads its rule files. It cannot be combined with --no-project.
|
|
45
|
+
STEERABLE_APPROVAL=0 turns the approval layer off for this process. A destructive tool
|
|
46
|
+
is then rejected by the tool router. Use --approve allow-all to allow those tools instead.
|
|
41
47
|
|
|
42
48
|
${EXAMPLES}
|
|
43
49
|
`.trim();
|
|
@@ -96,9 +102,15 @@ Examples:
|
|
|
96
102
|
export function tuiHelp() {
|
|
97
103
|
return `
|
|
98
104
|
Usage:
|
|
99
|
-
<product> tui [--
|
|
105
|
+
<product> tui [--project <dir> | --cwd <dir> | --no-project] [--trust]
|
|
106
|
+
[--approve deny|allow-read|allow-all] [--data-dir <dir>]
|
|
100
107
|
|
|
101
|
-
Starts the terminal interface
|
|
108
|
+
Starts the terminal interface in the current directory's project.
|
|
109
|
+
--no-project keeps a workspace per chat.
|
|
110
|
+
--trust marks that project trusted and loads its rule files. It cannot be combined with --no-project.
|
|
111
|
+
STEERABLE_APPROVAL=0 turns the approval layer off for this process. A destructive tool
|
|
112
|
+
is then rejected by the tool router. Use --approve allow-all to allow those tools instead.
|
|
113
|
+
When stdin is not a terminal, use run instead.
|
|
102
114
|
|
|
103
115
|
/new /model /clear /help
|
|
104
116
|
Ctrl+C interrupts the current turn. Ctrl+L lists chats.
|
|
@@ -116,3 +128,21 @@ Examples:
|
|
|
116
128
|
<product> doctor
|
|
117
129
|
`.trim();
|
|
118
130
|
}
|
|
131
|
+
/** Placeholder left in help text until a product bin supplies its command name. */
|
|
132
|
+
export const COMMAND_NAME_TOKEN = '<product>';
|
|
133
|
+
/**
|
|
134
|
+
* Replaces {@link COMMAND_NAME_TOKEN} with the running bin name.
|
|
135
|
+
* A blank name, the token itself, or a name that is not a command token is left unchanged.
|
|
136
|
+
*
|
|
137
|
+
* @param text Help or protected-directory text.
|
|
138
|
+
* @param commandName Bin name such as `aroli` or `steerable-cli`.
|
|
139
|
+
* @returns Text with the placeholder replaced when the name is usable.
|
|
140
|
+
*/
|
|
141
|
+
export function renderCommandName(text, commandName) {
|
|
142
|
+
const name = commandName?.trim() ?? '';
|
|
143
|
+
if (!name || name === COMMAND_NAME_TOKEN)
|
|
144
|
+
return text;
|
|
145
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name))
|
|
146
|
+
return text;
|
|
147
|
+
return text.replaceAll(COMMAND_NAME_TOKEN, name);
|
|
148
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimum Node for the command line.
|
|
3
|
+
*
|
|
4
|
+
* `@opentui/core` 0.5.12 declares `engines.node` `>=26.4.0`. Its FFI packages
|
|
5
|
+
* and the rest of the agent-cli dependency tree do not declare a higher
|
|
6
|
+
* minimum. This module imports nothing: the bin loads it before the CLI.
|
|
7
|
+
*/
|
|
8
|
+
export declare const REQUIRED_NODE = "26.4.0";
|
|
9
|
+
export interface NodeVersionWriter {
|
|
10
|
+
write(chunk: string): unknown;
|
|
11
|
+
}
|
|
12
|
+
/** @param current Installed version, with or without a leading `v`. @param required `major.minor.patch`. @returns Whether `current` is at least `required`. */
|
|
13
|
+
export declare function satisfies(current: string, required: string): boolean;
|
|
14
|
+
/** @param current Installed version. @param stderr Where the failure text is written. @param product Command name, such as `aroli`. @returns `0` when the version is new enough, otherwise `1`. */
|
|
15
|
+
export declare function ensureSupportedNode(current: string, stderr: NodeVersionWriter, product: string): number;
|
|
16
|
+
/** @param current Installed version. @returns One doctor row, including whether it meets {@link REQUIRED_NODE}. */
|
|
17
|
+
export declare function nodeDoctorLine(current: string): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimum Node for the command line.
|
|
3
|
+
*
|
|
4
|
+
* `@opentui/core` 0.5.12 declares `engines.node` `>=26.4.0`. Its FFI packages
|
|
5
|
+
* and the rest of the agent-cli dependency tree do not declare a higher
|
|
6
|
+
* minimum. This module imports nothing: the bin loads it before the CLI.
|
|
7
|
+
*/
|
|
8
|
+
export const REQUIRED_NODE = '26.4.0';
|
|
9
|
+
/** @param current Installed version, with or without a leading `v`. @param required `major.minor.patch`. @returns Whether `current` is at least `required`. */
|
|
10
|
+
export function satisfies(current, required) {
|
|
11
|
+
const left = readVersion(current);
|
|
12
|
+
const right = readVersion(required);
|
|
13
|
+
if (!left || !right)
|
|
14
|
+
return false;
|
|
15
|
+
for (let index = 0; index < 3; index += 1) {
|
|
16
|
+
if (left[index] !== right[index])
|
|
17
|
+
return left[index] > right[index];
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
/** @param current Installed version. @param stderr Where the failure text is written. @param product Command name, such as `aroli`. @returns `0` when the version is new enough, otherwise `1`. */
|
|
22
|
+
export function ensureSupportedNode(current, stderr, product) {
|
|
23
|
+
if (satisfies(current, REQUIRED_NODE))
|
|
24
|
+
return 0;
|
|
25
|
+
const shown = current.replace(/^v/, '');
|
|
26
|
+
stderr.write(`${product} 需要 Node.js ${REQUIRED_NODE} 或更高版本,当前为 ${shown}。\n升级方法:https://nodejs.org/\n`);
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
/** @param current Installed version. @returns One doctor row, including whether it meets {@link REQUIRED_NODE}. */
|
|
30
|
+
export function nodeDoctorLine(current) {
|
|
31
|
+
const shown = current.replace(/^v/, '');
|
|
32
|
+
const mark = satisfies(current, REQUIRED_NODE) ? '✓' : '✗';
|
|
33
|
+
return `Node.js: ${shown}(需要 ≥ ${REQUIRED_NODE}) ${mark}\n`;
|
|
34
|
+
}
|
|
35
|
+
function readVersion(value) {
|
|
36
|
+
const match = /v?(\d+)\.(\d+)\.(\d+)/.exec(value.trim());
|
|
37
|
+
if (!match)
|
|
38
|
+
return null;
|
|
39
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
40
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ProjectRequest {
|
|
2
|
+
cwd: string;
|
|
3
|
+
projectArg?: string;
|
|
4
|
+
cwdArg?: string;
|
|
5
|
+
noProject: boolean;
|
|
6
|
+
home: string;
|
|
7
|
+
platform: NodeJS.Platform;
|
|
8
|
+
/** Windows `%SystemRoot%`. Other platforms ignore it. */
|
|
9
|
+
systemRoot?: string;
|
|
10
|
+
realpath: (target: string) => string;
|
|
11
|
+
isDirectory: (target: string) => boolean;
|
|
12
|
+
}
|
|
13
|
+
export type ProtectedDir = 'home' | 'root' | 'system';
|
|
14
|
+
export type ProjectSpec = {
|
|
15
|
+
kind: 'none';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'bind';
|
|
18
|
+
dir: string;
|
|
19
|
+
explicit: boolean;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'error';
|
|
22
|
+
code: 2;
|
|
23
|
+
message: string;
|
|
24
|
+
reason?: 'protected';
|
|
25
|
+
protectedKind?: ProtectedDir;
|
|
26
|
+
};
|
|
27
|
+
export declare function resolveProjectRequest(request: ProjectRequest): ProjectSpec;
|
|
28
|
+
export declare function shortenProjectPath(folder: string): string;
|
|
29
|
+
export declare function projectStatusLabel(project: {
|
|
30
|
+
name: string;
|
|
31
|
+
folderPath: string;
|
|
32
|
+
} | null): string;
|
|
33
|
+
export declare function protectedRunMessage(kind: ProtectedDir): string;
|
|
34
|
+
export declare function protectedTuiMessage(kind: ProtectedDir): string;
|
|
35
|
+
export declare function defaultProjectFs(): Pick<ProjectRequest, 'realpath' | 'isDirectory' | 'home' | 'platform' | 'systemRoot'>;
|