@dinoxx/dinox-cli 2.14.0 → 2.16.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 (97) hide show
  1. package/README.md +2 -0
  2. package/dist/ai/ragxClient.d.ts +15 -0
  3. package/dist/ai/ragxClient.js +71 -0
  4. package/dist/cli.js +27 -3
  5. package/dist/commands/batch.d.ts +35 -0
  6. package/dist/commands/batch.js +202 -0
  7. package/dist/commands/boxes/index.js +21 -21
  8. package/dist/commands/mcp.d.ts +6 -0
  9. package/dist/commands/mcp.js +47 -0
  10. package/dist/commands/notes/actions.js +18 -2
  11. package/dist/commands/notes/descriptors.js +111 -5
  12. package/dist/commands/notes/detailContent.d.ts +19 -0
  13. package/dist/commands/notes/detailContent.js +62 -0
  14. package/dist/commands/notes/export.js +4 -0
  15. package/dist/commands/notes/repo/search.d.ts +2 -1
  16. package/dist/commands/notes/repo/search.js +1 -1
  17. package/dist/commands/notes/repo/semantic.d.ts +14 -0
  18. package/dist/commands/notes/repo/semantic.js +31 -0
  19. package/dist/commands/notes/searchResponse.d.ts +3 -0
  20. package/dist/commands/notes/searchResponse.js +1 -0
  21. package/dist/commands/notes/semantic.d.ts +116 -0
  22. package/dist/commands/notes/semantic.js +332 -0
  23. package/dist/commands/prompt/index.js +7 -5
  24. package/dist/commands/savedViews/index.js +12 -3
  25. package/dist/commands/schema/globalOptions.d.ts +6 -0
  26. package/dist/commands/schema/globalOptions.js +6 -0
  27. package/dist/commands/schema/renderSkills.js +5 -2
  28. package/dist/commands/schema/spec.js +18 -7
  29. package/dist/commands/storage/index.js +7 -5
  30. package/dist/commands/sync.js +3 -3
  31. package/dist/commands/tags/index.js +21 -21
  32. package/dist/commands/todo/index.js +22 -2
  33. package/dist/commands/todo/repo.d.ts +5 -0
  34. package/dist/commands/todo/repo.js +7 -2
  35. package/dist/commands/writeReceipt.d.ts +13 -0
  36. package/dist/commands/writeReceipt.js +12 -0
  37. package/dist/config/serviceEndpoints.d.ts +2 -0
  38. package/dist/config/serviceEndpoints.js +2 -0
  39. package/dist/daemon/client.d.ts +2 -0
  40. package/dist/daemon/client.js +22 -2
  41. package/dist/daemon/forward.d.ts +4 -1
  42. package/dist/daemon/forward.js +13 -4
  43. package/dist/daemon/server.d.ts +1 -1
  44. package/dist/daemon/server.js +17 -1
  45. package/dist/daemon/types.d.ts +6 -0
  46. package/dist/dinox.js +12 -10
  47. package/dist/gateway/adapters/acp.d.ts +5 -1
  48. package/dist/gateway/adapters/acp.js +21 -9
  49. package/dist/gateway/adapters/echo.js +7 -4
  50. package/dist/gateway/adapters/index.d.ts +1 -0
  51. package/dist/gateway/adapters/index.js +5 -3
  52. package/dist/gateway/deviceHost.d.ts +3 -1
  53. package/dist/gateway/deviceHost.js +75 -5
  54. package/dist/gateway/mcpCommandTools.d.ts +15 -5
  55. package/dist/gateway/mcpCommandTools.js +30 -19
  56. package/dist/gateway/mcpEnv.d.ts +4 -0
  57. package/dist/gateway/mcpEnv.js +4 -0
  58. package/dist/gateway/mcpGroups.d.ts +3 -0
  59. package/dist/gateway/mcpGroups.js +4 -0
  60. package/dist/gateway/mcpServer.d.ts +10 -1
  61. package/dist/gateway/mcpServer.js +26 -5
  62. package/dist/gateway/protocol.d.ts +31 -2
  63. package/dist/gateway/protocol.js +15 -3
  64. package/dist/gateway/runExecutor.js +23 -12
  65. package/dist/gateway/service.d.ts +2 -0
  66. package/dist/gateway/service.js +29 -4
  67. package/dist/gateway/threadStore.d.ts +2 -0
  68. package/dist/gateway/threadStore.js +13 -0
  69. package/dist/gateway/types.d.ts +5 -4
  70. package/dist/gateway/workspace.d.ts +5 -0
  71. package/dist/gateway/workspace.js +30 -0
  72. package/dist/gateway/workspaceFolders.d.ts +5 -3
  73. package/dist/gateway/workspaceFolders.js +20 -7
  74. package/dist/gateway/workspaceListing.d.ts +5 -0
  75. package/dist/gateway/workspaceListing.js +78 -0
  76. package/dist/powersync/syncPass.js +13 -1
  77. package/dist/powersync/tokenIndex.d.ts +2 -0
  78. package/dist/powersync/tokenIndex.js +1 -0
  79. package/dist/requests/execute.d.ts +11 -0
  80. package/dist/requests/execute.js +26 -0
  81. package/dist/skills/commandRefs.js +2 -10
  82. package/dist/utils/cursor.d.ts +9 -0
  83. package/dist/utils/cursor.js +61 -0
  84. package/dist/utils/defaultFormat.d.ts +8 -0
  85. package/dist/utils/defaultFormat.js +38 -0
  86. package/dist/utils/errorCodes.d.ts +4 -0
  87. package/dist/utils/errorCodes.js +1 -0
  88. package/dist/utils/output.d.ts +2 -0
  89. package/dist/utils/output.js +41 -3
  90. package/dist/utils/progress.d.ts +25 -0
  91. package/dist/utils/progress.js +59 -0
  92. package/dist/utils/requestContext.d.ts +8 -0
  93. package/dist/utils/select.d.ts +20 -0
  94. package/dist/utils/select.js +92 -0
  95. package/dist/utils/usageError.d.ts +37 -0
  96. package/dist/utils/usageError.js +127 -0
  97. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { type FieldSelection } from './select.js';
