@dinoxx/dinox-cli 2.1.1 → 2.3.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/cli.js +4 -0
- package/dist/commands/domain/options.d.ts +10 -0
- package/dist/commands/domain/options.js +75 -0
- package/dist/commands/domain/run.d.ts +24 -0
- package/dist/commands/domain/run.js +137 -0
- package/dist/commands/notes/actions.js +72 -20
- package/dist/commands/notes/createFromTemplate.d.ts +35 -0
- package/dist/commands/notes/createFromTemplate.js +58 -0
- package/dist/commands/notes/descriptors.js +12 -5
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +8 -2
- package/dist/commands/notes/repo/read.js +4 -23
- package/dist/commands/notes/repo/shared.d.ts +2 -0
- package/dist/commands/notes/repo/write.d.ts +3 -0
- package/dist/commands/notes/repo/write.js +16 -5
- package/dist/commands/properties/index.d.ts +16 -0
- package/dist/commands/properties/index.js +640 -0
- package/dist/commands/properties/repo.d.ts +150 -0
- package/dist/commands/properties/repo.js +272 -0
- package/dist/commands/savedViews/fields.d.ts +28 -0
- package/dist/commands/savedViews/fields.js +87 -0
- package/dist/commands/savedViews/index.js +203 -164
- package/dist/commands/savedViews/model.d.ts +27 -69
- package/dist/commands/savedViews/model.js +63 -279
- package/dist/commands/savedViews/repo.d.ts +64 -62
- package/dist/commands/savedViews/repo.js +309 -428
- package/dist/commands/schema/renderSkills.js +29 -5
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/templates/index.d.ts +4 -0
- package/dist/commands/templates/index.js +290 -0
- package/dist/commands/templates/model.d.ts +12 -34
- package/dist/commands/templates/model.js +39 -83
- package/dist/commands/templates/repo.d.ts +53 -0
- package/dist/commands/templates/repo.js +78 -0
- package/dist/skills/registry.js +7 -0
- package/dist/utils/errorCodes.d.ts +212 -0
- package/dist/utils/errorCodes.js +53 -0
- package/dist/vendor/electron/domain/note-templates.d.ts +3 -0
- package/dist/vendor/electron/domain/note-templates.js +232 -0
- package/dist/vendor/electron/domain/properties.d.ts +10 -0
- package/dist/vendor/electron/domain/properties.js +1538 -0
- package/dist/vendor/electron/domain/saved-view-analytics.d.ts +45 -0
- package/dist/vendor/electron/domain/saved-view-analytics.js +365 -0
- package/dist/vendor/electron/domain/saved-view-relative-date.d.ts +21 -0
- package/dist/vendor/electron/domain/saved-view-relative-date.js +153 -0
- package/dist/vendor/electron/domain/saved-views.d.ts +61 -0
- package/dist/vendor/electron/domain/saved-views.js +1117 -0
- package/dist/vendor/electron/rpc.d.ts +22 -0
- package/dist/vendor/electron/rpc.js +27 -0
- package/dist/vendor/electron/shared/ai-errors.d.ts +11 -0
- package/dist/vendor/electron/shared/ai-errors.js +107 -0
- package/dist/vendor/electron/shared/external-types.d.ts +2 -0
- package/dist/vendor/electron/shared/external-types.js +1 -0
- package/dist/vendor/electron/shared/index.d.ts +9 -0
- package/dist/vendor/electron/shared/index.js +7 -0
- package/dist/vendor/electron/shared/note-properties.d.ts +312 -0
- package/dist/vendor/electron/shared/note-properties.js +982 -0
- package/dist/vendor/electron/shared/note-templates.d.ts +82 -0
- package/dist/vendor/electron/shared/note-templates.js +84 -0
- package/dist/vendor/electron/shared/powersync-read-models.d.ts +1710 -0
- package/dist/vendor/electron/shared/powersync-read-models.js +1 -0
- package/dist/vendor/electron/shared/saved-views.d.ts +394 -0
- package/dist/vendor/electron/shared/saved-views.js +981 -0
- package/dist/vendor/electron/sql-utils.d.ts +5 -0
- package/dist/vendor/electron/sql-utils.js +32 -0
- package/package.json +6 -3
- package/dist/commands/savedViews/semantics.d.ts +0 -46
- package/dist/commands/savedViews/semantics.js +0 -738
|
@@ -1,95 +1,65 @@
|
|
|
1
1
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
2
|
-
import { readStringOrFile } from '../../utils/argValue.js';
|
|
3
2
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
4
3
|
import { DinoxError } from '../../utils/errors.js';
|
|
5
4
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
6
5
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
6
|
+
import { assertExclusive, parseIntegerOption, parseNonNegativeIntegerOption, parsePositiveIntegerOption, parseTimeZoneOption, readJsonOption, requiredArgument, } from '../domain/options.js';
|
|
7
7
|
import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
8
8
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS, } from '../schema/globalOptions.js';
|
|
9
9
|
import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
|
|
10
|
-
import { validateSavedViewConfig, validateSavedViewFilter, } from './model.js';
|
|
11
|
-
import { countSavedViewRows, createSavedView, deleteSavedView, getSavedView,
|
|
10
|
+
import { SAVED_VIEW_LAYOUTS, validateSavedViewConfig, validateSavedViewFilter, } from './model.js';
|
|
11
|
+
import { countSavedViewRows, createSavedView, deleteSavedView, getNoteDetailSections, getSavedView, getSavedViewAnalytics, listSavedViewFields, listSavedViews, previewCreateSavedView, previewDeleteSavedView, previewUpdateSavedView, querySavedView, updateSavedView, } from './repo.js';
|
|
12
12
|
const VIEW_GLOBAL_OPTIONS = [
|
|
13
13
|
...FORMAT_OPTIONS,
|
|
14
14
|
...OFFLINE_OPTIONS,
|
|
15
15
|
...REQUIRE_SYNC_OPTIONS,
|
|
16
16
|
...SYNC_TIMEOUT_OPTIONS,
|
|
17
17
|
];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
}
|
|
18
|
+
const TIME_ZONE_OPTION = {
|
|
19
|
+
flags: '--time-zone <iana>',
|
|
20
|
+
description: 'IANA time zone for dynamic date filters (isToday, isThisWeek, withinLastDays); defaults to the system zone',
|
|
21
|
+
parser: parseTimeZoneOption,
|
|
22
|
+
type: 'string',
|
|
23
|
+
};
|
|
24
|
+
const LAYOUT_OPTION_DESCRIPTION = `View layout (${SAVED_VIEW_LAYOUTS.join(', ')}); overrides config.layout`;
|
|
60
25
|
export async function parseSavedViewFilterOption(value) {
|
|
61
26
|
if (value === undefined)
|
|
62
27
|
return undefined;
|
|
63
|
-
|
|
64
|
-
throw new DinoxError('--filter requires inline JSON or @file', { code: 'USAGE_ERROR' });
|
|
65
|
-
}
|
|
66
|
-
return validateSavedViewFilter(await readJsonOption(value, 'Saved view filter'));
|
|
28
|
+
return validateSavedViewFilter(await readJsonOption(value, '--filter', 'view.create'));
|
|
67
29
|
}
|
|
68
30
|
export async function parseSavedViewConfigOption(value) {
|
|
69
31
|
if (value === undefined)
|
|
70
32
|
return undefined;
|
|
71
|
-
|
|
72
|
-
|
|
33
|
+
return validateSavedViewConfig(await readJsonOption(value, '--config', 'view.create'));
|
|
34
|
+
}
|
|
35
|
+
function parseLayoutOption(value) {
|
|
36
|
+
const normalized = value.trim();
|
|
37
|
+
if (!SAVED_VIEW_LAYOUTS.includes(normalized)) {
|
|
38
|
+
throw new DinoxError(`--layout must be one of ${SAVED_VIEW_LAYOUTS.join(', ')}`, { code: 'USAGE_ERROR' });
|
|
73
39
|
}
|
|
74
|
-
return
|
|
40
|
+
return normalized;
|
|
75
41
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
42
|
+
function inspectionOutput(inspection) {
|
|
43
|
+
return {
|
|
44
|
+
view: inspection.view,
|
|
45
|
+
valid: inspection.valid,
|
|
46
|
+
...(inspection.valid ? {} : { error: inspection.error }),
|
|
47
|
+
};
|
|
81
48
|
}
|
|
82
|
-
function
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
49
|
+
function printPreview(command, action, summary, preview, runtime) {
|
|
50
|
+
const target = preview.before?.view ?? preview.after?.view;
|
|
51
|
+
printDryRunPreview({
|
|
52
|
+
dry_run: true,
|
|
53
|
+
command,
|
|
54
|
+
action,
|
|
55
|
+
risk: 'write',
|
|
56
|
+
summary,
|
|
57
|
+
...(target ? { targets: [{ id: target.id, name: target.name }] } : {}),
|
|
58
|
+
changes: {
|
|
59
|
+
before: preview.before ? inspectionOutput(preview.before) : null,
|
|
60
|
+
after: preview.after ? inspectionOutput(preview.after) : null,
|
|
61
|
+
},
|
|
62
|
+
}, runtime);
|
|
93
63
|
}
|
|
94
64
|
async function runViewList(context) {
|
|
95
65
|
const limit = context.options.limit;
|
|
@@ -102,7 +72,7 @@ async function runViewList(context) {
|
|
|
102
72
|
return;
|
|
103
73
|
}
|
|
104
74
|
for (const view of views) {
|
|
105
|
-
printHumanReadableLine(`${view.id}\t${view.emoji ?? ''}\t${view.name}${view.valid ? '' : '\t[invalid]'}`);
|
|
75
|
+
printHumanReadableLine(`${view.id}\t${view.emoji ?? ''}\t${view.layout ?? ''}\t${view.name}${view.valid ? '' : '\t[invalid]'}`);
|
|
106
76
|
}
|
|
107
77
|
if (views.length === 0)
|
|
108
78
|
printHumanReadableLine('No saved views found.');
|
|
@@ -110,19 +80,19 @@ async function runViewList(context) {
|
|
|
110
80
|
});
|
|
111
81
|
}
|
|
112
82
|
async function runViewGet(context) {
|
|
113
|
-
const id =
|
|
83
|
+
const id = requiredArgument(context.args.id, 'Saved view id');
|
|
114
84
|
await withCommandRuntime(context, async (runtime) => {
|
|
115
85
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
116
86
|
await syncBeforeCommand(runtime);
|
|
117
|
-
const
|
|
118
|
-
printSuccess({ ok: true, stale: commandStale(runtime),
|
|
87
|
+
const inspection = await getSavedView(runtime.db, { id, userId: identity.userId });
|
|
88
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...inspectionOutput(inspection) });
|
|
119
89
|
});
|
|
120
90
|
}
|
|
121
91
|
async function runViewFields(context) {
|
|
122
92
|
await withCommandRuntime(context, async (runtime) => {
|
|
123
93
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
124
94
|
await syncBeforeCommand(runtime);
|
|
125
|
-
const fields = await
|
|
95
|
+
const fields = await listSavedViewFields(runtime.db, identity.userId);
|
|
126
96
|
if (getRequestedStructuredOutputFormat()) {
|
|
127
97
|
printSuccess({ ok: true, stale: commandStale(runtime), count: fields.length, fields });
|
|
128
98
|
return;
|
|
@@ -134,9 +104,10 @@ async function runViewFields(context) {
|
|
|
134
104
|
});
|
|
135
105
|
}
|
|
136
106
|
async function runViewQuery(context) {
|
|
137
|
-
const viewId =
|
|
107
|
+
const viewId = requiredArgument(context.args.id, 'Saved view id');
|
|
138
108
|
const limit = context.options.limit;
|
|
139
109
|
const offset = context.options.offset;
|
|
110
|
+
const timeZone = context.options.timeZone;
|
|
140
111
|
await withCommandRuntime(context, async (runtime) => {
|
|
141
112
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
142
113
|
await syncBeforeCommand(runtime);
|
|
@@ -145,6 +116,7 @@ async function runViewQuery(context) {
|
|
|
145
116
|
userId: identity.userId,
|
|
146
117
|
limit,
|
|
147
118
|
offset,
|
|
119
|
+
timeZone,
|
|
148
120
|
});
|
|
149
121
|
if (getRequestedStructuredOutputFormat()) {
|
|
150
122
|
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
@@ -158,11 +130,12 @@ async function runViewQuery(context) {
|
|
|
158
130
|
});
|
|
159
131
|
}
|
|
160
132
|
async function runViewCount(context) {
|
|
161
|
-
const viewId =
|
|
133
|
+
const viewId = requiredArgument(context.args.id, 'Saved view id');
|
|
134
|
+
const timeZone = context.options.timeZone;
|
|
162
135
|
await withCommandRuntime(context, async (runtime) => {
|
|
163
136
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
164
137
|
await syncBeforeCommand(runtime);
|
|
165
|
-
const totalCount = await countSavedViewRows(runtime.db, { viewId, userId: identity.userId });
|
|
138
|
+
const totalCount = await countSavedViewRows(runtime.db, { viewId, userId: identity.userId, timeZone });
|
|
166
139
|
if (getRequestedStructuredOutputFormat()) {
|
|
167
140
|
printSuccess({ ok: true, stale: commandStale(runtime), viewId, totalCount });
|
|
168
141
|
return;
|
|
@@ -171,134 +144,159 @@ async function runViewCount(context) {
|
|
|
171
144
|
printStaleCacheWarning(runtime);
|
|
172
145
|
});
|
|
173
146
|
}
|
|
174
|
-
async function
|
|
175
|
-
const
|
|
176
|
-
const
|
|
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
|
-
]);
|
|
147
|
+
async function runViewAnalytics(context) {
|
|
148
|
+
const viewId = requiredArgument(context.args.id, 'Saved view id');
|
|
149
|
+
const timeZone = context.options.timeZone;
|
|
183
150
|
await withCommandRuntime(context, async (runtime) => {
|
|
184
151
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
185
152
|
await syncBeforeCommand(runtime);
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
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);
|
|
153
|
+
const analytics = await getSavedViewAnalytics(runtime.db, { viewId, userId: identity.userId, timeZone });
|
|
154
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
155
|
+
printSuccess({ ok: true, stale: commandStale(runtime), viewId, ...analytics });
|
|
208
156
|
return;
|
|
209
157
|
}
|
|
210
|
-
const
|
|
211
|
-
|
|
158
|
+
for (const stat of analytics.stats) {
|
|
159
|
+
printHumanReadableLine(`stat\t${stat.id}\t${stat.unavailable ? 'unavailable' : String(stat.value)}`);
|
|
160
|
+
}
|
|
161
|
+
for (const chart of analytics.charts) {
|
|
162
|
+
printHumanReadableLine(`chart\t${chart.id}\t${chart.unavailable ? 'unavailable' : `${chart.rows.length} rows`}`);
|
|
163
|
+
}
|
|
164
|
+
for (const item of analytics.summary) {
|
|
165
|
+
printHumanReadableLine(`summary\t${item.column}\t${item.aggregation}\t${item.unavailable ? 'unavailable' : String(item.value)}`);
|
|
166
|
+
}
|
|
167
|
+
if (analytics.stats.length + analytics.charts.length + analytics.summary.length === 0) {
|
|
168
|
+
printHumanReadableLine('This view has no stats, charts, or summary configured.');
|
|
169
|
+
}
|
|
170
|
+
printStaleCacheWarning(runtime);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async function runViewNoteDetail(context) {
|
|
174
|
+
const noteId = requiredArgument(context.args.noteId, 'Note id');
|
|
175
|
+
const limit = context.options.limit;
|
|
176
|
+
const timeZone = context.options.timeZone;
|
|
177
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
178
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
179
|
+
await syncBeforeCommand(runtime);
|
|
180
|
+
const sections = await getNoteDetailSections(runtime.db, { noteId, userId: identity.userId, limit, timeZone });
|
|
212
181
|
if (getRequestedStructuredOutputFormat()) {
|
|
213
|
-
printSuccess({ ok: true, stale: commandStale(runtime),
|
|
182
|
+
printSuccess({ ok: true, stale: commandStale(runtime), noteId, count: sections.length, sections });
|
|
214
183
|
return;
|
|
215
184
|
}
|
|
216
|
-
|
|
185
|
+
for (const section of sections) {
|
|
186
|
+
printHumanReadableLine(`${section.viewId}\t${section.emoji ?? ''}\t${section.name}\t${section.totalCount} notes`);
|
|
187
|
+
for (const row of section.rows)
|
|
188
|
+
printHumanReadableLine(` ${row.id}\t${row.title ?? ''}`);
|
|
189
|
+
}
|
|
190
|
+
if (sections.length === 0)
|
|
191
|
+
printHumanReadableLine('No saved view embeds this note.');
|
|
217
192
|
printStaleCacheWarning(runtime);
|
|
218
193
|
});
|
|
219
194
|
}
|
|
220
|
-
async function
|
|
221
|
-
const id = requiredViewId(context);
|
|
222
|
-
const options = context.options;
|
|
223
|
-
assertExclusiveOptions(options);
|
|
195
|
+
async function readWriteInput(options) {
|
|
224
196
|
const [filter, config] = await Promise.all([
|
|
225
197
|
parseSavedViewFilterOption(options.filter),
|
|
226
198
|
parseSavedViewConfigOption(options.config),
|
|
227
199
|
]);
|
|
228
|
-
|
|
200
|
+
return {
|
|
229
201
|
...(options.name === undefined ? {} : { name: options.name }),
|
|
230
202
|
...(options.clearEmoji ? { emoji: null } : options.emoji === undefined ? {} : { emoji: options.emoji }),
|
|
231
203
|
...(filter === undefined ? {} : { filter }),
|
|
232
204
|
...(config === undefined ? {} : { config }),
|
|
205
|
+
...(options.layout === undefined ? {} : { layout: parseLayoutOption(options.layout) }),
|
|
233
206
|
...(options.clearTemplate ? { templateId: null } : options.templateId === undefined ? {} : { templateId: options.templateId }),
|
|
234
207
|
...(options.clearGroup ? { groupId: null } : options.groupId === undefined ? {} : { groupId: options.groupId }),
|
|
235
208
|
...(options.clearSortRank ? { sortRank: null } : options.sortRank === undefined ? {} : { sortRank: options.sortRank }),
|
|
236
209
|
...(options.unpin ? { pinnedAt: null } : options.pinnedAt === undefined ? {} : { pinnedAt: options.pinnedAt }),
|
|
237
210
|
};
|
|
211
|
+
}
|
|
212
|
+
async function runViewCreate(context) {
|
|
213
|
+
const options = context.options;
|
|
214
|
+
const name = options.name?.trim() ?? '';
|
|
215
|
+
if (!name)
|
|
216
|
+
throw new DinoxError('Saved view name is required. Use --name <string>.', { code: 'USAGE_ERROR' });
|
|
217
|
+
const patch = await readWriteInput(options);
|
|
238
218
|
await withCommandRuntime(context, async (runtime) => {
|
|
239
219
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
240
220
|
await syncBeforeCommand(runtime);
|
|
241
|
-
const input = {
|
|
221
|
+
const input = { ...patch, userId: identity.userId, name };
|
|
242
222
|
if (options.dryRun) {
|
|
243
|
-
const
|
|
244
|
-
|
|
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);
|
|
223
|
+
const preview = await previewCreateSavedView(runtime.db, input);
|
|
224
|
+
printPreview('dino view create', 'create_saved_view', `Create saved view "${name}".`, preview, runtime);
|
|
253
225
|
return;
|
|
254
226
|
}
|
|
255
|
-
const
|
|
227
|
+
const inspection = await createSavedView(runtime.db, input);
|
|
256
228
|
const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
|
|
257
229
|
if (getRequestedStructuredOutputFormat()) {
|
|
258
|
-
printSuccess({ ok: true, stale: commandStale(runtime),
|
|
230
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...inspectionOutput(inspection), ...receipt });
|
|
259
231
|
return;
|
|
260
232
|
}
|
|
261
|
-
printHumanReadableLine(view.id);
|
|
233
|
+
printHumanReadableLine(inspection.view.id);
|
|
234
|
+
printStaleCacheWarning(runtime);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
async function runViewUpdate(context) {
|
|
238
|
+
const id = requiredArgument(context.args.id, 'Saved view id');
|
|
239
|
+
const options = context.options;
|
|
240
|
+
assertExclusive([
|
|
241
|
+
[options.emoji !== undefined && Boolean(options.clearEmoji), '--emoji cannot be used with --clear-emoji'],
|
|
242
|
+
[options.templateId !== undefined && Boolean(options.clearTemplate), '--template-id cannot be used with --clear-template'],
|
|
243
|
+
[options.groupId !== undefined && Boolean(options.clearGroup), '--group-id cannot be used with --clear-group'],
|
|
244
|
+
[options.sortRank !== undefined && Boolean(options.clearSortRank), '--sort-rank cannot be used with --clear-sort-rank'],
|
|
245
|
+
[options.pinnedAt !== undefined && Boolean(options.unpin), '--pinned-at cannot be used with --unpin'],
|
|
246
|
+
]);
|
|
247
|
+
const patch = await readWriteInput(options);
|
|
248
|
+
if (Object.keys(patch).length === 0) {
|
|
249
|
+
throw new DinoxError('At least one saved-view field must be updated', { code: 'USAGE_ERROR' });
|
|
250
|
+
}
|
|
251
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
252
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
253
|
+
await syncBeforeCommand(runtime);
|
|
254
|
+
const input = { ...patch, id, userId: identity.userId };
|
|
255
|
+
if (options.dryRun) {
|
|
256
|
+
const preview = await previewUpdateSavedView(runtime.db, input);
|
|
257
|
+
printPreview('dino view update', 'update_saved_view', `Update saved view "${preview.before?.view.name ?? id}".`, preview, runtime);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const inspection = await updateSavedView(runtime.db, input);
|
|
261
|
+
const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
|
|
262
|
+
if (getRequestedStructuredOutputFormat()) {
|
|
263
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...inspectionOutput(inspection), ...receipt });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
printHumanReadableLine(inspection.view.id);
|
|
262
267
|
printStaleCacheWarning(runtime);
|
|
263
268
|
});
|
|
264
269
|
}
|
|
265
270
|
async function runViewDelete(context) {
|
|
266
|
-
const id =
|
|
271
|
+
const id = requiredArgument(context.args.id, 'Saved view id');
|
|
267
272
|
const options = context.options;
|
|
268
273
|
await withCommandRuntime(context, async (runtime) => {
|
|
269
274
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
270
275
|
await syncBeforeCommand(runtime);
|
|
271
276
|
const input = { id, userId: identity.userId };
|
|
272
277
|
if (options.dryRun) {
|
|
273
|
-
const
|
|
274
|
-
|
|
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);
|
|
278
|
+
const preview = await previewDeleteSavedView(runtime.db, input);
|
|
279
|
+
printPreview('dino view delete', 'delete_saved_view', `Soft-delete saved view "${preview.before?.view.name ?? id}".`, preview, runtime);
|
|
283
280
|
return;
|
|
284
281
|
}
|
|
285
|
-
const
|
|
282
|
+
const inspection = await deleteSavedView(runtime.db, input);
|
|
286
283
|
const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
|
|
287
284
|
if (getRequestedStructuredOutputFormat()) {
|
|
288
|
-
printSuccess({ ok: true, stale: commandStale(runtime), deleted: true, view, ...receipt });
|
|
285
|
+
printSuccess({ ok: true, stale: commandStale(runtime), deleted: true, view: inspection.view, ...receipt });
|
|
289
286
|
return;
|
|
290
287
|
}
|
|
291
|
-
printHumanReadableLine(`Deleted ${view.id}`);
|
|
288
|
+
printHumanReadableLine(`Deleted ${inspection.view.id}`);
|
|
292
289
|
printStaleCacheWarning(runtime);
|
|
293
290
|
});
|
|
294
291
|
}
|
|
292
|
+
const WRITE_OUTPUT_FIELDS = ['view', 'valid', 'error', 'durability', 'upload_queue_remaining', 'stale'];
|
|
295
293
|
export const savedViewCommandGroup = {
|
|
296
294
|
name: 'view',
|
|
297
|
-
description: 'Manage and query saved table
|
|
295
|
+
description: 'Manage and query saved views (table, list, card) and their analytics',
|
|
298
296
|
commands: [
|
|
299
297
|
{
|
|
300
298
|
name: 'list',
|
|
301
|
-
description: 'List active saved
|
|
299
|
+
description: 'List active saved views',
|
|
302
300
|
options: [
|
|
303
301
|
{ flags: '--limit <n>', description: 'Maximum returned views (1-5000)', parser: parsePositiveIntegerOption, type: 'number', defaultValue: 200 },
|
|
304
302
|
],
|
|
@@ -307,28 +305,28 @@ export const savedViewCommandGroup = {
|
|
|
307
305
|
risk: 'read',
|
|
308
306
|
auth: 'required',
|
|
309
307
|
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
310
|
-
output: { type: 'structured', summary: 'Saved view catalog', fields: ['stale', 'count', 'views'] },
|
|
308
|
+
output: { type: 'structured', summary: 'Saved view catalog in desktop sidebar order; invalid rows are listed with valid=false', fields: ['stale', 'count', 'views'] },
|
|
311
309
|
examples: ['dino view list --format json'],
|
|
312
310
|
},
|
|
313
311
|
action: runViewList,
|
|
314
312
|
},
|
|
315
313
|
{
|
|
316
314
|
name: 'get',
|
|
317
|
-
description: 'Get one saved view definition',
|
|
315
|
+
description: 'Get one saved view definition (stored rows the CLI cannot parse return valid=false with the raw JSON)',
|
|
318
316
|
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
319
317
|
schema: {
|
|
320
318
|
path: 'view.get',
|
|
321
319
|
risk: 'read',
|
|
322
320
|
auth: 'required',
|
|
323
321
|
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
324
|
-
output: { type: 'structured', summary: '
|
|
322
|
+
output: { type: 'structured', summary: 'Saved view definition with validity', fields: ['stale', 'view', 'valid', 'error'] },
|
|
325
323
|
examples: ['dino view get <id> --format json'],
|
|
326
324
|
},
|
|
327
325
|
action: runViewGet,
|
|
328
326
|
},
|
|
329
327
|
{
|
|
330
328
|
name: 'fields',
|
|
331
|
-
description: 'List system and property fields
|
|
329
|
+
description: 'List system and property fields with their filter operators, sortability, and option ids',
|
|
332
330
|
schema: {
|
|
333
331
|
path: 'view.fields',
|
|
334
332
|
risk: 'read',
|
|
@@ -346,14 +344,18 @@ export const savedViewCommandGroup = {
|
|
|
346
344
|
options: [
|
|
347
345
|
{ flags: '--limit <n>', description: 'Maximum returned notes (1-500)', parser: parsePositiveIntegerOption, type: 'number', defaultValue: 100 },
|
|
348
346
|
{ flags: '--offset <n>', description: 'Result offset for pagination', parser: parseNonNegativeIntegerOption, type: 'number', defaultValue: 0 },
|
|
347
|
+
TIME_ZONE_OPTION,
|
|
349
348
|
],
|
|
350
349
|
schema: {
|
|
351
350
|
path: 'view.query',
|
|
352
351
|
risk: 'read',
|
|
353
352
|
auth: 'required',
|
|
354
353
|
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: [
|
|
354
|
+
output: { type: 'structured', summary: 'Saved view page with projected configured and computed columns', fields: ['stale', 'view', 'columns', 'totalCount', 'offset', 'limit', 'nextOffset', 'hasMore', 'rows'] },
|
|
355
|
+
examples: [
|
|
356
|
+
'dino view query <id> --limit 50 --offset 0 --format json',
|
|
357
|
+
'dino view query <id> --time-zone Asia/Shanghai --format json',
|
|
358
|
+
],
|
|
357
359
|
},
|
|
358
360
|
action: runViewQuery,
|
|
359
361
|
},
|
|
@@ -361,6 +363,7 @@ export const savedViewCommandGroup = {
|
|
|
361
363
|
name: 'count',
|
|
362
364
|
description: 'Count notes matching a saved view',
|
|
363
365
|
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
366
|
+
options: [TIME_ZONE_OPTION],
|
|
364
367
|
schema: {
|
|
365
368
|
path: 'view.count',
|
|
366
369
|
risk: 'read',
|
|
@@ -371,44 +374,79 @@ export const savedViewCommandGroup = {
|
|
|
371
374
|
},
|
|
372
375
|
action: runViewCount,
|
|
373
376
|
},
|
|
377
|
+
{
|
|
378
|
+
name: 'analytics',
|
|
379
|
+
description: 'Compute the stats, charts, and summary row configured on a saved view',
|
|
380
|
+
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
381
|
+
options: [TIME_ZONE_OPTION],
|
|
382
|
+
schema: {
|
|
383
|
+
path: 'view.analytics',
|
|
384
|
+
risk: 'read',
|
|
385
|
+
auth: 'required',
|
|
386
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
387
|
+
output: { type: 'structured', summary: 'Aggregates over the full view scope (not the visible page)', fields: ['stale', 'viewId', 'stats', 'charts', 'summary', 'config'] },
|
|
388
|
+
examples: ['dino view analytics <id> --format json'],
|
|
389
|
+
},
|
|
390
|
+
action: runViewAnalytics,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'note-detail',
|
|
394
|
+
description: 'List saved views embedded on a note (views with a noteDetail rule, $self bound to the note)',
|
|
395
|
+
arguments: [{ syntax: '<noteId>', description: 'Host note id' }],
|
|
396
|
+
options: [
|
|
397
|
+
{ flags: '--limit <n>', description: 'Maximum rows per section (1-100)', parser: parsePositiveIntegerOption, type: 'number', defaultValue: 6 },
|
|
398
|
+
TIME_ZONE_OPTION,
|
|
399
|
+
],
|
|
400
|
+
schema: {
|
|
401
|
+
path: 'view.note-detail',
|
|
402
|
+
risk: 'read',
|
|
403
|
+
auth: 'required',
|
|
404
|
+
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
405
|
+
output: { type: 'structured', summary: 'Note-detail sections with projected rows', fields: ['stale', 'noteId', 'count', 'sections'] },
|
|
406
|
+
examples: ['dino view note-detail <noteId> --format json'],
|
|
407
|
+
},
|
|
408
|
+
action: runViewNoteDetail,
|
|
409
|
+
},
|
|
374
410
|
{
|
|
375
411
|
name: 'create',
|
|
376
|
-
description: 'Create a
|
|
412
|
+
description: 'Create a saved view',
|
|
377
413
|
options: [
|
|
378
414
|
{ flags: '--name <string>', description: 'Saved view name', required: true, type: 'string' },
|
|
379
415
|
{ flags: '--emoji <string>', description: 'Optional emoji or short icon text', type: 'string' },
|
|
380
|
-
{ flags: '--filter <json|@file>', description: '
|
|
381
|
-
{ flags: '--config <json|@file>', description: '
|
|
416
|
+
{ flags: '--filter <json|@file>', description: 'Saved view filter JSON (version 1-3); defaults to no filter', type: 'string' },
|
|
417
|
+
{ flags: '--config <json|@file>', description: 'Saved view config JSON (columns, sort, layout, noteDetail, stats, charts, computedColumns, summary); defaults to title + updated_at', type: 'string' },
|
|
418
|
+
{ flags: '--layout <layout>', description: LAYOUT_OPTION_DESCRIPTION, type: 'string', enum: [...SAVED_VIEW_LAYOUTS] },
|
|
382
419
|
{ flags: '--template-id <id>', description: 'Optional note template id associated with the view', type: 'string' },
|
|
383
420
|
{ 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' },
|
|
421
|
+
{ flags: '--sort-rank <n>', description: 'Explicit catalog sort rank (defaults to the end of the sidebar)', parser: parseIntegerOption, type: 'number' },
|
|
385
422
|
{ flags: '--pinned-at <timestamp>', description: 'Pinned timestamp', type: 'string' },
|
|
386
423
|
WRITE_DURABILITY_OPTION,
|
|
387
|
-
{ flags: '--dry-run', description: 'Validate and preview the view without writing', type: 'boolean' },
|
|
424
|
+
{ flags: '--dry-run', description: 'Validate and preview the stored view without writing', type: 'boolean' },
|
|
388
425
|
],
|
|
389
426
|
schema: {
|
|
390
427
|
path: 'view.create',
|
|
391
428
|
risk: 'write',
|
|
392
429
|
auth: 'required',
|
|
393
430
|
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
394
|
-
output: { type: 'structured', summary: 'Created saved view definition or dry-run plan', fields:
|
|
431
|
+
output: { type: 'structured', summary: 'Created saved view definition or dry-run plan', fields: WRITE_OUTPUT_FIELDS },
|
|
395
432
|
examples: [
|
|
396
433
|
'dino view create --name "Recent notes" --format json --dry-run',
|
|
397
|
-
'dino view create --name "
|
|
434
|
+
'dino view create --name "Reading list" --layout card --filter @filter.json --config @config.json --format json',
|
|
398
435
|
],
|
|
399
436
|
},
|
|
400
437
|
action: runViewCreate,
|
|
401
438
|
},
|
|
402
439
|
{
|
|
403
440
|
name: 'update',
|
|
404
|
-
description: 'Update a
|
|
441
|
+
description: 'Update a saved view; fields you do not pass are left as stored',
|
|
405
442
|
arguments: [{ syntax: '<id>', description: 'Saved view id' }],
|
|
406
443
|
options: [
|
|
407
444
|
{ flags: '--name <string>', description: 'Replace the saved view name', type: 'string' },
|
|
408
445
|
{ flags: '--emoji <string>', description: 'Replace emoji or short icon text', type: 'string' },
|
|
409
446
|
{ flags: '--clear-emoji', description: 'Clear the saved view emoji', type: 'boolean' },
|
|
410
|
-
{ flags: '--filter <json|@file>', description: 'Replace the
|
|
411
|
-
{ flags: '--config <json|@file>', description: 'Replace the
|
|
447
|
+
{ flags: '--filter <json|@file>', description: 'Replace the filter JSON (version 1-3)', type: 'string' },
|
|
448
|
+
{ flags: '--config <json|@file>', description: 'Replace the whole config JSON', type: 'string' },
|
|
449
|
+
{ flags: '--layout <layout>', description: LAYOUT_OPTION_DESCRIPTION, type: 'string', enum: [...SAVED_VIEW_LAYOUTS] },
|
|
412
450
|
{ flags: '--template-id <id>', description: 'Replace the associated note template id', type: 'string' },
|
|
413
451
|
{ flags: '--clear-template', description: 'Clear the associated note template id', type: 'boolean' },
|
|
414
452
|
{ flags: '--group-id <id>', description: 'Replace the saved view group id', type: 'string' },
|
|
@@ -418,16 +456,17 @@ export const savedViewCommandGroup = {
|
|
|
418
456
|
{ flags: '--pinned-at <timestamp>', description: 'Replace the pinned timestamp', type: 'string' },
|
|
419
457
|
{ flags: '--unpin', description: 'Clear the pinned timestamp', type: 'boolean' },
|
|
420
458
|
WRITE_DURABILITY_OPTION,
|
|
421
|
-
{ flags: '--dry-run', description: 'Validate and preview the
|
|
459
|
+
{ flags: '--dry-run', description: 'Validate and preview the stored view without writing', type: 'boolean' },
|
|
422
460
|
],
|
|
423
461
|
schema: {
|
|
424
462
|
path: 'view.update',
|
|
425
463
|
risk: 'write',
|
|
426
464
|
auth: 'required',
|
|
427
465
|
globalOptions: VIEW_GLOBAL_OPTIONS,
|
|
428
|
-
output: { type: 'structured', summary: 'Updated saved view definition or dry-run plan', fields:
|
|
466
|
+
output: { type: 'structured', summary: 'Updated saved view definition or dry-run plan', fields: WRITE_OUTPUT_FIELDS },
|
|
429
467
|
examples: [
|
|
430
468
|
'dino view update <id> --filter @filter.json --format json --dry-run',
|
|
469
|
+
'dino view update <id> --layout list --format json',
|
|
431
470
|
'dino view update <id> --name "Priority work" --unpin --format json',
|
|
432
471
|
],
|
|
433
472
|
},
|