@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.
Files changed (112) hide show
  1. package/dist/ai/ragxClient.d.ts +15 -0
  2. package/dist/ai/ragxClient.js +71 -0
  3. package/dist/cli.js +31 -4
  4. package/dist/commands/auth/index.d.ts +2 -1
  5. package/dist/commands/auth/index.js +18 -1
  6. package/dist/commands/batch.d.ts +35 -0
  7. package/dist/commands/batch.js +202 -0
  8. package/dist/commands/boxes/index.js +21 -21
  9. package/dist/commands/doctor.d.ts +4 -1
  10. package/dist/commands/doctor.js +20 -1
  11. package/dist/commands/graph/index.js +2 -1
  12. package/dist/commands/mcp.d.ts +6 -0
  13. package/dist/commands/mcp.js +47 -0
  14. package/dist/commands/notes/actions.js +18 -2
  15. package/dist/commands/notes/contentPatch.js +2 -1
  16. package/dist/commands/notes/descriptors.js +111 -5
  17. package/dist/commands/notes/detailContent.d.ts +19 -0
  18. package/dist/commands/notes/detailContent.js +62 -0
  19. package/dist/commands/notes/export.js +7 -2
  20. package/dist/commands/notes/repo/search.d.ts +2 -1
  21. package/dist/commands/notes/repo/search.js +1 -1
  22. package/dist/commands/notes/repo/semantic.d.ts +14 -0
  23. package/dist/commands/notes/repo/semantic.js +31 -0
  24. package/dist/commands/notes/searchResponse.d.ts +3 -0
  25. package/dist/commands/notes/searchResponse.js +1 -0
  26. package/dist/commands/notes/semantic.d.ts +116 -0
  27. package/dist/commands/notes/semantic.js +332 -0
  28. package/dist/commands/prompt/index.js +7 -5
  29. package/dist/commands/runtime.d.ts +8 -0
  30. package/dist/commands/runtime.js +49 -1
  31. package/dist/commands/savedViews/index.js +12 -3
  32. package/dist/commands/schema/globalOptions.d.ts +7 -0
  33. package/dist/commands/schema/globalOptions.js +7 -0
  34. package/dist/commands/schema/renderSkills.js +6 -2
  35. package/dist/commands/schema/spec.js +20 -1
  36. package/dist/commands/storage/index.js +7 -5
  37. package/dist/commands/storage/repo/operations.js +3 -1
  38. package/dist/commands/sync.d.ts +2 -1
  39. package/dist/commands/sync.js +27 -8
  40. package/dist/commands/tags/index.js +21 -21
  41. package/dist/commands/todo/index.js +22 -2
  42. package/dist/commands/todo/repo.d.ts +5 -0
  43. package/dist/commands/todo/repo.js +7 -2
  44. package/dist/commands/writeReceipt.d.ts +13 -0
  45. package/dist/commands/writeReceipt.js +12 -0
  46. package/dist/config/serviceEndpoints.d.ts +2 -0
  47. package/dist/config/serviceEndpoints.js +2 -0
  48. package/dist/daemon/client.d.ts +2 -0
  49. package/dist/daemon/client.js +53 -3
  50. package/dist/daemon/connectivity.d.ts +20 -0
  51. package/dist/daemon/connectivity.js +42 -0
  52. package/dist/daemon/ensure.js +48 -12
  53. package/dist/daemon/forward.d.ts +37 -0
  54. package/dist/daemon/forward.js +155 -0
  55. package/dist/daemon/fresh.d.ts +4 -0
  56. package/dist/daemon/fresh.js +10 -0
  57. package/dist/daemon/index.d.ts +11 -6
  58. package/dist/daemon/index.js +90 -25
  59. package/dist/daemon/launch.d.ts +13 -1
  60. package/dist/daemon/launch.js +31 -2
  61. package/dist/daemon/lifecycle.d.ts +3 -1
  62. package/dist/daemon/lifecycle.js +27 -3
  63. package/dist/daemon/pid.d.ts +8 -0
  64. package/dist/daemon/pid.js +20 -0
  65. package/dist/daemon/route.js +2 -1
  66. package/dist/daemon/server.d.ts +45 -1
  67. package/dist/daemon/server.js +192 -4
  68. package/dist/daemon/supervise.d.ts +6 -6
  69. package/dist/daemon/supervise.js +12 -10
  70. package/dist/daemon/supervisor.d.ts +21 -5
  71. package/dist/daemon/supervisor.js +125 -6
  72. package/dist/daemon/types.d.ts +22 -0
  73. package/dist/dinox.js +43 -64
  74. package/dist/gateway/deviceHost.js +2 -1
  75. package/dist/gateway/mcpCommandTools.d.ts +24 -5
  76. package/dist/gateway/mcpCommandTools.js +70 -29
  77. package/dist/gateway/mcpEnv.d.ts +2 -0
  78. package/dist/gateway/mcpEnv.js +2 -0
  79. package/dist/gateway/mcpGroups.d.ts +3 -0
  80. package/dist/gateway/mcpGroups.js +4 -0
  81. package/dist/gateway/mcpServer.d.ts +10 -1
  82. package/dist/gateway/mcpServer.js +25 -5
  83. package/dist/powersync/runtime.d.ts +2 -0
  84. package/dist/powersync/runtime.js +3 -1
  85. package/dist/powersync/syncPass.js +13 -1
  86. package/dist/powersync/tokenIndex.d.ts +2 -0
  87. package/dist/powersync/tokenIndex.js +1 -0
  88. package/dist/requests/execute.d.ts +11 -0
  89. package/dist/requests/execute.js +26 -0
  90. package/dist/requests/store.d.ts +10 -0
  91. package/dist/requests/store.js +190 -0
  92. package/dist/runCli.d.ts +2 -0
  93. package/dist/runCli.js +64 -0
  94. package/dist/skills/commandRefs.js +2 -10
  95. package/dist/utils/argValue.js +2 -1
  96. package/dist/utils/cursor.d.ts +9 -0
  97. package/dist/utils/cursor.js +61 -0
  98. package/dist/utils/defaultFormat.d.ts +8 -0
  99. package/dist/utils/defaultFormat.js +38 -0
  100. package/dist/utils/errorCodes.d.ts +24 -0
  101. package/dist/utils/errorCodes.js +6 -0
  102. package/dist/utils/output.d.ts +4 -0
  103. package/dist/utils/output.js +72 -10
  104. package/dist/utils/progress.d.ts +25 -0
  105. package/dist/utils/progress.js +59 -0
  106. package/dist/utils/requestContext.d.ts +32 -0
  107. package/dist/utils/requestContext.js +13 -0
  108. package/dist/utils/select.d.ts +20 -0
  109. package/dist/utils/select.js +92 -0
  110. package/dist/utils/usageError.d.ts +37 -0
  111. package/dist/utils/usageError.js +127 -0
  112. 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(/&lt;/g, '<')