1
2
  export type StructuredOutputFormat = 'yaml' | 'json';
2
3
  export type StructuredNoticeMap = Record<string, unknown>;
3
4
  export declare function toYaml(value: unknown): string;
@@ -16,6 +17,7 @@ export declare function parseStructuredOutputFormatFromArgv(argv: string[]): Str
16
17
  export declare function setRequestedStructuredOutputFormat(format: StructuredOutputFormat | null): void;
17
18
  export declare function clearRequestedStructuredOutputFormat(): void;
18
19
  export declare function getRequestedStructuredOutputFormat(): StructuredOutputFormat | null;
20
+ export declare function setRequestedFieldSelection(selection: FieldSelection | null): void;
19
21
  export declare function setPendingStructuredNoticeProvider(provider: (() => StructuredNoticeMap | null) | null): void;
20
22
  export declare function clearPendingStructuredNoticeProvider(): void;
21
23
  export declare function printStructuredData(value: unknown, format?: StructuredOutputFormat | null): void;
@@ -2,8 +2,10 @@ import { stringify as stringifyYaml } from 'yaml';
2
2
  import { DinoxError } from './errors.js';
3
3
  import { sanitizeForTerminal } from './terminalSanitization.js';
4
4
  import { requestContext } from './requestContext.js';
5
+ import { projectFields } from './select.js';
5
6
  let requestedStructuredOutputFormat = null;
6
7
  let pendingStructuredNoticeProvider = null;
