@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
package/dist/utils/errorCodes.js
CHANGED
|
@@ -17,6 +17,7 @@ export const ERROR_CODES = {
|
|
|
17
17
|
COMMAND_FAILED: { exitCode: 1, description: 'Generic command failure without a more specific code.' },
|
|
18
18
|
CONFIG_INVALID_JSON: { exitCode: 1, description: 'The CLI config file is not valid JSON.' },
|
|
19
19
|
CONFIG_WRITE_BUSY: { exitCode: 1, description: 'Another process is writing the CLI config.' },
|
|
20
|
+
DAEMON_COMMAND_NOT_FORWARDABLE: { exitCode: 2, description: 'The command must run in the CLI process.' },
|
|
20
21
|
DAEMON_ERROR: { exitCode: 5, description: 'The background daemon reported an error.' },
|
|
21
22
|
DAEMON_INSTANCE_CHANGED: { exitCode: 1, description: 'The daemon instance changed between requests.' },
|
|
22
23
|
DAEMON_INSTANCE_MISMATCH: { exitCode: 1, description: 'The running daemon belongs to a different CLI version or account.' },
|
|
@@ -25,6 +26,7 @@ export const ERROR_CODES = {
|
|
|
25
26
|
DAEMON_RELEASE_FAILED: { exitCode: 1, description: 'The daemon could not release database ownership.' },
|
|
26
27
|
DAEMON_RELEASE_TIMEOUT: { exitCode: 1, description: 'The daemon did not release database ownership in time.' },
|
|
27
28
|
DAEMON_SERVICE_FAILED: { exitCode: 1, description: 'The service manager (launchd / systemd) rejected a daemon service operation.' },
|
|
29
|
+
DAEMON_SERVICE_FOREIGN: { exitCode: 1, description: 'The daemon service runs another dino install; it was left unchanged.' },
|
|
28
30
|
DAEMON_SERVICE_UNSUPPORTED: { exitCode: 5, description: 'The daemon cannot run as a login service on this platform or with this auth setup.' },
|
|
29
31
|
DAEMON_STALE: { exitCode: 5, description: 'The daemon state is stale and needs a restart.' },
|
|
30
32
|
DAEMON_START_FAILED: { exitCode: 5, description: 'The daemon process failed to start.' },
|
|
@@ -91,6 +93,9 @@ export const ERROR_CODES = {
|
|
|
91
93
|
PROPERTY_UNIQUE_ID_TYPE_MISMATCH: { exitCode: 1, description: 'A unique-id setting was supplied for a different property type.' },
|
|
92
94
|
PROPERTY_URL_INVALID: { exitCode: 1, description: 'A url property value is invalid.' },
|
|
93
95
|
PROPERTY_VALUE_INVALID: { exitCode: 1, description: 'A property value does not match its definition.' },
|
|
96
|
+
REQUEST_ID_CONFLICT: { exitCode: 2, description: 'The request id was already used for different arguments.' },
|
|
97
|
+
REQUEST_INTERRUPTED: { exitCode: 1, description: 'The earlier request owner exited; its outcome is unknown.' },
|
|
98
|
+
REQUEST_IN_PROGRESS: { exitCode: 1, description: 'The earlier request is still running; retry with the same id.' },
|
|
94
99
|
SAVED_VIEW_FILTER_CORRUPT: { exitCode: 1, description: 'A stored saved view filter is corrupt.' },
|
|
95
100
|
SAVED_VIEW_FILTER_TOO_COMPLEX: { exitCode: 1, description: 'The saved view filter exceeds supported complexity.' },
|
|
96
101
|
SAVED_VIEW_FILTER_VALUE_REQUIRED: { exitCode: 5, description: 'A saved view filter condition is missing its value.' },
|
|
@@ -118,6 +123,7 @@ export const ERROR_CODES = {
|
|
|
118
123
|
SAVED_VIEW_UNSUPPORTED_LAYOUT: { exitCode: 1, description: 'The saved view layout is unsupported.' },
|
|
119
124
|
SAVED_VIEW_VALUE_TYPE_MISMATCH: { exitCode: 1, description: 'A filter value does not match the field type.' },
|
|
120
125
|
SCHEMA_PATH_NOT_FOUND: { exitCode: 5, description: 'No command schema exists for the requested path.' },
|
|
126
|
+
SEMANTIC_SEARCH_UNAVAILABLE: { exitCode: 1, description: 'The semantic search service could not be reached or failed; keyword search (note search) still works.' },
|
|
121
127
|
SKILLS_LOCK_INVALID: { exitCode: 1, description: 'The skills installer lock file is malformed.' },
|
|
122
128
|
SKILLS_LOCK_READ_FAILED: { exitCode: 1, description: 'The skills installer lock file could not be read.' },
|
|
123
129
|
SKILLS_MIGRATION_FAILED: { exitCode: 1, description: 'The skills installer failed during migration.' },
|
package/dist/utils/output.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
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;
|
|
4
5
|
export declare function toJson(value: unknown): string;
|
|
6
|
+
export declare function writeToStdout(text: string): void;
|
|
7
|
+
export declare function writeToStderr(text: string): void;
|
|
5
8
|
export declare function printHumanReadableLine(text: string): void;
|
|
6
9
|
export declare function printHumanReadableBlock(text: string): void;
|
|
7
10
|
export declare function printHumanReadableWarning(text: string): void;
|
|
@@ -14,6 +17,7 @@ export declare function parseStructuredOutputFormatFromArgv(argv: string[]): Str
|
|
|
14
17
|
export declare function setRequestedStructuredOutputFormat(format: StructuredOutputFormat | null): void;
|
|
15
18
|
export declare function clearRequestedStructuredOutputFormat(): void;
|
|
16
19
|
export declare function getRequestedStructuredOutputFormat(): StructuredOutputFormat | null;
|
|
20
|
+
export declare function setRequestedFieldSelection(selection: FieldSelection | null): void;
|
|
17
21
|
export declare function setPendingStructuredNoticeProvider(provider: (() => StructuredNoticeMap | null) | null): void;
|
|
18
22
|
export declare function clearPendingStructuredNoticeProvider(): void;
|
|
19
23
|
export declare function printStructuredData(value: unknown, format?: StructuredOutputFormat | null): void;
|
package/dist/utils/output.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { stringify as stringifyYaml } from 'yaml';
|
|
2
2
|
import { DinoxError } from './errors.js';
|
|
3
3
|
import { sanitizeForTerminal } from './terminalSanitization.js';
|
|
4
|
+
import { requestContext } from './requestContext.js';
|
|
5
|
+
import { projectFields } from './select.js';
|
|
4
6
|
let requestedStructuredOutputFormat = null;
|
|
5
7
|
let pendingStructuredNoticeProvider = null;
|
|
8
|
+
let requestedFieldSelection = null;
|
|
6
9
|
export function toYaml(value) {
|
|
7
10
|
return stringifyYaml(value, {
|
|
8
11
|
lineWidth: 0,
|
|
@@ -12,10 +15,20 @@ export function toYaml(value) {
|
|
|
12
15
|
export function toJson(value) {
|
|
13
16
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
14
17
|
}
|
|
15
|
-
function writeToStdout(text) {
|
|
18
|
+
export function writeToStdout(text) {
|
|
19
|
+
const context = requestContext.getStore();
|
|
20
|
+
if (context) {
|
|
21
|
+
context.stdout.push(text);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
16
24
|
process.stdout.write(text);
|
|
17
25
|
}
|
|
18
|
-
function writeToStderr(text) {
|
|
26
|
+
export function writeToStderr(text) {
|
|
27
|
+
const context = requestContext.getStore();
|
|
28
|
+
if (context) {
|
|
29
|
+
context.stderr.push(text);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
19
32
|
process.stderr.write(text);
|
|
20
33
|
}
|
|
21
34
|
export function printHumanReadableLine(text) {
|
|
@@ -92,25 +105,68 @@ export function parseStructuredOutputFormatFromArgv(argv) {
|
|
|
92
105
|
return explicitFormat ?? (hasLegacyJson ? 'yaml' : null);
|
|
93
106
|
}
|
|
94
107
|
export function setRequestedStructuredOutputFormat(format) {
|
|
108
|
+
const context = requestContext.getStore();
|
|
109
|
+
if (context) {
|
|
110
|
+
context.format = format;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
95
113
|
requestedStructuredOutputFormat = format;
|
|
96
114
|
}
|
|
97
115
|
export function clearRequestedStructuredOutputFormat() {
|
|
98
|
-
|
|
116
|
+
setRequestedStructuredOutputFormat(null);
|
|
99
117
|
}
|
|
100
118
|
export function getRequestedStructuredOutputFormat() {
|
|
101
|
-
|
|
119
|
+
const context = requestContext.getStore();
|
|
120
|
+
return context ? context.format : requestedStructuredOutputFormat;
|
|
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
|
+
};
|
|
102
151
|
}
|
|
103
152
|
export function setPendingStructuredNoticeProvider(provider) {
|
|
153
|
+
const context = requestContext.getStore();
|
|
154
|
+
if (context) {
|
|
155
|
+
context.noticeProvider = provider;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
104
158
|
pendingStructuredNoticeProvider = provider;
|
|
105
159
|
}
|
|
106
160
|
export function clearPendingStructuredNoticeProvider() {
|
|
107
|
-
|
|
161
|
+
setPendingStructuredNoticeProvider(null);
|
|
108
162
|
}
|
|
109
163
|
function getPendingStructuredNotice() {
|
|
110
|
-
|
|
164
|
+
const context = requestContext.getStore();
|
|
165
|
+
const provider = context ? context.noticeProvider : pendingStructuredNoticeProvider;
|
|
166
|
+
if (!provider) {
|
|
111
167
|
return null;
|
|
112
168
|
}
|
|
113
|
-
return
|
|
169
|
+
return provider();
|
|
114
170
|
}
|
|
115
171
|
export function printStructuredData(value, format = getRequestedStructuredOutputFormat()) {
|
|
116
172
|
if (format === 'json') {
|
|
@@ -166,15 +222,21 @@ function summarizeStructuredError(value) {
|
|
|
166
222
|
export function printSuccess(value, format = getRequestedStructuredOutputFormat()) {
|
|
167
223
|
if (format === 'json') {
|
|
168
224
|
const data = normalizeSuccessEnvelopeData(value);
|
|
169
|
-
const
|
|
225
|
+
const selected = selectRequestedFields(data);
|
|
226
|
+
const notice = mergeStructuredNotices(mergeStructuredNotices(getPendingStructuredNotice(), deriveStructuredNotice(data)) ?? null, selected.notice);
|
|
170
227
|
printStructuredData({
|
|
171
228
|
ok: true,
|
|
172
|
-
data,
|
|
229
|
+
data: selected.value,
|
|
173
230
|
...(notice ? { _notice: notice } : {}),
|
|
174
231
|
}, 'json');
|
|
175
232
|
return;
|
|
176
233
|
}
|
|
177
|
-
|
|
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);
|
|
178
240
|
}
|
|
179
241
|
export function printError(value, format = getRequestedStructuredOutputFormat()) {
|
|
180
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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
3
|
+
import type { ResolvedDinoxConfig } from '../config/types.js';
|
|
4
|
+
import type { StructuredNoticeMap, StructuredOutputFormat } from './output.js';
|
|
5
|
+
import type { FieldSelection } from './select.js';
|
|
6
|
+
import type { ProgressReporting } from './progress.js';
|
|
7
|
+
export type RequestContext = {
|
|
8
|
+
stdout: string[];
|
|
9
|
+
stderr: string[];
|
|
10
|
+
format: StructuredOutputFormat | null;
|
|
11
|
+
/** `--select` of the running command; absent means the whole result. */
|
|
12
|
+
select?: FieldSelection | null;
|
|
13
|
+
noticeProvider: (() => StructuredNoticeMap | null) | null;
|
|
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;
|
|
19
|
+
daemon?: {
|
|
20
|
+
db: AbstractPowerSyncDatabase;
|
|
21
|
+
config: ResolvedDinoxConfig;
|
|
22
|
+
/**
|
|
23
|
+
* Set after the daemon reconnected (wake from sleep, network change): the
|
|
24
|
+
* stream is only known to be current once a checkpoint completes after it.
|
|
25
|
+
*/
|
|
26
|
+
syncUnconfirmedSince?: () => string | null;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export declare const requestContext: AsyncLocalStorage<RequestContext>;
|
|
30
|
+
export declare function resolveUserPath(p: string): string;
|
|
31
|
+
export declare function assertStdinAvailable(): void;
|
|
32
|
+
export declare const localCommandExecution: AsyncLocalStorage<boolean>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { DinoxError } from './errors.js';
|
|
4
|
+
export const requestContext = new AsyncLocalStorage();
|
|
5
|
+
export function resolveUserPath(p) {
|
|
6
|
+
return path.resolve(requestContext.getStore()?.cwd ?? process.cwd(), p);
|
|
7
|
+
}
|
|
8
|
+
export function assertStdinAvailable() {
|
|
9
|
+
if (requestContext.getStore()?.daemon) {
|
|
10
|
+
throw new DinoxError('Reading stdin inside the daemon is not supported.', { code: 'USAGE_ERROR' });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export const localCommandExecution = new AsyncLocalStorage();
|
|
@@ -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,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dinoxx/dinox-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"description": "Dinox CLI",
|
|
5
5
|
"main": "dist/dinox.js",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"bench:startup": "node scripts/bench-startup.mjs",
|
|
7
8
|
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/add-shebang.mjs",
|
|
8
9
|
"dev": "tsx src/dinox.ts",
|
|
9
10
|
"prepublishOnly": "pnpm run skills:check && pnpm run build",
|