@dinoxx/dinox-cli 2.13.1 → 2.15.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/dist/ai/ragxClient.d.ts +15 -0
- package/dist/ai/ragxClient.js +71 -0
- package/dist/cli.js +31 -4
- package/dist/commands/auth/index.d.ts +2 -1
- package/dist/commands/auth/index.js +18 -1
- package/dist/commands/batch.d.ts +35 -0
- package/dist/commands/batch.js +202 -0
- package/dist/commands/boxes/index.js +21 -21
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.js +20 -1
- package/dist/commands/graph/index.js +2 -1
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +47 -0
- package/dist/commands/notes/actions.js +18 -2
- package/dist/commands/notes/contentPatch.js +2 -1
- package/dist/commands/notes/descriptors.js +111 -5
- package/dist/commands/notes/detailContent.d.ts +19 -0
- package/dist/commands/notes/detailContent.js +62 -0
- package/dist/commands/notes/export.js +7 -2
- package/dist/commands/notes/repo/search.d.ts +2 -1
- package/dist/commands/notes/repo/search.js +1 -1
- package/dist/commands/notes/repo/semantic.d.ts +14 -0
- package/dist/commands/notes/repo/semantic.js +31 -0
- package/dist/commands/notes/searchResponse.d.ts +3 -0
- package/dist/commands/notes/searchResponse.js +1 -0
- package/dist/commands/notes/semantic.d.ts +116 -0
- package/dist/commands/notes/semantic.js +332 -0
- package/dist/commands/prompt/index.js +7 -5
- package/dist/commands/runtime.d.ts +8 -0
- package/dist/commands/runtime.js +49 -1
- package/dist/commands/savedViews/index.js +12 -3
- package/dist/commands/schema/globalOptions.d.ts +7 -0
- package/dist/commands/schema/globalOptions.js +7 -0
- package/dist/commands/schema/renderSkills.js +6 -2
- package/dist/commands/schema/spec.js +20 -1
- package/dist/commands/storage/index.js +7 -5
- package/dist/commands/storage/repo/operations.js +3 -1
- package/dist/commands/sync.d.ts +2 -1
- package/dist/commands/sync.js +27 -8
- package/dist/commands/tags/index.js +21 -21
- package/dist/commands/todo/index.js +22 -2
- package/dist/commands/todo/repo.d.ts +5 -0
- package/dist/commands/todo/repo.js +7 -2
- package/dist/commands/writeReceipt.d.ts +13 -0
- package/dist/commands/writeReceipt.js +12 -0
- package/dist/config/serviceEndpoints.d.ts +2 -0
- package/dist/config/serviceEndpoints.js +2 -0
- package/dist/daemon/client.d.ts +2 -0
- package/dist/daemon/client.js +53 -3
- package/dist/daemon/connectivity.d.ts +20 -0
- package/dist/daemon/connectivity.js +42 -0
- package/dist/daemon/ensure.js +48 -12
- package/dist/daemon/forward.d.ts +37 -0
- package/dist/daemon/forward.js +155 -0
- package/dist/daemon/fresh.d.ts +4 -0
- package/dist/daemon/fresh.js +10 -0
- package/dist/daemon/index.d.ts +11 -6
- package/dist/daemon/index.js +90 -25
- package/dist/daemon/launch.d.ts +13 -1
- package/dist/daemon/launch.js +31 -2
- package/dist/daemon/lifecycle.d.ts +3 -1
- package/dist/daemon/lifecycle.js +27 -3
- package/dist/daemon/pid.d.ts +8 -0
- package/dist/daemon/pid.js +20 -0
- package/dist/daemon/route.js +2 -1
- package/dist/daemon/server.d.ts +45 -1
- package/dist/daemon/server.js +192 -4
- package/dist/daemon/supervise.d.ts +6 -6
- package/dist/daemon/supervise.js +12 -10
- package/dist/daemon/supervisor.d.ts +21 -5
- package/dist/daemon/supervisor.js +125 -6
- package/dist/daemon/types.d.ts +22 -0
- package/dist/dinox.js +43 -64
- package/dist/gateway/deviceHost.js +2 -1
- package/dist/gateway/mcpCommandTools.d.ts +24 -5
- package/dist/gateway/mcpCommandTools.js +70 -29
- package/dist/gateway/mcpEnv.d.ts +2 -0
- package/dist/gateway/mcpEnv.js +2 -0
- package/dist/gateway/mcpGroups.d.ts +3 -0
- package/dist/gateway/mcpGroups.js +4 -0
- package/dist/gateway/mcpServer.d.ts +10 -1
- package/dist/gateway/mcpServer.js +25 -5
- package/dist/powersync/runtime.d.ts +2 -0
- package/dist/powersync/runtime.js +3 -1
- package/dist/powersync/syncPass.js +13 -1
- package/dist/powersync/tokenIndex.d.ts +2 -0
- package/dist/powersync/tokenIndex.js +1 -0
- package/dist/requests/execute.d.ts +11 -0
- package/dist/requests/execute.js +26 -0
- package/dist/requests/store.d.ts +10 -0
- package/dist/requests/store.js +190 -0
- package/dist/runCli.d.ts +2 -0
- package/dist/runCli.js +64 -0
- package/dist/skills/commandRefs.js +2 -10
- package/dist/utils/argValue.js +2 -1
- package/dist/utils/cursor.d.ts +9 -0
- package/dist/utils/cursor.js +61 -0
- package/dist/utils/defaultFormat.d.ts +8 -0
- package/dist/utils/defaultFormat.js +38 -0
- package/dist/utils/errorCodes.d.ts +24 -0
- package/dist/utils/errorCodes.js +6 -0
- package/dist/utils/output.d.ts +4 -0
- package/dist/utils/output.js +72 -10
- package/dist/utils/progress.d.ts +25 -0
- package/dist/utils/progress.js +59 -0
- package/dist/utils/requestContext.d.ts +32 -0
- package/dist/utils/requestContext.js +13 -0
- package/dist/utils/select.d.ts +20 -0
- package/dist/utils/select.js +92 -0
- package/dist/utils/usageError.d.ts +37 -0
- package/dist/utils/usageError.js +127 -0
- package/package.json +2 -1
|
@@ -8,6 +8,8 @@ import { DinoxError } from '../utils/errors.js';
|
|
|
8
8
|
/** Set in the service definition so the daemon can report who supervises it. */
|
|
9
9
|
export const DAEMON_SERVICE_ENV = 'DINOX_DAEMON_SUPERVISOR';
|
|
10
10
|
const DEFAULT_LABEL = 'com.dinox.cli.daemon';
|
|
11
|
+
/** Windows: file in the daemon directory telling the supervisor not to restart. */
|
|
12
|
+
export const SUPERVISOR_STOP_MARKER = 'supervisor.stop';
|
|
11
13
|
const COMMAND_TIMEOUT_MS = 15_000;
|
|
12
14
|
const LAUNCHD_BOOTSTRAP_ATTEMPTS = 5;
|
|
13
15
|
const LAUNCHD_BOOTSTRAP_RETRY_MS = 300;
|
|
@@ -54,6 +56,21 @@ function xmlEscape(value) {
|
|
|
54
56
|
.replace(/>/g, '>')
|
|
55
57
|
.replace(/"/g, '"');
|
|
56
58
|
}
|
|
59
|
+
function xmlUnescape(value) {
|
|
60
|
+
return value
|
|
61
|
+
.replace(/</g, '<')
|
|
62
|
+
.replace(/>/g, '>')
|
|
63
|
+
.replace(/"/g, '"')
|
|
64
|
+
.replace(/'/g, "'")
|
|
65
|
+
.replace(/&/g, '&');
|
|
66
|
+
}
|
|
67
|
+
/** ProgramArguments of a plist written by renderLaunchdPlist. */
|
|
68
|
+
export function parseLaunchdProgramArguments(plist) {
|
|
69
|
+
const array = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(plist);
|
|
70
|
+
if (!array)
|
|
71
|
+
return null;
|
|
72
|
+
return [...array[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((match) => xmlUnescape(match[1]));
|
|
73
|
+
}
|
|
57
74
|
export function renderLaunchdPlist(label, spec) {
|
|
58
75
|
const string = (value) => `<string>${xmlEscape(value)}</string>`;
|
|
59
76
|
const args = [spec.program, ...spec.args].map((arg) => `\t\t${string(arg)}`).join('\n');
|
|
@@ -80,10 +97,7 @@ ${env}
|
|
|
80
97
|
\t<key>RunAtLoad</key>
|
|
81
98
|
\t<true/>
|
|
82
99
|
\t<key>KeepAlive</key>
|
|
83
|
-
\t<
|
|
84
|
-
\t\t<key>SuccessfulExit</key>
|
|
85
|
-
\t\t<false/>
|
|
86
|
-
\t</dict>
|
|
100
|
+
\t<true/>
|
|
87
101
|
\t<key>ThrottleInterval</key>
|
|
88
102
|
\t<integer>30</integer>
|
|
89
103
|
\t<key>StandardOutPath</key>
|
|
@@ -117,6 +131,10 @@ class LaunchdService {
|
|
|
117
131
|
isCurrent(spec) {
|
|
118
132
|
return readDefinition(this.definitionPath) === this.render(spec);
|
|
119
133
|
}
|
|
134
|
+
installedCommand() {
|
|
135
|
+
const plist = readDefinition(this.definitionPath);
|
|
136
|
+
return plist === null ? null : parseLaunchdProgramArguments(plist);
|
|
137
|
+
}
|
|
120
138
|
async isLoaded() {
|
|
121
139
|
return (await this.run('launchctl', ['print', this.target])).code === 0;
|
|
122
140
|
}
|
|
@@ -147,6 +165,18 @@ class LaunchdService {
|
|
|
147
165
|
if (result.code !== 0)
|
|
148
166
|
throw failed(this.kind, 'kickstart', result);
|
|
149
167
|
}
|
|
168
|
+
async stop() {
|
|
169
|
+
const result = await this.run('launchctl', ['bootout', this.target]);
|
|
170
|
+
if (result.code !== 0 && await this.isLoaded())
|
|
171
|
+
throw failed(this.kind, 'bootout', result);
|
|
172
|
+
}
|
|
173
|
+
async restart() {
|
|
174
|
+
if (!(await this.isLoaded()))
|
|
175
|
+
await this.bootstrap();
|
|
176
|
+
const result = await this.run('launchctl', ['kickstart', '-k', this.target]);
|
|
177
|
+
if (result.code !== 0)
|
|
178
|
+
throw failed(this.kind, 'kickstart', result);
|
|
179
|
+
}
|
|
150
180
|
async uninstall() {
|
|
151
181
|
const installed = this.isInstalled();
|
|
152
182
|
if (await this.isLoaded()) {
|
|
@@ -170,6 +200,16 @@ function systemdQuote(value) {
|
|
|
170
200
|
.replace(/\n/g, '\\n');
|
|
171
201
|
return `"${escaped}"`;
|
|
172
202
|
}
|
|
203
|
+
/** The words of an ExecStart line written by renderSystemdUnit. */
|
|
204
|
+
export function parseSystemdExecStart(unit) {
|
|
205
|
+
const line = /^ExecStart=(.*)$/m.exec(unit);
|
|
206
|
+
if (!line)
|
|
207
|
+
return null;
|
|
208
|
+
return [...line[1].matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1]
|
|
209
|
+
.replace(/%%/g, '%')
|
|
210
|
+
.replace(/\$\$/g, '$')
|
|
211
|
+
.replace(/\\(.)/g, (_, char) => (char === 'n' ? '\n' : char)));
|
|
212
|
+
}
|
|
173
213
|
export function renderSystemdUnit(spec) {
|
|
174
214
|
const execStart = [spec.program, ...spec.args].map(systemdQuote).join(' ');
|
|
175
215
|
const env = Object.keys(spec.env)
|
|
@@ -185,8 +225,8 @@ Type=simple
|
|
|
185
225
|
ExecStart=${execStart}
|
|
186
226
|
WorkingDirectory=${spec.workingDirectory.replace(/%/g, '%%')}
|
|
187
227
|
${env}
|
|
188
|
-
Restart=
|
|
189
|
-
RestartSec=
|
|
228
|
+
Restart=always
|
|
229
|
+
RestartSec=5
|
|
190
230
|
StandardOutput=append:${spec.logPath}
|
|
191
231
|
StandardError=append:${spec.logPath}
|
|
192
232
|
|
|
@@ -215,6 +255,10 @@ class SystemdService {
|
|
|
215
255
|
isCurrent(spec) {
|
|
216
256
|
return readDefinition(this.definitionPath) === this.render(spec);
|
|
217
257
|
}
|
|
258
|
+
installedCommand() {
|
|
259
|
+
const unit = readDefinition(this.definitionPath);
|
|
260
|
+
return unit === null ? null : parseSystemdExecStart(unit);
|
|
261
|
+
}
|
|
218
262
|
async systemctl(step, ...args) {
|
|
219
263
|
const result = await this.run('systemctl', ['--user', ...args]);
|
|
220
264
|
if (result.code !== 0)
|
|
@@ -229,6 +273,12 @@ class SystemdService {
|
|
|
229
273
|
async start() {
|
|
230
274
|
await this.systemctl('start', 'start', this.unit);
|
|
231
275
|
}
|
|
276
|
+
async stop() {
|
|
277
|
+
await this.systemctl('stop', 'stop', this.unit);
|
|
278
|
+
}
|
|
279
|
+
async restart() {
|
|
280
|
+
await this.systemctl('restart', 'restart', this.unit);
|
|
281
|
+
}
|
|
232
282
|
async uninstall() {
|
|
233
283
|
if (!this.isInstalled())
|
|
234
284
|
return false;
|
|
@@ -260,6 +310,57 @@ export function quoteWindowsArg(value) {
|
|
|
260
310
|
}
|
|
261
311
|
return `${quoted}${'\\'.repeat(backslashes * 2)}"`;
|
|
262
312
|
}
|
|
313
|
+
/** Splits a command line the way the Windows C runtime does (the inverse of quoteWindowsArg). */
|
|
314
|
+
export function splitWindowsCommandLine(commandLine) {
|
|
315
|
+
const args = [];
|
|
316
|
+
let current = '';
|
|
317
|
+
let inArg = false;
|
|
318
|
+
let quoted = false;
|
|
319
|
+
for (let index = 0; index < commandLine.length; index += 1) {
|
|
320
|
+
const char = commandLine[index];
|
|
321
|
+
if (char === '\\') {
|
|
322
|
+
let backslashes = 0;
|
|
323
|
+
while (commandLine[index] === '\\') {
|
|
324
|
+
backslashes += 1;
|
|
325
|
+
index += 1;
|
|
326
|
+
}
|
|
327
|
+
if (commandLine[index] === '"') {
|
|
328
|
+
current += '\\'.repeat(Math.floor(backslashes / 2));
|
|
329
|
+
if (backslashes % 2 === 1)
|
|
330
|
+
current += '"';
|
|
331
|
+
else
|
|
332
|
+
quoted = !quoted;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
current += '\\'.repeat(backslashes);
|
|
336
|
+
index -= 1;
|
|
337
|
+
}
|
|
338
|
+
inArg = true;
|
|
339
|
+
}
|
|
340
|
+
else if (char === '"') {
|
|
341
|
+
quoted = !quoted;
|
|
342
|
+
inArg = true;
|
|
343
|
+
}
|
|
344
|
+
else if (/\s/.test(char) && !quoted) {
|
|
345
|
+
if (inArg)
|
|
346
|
+
args.push(current);
|
|
347
|
+
current = '';
|
|
348
|
+
inArg = false;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
current += char;
|
|
352
|
+
inArg = true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (inArg)
|
|
356
|
+
args.push(current);
|
|
357
|
+
return args;
|
|
358
|
+
}
|
|
359
|
+
/** The command line of a launcher written by renderWindowsLauncher. */
|
|
360
|
+
export function parseWindowsLauncherCommand(launcher) {
|
|
361
|
+
const line = /^shell\.Run "((?:[^"]|"")*)", 0, False\r?$/m.exec(launcher);
|
|
362
|
+
return line ? splitWindowsCommandLine(line[1].replace(/""/g, '"')) : null;
|
|
363
|
+
}
|
|
263
364
|
function vbsString(value) {
|
|
264
365
|
return `"${value.replace(/"/g, '""').replace(/\r?\n/g, '" & vbLf & "')}"`;
|
|
265
366
|
}
|
|
@@ -366,6 +467,10 @@ class WindowsTaskService {
|
|
|
366
467
|
isCurrent(spec) {
|
|
367
468
|
return readUtf16(this.definitionPath) === this.render(spec);
|
|
368
469
|
}
|
|
470
|
+
installedCommand() {
|
|
471
|
+
const launcher = readUtf16(this.definitionPath);
|
|
472
|
+
return launcher === null ? null : parseWindowsLauncherCommand(launcher);
|
|
473
|
+
}
|
|
369
474
|
async isRegistered() {
|
|
370
475
|
return (await this.run('schtasks', ['/Query', '/TN', this.taskName])).code === 0;
|
|
371
476
|
}
|
|
@@ -398,15 +503,29 @@ class WindowsTaskService {
|
|
|
398
503
|
throw failed(this.kind, 'run', result);
|
|
399
504
|
}
|
|
400
505
|
async install(spec) {
|
|
506
|
+
this.clearStopMarker();
|
|
401
507
|
writeUtf16(this.definitionPath, this.render(spec));
|
|
402
508
|
await this.register();
|
|
403
509
|
await this.runTask();
|
|
404
510
|
}
|
|
405
511
|
async start() {
|
|
512
|
+
this.clearStopMarker();
|
|
406
513
|
if (!(await this.isRegistered()))
|
|
407
514
|
await this.register();
|
|
408
515
|
await this.runTask();
|
|
409
516
|
}
|
|
517
|
+
async stop() {
|
|
518
|
+
mkdirSync(this.launcherDir, { recursive: true });
|
|
519
|
+
writeFileSync(path.join(this.launcherDir, SUPERVISOR_STOP_MARKER), '');
|
|
520
|
+
}
|
|
521
|
+
async restart() {
|
|
522
|
+
this.clearStopMarker();
|
|
523
|
+
}
|
|
524
|
+
clearStopMarker() {
|
|
525
|
+
const marker = path.join(this.launcherDir, SUPERVISOR_STOP_MARKER);
|
|
526
|
+
if (existsSync(marker))
|
|
527
|
+
unlinkSync(marker);
|
|
528
|
+
}
|
|
410
529
|
async uninstall() {
|
|
411
530
|
const installed = this.isInstalled();
|
|
412
531
|
if (await this.isRegistered()) {
|
package/dist/daemon/types.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export type DaemonResponse = {
|
|
|
12
12
|
};
|
|
13
13
|
export type DaemonShutdownParams = {
|
|
14
14
|
expectedInstanceId: string;
|
|
15
|
+
requester?: {
|
|
16
|
+
pid: number;
|
|
17
|
+
version: string;
|
|
18
|
+
command?: string;
|
|
19
|
+
};
|
|
15
20
|
};
|
|
16
21
|
export type DaemonShutdownResult = {
|
|
17
22
|
stopping: true;
|
|
@@ -47,3 +52,20 @@ export type DaemonPingResult = {
|
|
|
47
52
|
};
|
|
48
53
|
/** Kept only for old scripts that pass --port; the daemon transport is socket-based. */
|
|
49
54
|
export declare const DEFAULT_DAEMON_PORT = 45678;
|
|
55
|
+
export type CommandExecuteParams = {
|
|
56
|
+
argv: string[];
|
|
57
|
+
cwd: string;
|
|
58
|
+
clientVersion: string;
|
|
59
|
+
requestId?: string;
|
|
60
|
+
/** Answer as NDJSON: `{"progress": text}` frames while the command runs, then the response. */
|
|
61
|
+
stream?: boolean;
|
|
62
|
+
};
|
|
63
|
+
/** A frame the daemon streams ahead of a `stream: true` response. */
|
|
64
|
+
export type CommandExecuteFrame = {
|
|
65
|
+
progress: string;
|
|
66
|
+
};
|
|
67
|
+
export type CommandExecuteResult = {
|
|
68
|
+
stdout: string;
|
|
69
|
+
stderr: string;
|
|
70
|
+
exitCode: number;
|
|
71
|
+
};
|
package/dist/dinox.js
CHANGED
|
@@ -1,70 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { commandExecutionTarget, tryForwardToDaemon } from './daemon/forward.js';
|
|
3
|
+
import { applyDefaultFormat } from './utils/defaultFormat.js';
|
|
4
|
+
import { getPackageVersion } from './utils/version.js';
|
|
5
|
+
import { requestIdFromArgv, runWithRequestId } from './requests/store.js';
|
|
6
|
+
import { isDinoxError } from './utils/errors.js';
|
|
5
7
|
import { exitProcessAfterFlushing } from './utils/processExit.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return argv;
|
|
12
|
-
}
|
|
13
|
-
async function main() {
|
|
14
|
-
const program = createCli();
|
|
15
|
-
const argv = normalizeArgv(process.argv.slice(2));
|
|
16
|
-
const executable = process.argv[1] ?? 'dino';
|
|
17
|
-
await program.parseAsync(['node', executable, ...argv]);
|
|
8
|
+
import { localCommandExecution } from './utils/requestContext.js';
|
|
9
|
+
/** `--progress` before `--`: the daemon streams events back while it runs the command. */
|
|
10
|
+
function wantsProgress(args) {
|
|
11
|
+
const end = args.indexOf('--');
|
|
12
|
+
return (end === -1 ? args : args.slice(0, end)).includes('--progress');
|
|
18
13
|
}
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
let argv = process.argv.slice(2);
|
|
15
|
+
let fatalError;
|
|
16
|
+
try {
|
|
17
|
+
argv = applyDefaultFormat(argv);
|
|
18
|
+
const versionOnly = argv.length === 1 && ['--version', '-V'].includes(argv[0]);
|
|
19
|
+
const forwarded = versionOnly
|
|
20
|
+
? { stdout: `${getPackageVersion()}\n`, stderr: '', exitCode: 0 }
|
|
21
|
+
: await tryForwardToDaemon(argv, wantsProgress(argv) ? { onProgress: (text) => process.stderr.write(text) } : {});
|
|
22
|
+
if (forwarded) {
|
|
23
|
+
process.stdout.write(forwarded.stdout);
|
|
24
|
+
process.stderr.write(forwarded.stderr);
|
|
25
|
+
process.exitCode = forwarded.exitCode;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const { runCli } = await import('./runCli.js');
|
|
29
|
+
const key = requestIdFromArgv(argv);
|
|
30
|
+
if (key && commandExecutionTarget(argv) === 'daemon') {
|
|
31
|
+
const { runCapturedInProcess } = await import('./requests/execute.js');
|
|
32
|
+
const result = await runWithRequestId(key, argv, () => runCapturedInProcess(argv, error => { fatalError = error; }));
|
|
33
|
+
process.stdout.write(result.stdout);
|
|
34
|
+
process.stderr.write(result.stderr);
|
|
35
|
+
process.exitCode = result.exitCode;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
process.exitCode = await localCommandExecution.run(true, () => runCli(argv, error => { fatalError = error; }));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
21
41
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
&& error.code.startsWith('commander.')
|
|
27
|
-
&& typeof error.exitCode === 'number';
|
|
42
|
+
catch (error) {
|
|
43
|
+
// Routing failures use the same printer as command failures.
|
|
44
|
+
const { printCliError } = await import('./runCli.js');
|
|
45
|
+
process.exitCode = printCliError(error, argv);
|
|
28
46
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
await exitProcessAfterFlushing(exitCode);
|
|
32
|
-
}
|
|
47
|
+
if (isDinoxError(fatalError) && fatalError.requiresProcessExit) {
|
|
48
|
+
await exitProcessAfterFlushing(Number(process.exitCode));
|
|
33
49
|
}
|
|
34
|
-
main().catch(async (error) => {
|
|
35
|
-
if (isCommanderError(error)) {
|
|
36
|
-
process.exitCode = error.exitCode === 0 ? 0 : 2;
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const argv = normalizeArgv(process.argv.slice(2));
|
|
40
|
-
let format = null;
|
|
41
|
-
try {
|
|
42
|
-
format = parseStructuredOutputFormatFromArgv(argv);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
format = null;
|
|
46
|
-
}
|
|
47
|
-
const verbose = isVerbose(argv);
|
|
48
|
-
const payload = buildStructuredErrorPayload(error, verbose);
|
|
49
|
-
const exitCode = isDinoxError(error) ? error.exitCode : 1;
|
|
50
|
-
process.exitCode = exitCode;
|
|
51
|
-
if (format) {
|
|
52
|
-
setRequestedStructuredOutputFormat(format);
|
|
53
|
-
printError(payload, format);
|
|
54
|
-
clearRequestedStructuredOutputFormat();
|
|
55
|
-
await exitAfterFatalCleanup(error, exitCode);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
printHumanReadableError(payload.message);
|
|
59
|
-
if (payload.suggested_action) {
|
|
60
|
-
printHumanReadableError(`suggested action: ${payload.suggested_action.command}`);
|
|
61
|
-
printHumanReadableError(`reason: ${payload.suggested_action.reason}`);
|
|
62
|
-
}
|
|
63
|
-
else if (payload.suggestion) {
|
|
64
|
-
printHumanReadableError(`suggestion: ${payload.suggestion}`);
|
|
65
|
-
}
|
|
66
|
-
if (verbose && payload.stack) {
|
|
67
|
-
printHumanReadableError(payload.stack);
|
|
68
|
-
}
|
|
69
|
-
await exitAfterFatalCleanup(error, exitCode);
|
|
70
|
-
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import { credsAuthenticator, wsconnect, } from '@nats-io/nats-core';
|
|
3
|
+
import { AI_SERVICE_URL } from '../config/serviceEndpoints.js';
|
|
3
4
|
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
4
5
|
import { REMOTE_DEVICE_PRESENCE_INTERVAL_MS, REMOTE_DEVICE_PROTOCOL_NAME, REMOTE_DEVICE_PROTOCOL_VERSION, remoteDeviceSubject, remoteDiscoverSubject, remotePresenceSubject, remoteRunStreamSubject, } from '../vendor/electron/shared/remote-device-agent.js';
|
|
5
6
|
import { projectPendingApprovals } from './assistantProjection.js';
|
|
@@ -9,7 +10,7 @@ import { RunStreamPublisher } from './streamPublisher.js';
|
|
|
9
10
|
import { findRun, readRecentSessionFolders, resolveTrustedResource, TERMINAL_RUN_STATUSES, threadExists, } from './threadStore.js';
|
|
10
11
|
import { uploadToOfficialStorage } from './fileUpload.js';
|
|
11
12
|
import { agentHomeFolder, ensureAgentHomeFolder, existingWorkspaceFolders, listWorkspaceFolders, resolveWorkspaceFolder, WorkspaceFolderError, } from './workspaceFolders.js';
|
|
12
|
-
export const DEFAULT_NATS_CREDENTIALS_URL =
|
|
13
|
+
export const DEFAULT_NATS_CREDENTIALS_URL = `${AI_SERVICE_URL}/api/nats/credentials`;
|
|
13
14
|
const CREDENTIALS_TIMEOUT_MS = 10_000;
|
|
14
15
|
const CREDENTIALS_REFRESH_LEAD_MS = 60_000;
|
|
15
16
|
const ACK_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
@@ -1,27 +1,46 @@
|
|
|
1
|
+
import { type ExecFileOptionsWithStringEncoding } from 'node:child_process';
|
|
1
2
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import type { CommandSchema } from '../commands/schema/types.js';
|
|
5
|
+
import { tryForwardToDaemon } from '../daemon/forward.js';
|
|
6
|
+
import { MCP_COMMAND_GROUPS, type McpCommandGroup } from './mcpGroups.js';
|
|
4
7
|
/**
|
|
5
8
|
* One MCP tool per command group, generated from the command schemas. Each runs
|
|
6
9
|
* the real CLI as a child process without a shell, preserving the CLI's own
|
|
7
10
|
* validation, dry-run and confirmation guards. Approval belongs to the agent's
|
|
8
11
|
* permission flow, guided by the tool annotations.
|
|
9
12
|
*/
|
|
10
|
-
|
|
11
|
-
export declare const MCP_COMMAND_GROUPS: readonly ["note", "tag", "box", "todo", "view", "prop", "template", "prompt", "graph", "storage", "meta.stats"];
|
|
13
|
+
export { MCP_COMMAND_GROUPS, type McpCommandGroup };
|
|
12
14
|
/** Commands deliberately excluded from the MCP surface. */
|
|
13
|
-
export declare const MCP_COMMAND_EXCLUSIONS: readonly ["auth.*", "config.*", "daemon.*", "gateway.*", "doctor", "skills.*", "update", "sync", "info", "schema", "meta.schema"];
|
|
15
|
+
export declare const MCP_COMMAND_EXCLUSIONS: readonly ["auth.*", "config.*", "daemon.*", "gateway.*", "doctor", "skills.*", "update", "sync", "info", "schema", "meta.schema", "batch", "mcp"];
|
|
16
|
+
export type CommandToolOptions = {
|
|
17
|
+
/** Only commands whose risk is `read`; groups left without any are dropped. */
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
/** Subset of MCP_COMMAND_GROUPS to expose; default all. */
|
|
20
|
+
groups?: readonly McpCommandGroup[];
|
|
21
|
+
/** The curated search_notes/read_note/... tools are registered alongside. */
|
|
22
|
+
knowledgeTools?: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** The tool name a command group is published under. */
|
|
25
|
+
export declare function commandToolName(group: McpCommandGroup): string;
|
|
14
26
|
/** Runs a CLI invocation and captures its output. */
|
|
15
27
|
export type CommandRunner = (argv: string[]) => Promise<{
|
|
16
28
|
stdout: string;
|
|
17
29
|
stderr: string;
|
|
18
30
|
}>;
|
|
19
31
|
/** Runs the current CLI entry point without a shell. */
|
|
32
|
+
export declare function createCliCommandRunner(overrides?: {
|
|
33
|
+
forward?: typeof tryForwardToDaemon;
|
|
34
|
+
execute?: (file: string, args: string[], options: ExecFileOptionsWithStringEncoding) => Promise<{
|
|
35
|
+
stdout: string;
|
|
36
|
+
stderr: string;
|
|
37
|
+
}>;
|
|
38
|
+
}): CommandRunner;
|
|
20
39
|
export declare const runCliCommand: CommandRunner;
|
|
21
40
|
/** Validates tool inputs and builds CLI arguments in schema order. */
|
|
22
41
|
export declare function commandArgv(schema: CommandSchema, input: Record<string, unknown>): string[];
|
|
23
42
|
/** Builds grouped MCP tool definitions from the command schemas. */
|
|
24
|
-
export declare function commandToolDefinitions(): {
|
|
43
|
+
export declare function commandToolDefinitions(options?: CommandToolOptions): {
|
|
25
44
|
name: string;
|
|
26
45
|
commands: CommandSchema[];
|
|
27
46
|
config: {
|
|
@@ -40,4 +59,4 @@ export declare function commandToolDefinitions(): {
|
|
|
40
59
|
};
|
|
41
60
|
}[];
|
|
42
61
|
/** Registers command tools using the real CLI or an injected runner. */
|
|
43
|
-
export declare function registerCommandTools(server: McpServer, runner?: CommandRunner): void;
|
|
62
|
+
export declare function registerCommandTools(server: McpServer, runner?: CommandRunner, options?: CommandToolOptions): void;
|
|
@@ -2,32 +2,68 @@ import { execFile } from 'node:child_process';
|
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { listCommandSchemas } from '../commands/schema/spec.js';
|
|
5
|
+
import { tryForwardToDaemon } from '../daemon/forward.js';
|
|
6
|
+
import { MCP_COMMAND_GROUPS } from './mcpGroups.js';
|
|
7
|
+
import { requestContext } from '../utils/requestContext.js';
|
|
5
8
|
/**
|
|
6
9
|
* One MCP tool per command group, generated from the command schemas. Each runs
|
|
7
10
|
* the real CLI as a child process without a shell, preserving the CLI's own
|
|
8
11
|
* validation, dry-run and confirmation guards. Approval belongs to the agent's
|
|
9
12
|
* permission flow, guided by the tool annotations.
|
|
10
13
|
*/
|
|
11
|
-
|
|
12
|
-
export const MCP_COMMAND_GROUPS = [
|
|
13
|
-
'note', 'tag', 'box', 'todo', 'view', 'prop', 'template', 'prompt', 'graph', 'storage', 'meta.stats',
|
|
14
|
-
];
|
|
14
|
+
export { MCP_COMMAND_GROUPS };
|
|
15
15
|
/** Commands deliberately excluded from the MCP surface. */
|
|
16
16
|
export const MCP_COMMAND_EXCLUSIONS = [
|
|
17
17
|
'auth.*', 'config.*', 'daemon.*', 'gateway.*', 'doctor', 'skills.*', 'update', 'sync', 'info', 'schema', 'meta.schema',
|
|
18
|
+
// MCP clients issue several tool calls themselves; batch is the shell's equivalent.
|
|
19
|
+
'batch',
|
|
20
|
+
// The MCP server itself.
|
|
21
|
+
'mcp',
|
|
18
22
|
];
|
|
23
|
+
/** The tool name a command group is published under. */
|
|
24
|
+
export function commandToolName(group) {
|
|
25
|
+
return group === 'meta.stats' ? 'dinox_stats' : `dinox_${group}`;
|
|
26
|
+
}
|
|
19
27
|
const execute = promisify(execFile);
|
|
20
28
|
/** Runs the current CLI entry point without a shell. */
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
29
|
+
export function createCliCommandRunner(overrides = {}) {
|
|
30
|
+
return (argv) => {
|
|
31
|
+
const entryPoint = process.argv[1];
|
|
32
|
+
if (entryPoint === undefined)
|
|
33
|
+
throw new Error('Cannot run CLI command: process.argv[1] is undefined');
|
|
34
|
+
return (async () => {
|
|
35
|
+
let result;
|
|
36
|
+
try {
|
|
37
|
+
result = await (overrides.forward ?? tryForwardToDaemon)(argv);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
const context = {
|
|
41
|
+
stdout: [], stderr: [], format: null, noticeProvider: null, cwd: process.cwd(),
|
|
42
|
+
};
|
|
43
|
+
const { printCliError } = await import('../runCli.js');
|
|
44
|
+
const code = requestContext.run(context, () => printCliError(error, argv));
|
|
45
|
+
throw Object.assign(new Error(`Command failed with exit code ${code}`), {
|
|
46
|
+
code, stdout: context.stdout.join(''), stderr: context.stderr.join(''),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (result) {
|
|
50
|
+
if (result.exitCode !== 0) {
|
|
51
|
+
throw Object.assign(new Error(`Command failed with exit code ${result.exitCode}`), {
|
|
52
|
+
code: result.exitCode, stdout: result.stdout, stderr: result.stderr,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
return (overrides.execute ?? execute)(process.execPath, [...process.execArgv, entryPoint, ...argv], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
env: process.env,
|
|
60
|
+
timeout: 120_000,
|
|
61
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
62
|
+
});
|
|
63
|
+
})();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export const runCliCommand = createCliCommandRunner();
|
|
31
67
|
function key(name) {
|
|
32
68
|
return name
|
|
33
69
|
.replace(/^[<[.]+|[>\].]+$/g, '')
|
|
@@ -139,28 +175,33 @@ export function commandArgv(schema, input) {
|
|
|
139
175
|
return [...schema.path.split('.'), ...flags, '--format', 'json', '--', ...positionals];
|
|
140
176
|
}
|
|
141
177
|
/** Builds grouped MCP tool definitions from the command schemas. */
|
|
142
|
-
export function commandToolDefinitions() {
|
|
143
|
-
|
|
144
|
-
|
|
178
|
+
export function commandToolDefinitions(options = {}) {
|
|
179
|
+
const { readOnly = false, groups = MCP_COMMAND_GROUPS, knowledgeTools = true } = options;
|
|
180
|
+
return MCP_COMMAND_GROUPS.filter((group) => groups.includes(group)).flatMap((group) => {
|
|
181
|
+
const commands = listCommandSchemas().filter((schema) => (group === 'meta.stats' ? schema.path === group : schema.path.startsWith(`${group}.`))
|
|
182
|
+
&& (!readOnly || schema.risk === 'read'));
|
|
183
|
+
if (commands.length === 0)
|
|
184
|
+
return [];
|
|
145
185
|
const actions = commands.map(actionName);
|
|
146
|
-
const description = 'Dinox CLI commands.
|
|
186
|
+
const description = 'Dinox CLI commands. ' +
|
|
187
|
+
(knowledgeTools ? 'search_notes/read_note/search_todos/related_notes are the fast daemon-backed path for plain search/read. ' : '') +
|
|
147
188
|
'High-risk writes need dryRun: true first, then the CLI confirmation options listed below (e.g. confirm/expectedCount); ' +
|
|
148
189
|
'CLI guards enforce them. Input keys are camelCase.\n' +
|
|
149
190
|
commands.map(describeAction).join('\n');
|
|
150
|
-
return {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
return [{
|
|
192
|
+
name: commandToolName(group),
|
|
193
|
+
commands,
|
|
194
|
+
config: {
|
|
195
|
+
description,
|
|
196
|
+
annotations: { ...toolAnnotations(commands), openWorldHint: group === 'storage' },
|
|
197
|
+
inputSchema: { action: z.enum(actions), input: z.record(z.string(), z.unknown()).optional() },
|
|
198
|
+
},
|
|
199
|
+
}];
|
|
159
200
|
});
|
|
160
201
|
}
|
|
161
202
|
/** Registers command tools using the real CLI or an injected runner. */
|
|
162
|
-
export function registerCommandTools(server, runner = runCliCommand) {
|
|
163
|
-
for (const tool of commandToolDefinitions()) {
|
|
203
|
+
export function registerCommandTools(server, runner = runCliCommand, options = {}) {
|
|
204
|
+
for (const tool of commandToolDefinitions(options)) {
|
|
164
205
|
server.registerTool(tool.name, tool.config, async ({ action, input }) => {
|
|
165
206
|
try {
|
|
166
207
|
const schema = tool.commands.find((command) => actionName(command) === action);
|
package/dist/gateway/mcpEnv.d.ts
CHANGED
|
@@ -5,3 +5,5 @@ export declare const GATEWAY_SESSION_TOKEN_ENV = "DINOX_GATEWAY_SESSION_TOKEN";
|
|
|
5
5
|
export declare const GATEWAY_KNOWLEDGE_TOOLS_ENV = "DINOX_GATEWAY_KNOWLEDGE_TOOLS";
|
|
6
6
|
/** Must not collide with MCP servers the user already registered with the agent. */
|
|
7
7
|
export declare const DINOX_MCP_SERVER_NAME = "dinox-gateway";
|
|
8
|
+
/** `dino mcp`, the server users register with their own MCP clients. */
|
|
9
|
+
export declare const DINOX_STANDALONE_MCP_SERVER_NAME = "dinox";
|
package/dist/gateway/mcpEnv.js
CHANGED
|
@@ -5,3 +5,5 @@ export const GATEWAY_SESSION_TOKEN_ENV = 'DINOX_GATEWAY_SESSION_TOKEN';
|
|
|
5
5
|
export const GATEWAY_KNOWLEDGE_TOOLS_ENV = 'DINOX_GATEWAY_KNOWLEDGE_TOOLS';
|
|
6
6
|
/** Must not collide with MCP servers the user already registered with the agent. */
|
|
7
7
|
export const DINOX_MCP_SERVER_NAME = 'dinox-gateway';
|
|
8
|
+
/** `dino mcp`, the server users register with their own MCP clients. */
|
|
9
|
+
export const DINOX_STANDALONE_MCP_SERVER_NAME = 'dinox';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Command groups exposed as MCP tools. Kept free of imports: command descriptors read it at load time. */
|
|
2
|
+
export declare const MCP_COMMAND_GROUPS: readonly ["note", "tag", "box", "todo", "view", "prop", "template", "prompt", "graph", "storage", "meta.stats"];
|
|
3
|
+
export type McpCommandGroup = (typeof MCP_COMMAND_GROUPS)[number];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CommandRunner } from './mcpCommandTools.js';
|
|
1
|
+
import { type CommandRunner, type CommandToolOptions } from './mcpCommandTools.js';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
export type DaemonCall = (method: string, params?: unknown, options?: {
|
|
4
4
|
timeoutMs?: number;
|
|
@@ -13,4 +13,13 @@ export type DinoxMcpOptions = {
|
|
|
13
13
|
cwd?: string;
|
|
14
14
|
};
|
|
15
15
|
export declare function createDinoxMcpServer(call?: DaemonCall, options?: DinoxMcpOptions): McpServer;
|
|
16
|
+
/**
|
|
17
|
+
* The Dinox MCP server for any MCP client (`dino mcp`). Only the generated
|
|
18
|
+
* command tools: they reach the daemon when it is available and otherwise run
|
|
19
|
+
* the CLI in a child process, so no daemon is required.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createStandaloneMcpServer(options?: Omit<CommandToolOptions, 'knowledgeTools'> & {
|
|
22
|
+
commandRunner?: CommandRunner;
|
|
23
|
+
}): McpServer;
|
|
16
24
|
export declare function serveDinoxMcpOverStdio(): Promise<void>;
|
|
25
|
+
export declare function serveStandaloneMcpOverStdio(options: Omit<CommandToolOptions, 'knowledgeTools'>): Promise<void>;
|