8
+ let requestedFieldSelection = null;
7
9
  export function toYaml(value) {
8
10
  return stringifyYaml(value, {
9
11
  lineWidth: 0,
@@ -117,6 +119,36 @@ export function getRequestedStructuredOutputFormat() {
117
119
  const context = requestContext.getStore();
118
120
  return context ? context.format : requestedStructuredOutputFormat;
119
121
  }
122
+ export function setRequestedFieldSelection(selection) {
123
+ const context = requestContext.getStore();
124
+ if (context) {
125
+ context.select = selection;
126
+ return;
127
+ }
128
+ requestedFieldSelection = selection;
129
+ }
130
+ function getRequestedFieldSelection() {
131
+ const context = requestContext.getStore();
132
+ return context ? context.select ?? null : requestedFieldSelection;
133
+ }
134
+ /** Applies `--select` to a command result; unmatched paths become a notice. */
135
+ function selectRequestedFields(value) {
136
+ const selection = getRequestedFieldSelection();
137
+ if (!selection)
138
+ return { value, notice: null };
139
+ const { value: projected, missing } = projectFields(value, selection);
140
+ if (missing.length === 0)
141
+ return { value: projected, notice: null };
142
+ return {
143
+ value: projected,
144
+ notice: {
145
+ select_missing: {
146
+ message: 'These --select fields are not in the result; check `dino schema <command>` output fields.',
147
+ fields: missing,
148
+ },
149
+ },
150
+ };
151
+ }
120
152
  export function setPendingStructuredNoticeProvider(provider) {
121
153
  const context = requestContext.getStore();
122
154
  if (context) {
@@ -190,15 +222,21 @@ function summarizeStructuredError(value) {
190
222
  export function printSuccess(value, format = getRequestedStructuredOutputFormat()) {
191
223
  if (format === 'json') {
192
224
  const data = normalizeSuccessEnvelopeData(value);
193
- const notice = mergeStructuredNotices(getPendingStructuredNotice(), deriveStructuredNotice(data));
225
+ const selected = selectRequestedFields(data);
226
+ const notice = mergeStructuredNotices(mergeStructuredNotices(getPendingStructuredNotice(), deriveStructuredNotice(data)) ?? null, selected.notice);
194
227
  printStructuredData({
195
228
  ok: true,
196
- data,
229
+ data: selected.value,
197
230
  ...(notice ? { _notice: notice } : {}),
198
231
  }, 'json');
199
232
  return;
200
233
  }
201
- printStructuredData(value, format);
234
+ const selected = selectRequestedFields(value);
235
+ if (selected.notice) {
236
+ const fields = selected.notice.select_missing.fields;
237
+ printHumanReadableWarning(`warning: --select fields not in the result: ${fields.join(', ')}`);
238
+ }
239
+ printStructuredData(selected.value, format);
202
240
  }
203
241
  export function printError(value, format = getRequestedStructuredOutputFormat()) {
204
242
  if (format === 'json') {
@@ -0,0 +1,25 @@
1
+ /** One step of a long-running command, reported on stderr under `--progress`. */
2
+ export type ProgressEvent = {
3
+ /** What the command is doing: `download`, `index`, `write`, ... */
4
+ phase: string;
5
+ current?: number;
6
+ total?: number;
7
+ message?: string;
8
+ };
9
+ export type ProgressReporting = {
10
+ /** Command path (`sync`, `note.export`), carried by every event. */
11
+ command: string;
12
+ startedAtMs: number;
13
+ lastPhase?: string;
14
+ lastAtMs?: number;
15
+ };
16
+ /** Enables (`command`) or disables (`null`) progress events for the running command. */
17
+ export declare function setProgressReporting(command: string | null, now?: number): void;
18
+ /**
19
+ * Structured output gets one JSON object per line (`"type":"progress"`), so
20
+ * stdout stays a single result while stderr streams events; human output gets
21
+ * a short line.
22
+ */
23
+ export declare function renderProgressEvent(reporting: ProgressReporting, event: ProgressEvent, structured: boolean, now: number): string;
24
+ /** Reports a progress event when `--progress` is on; otherwise does nothing. */
25
+ export declare function reportProgress(event: ProgressEvent, now?: number): void;
@@ -0,0 +1,59 @@
1
+ import { getRequestedStructuredOutputFormat, writeToStderr } from './output.js';
2
+ import { requestContext } from './requestContext.js';
3
+ /** Repeated events of one phase closer together than this are dropped, except the last of a known total. */
4
+ const MIN_INTERVAL_MS = 200;
5
+ let localReporting = null;
6
+ /** Enables (`command`) or disables (`null`) progress events for the running command. */
7
+ export function setProgressReporting(command, now = Date.now()) {
8
+ const reporting = command ? { command, startedAtMs: now } : null;
9
+ const context = requestContext.getStore();
10
+ if (context) {
11
+ context.progress = reporting;
12
+ return;
13
+ }
14
+ localReporting = reporting;
15
+ }
16
+ /**
17
+ * Structured output gets one JSON object per line (`"type":"progress"`), so
18
+ * stdout stays a single result while stderr streams events; human output gets
19
+ * a short line.
20
+ */
21
+ export function renderProgressEvent(reporting, event, structured, now) {
22
+ if (structured) {
23
+ return `${JSON.stringify({
24
+ type: 'progress',
25
+ command: reporting.command,
26
+ phase: event.phase,
27
+ ...(event.current !== undefined ? { current: event.current } : {}),
28
+ ...(event.total !== undefined ? { total: event.total } : {}),
29
+ ...(event.message !== undefined ? { message: event.message } : {}),
30
+ elapsed_ms: Math.max(0, now - reporting.startedAtMs),
31
+ })}\n`;
32
+ }
33
+ const count = event.current === undefined
34
+ ? ''
35
+ : event.total === undefined
36
+ ? ` ${event.current}`
37
+ : ` ${event.current}/${event.total}${event.total > 0 ? ` (${Math.floor((event.current / event.total) * 100)}%)` : ''}`;
38
+ return `[${reporting.command}] ${event.phase}${count}${event.message ? ` ${event.message}` : ''}\n`;
39
+ }
40
+ /** Reports a progress event when `--progress` is on; otherwise does nothing. */
41
+ export function reportProgress(event, now = Date.now()) {
42
+ const context = requestContext.getStore();
43
+ const reporting = context ? context.progress ?? null : localReporting;
44
+ if (!reporting)
45
+ return;
46
+ const finished = event.total !== undefined && event.current === event.total;
47
+ if (reporting.lastPhase === event.phase
48
+ && reporting.lastAtMs !== undefined
49
+ && now - reporting.lastAtMs < MIN_INTERVAL_MS
50
+ && !finished)
51
+ return;
52
+ reporting.lastPhase = event.phase;
53
+ reporting.lastAtMs = now;
54
+ const text = renderProgressEvent(reporting, event, getRequestedStructuredOutputFormat() !== null, now);
55
+ if (context?.onProgress)
56
+ context.onProgress(text);
57
+ else
58
+ writeToStderr(text);
59
+ }
@@ -2,12 +2,20 @@ import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import type { AbstractPowerSyncDatabase } from '@powersync/common';
3
3
  import type { ResolvedDinoxConfig } from '../config/types.js';
4
4
  import type { StructuredNoticeMap, StructuredOutputFormat } from './output.js';
5
+ import type { FieldSelection } from './select.js';
6
+ import type { ProgressReporting } from './progress.js';
5
7
  export type RequestContext = {
6
8
  stdout: string[];
7
9
  stderr: string[];
8
10
  format: StructuredOutputFormat | null;
11
+ /** `--select` of the running command; absent means the whole result. */
12
+ select?: FieldSelection | null;
9
13
  noticeProvider: (() => StructuredNoticeMap | null) | null;
10
14
  cwd: string;
15
+ /** `--progress` of the running command; absent means off. */
16
+ progress?: ProgressReporting | null;
17
+ /** Delivers rendered progress events live (the daemon streams them to the client); default stderr. */
18
+ onProgress?: (text: string) => void;
11
19
  daemon?: {
12
20
  db: AbstractPowerSyncDatabase;
13
21
  config: ResolvedDinoxConfig;
@@ -0,0 +1,20 @@
1
+ /** A parsed `--select` list: each key keeps the whole value (true) or a nested selection. */
2
+ export type FieldSelection = {
3
+ [key: string]: FieldSelection | true;
4
+ };
5
+ /**
6
+ * Parses `--select notes.id,notes.title,count` into a selection tree. A path
7
+ * that names a parent also keeps everything below it (`notes,notes.id` keeps
8
+ * whole notes).
9
+ */
10
+ export declare function parseFieldSelection(value: unknown): FieldSelection;
11
+ /**
12
+ * Keeps only the selected fields. Arrays are projected item by item, so
13
+ * `notes.id` selects the id of every note. Returns the projected value and the
14
+ * paths that matched nothing anywhere, so a typo is reported instead of
15
+ * silently producing empty output.
16
+ */
17
+ export declare function projectFields(value: unknown, selection: FieldSelection): {
18
+ value: unknown;
19
+ missing: string[];
20
+ };
@@ -0,0 +1,92 @@
1
+ import { DinoxError } from './errors.js';
2
+ const FIELD_PATH = /^[A-Za-z0-9_$-]+(\.[A-Za-z0-9_$-]+)*$/;
3
+ /**
4
+ * Parses `--select notes.id,notes.title,count` into a selection tree. A path
5
+ * that names a parent also keeps everything below it (`notes,notes.id` keeps
6
+ * whole notes).
7
+ */
8
+ export function parseFieldSelection(value) {
9
+ const raw = Array.isArray(value) ? value.join(',') : String(value ?? '');
10
+ const paths = raw.split(',').map((path) => path.trim()).filter(Boolean);
11
+ if (paths.length === 0) {
12
+ throw new DinoxError('--select needs at least one field path, e.g. --select notes.id,notes.title', {
13
+ code: 'INVALID_ARGUMENT',
14
+ recoverable: true,
15
+ });
16
+ }
17
+ const selection = {};
18
+ for (const path of paths) {
19
+ if (!FIELD_PATH.test(path)) {
20
+ throw new DinoxError(`Invalid --select field path: ${path}`, {
21
+ code: 'INVALID_ARGUMENT',
22
+ recoverable: true,
23
+ suggestion: 'Use dot-separated output field names from `dino schema <command> --format json`.',
24
+ });
25
+ }
26
+ let node = selection;
27
+ const segments = path.split('.');
28
+ segments.forEach((segment, index) => {
29
+ if (node[segment] === true)
30
+ return;
31
+ if (index === segments.length - 1) {
32
+ node[segment] = true;
33
+ return;
34
+ }
35
+ node[segment] ??= {};
36
+ node = node[segment];
37
+ });
38
+ }
39
+ return selection;
40
+ }
41
+ /**
42
+ * Keeps only the selected fields. Arrays are projected item by item, so
43
+ * `notes.id` selects the id of every note. Returns the projected value and the
44
+ * paths that matched nothing anywhere, so a typo is reported instead of
45
+ * silently producing empty output.
46
+ */
47
+ export function projectFields(value, selection) {
48
+ const matched = new Set();
49
+ // Paths below an empty list or a null value cannot be checked; never report them.
50
+ const assumeMatched = (node, prefix) => {
51
+ for (const [key, child] of Object.entries(node)) {
52
+ const path = prefix ? `${prefix}.${key}` : key;
53
+ matched.add(path);
54
+ if (child !== true)
55
+ assumeMatched(child, path);
56
+ }
57
+ };
58
+ const project = (current, node, prefix) => {
59
+ if (Array.isArray(current)) {
60
+ if (current.length === 0)
61
+ assumeMatched(node, prefix);
62
+ return current.map((item) => project(item, node, prefix));
63
+ }
64
+ if (!current || typeof current !== 'object') {
65
+ assumeMatched(node, prefix);
66
+ return current;
67
+ }
68
+ const record = current;
69
+ const result = {};
70
+ for (const [key, child] of Object.entries(node)) {
71
+ if (!Object.prototype.hasOwnProperty.call(record, key))
72
+ continue;
73
+ const path = prefix ? `${prefix}.${key}` : key;
74
+ matched.add(path);
75
+ result[key] = child === true ? record[key] : project(record[key], child, path);
76
+ }
77
+ return result;
78
+ };
79
+ const projected = project(value, selection, '');
80
+ const missing = [];
81
+ const collect = (node, prefix) => {
82
+ for (const [key, child] of Object.entries(node)) {
83
+ const path = prefix ? `${prefix}.${key}` : key;
84
+ if (!matched.has(path))
85
+ missing.push(path);
86
+ else if (child !== true)
87
+ collect(child, path);
88
+ }
89
+ };
90
+ collect(selection, '');
91
+ return { value: projected, missing };
92
+ }
@@ -0,0 +1,37 @@
1
+ import type { Command } from 'commander';
2
+ import type { SuggestedAction } from './errors.js';
3
+ export type UsageErrorKind = 'unknown_option' | 'unknown_command' | 'missing_argument' | 'missing_option_value' | 'missing_required_option' | 'invalid_option_value' | 'too_many_arguments' | 'usage';
4
+ /** Machine-readable form of a Commander usage error, carried in the error's `details`. */
5
+ export type UsageErrorDetails = {
6
+ kind: UsageErrorKind;
7
+ /** The offending option, command, or argument name, when Commander names one. */
8
+ token: string | null;
9
+ /** Commander's spelling suggestions, closest first. */
10
+ did_you_mean: string[];
11
+ /** Dot path of the deepest command that was recognized, e.g. `note.search`. */
12
+ command: string | null;
13
+ };
14
+ export type ParsedUsageError = {
15
+ message: string;
16
+ details: UsageErrorDetails;
17
+ suggestion?: string;
18
+ suggestedAction?: SuggestedAction;
19
+ };
20
+ type ResolvedCommand = {
21
+ /** Names of the recognized commands, outermost first. */
22
+ path: string[];
23
+ command: Command;
24
+ /** The first word that did not name a subcommand of `command`. */
25
+ unmatched: string | null;
26
+ };
27
+ /** Walks argv through the command tree as far as it names commands. */
28
+ export declare function resolveCommand(program: Command, argv: string[]): ResolvedCommand;
29
+ /** Closest candidates, using the same thresholds as Commander's own suggestions. */
30
+ export declare function suggestSimilar(word: string, candidates: string[]): string[];
31
+ /**
32
+ * Turns Commander's error text ("error: unknown option '--limt'\n(Did you mean
33
+ * --limit?)") into a message, structured details and, when there is exactly
34
+ * one suggestion, the corrected command line an agent can run.
35
+ */
36
+ export declare function parseCommanderUsageError(text: string, argv: string[], program: Command): ParsedUsageError;
37
+ export {};
@@ -0,0 +1,127 @@
1
+ import { GLOBAL_VALUE_OPTIONS } from '../commands/schema/globalOptions.js';
2
+ const PATTERNS = [
3
+ ['unknown_option', /^unknown option '([^']+)'/],
4
+ ['unknown_command', /^unknown command '([^']+)'/],
5
+ ['missing_argument', /^missing required argument '([^']+)'/],
6
+ ['missing_option_value', /^option '([^']+)' argument missing/],
7
+ ['missing_required_option', /^required option '([^']+)' not specified/],
8
+ ['invalid_option_value', /^option '([^']+)' argument '.*' is invalid/s],
9
+ ['too_many_arguments', /^too many arguments/],
10
+ ];
11
+ /** Walks argv through the command tree as far as it names commands. */
12
+ export function resolveCommand(program, argv) {
13
+ const path = [];
14
+ let current = program;
15
+ for (let i = 0; i < argv.length; i++) {
16
+ const arg = argv[i];
17
+ if (arg === '--')
18
+ break;
19
+ if (GLOBAL_VALUE_OPTIONS.includes(arg)) {
20
+ i++;
21
+ continue;
22
+ }
23
+ if (arg.startsWith('-'))
24
+ continue;
25
+ const next = current.commands.find((command) => command.name() === arg || command.aliases().includes(arg));
26
+ if (!next)
27
+ return { path, command: current, unmatched: arg };
28
+ path.push(next.name());
29
+ current = next;
30
+ }
31
+ return { path, command: current, unmatched: null };
32
+ }
33
+ /** Optimal string alignment distance (Damerau-Levenshtein with adjacent transpositions). */
34
+ function editDistance(a, b) {
35
+ const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
36
+ for (let j = 0; j <= b.length; j++)
37
+ d[0][j] = j;
38
+ for (let i = 1; i <= a.length; i++) {
39
+ for (let j = 1; j <= b.length; j++) {
40
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
41
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
42
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
43
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
44
+ }
45
+ }
46
+ }
47
+ return d[a.length][b.length];
48
+ }
49
+ /** Closest candidates, using the same thresholds as Commander's own suggestions. */
50
+ export function suggestSimilar(word, candidates) {
51
+ let best = 3;
52
+ let similar = [];
53
+ for (const candidate of new Set(candidates)) {
54
+ if (candidate.length <= 1)
55
+ continue;
56
+ const distance = editDistance(word, candidate);
57
+ const length = Math.max(word.length, candidate.length);
58
+ if ((length - distance) / length <= 0.4)
59
+ continue;
60
+ if (distance < best) {
61
+ best = distance;
62
+ similar = [candidate];
63
+ }
64
+ else if (distance === best) {
65
+ similar.push(candidate);
66
+ }
67
+ }
68
+ return similar.sort((a, b) => a.localeCompare(b));
69
+ }
70
+ function shellQuote(arg) {
71
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
72
+ }
73
+ /**
74
+ * Turns Commander's error text ("error: unknown option '--limt'\n(Did you mean
75
+ * --limit?)") into a message, structured details and, when there is exactly
76
+ * one suggestion, the corrected command line an agent can run.
77
+ */
78
+ export function parseCommanderUsageError(text, argv, program) {
79
+ const body = text.trim().replace(/^error:\s*/i, '');
80
+ const suggestionMatch = /\n?\(Did you mean (?:one of )?(.+)\?\)\s*$/.exec(body);
81
+ let message = (suggestionMatch ? body.slice(0, suggestionMatch.index) : body).trim();
82
+ let didYouMean = suggestionMatch ? suggestionMatch[1].split(',').map((item) => item.trim()).filter(Boolean) : [];
83
+ const resolved = resolveCommand(program, argv);
84
+ let kind = 'usage';
85
+ let token = null;
86
+ for (const [candidate, pattern] of PATTERNS) {
87
+ const match = pattern.exec(message);
88
+ if (match) {
89
+ kind = candidate;
90
+ token = match[1] ?? null;
91
+ break;
92
+ }
93
+ }
94
+ // A command group with its own action reports a mistyped subcommand as an
95
+ // extra argument; report it as the unknown command it is.
96
+ if (resolved.command.commands.length > 0 && resolved.unmatched && (kind === 'too_many_arguments' || kind === 'unknown_command')) {
97
+ kind = 'unknown_command';
98
+ token = resolved.unmatched;
99
+ message = `unknown command '${token}'${resolved.path.length > 0 ? ` for 'dino ${resolved.path.join(' ')}'` : ''}`;
100
+ }
101
+ if (kind === 'unknown_command' && token) {
102
+ didYouMean = suggestSimilar(token, resolved.command.commands.flatMap((command) => [command.name(), ...command.aliases()]));
103
+ }
104
+ const command = resolved.path.length > 0 ? resolved.path.join('.') : null;
105
+ const details = { kind, token, did_you_mean: didYouMean, command };
106
+ // Only leaf commands have a schema of their own; the bare form lists them all.
107
+ const schemaHint = command && resolved.command.commands.length === 0
108
+ ? `dino schema ${command} --format json`
109
+ : 'dino schema --format json';
110
+ const parsed = {
111
+ message,
112
+ details,
113
+ suggestion: didYouMean.length > 0
114
+ ? `Did you mean ${didYouMean.join(' or ')}? Run \`${schemaHint}\` to list valid ${kind === 'unknown_command' ? 'commands' : 'options'}.`
115
+ : `Run \`${schemaHint}\` for the valid arguments and options.`,
116
+ };
117
+ const index = token ? argv.indexOf(token) : -1;
118
+ if (didYouMean.length === 1 && index !== -1 && (kind === 'unknown_option' || kind === 'unknown_command')) {
119
+ const corrected = [...argv];
120
+ corrected[index] = didYouMean[0];
121
+ parsed.suggestedAction = {
122
+ command: `dino ${corrected.map(shellQuote).join(' ')}`,
123
+ reason: `${token} is not a valid ${kind === 'unknown_option' ? 'option' : 'command'}; ${didYouMean[0]} is the closest match.`,
124
+ };
125
+ }
126
+ return parsed;
127
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinoxx/dinox-cli",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "description": "Dinox CLI",
5
5
  "main": "dist/dinox.js",
6
6
  "scripts": {