@dinoxx/dinox-cli 1.1.1 → 2.0.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/README.md +44 -3
- package/dist/cli.js +4 -0
- package/dist/commands/auth/index.js +1 -2
- package/dist/commands/boxes/index.js +37 -13
- package/dist/commands/boxes/operations.d.ts +3 -3
- package/dist/commands/boxes/operations.js +51 -28
- package/dist/commands/info/index.js +30 -5
- package/dist/commands/meta/repo.js +2 -1
- package/dist/commands/notes/actions.js +20 -37
- package/dist/commands/notes/bulk.d.ts +2 -2
- package/dist/commands/notes/bulk.js +3 -18
- package/dist/commands/notes/contentPatch.d.ts +5 -1
- package/dist/commands/notes/contentPatch.js +12 -1
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +29 -8
- package/dist/commands/notes/descriptors.js +19 -16
- package/dist/commands/notes/export.js +3 -0
- package/dist/commands/notes/repo.d.ts +9 -0
- package/dist/commands/notes/repo.js +65 -15
- package/dist/commands/notes/searchOutput.d.ts +2 -1
- package/dist/commands/notes/searchOutput.js +2 -1
- package/dist/commands/notes/searchSql.js +9 -3
- package/dist/commands/prompt/index.d.ts +1 -0
- package/dist/commands/prompt/index.js +26 -11
- package/dist/commands/savedViews/index.d.ts +7 -0
- package/dist/commands/savedViews/index.js +458 -0
- package/dist/commands/savedViews/model.d.ts +87 -0
- package/dist/commands/savedViews/model.js +306 -0
- package/dist/commands/savedViews/repo.d.ts +123 -0
- package/dist/commands/savedViews/repo.js +512 -0
- package/dist/commands/savedViews/semantics.d.ts +46 -0
- package/dist/commands/savedViews/semantics.js +738 -0
- package/dist/commands/schema/renderSkills.d.ts +2 -0
- package/dist/commands/schema/renderSkills.js +69 -10
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/skills/index.d.ts +54 -0
- package/dist/commands/skills/index.js +468 -0
- package/dist/commands/storage/index.js +2 -2
- package/dist/commands/sync.js +2 -1
- package/dist/commands/tags/index.js +30 -11
- package/dist/commands/tags/operations.d.ts +3 -3
- package/dist/commands/tags/operations.js +45 -20
- package/dist/commands/templates/model.d.ts +50 -0
- package/dist/commands/templates/model.js +239 -0
- package/dist/commands/todo/index.js +3 -3
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +46 -9
- package/dist/commands/writeConfirmation.d.ts +13 -0
- package/dist/commands/writeConfirmation.js +54 -0
- package/dist/config/resolve.js +5 -10
- package/dist/config/types.d.ts +3 -6
- package/dist/constants/links.d.ts +18 -1
- package/dist/constants/links.js +26 -1
- package/dist/daemon/client.d.ts +2 -2
- package/dist/daemon/client.js +106 -52
- package/dist/daemon/index.d.ts +5 -3
- package/dist/daemon/index.js +28 -5
- package/dist/daemon/lifecycle.d.ts +7 -2
- package/dist/daemon/lifecycle.js +53 -13
- package/dist/daemon/pid.d.ts +1 -1
- package/dist/daemon/pid.js +6 -3
- package/dist/dinox.js +9 -1
- package/dist/powersync/exclusiveRuntime.d.ts +1 -0
- package/dist/powersync/exclusiveRuntime.js +26 -8
- package/dist/powersync/runtime.d.ts +3 -2
- package/dist/powersync/runtime.js +19 -3
- package/dist/powersync/schema/content.d.ts +1 -78
- package/dist/powersync/schema/content.js +1 -78
- package/dist/powersync/schema/electronCanonical.d.ts +1561 -0
- package/dist/powersync/schema/electronCanonical.js +1154 -0
- package/dist/powersync/schema/index.d.ts +573 -129
- package/dist/powersync/schema/index.js +3 -32
- package/dist/powersync/schema/local.d.ts +1 -56
- package/dist/powersync/schema/local.js +1 -60
- package/dist/powersync/schema/note.d.ts +1 -35
- package/dist/powersync/schema/note.js +1 -35
- package/dist/powersync/schema/notesExtras.d.ts +1 -62
- package/dist/powersync/schema/notesExtras.js +1 -71
- package/dist/powersync/schema/projects.d.ts +1 -104
- package/dist/powersync/schema/projects.js +1 -104
- package/dist/powersync/schema/savedViews.d.ts +1 -0
- package/dist/powersync/schema/savedViews.js +1 -0
- package/dist/powersync/schema/storage.d.ts +1 -24
- package/dist/powersync/schema/storage.js +1 -24
- package/dist/powersync/schema/tags.d.ts +1 -37
- package/dist/powersync/schema/tags.js +1 -37
- package/dist/powersync/syncGate.d.ts +20 -2
- package/dist/powersync/uploader.d.ts +16 -2
- package/dist/powersync/uploader.js +152 -158
- package/dist/skills/check.d.ts +1 -0
- package/dist/skills/check.js +538 -70
- package/dist/skills/retired.d.ts +60 -0
- package/dist/skills/retired.js +58 -0
- package/dist/utils/deadline.d.ts +8 -0
- package/dist/utils/deadline.js +31 -0
- package/dist/utils/dryRun.d.ts +20 -1
- package/dist/utils/dryRun.js +22 -3
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.js +2 -0
- package/dist/utils/processExit.d.ts +1 -0
- package/dist/utils/processExit.js +33 -0
- package/dist/utils/updateNotice.d.ts +1 -0
- package/dist/utils/updateNotice.js +1 -1
- package/package.json +8 -5
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
|
+
import { readStringOrFile } from '../../utils/argValue.js';
|
|
3
|
+
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
4
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
2
5
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
3
6
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
4
7
|
import { buildPromptAddDryRunPreview } from '../writeDryRun.js';
|
|
5
|
-
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
6
|
-
import { DinoxError } from '../../utils/errors.js';
|
|
7
8
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
8
9
|
import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
9
10
|
import { addPrompt, inspectAddPrompt, listPrompts } from './repo.js';
|
|
10
11
|
const PROMPT_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
12
|
+
export async function parsePromptTextInput(value) {
|
|
13
|
+
if (typeof value !== 'string' || !value) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
return (await readStringOrFile(value)).trim();
|
|
17
|
+
}
|
|
11
18
|
async function runPromptList(context) {
|
|
12
19
|
await withCommandRuntime(context, async (runtime) => {
|
|
13
20
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
14
21
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
15
|
-
|
|
22
|
+
const prompts = (await listPrompts(runtime.db, { userId: identity.userId })).map((prompt) => ({
|
|
16
23
|
name: prompt.name,
|
|
17
24
|
prompt: prompt.cmd,
|
|
18
|
-
}))
|
|
25
|
+
}));
|
|
26
|
+
printSuccess({
|
|
27
|
+
stale: commandStale(runtime),
|
|
28
|
+
count: prompts.length,
|
|
29
|
+
prompts,
|
|
30
|
+
});
|
|
19
31
|
});
|
|
20
32
|
}
|
|
21
33
|
async function runPromptAdd(context) {
|
|
22
34
|
const name = typeof context.options.name === 'string' ? context.options.name.trim() : '';
|
|
23
|
-
const cmd =
|
|
35
|
+
const cmd = await parsePromptTextInput(context.options.prompt);
|
|
24
36
|
if (!name) {
|
|
25
|
-
throw new DinoxError('Prompt name is required. Use `dino prompt add --name <name> --prompt <
|
|
37
|
+
throw new DinoxError('Prompt name is required. Use `dino prompt add --name <name> --prompt <string|@file>`.');
|
|
26
38
|
}
|
|
27
39
|
if (!cmd) {
|
|
28
|
-
throw new DinoxError('Prompt text is required. Use `dino prompt add --name <name> --prompt <
|
|
40
|
+
throw new DinoxError('Prompt text is required. Use `dino prompt add --name <name> --prompt <string|@file>`.');
|
|
29
41
|
}
|
|
30
42
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
31
43
|
await withCommandRuntime(context, async (runtime) => {
|
|
@@ -40,7 +52,7 @@ async function runPromptAdd(context) {
|
|
|
40
52
|
cmd,
|
|
41
53
|
userId: identity.userId,
|
|
42
54
|
})),
|
|
43
|
-
}));
|
|
55
|
+
}), runtime);
|
|
44
56
|
return;
|
|
45
57
|
}
|
|
46
58
|
const result = await addPrompt(runtime.db, {
|
|
@@ -80,7 +92,7 @@ export const promptCommandGroup = {
|
|
|
80
92
|
output: {
|
|
81
93
|
type: 'structured',
|
|
82
94
|
summary: 'Prompt list payload',
|
|
83
|
-
fields: ['name', 'prompt'],
|
|
95
|
+
fields: ['stale', 'count', 'prompts.name', 'prompts.prompt'],
|
|
84
96
|
},
|
|
85
97
|
examples: ['dino prompt list --format json'],
|
|
86
98
|
},
|
|
@@ -91,7 +103,7 @@ export const promptCommandGroup = {
|
|
|
91
103
|
description: 'Create a prompt template, restoring a deleted prompt when possible',
|
|
92
104
|
options: [
|
|
93
105
|
{ flags: '--name <string>', description: 'Prompt name', required: true, type: 'string' },
|
|
94
|
-
{ flags: '--prompt <string>', description: 'Prompt text', required: true, type: 'string' },
|
|
106
|
+
{ flags: '--prompt <string|@file>', description: 'Prompt text or @file containing prompt text', required: true, type: 'string' },
|
|
95
107
|
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
96
108
|
],
|
|
97
109
|
schema: {
|
|
@@ -104,7 +116,10 @@ export const promptCommandGroup = {
|
|
|
104
116
|
summary: 'Prompt creation acknowledgement',
|
|
105
117
|
fields: ['id', 'name', 'prompt', 'restored', 'stale'],
|
|
106
118
|
},
|
|
107
|
-
examples: [
|
|
119
|
+
examples: [
|
|
120
|
+
'dino prompt add --name "周报助手" --prompt "请基于本周笔记输出一份简洁周报" --format json --dry-run',
|
|
121
|
+
'dino prompt add --name "周报助手" --prompt @/tmp/weekly-prompt.txt --format json --dry-run',
|
|
122
|
+
],
|
|
108
123
|
},
|
|
109
124
|
action: runPromptAdd,
|
|
110
125
|
},
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { CommandGroupDescriptor } from '../descriptor/types.js';
|
|
3
|
+
import { type SavedViewConfig, type SavedViewFilter } from './model.js';
|
|
4
|
+
export declare function parseSavedViewFilterOption(value: unknown): Promise<SavedViewFilter | undefined>;
|
|
5
|
+
export declare function parseSavedViewConfigOption(value: unknown): Promise<SavedViewConfig | undefined>;
|
|
6
|
+
export declare const savedViewCommandGroup: CommandGroupDescriptor;
|
|
7
|
+
export declare function registerSavedViewCommands(program: Command): void;
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
|
+
import { readStringOrFile } from '../../utils/argValue.js';
|
|
3
|
+
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
4
|
+
import { DinoxError } from '../../utils/errors.js';
|
|
5
|
+
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
6
|
+
import { registerCommandGroup } from '../descriptor/register.js';
|
|
7
|
+
import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
8
|
+
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS, } from '../schema/globalOptions.js';
|
|
9
|
+
import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
|
|
10
|
+
import { validateSavedViewConfig, validateSavedViewFilter, } from './model.js';
|
|
11
|
+
import { countSavedViewRows, createSavedView, deleteSavedView, getSavedView, getSavedViewFieldCatalog, inspectCreateSavedView, inspectDeleteSavedView, inspectUpdateSavedView, listSavedViews, querySavedView, updateSavedView, } from './repo.js';
|
|
12
|
+
const VIEW_GLOBAL_OPTIONS = [
|
|
13
|
+
...FORMAT_OPTIONS,
|
|
14
|
+
...OFFLINE_OPTIONS,
|
|
15
|
+
...REQUIRE_SYNC_OPTIONS,
|
|
16
|
+
...SYNC_TIMEOUT_OPTIONS,
|
|
17
|
+
];
|
|
18
|
+
function parseIntegerOption(value) {
|
|
19
|
+
const normalized = value.trim();
|
|
20
|
+
if (!/^-?\d+$/.test(normalized)) {
|
|
21
|
+
throw new DinoxError('Expected an integer', { code: 'USAGE_ERROR' });
|
|
22
|
+
}
|
|
23
|
+
const parsed = Number(normalized);
|
|
24
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
25
|
+
throw new DinoxError('Expected a safe integer', { code: 'USAGE_ERROR' });
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
function parsePositiveIntegerOption(value) {
|
|
30
|
+
const parsed = parseIntegerOption(value);
|
|
31
|
+
if (parsed <= 0)
|
|
32
|
+
throw new DinoxError('Expected a positive integer', { code: 'USAGE_ERROR' });
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
function parseNonNegativeIntegerOption(value) {
|
|
36
|
+
const parsed = parseIntegerOption(value);
|
|
37
|
+
if (parsed < 0)
|
|
38
|
+
throw new DinoxError('Expected a non-negative integer', { code: 'USAGE_ERROR' });
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
async function readJsonOption(value, label) {
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = await readStringOrFile(value);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new DinoxError(`Failed to read ${label}`, { code: 'USAGE_ERROR', cause: error });
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new DinoxError(`${label} must be valid JSON`, {
|
|
54
|
+
code: 'USAGE_ERROR',
|
|
55
|
+
cause: error,
|
|
56
|
+
suggestion: `Pass inline JSON or use @file. See \`dino schema view.create --format json\`.`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function parseSavedViewFilterOption(value) {
|
|
61
|
+
if (value === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
64
|
+
throw new DinoxError('--filter requires inline JSON or @file', { code: 'USAGE_ERROR' });
|
|
65
|
+
}
|
|
66
|
+
return validateSavedViewFilter(await readJsonOption(value, 'Saved view filter'));
|
|
67
|
+
}
|
|
68
|
+
export async function parseSavedViewConfigOption(value) {
|
|
69
|
+
if (value === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
72
|
+
throw new DinoxError('--config requires inline JSON or @file', { code: 'USAGE_ERROR' });
|
|
73
|
+
}
|
|
74
|
+
return validateSavedViewConfig(await readJsonOption(value, 'Saved view config'));
|
|
75
|
+
}
|
|
76
|
+
function requiredViewId(context) {
|
|
77
|
+
const id = context.args.id?.trim();
|
|
78
|
+
if (!id)
|
|
79
|
+
throw new DinoxError('Saved view id is required', { code: 'USAGE_ERROR' });
|
|
80
|
+
return id;
|
|
81
|
+
}
|
|
82
|
+
function assertExclusiveOptions(options) {
|
|
83
|
+
const conflicts = [
|
|
84
|
+
[Boolean(options.emoji !== undefined && options.clearEmoji), '--emoji cannot be used with --clear-emoji'],
|
|
85
|
+
[Boolean(options.templateId !== undefined && options.clearTemplate), '--template-id cannot be used with --clear-template'],
|
|
86
|
+
[Boolean(options.groupId !== undefined && options.clearGroup), '--group-id cannot be used with --clear-group'],
|
|
87
|
+
[Boolean(options.sortRank !== undefined && options.clearSortRank), '--sort-rank cannot be used with --clear-sort-rank'],
|
|
88
|
+
[Boolean(options.pinnedAt !== undefined && options.unpin), '--pinned-at cannot be used with --unpin'],
|
|
89
|
+
];
|
|
90
|
+
const conflict = conflicts.find(([active]) => active);
|
|
91
|
+
if (conflict)
|
|
92
|
+
throw new DinoxError(conflict[1], { code: 'USAGE_ERROR' });
|
|
93
|
+
}
|
|
94
|
+
async function runViewList(context) {
|
|
95
|
+
const limit = context.options.limit;
|
|
96
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
97
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
98
|
+
await syncBeforeCommand(runtime);
|
|
99
|
+
const views = await listSavedViews(runtime.db, { userId: identity.userId, limit });
|
|
100
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
101
|
+
printSuccess({ ok: true, stale: commandStale(runtime), count: views.length, views });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
for (const view of views) {
|
|
105
|
+
printHumanReadableLine(`${view.id}\t${view.emoji ?? ''}\t${view.name}${view.valid ? '' : '\t[invalid]'}`);
|
|
106
|
+
}
|
|
107
|
+
if (views.length === 0)
|
|
108
|
+
printHumanReadableLine('No saved views found.');
|
|
109
|
+
printStaleCacheWarning(runtime);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function runViewGet(context) {
|
|
113
|
+
const id = requiredViewId(context);
|
|
114
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
115
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
116
|
+
await syncBeforeCommand(runtime);
|
|
117
|
+
const view = await getSavedView(runtime.db, { id, userId: identity.userId });
|
|
118
|
+
printSuccess({ ok: true, stale: commandStale(runtime), view });
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function runViewFields(context) {
|
|
122
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
123
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
124
|
+
await syncBeforeCommand(runtime);
|
|
125
|
+
const fields = await getSavedViewFieldCatalog(runtime.db, identity.userId);
|
|
126
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
127
|
+
printSuccess({ ok: true, stale: commandStale(runtime), count: fields.length, fields });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const field of fields) {
|
|
131
|
+
printHumanReadableLine(`${field.field}\t${field.propertyType}\t${field.sortable ? 'sortable' : 'not-sortable'}\t${field.available ? 'available' : 'unavailable'}\t${field.name}`);
|
|
132
|
+
}
|
|
133
|
+
printStaleCacheWarning(runtime);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function runViewQuery(context) {
|
|
137
|
+
const viewId = requiredViewId(context);
|
|
138
|
+
const limit = context.options.limit;
|
|
139
|
+
const offset = context.options.offset;
|
|
140
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
141
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
142
|
+
await syncBeforeCommand(runtime);
|
|
143
|
+
const result = await querySavedView(runtime.db, {
|
|
144
|
+
viewId,
|
|
145
|
+
userId: identity.userId,
|
|
146
|
+
limit,
|
|
147
|
+
offset,
|
|
148
|
+
});
|
|
149
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
150
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (const row of result.rows) {
|
|
154
|
+
printHumanReadableLine(`${row.id}\t${row.title ?? ''}`);
|
|
155
|
+
}
|
|
156
|
+
printHumanReadableLine(`Showing ${result.rows.length} of ${result.totalCount} matching notes.`);
|
|
157
|
+
printStaleCacheWarning(runtime);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async function runViewCount(context) {
|
|
161
|
+
const viewId = requiredViewId(context);
|
|
162
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
163
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
164
|
+
await syncBeforeCommand(runtime);
|
|
165
|
+
const totalCount = await countSavedViewRows(runtime.db, { viewId, userId: identity.userId });
|
|
166
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
167
|
+
printSuccess({ ok: true, stale: commandStale(runtime), viewId, totalCount });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
printHumanReadableLine(String(totalCount));
|
|
171
|
+
printStaleCacheWarning(runtime);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async function runViewCreate(context) {
|
|
175
|
+
const options = context.options;
|
|
176
|
+
const name = options.name?.trim() ?? '';
|
|
177
|
+
if (!name)
|
|
178
|
+
throw new DinoxError('Saved view name is required. Use --name <string>.', { code: 'USAGE_ERROR' });
|
|
179
|
+
const [filter, config] = await Promise.all([
|
|
180
|
+
parseSavedViewFilterOption(options.filter),
|
|
181
|
+
parseSavedViewConfigOption(options.config),
|
|
182
|
+
]);
|
|
183
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
184
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
185
|
+
await syncBeforeCommand(runtime);
|
|
186
|
+
const input = {
|
|
187
|
+
userId: identity.userId,
|
|
188
|
+
name,
|
|
189
|
+
emoji: options.emoji,
|
|
190
|
+
filter,
|
|
191
|
+
config,
|
|
192
|
+
templateId: options.templateId,
|
|
193
|
+
groupId: options.groupId,
|
|
194
|
+
sortRank: options.sortRank,
|
|
195
|
+
pinnedAt: options.pinnedAt,
|
|
196
|
+
};
|
|
197
|
+
if (options.dryRun) {
|
|
198
|
+
const plan = await inspectCreateSavedView(runtime.db, input);
|
|
199
|
+
printDryRunPreview({
|
|
200
|
+
dry_run: true,
|
|
201
|
+
command: 'dino view create',
|
|
202
|
+
action: 'create_saved_view',
|
|
203
|
+
risk: 'write',
|
|
204
|
+
summary: `Create saved view "${name}".`,
|
|
205
|
+
inputs: { name, ...(filter ? { filter } : {}), ...(config ? { config } : {}) },
|
|
206
|
+
changes: plan.changes,
|
|
207
|
+
}, runtime);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const view = await createSavedView(runtime.db, input);
|
|
211
|
+
const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
|
|
212
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
213
|
+
printSuccess({ ok: true, stale: commandStale(runtime), view, ...receipt });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
printHumanReadableLine(view.id);
|
|
217
|
+
printStaleCacheWarning(runtime);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function runViewUpdate(context) {
|
|
221
|
+
const id = requiredViewId(context);
|
|
222
|
+
const options = context.options;
|
|
223
|
+
assertExclusiveOptions(options);
|
|
224
|
+
const [filter, config] = await Promise.all([
|
|
225
|
+
parseSavedViewFilterOption(options.filter),
|
|
226
|
+
parseSavedViewConfigOption(options.config),
|
|
227
|
+
]);
|
|
228
|
+
const inputPatch = {
|
|
229
|
+
...(options.name === undefined ? {} : { name: options.name }),
|
|
230
|
+
...(options.clearEmoji ? { emoji: null } : options.emoji === undefined ? {} : { emoji: options.emoji }),
|
|
231
|
+
...(filter === undefined ? {} : { filter }),
|
|
232
|
+
...(config === undefined ? {} : { config }),
|
|
233
|
+
...(options.clearTemplate ? { templateId: null } : options.templateId === undefined ? {} : { templateId: options.templateId }),
|
|
234
|
+
...(options.clearGroup ? { groupId: null } : options.groupId === undefined ? {} : { groupId: options.groupId }),
|
|
235
|
+
...(options.clearSortRank ? { sortRank: null } : options.sortRank === undefined ? {} : { sortRank: options.sortRank }),
|
|
236
|
+
...(options.unpin ? { pinnedAt: null } : options.pinnedAt === undefined ? {} : { pinnedAt: options.pinnedAt }),
|
|
237
|
+
};
|
|
238
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
239
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
240
|
+
await syncBeforeCommand(runtime);
|
|
241
|
+
const input = { id, userId: identity.userId, ...inputPatch };
|
|
242
|
+
if (options.dryRun) {
|
|
243
|
+
const plan = await inspectUpdateSavedView(runtime.db, input);
|
|
244
|
+
printDryRunPreview({
|
|
245
|
+
dry_run: true,
|
|
246
|
+
command: 'dino view update',
|
|
247
|
+
action: 'update_saved_view',
|
|
248
|
+
risk: 'write',
|
|
249
|
+
summary: `Update saved view "${plan.target?.name ?? id}".`,
|
|
250
|
+
targets: plan.target ? [{ id: plan.target.id, name: plan.target.name }] : undefined,
|
|
251
|
+
changes: plan.changes,
|
|
252
|
+
}, runtime);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const view = await updateSavedView(runtime.db, input);
|
|
256
|
+
const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
|
|
257
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
258
|
+
printSuccess({ ok: true, stale: commandStale(runtime), view, ...receipt });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
printHumanReadableLine(view.id);
|
|
262
|
+
printStaleCacheWarning(runtime);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async function runViewDelete(context) {
|
|
266
|
+
const id = requiredViewId(context);
|
|
267
|
+
const options = context.options;
|
|
268
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
269
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
270
|
+
await syncBeforeCommand(runtime);
|
|
271
|
+
const input = { id, userId: identity.userId };
|
|
272
|
+
if (options.dryRun) {
|
|
273
|
+
const plan = await inspectDeleteSavedView(runtime.db, input);
|
|
274
|
+
printDryRunPreview({
|
|
275
|
+
dry_run: true,
|
|
276
|
+
command: 'dino view delete',
|
|
277
|
+
action: 'delete_saved_view',
|
|
278
|
+
risk: 'write',
|
|
279
|
+
summary: `Soft-delete saved view "${plan.target?.name ?? id}".`,
|
|
280
|
+
targets: plan.target ? [{ id: plan.target.id, name: plan.target.name }] : undefined,
|
|
281
|
+
changes: plan.changes,
|
|
282
|
+
}, runtime);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const view = await deleteSavedView(runtime.db, input);
|
|
286
|
+
const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
|
|
287
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
288
|
+
printSuccess({ ok: true, stale: commandStale(runtime), deleted: true, view, ...receipt });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
printHumanReadableLine(`Deleted ${view.id}`);
|
|
292
|
+
printStaleCacheWarning(runtime);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
export const savedViewCommandGroup = {
|
|
296
|
+
name: 'view',
|
|
297
|
+
description: 'Manage and query saved table views',
|
|
298
|
+
commands: [
|
|
299
|
+
{
|
|
300
|
+
name: 'list',
|
|
301
|
+
description: 'List active saved table views',
|
|
302
|
+
options: [
|
|
303
|
+
{ flags: '--limit <n>', description: 'Maximum returned views (1-5000)', parser: parsePositiveIntegerOption, type: 'number', defaultValue: 200 },
|
|
304
|
+
],
|
|
305
|
+
schema: {
|
|
306
|
+
path: 'view.list',
|
|
307
|
+
risk: 'read',
|
|
308
|
+
auth: 'required',
|
|
309
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
310
|
+
output: { type: 'structured', summary: 'Saved view catalog', fields: ['stale', 'count', 'views'] },
|
|
311
|
+
examples: ['dino view list --format json'],
|
|
312
|
+
},
|
|
313
|
+
action: runViewList,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: 'get',
|
|
317
|
+
description: 'Get one saved view definition',
|
|
318
|
+
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
319
|
+
schema: {
|
|
320
|
+
path: 'view.get',
|
|
321
|
+
risk: 'read',
|
|
322
|
+
auth: 'required',
|
|
323
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
324
|
+
output: { type: 'structured', summary: 'Canonical saved view definition', fields: ['stale', 'view'] },
|
|
325
|
+
examples: ['dino view get <id> --format json'],
|
|
326
|
+
},
|
|
327
|
+
action: runViewGet,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: 'fields',
|
|
331
|
+
description: 'List system and property fields available to saved views',
|
|
332
|
+
schema: {
|
|
333
|
+
path: 'view.fields',
|
|
334
|
+
risk: 'read',
|
|
335
|
+
auth: 'required',
|
|
336
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
337
|
+
output: { type: 'structured', summary: 'Saved view field catalog and filter semantics', fields: ['stale', 'count', 'fields'] },
|
|
338
|
+
examples: ['dino view fields --format json'],
|
|
339
|
+
},
|
|
340
|
+
action: runViewFields,
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: 'query',
|
|
344
|
+
description: 'Query notes through a saved view filter and sort definition',
|
|
345
|
+
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
346
|
+
options: [
|
|
347
|
+
{ flags: '--limit <n>', description: 'Maximum returned notes (1-500)', parser: parsePositiveIntegerOption, type: 'number', defaultValue: 100 },
|
|
348
|
+
{ flags: '--offset <n>', description: 'Result offset for pagination', parser: parseNonNegativeIntegerOption, type: 'number', defaultValue: 0 },
|
|
349
|
+
],
|
|
350
|
+
schema: {
|
|
351
|
+
path: 'view.query',
|
|
352
|
+
risk: 'read',
|
|
353
|
+
auth: 'required',
|
|
354
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
355
|
+
output: { type: 'structured', summary: 'Saved view page with projected configured columns', fields: ['stale', 'view', 'columns', 'totalCount', 'offset', 'limit', 'nextOffset', 'hasMore', 'rows'] },
|
|
356
|
+
examples: ['dino view query <id> --limit 50 --offset 0 --format json'],
|
|
357
|
+
},
|
|
358
|
+
action: runViewQuery,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: 'count',
|
|
362
|
+
description: 'Count notes matching a saved view',
|
|
363
|
+
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
364
|
+
schema: {
|
|
365
|
+
path: 'view.count',
|
|
366
|
+
risk: 'read',
|
|
367
|
+
auth: 'required',
|
|
368
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
369
|
+
output: { type: 'structured', summary: 'Saved view matching note count', fields: ['stale', 'viewId', 'totalCount'] },
|
|
370
|
+
examples: ['dino view count <id> --format json'],
|
|
371
|
+
},
|
|
372
|
+
action: runViewCount,
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: 'create',
|
|
376
|
+
description: 'Create a canonical saved table view',
|
|
377
|
+
options: [
|
|
378
|
+
{ flags: '--name <string>', description: 'Saved view name', required: true, type: 'string' },
|
|
379
|
+
{ flags: '--emoji <string>', description: 'Optional emoji or short icon text', type: 'string' },
|
|
380
|
+
{ flags: '--filter <json|@file>', description: 'Canonical version 1 saved view filter JSON; defaults to no filter', type: 'string' },
|
|
381
|
+
{ flags: '--config <json|@file>', description: 'Canonical version 1 saved view config JSON; defaults to title + updated_at', type: 'string' },
|
|
382
|
+
{ flags: '--template-id <id>', description: 'Optional note template id associated with the view', type: 'string' },
|
|
383
|
+
{ flags: '--group-id <id>', description: 'Optional saved view group id', type: 'string' },
|
|
384
|
+
{ flags: '--sort-rank <n>', description: 'Explicit catalog sort rank', parser: parseIntegerOption, type: 'number' },
|
|
385
|
+
{ flags: '--pinned-at <timestamp>', description: 'Pinned timestamp', type: 'string' },
|
|
386
|
+
WRITE_DURABILITY_OPTION,
|
|
387
|
+
{ flags: '--dry-run', description: 'Validate and preview the view without writing', type: 'boolean' },
|
|
388
|
+
],
|
|
389
|
+
schema: {
|
|
390
|
+
path: 'view.create',
|
|
391
|
+
risk: 'write',
|
|
392
|
+
auth: 'required',
|
|
393
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
394
|
+
output: { type: 'structured', summary: 'Created saved view definition or dry-run plan', fields: ['view', 'durability', 'upload_queue_remaining', 'stale'] },
|
|
395
|
+
examples: [
|
|
396
|
+
'dino view create --name "Recent notes" --format json --dry-run',
|
|
397
|
+
'dino view create --name "Active projects" --filter @filter.json --config @config.json --format json',
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
action: runViewCreate,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: 'update',
|
|
404
|
+
description: 'Update a canonical saved table view',
|
|
405
|
+
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
406
|
+
options: [
|
|
407
|
+
{ flags: '--name <string>', description: 'Replace the saved view name', type: 'string' },
|
|
408
|
+
{ flags: '--emoji <string>', description: 'Replace emoji or short icon text', type: 'string' },
|
|
409
|
+
{ flags: '--clear-emoji', description: 'Clear the saved view emoji', type: 'boolean' },
|
|
410
|
+
{ flags: '--filter <json|@file>', description: 'Replace the canonical version 1 filter JSON', type: 'string' },
|
|
411
|
+
{ flags: '--config <json|@file>', description: 'Replace the canonical version 1 config JSON', type: 'string' },
|
|
412
|
+
{ flags: '--template-id <id>', description: 'Replace the associated note template id', type: 'string' },
|
|
413
|
+
{ flags: '--clear-template', description: 'Clear the associated note template id', type: 'boolean' },
|
|
414
|
+
{ flags: '--group-id <id>', description: 'Replace the saved view group id', type: 'string' },
|
|
415
|
+
{ flags: '--clear-group', description: 'Clear the saved view group id', type: 'boolean' },
|
|
416
|
+
{ flags: '--sort-rank <n>', description: 'Replace the catalog sort rank', parser: parseIntegerOption, type: 'number' },
|
|
417
|
+
{ flags: '--clear-sort-rank', description: 'Clear the catalog sort rank', type: 'boolean' },
|
|
418
|
+
{ flags: '--pinned-at <timestamp>', description: 'Replace the pinned timestamp', type: 'string' },
|
|
419
|
+
{ flags: '--unpin', description: 'Clear the pinned timestamp', type: 'boolean' },
|
|
420
|
+
WRITE_DURABILITY_OPTION,
|
|
421
|
+
{ flags: '--dry-run', description: 'Validate and preview the update without writing', type: 'boolean' },
|
|
422
|
+
],
|
|
423
|
+
schema: {
|
|
424
|
+
path: 'view.update',
|
|
425
|
+
risk: 'write',
|
|
426
|
+
auth: 'required',
|
|
427
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
428
|
+
output: { type: 'structured', summary: 'Updated saved view definition or dry-run plan', fields: ['view', 'durability', 'upload_queue_remaining', 'stale'] },
|
|
429
|
+
examples: [
|
|
430
|
+
'dino view update <id> --filter @filter.json --format json --dry-run',
|
|
431
|
+
'dino view update <id> --name "Priority work" --unpin --format json',
|
|
432
|
+
],
|
|
433
|
+
},
|
|
434
|
+
action: runViewUpdate,
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'delete',
|
|
438
|
+
description: 'Soft-delete a saved view',
|
|
439
|
+
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
440
|
+
options: [
|
|
441
|
+
WRITE_DURABILITY_OPTION,
|
|
442
|
+
{ flags: '--dry-run', description: 'Preview the soft delete without writing', type: 'boolean' },
|
|
443
|
+
],
|
|
444
|
+
schema: {
|
|
445
|
+
path: 'view.delete',
|
|
446
|
+
risk: 'write',
|
|
447
|
+
auth: 'required',
|
|
448
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
449
|
+
output: { type: 'structured', summary: 'Saved view soft-delete acknowledgement or dry-run plan', fields: ['deleted', 'view', 'durability', 'upload_queue_remaining', 'stale'] },
|
|
450
|
+
examples: ['dino view delete <id> --format json --dry-run'],
|
|
451
|
+
},
|
|
452
|
+
action: runViewDelete,
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
};
|
|
456
|
+
export function registerSavedViewCommands(program) {
|
|
457
|
+
registerCommandGroup(program, savedViewCommandGroup);
|
|
458
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export declare const SAVED_VIEW_FILTER_VERSION = 1;
|
|
2
|
+
export declare const SAVED_VIEW_CONFIG_VERSION = 1;
|
|
3
|
+
export declare const MAX_SAVED_VIEW_FILTER_DEPTH = 8;
|
|
4
|
+
export declare const MAX_SAVED_VIEW_FILTER_NODES = 100;
|
|
5
|
+
export declare const MAX_SAVED_VIEW_FILTER_VALUE_LENGTH = 2000;
|
|
6
|
+
export declare const MAX_SAVED_VIEW_COLUMNS = 100;
|
|
7
|
+
export declare const MAX_SAVED_VIEW_SORTS = 20;
|
|
8
|
+
export declare const SAVED_VIEW_SYSTEM_FIELDS: readonly ["title", "created_at", "updated_at", "tags", "zettel_boxes", "source", "type", "starred_at"];
|
|
9
|
+
export declare const SAVED_VIEW_FILTER_OPERATORS: readonly ["equals", "notEquals", "contains", "notContains", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "isEmpty", "isNotEmpty"];
|
|
10
|
+
export type SavedViewSystemField = (typeof SAVED_VIEW_SYSTEM_FIELDS)[number];
|
|
11
|
+
export type SavedViewFieldRef = `sys.${SavedViewSystemField}` | `prop.${string}`;
|
|
12
|
+
export type SavedViewFilterOperator = (typeof SAVED_VIEW_FILTER_OPERATORS)[number];
|
|
13
|
+
export type SavedViewFilterValue = string | number | boolean | null;
|
|
14
|
+
export type SavedViewFilterCondition = {
|
|
15
|
+
kind: 'condition';
|
|
16
|
+
field: SavedViewFieldRef;
|
|
17
|
+
operator: SavedViewFilterOperator;
|
|
18
|
+
value?: SavedViewFilterValue;
|
|
19
|
+
};
|
|
20
|
+
export type SavedViewFilterGroup = {
|
|
21
|
+
kind: 'group';
|
|
22
|
+
operator: 'and' | 'or';
|
|
23
|
+
children: SavedViewFilterNode[];
|
|
24
|
+
};
|
|
25
|
+
export type SavedViewFilterNot = {
|
|
26
|
+
kind: 'not';
|
|
27
|
+
child: SavedViewFilterNode;
|
|
28
|
+
};
|
|
29
|
+
export type SavedViewFilterNode = SavedViewFilterCondition | SavedViewFilterGroup | SavedViewFilterNot;
|
|
30
|
+
export type SavedViewFilter = {
|
|
31
|
+
version: typeof SAVED_VIEW_FILTER_VERSION;
|
|
32
|
+
root: SavedViewFilterNode | null;
|
|
33
|
+
};
|
|
34
|
+
export type SavedViewColumn = {
|
|
35
|
+
field: SavedViewFieldRef;
|
|
36
|
+
};
|
|
37
|
+
export type SavedViewSort = {
|
|
38
|
+
field: SavedViewFieldRef;
|
|
39
|
+
direction: 'asc' | 'desc';
|
|
40
|
+
};
|
|
41
|
+
export type SavedViewConfig = {
|
|
42
|
+
version: typeof SAVED_VIEW_CONFIG_VERSION;
|
|
43
|
+
layout: 'table';
|
|
44
|
+
columns: SavedViewColumn[];
|
|
45
|
+
sort?: SavedViewSort[];
|
|
46
|
+
};
|
|
47
|
+
export type SavedViewRow = {
|
|
48
|
+
id: string;
|
|
49
|
+
user_id: string | null;
|
|
50
|
+
name: string | null;
|
|
51
|
+
emoji: string | null;
|
|
52
|
+
filter_json: string | null;
|
|
53
|
+
view_type: string | null;
|
|
54
|
+
config_json: string | null;
|
|
55
|
+
template_id: string | null;
|
|
56
|
+
group_id: string | null;
|
|
57
|
+
sort_rank: number | null;
|
|
58
|
+
pinned_at: string | null;
|
|
59
|
+
created_at: string | null;
|
|
60
|
+
updated_at: string | null;
|
|
61
|
+
is_del: number | null;
|
|
62
|
+
};
|
|
63
|
+
export type SavedViewDefinition = {
|
|
64
|
+
id: string;
|
|
65
|
+
userId: string;
|
|
66
|
+
name: string;
|
|
67
|
+
emoji: string | null;
|
|
68
|
+
filter: SavedViewFilter;
|
|
69
|
+
config: SavedViewConfig;
|
|
70
|
+
templateId: string | null;
|
|
71
|
+
groupId: string | null;
|
|
72
|
+
sortRank: number | null;
|
|
73
|
+
pinnedAt: string | null;
|
|
74
|
+
createdAt: string | null;
|
|
75
|
+
updatedAt: string | null;
|
|
76
|
+
};
|
|
77
|
+
export declare function isNotePropertyKeyValid(value: unknown): value is string;
|
|
78
|
+
export declare function isSavedViewFieldRef(value: unknown): value is SavedViewFieldRef;
|
|
79
|
+
export declare function validateSavedViewFilter(value: unknown): SavedViewFilter;
|
|
80
|
+
export declare function parseSavedViewFilter(raw: string | null | undefined): SavedViewFilter;
|
|
81
|
+
export declare function serializeSavedViewFilter(value: unknown): string;
|
|
82
|
+
export declare function validateSavedViewConfig(value: unknown): SavedViewConfig;
|
|
83
|
+
export declare function parseSavedViewConfig(raw: string | null | undefined): SavedViewConfig;
|
|
84
|
+
export declare function serializeSavedViewConfig(value: unknown): string;
|
|
85
|
+
export declare function createDefaultSavedViewFilter(): SavedViewFilter;
|
|
86
|
+
export declare function createDefaultSavedViewConfig(): SavedViewConfig;
|
|
87
|
+
export declare function mapSavedViewRow(row: SavedViewRow): SavedViewDefinition;
|