62
+ .replace(/&gt;/g, '>')
63
+ .replace(/&quot;/g, '"')
64
+ .replace(/&apos;/g, "'")
65
+ .replace(/&amp;/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<dict>
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=on-failure
189
- RestartSec=30
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()) {
@@ -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 { createCli } from './cli.js';
3
- import { buildStructuredErrorPayload, isDinoxError } from './utils/errors.js';
4
- import { clearRequestedStructuredOutputFormat, parseStructuredOutputFormatFromArgv, printError, printHumanReadableError, setRequestedStructuredOutputFormat, } from './utils/output.js';
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
- function normalizeArgv(argv) {
7
- // pnpm/npm script forwarding can prepend a standalone "--".
8
- if (argv[0] === '--') {
9
- return argv.slice(1);
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
- function isVerbose(argv) {
20
- return argv.includes('--verbose');
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
- function isCommanderError(error) {
23
- return Boolean(error)
24
- && typeof error === 'object'
25
- && typeof error.code === 'string'
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
- async function exitAfterFatalCleanup(error, exitCode) {
30
- if (isDinoxError(error) && error.requiresProcessExit) {
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 = 'https://aisdk.chatgo.pro/api/nats/credentials';
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
- /** Command groups exposed as MCP tools. */
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
- /** Command groups exposed as MCP tools. */
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 const runCliCommand = (argv) => {
22
- const entryPoint = process.argv[1];
23
- if (entryPoint === undefined)
24
- throw new Error('Cannot run CLI command: process.argv[1] is undefined');
25
- return execute(process.execPath, [...process.execArgv, entryPoint, ...argv], {
26
- env: process.env,
27
- timeout: 120_000,
28
- maxBuffer: 32 * 1024 * 1024,
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
- return MCP_COMMAND_GROUPS.map((group) => {
144
- const commands = listCommandSchemas().filter((schema) => group === 'meta.stats' ? schema.path === group : schema.path.startsWith(`${group}.`));
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. search_notes/read_note/search_todos/related_notes are the fast daemon-backed path for plain search/read. ' +
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
- name: group === 'meta.stats' ? 'dinox_stats' : `dinox_${group}`,
152
- commands,
153
- config: {
154
- description,
155
- annotations: { ...toolAnnotations(commands), openWorldHint: group === 'storage' },
156
- inputSchema: { action: z.enum(actions), input: z.record(z.string(), z.unknown()).optional() },
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);
@@ -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";
@@ -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];
@@ -0,0 +1,4 @@
1
+ /** Command groups exposed as MCP tools. Kept free of imports: command descriptors read it at load time. */
2
+ export const MCP_COMMAND_GROUPS = [
3
+ 'note', 'tag', 'box', 'todo', 'view', 'prop', 'template', 'prompt', 'graph', 'storage', 'meta.stats',
4
+ ];
@@ -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>;