@dinoxx/dinox-cli 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +17 -3
  2. package/dist/cli.js +6 -0
  3. package/dist/commands/domain/options.d.ts +10 -0
  4. package/dist/commands/domain/options.js +75 -0
  5. package/dist/commands/domain/run.d.ts +24 -0
  6. package/dist/commands/domain/run.js +137 -0
  7. package/dist/commands/gateway/index.d.ts +4 -0
  8. package/dist/commands/gateway/index.js +300 -0
  9. package/dist/commands/notes/actions.js +47 -18
  10. package/dist/commands/notes/createFromTemplate.d.ts +35 -0
  11. package/dist/commands/notes/createFromTemplate.js +58 -0
  12. package/dist/commands/notes/descriptors.js +9 -4
  13. package/dist/commands/notes/repo/read.js +4 -23
  14. package/dist/commands/notes/repo/write.d.ts +2 -0
  15. package/dist/commands/notes/repo/write.js +1 -1
  16. package/dist/commands/properties/index.d.ts +16 -0
  17. package/dist/commands/properties/index.js +640 -0
  18. package/dist/commands/properties/repo.d.ts +150 -0
  19. package/dist/commands/properties/repo.js +272 -0
  20. package/dist/commands/savedViews/fields.d.ts +28 -0
  21. package/dist/commands/savedViews/fields.js +87 -0
  22. package/dist/commands/savedViews/index.js +203 -164
  23. package/dist/commands/savedViews/model.d.ts +27 -69
  24. package/dist/commands/savedViews/model.js +63 -279
  25. package/dist/commands/savedViews/repo.d.ts +64 -62
  26. package/dist/commands/savedViews/repo.js +309 -428
  27. package/dist/commands/schema/renderSkills.js +33 -5
  28. package/dist/commands/schema/spec.js +6 -0
  29. package/dist/commands/templates/index.d.ts +4 -0
  30. package/dist/commands/templates/index.js +290 -0
  31. package/dist/commands/templates/model.d.ts +12 -34
  32. package/dist/commands/templates/model.js +39 -83
  33. package/dist/commands/templates/repo.d.ts +53 -0
  34. package/dist/commands/templates/repo.js +78 -0
  35. package/dist/config/paths.d.ts +1 -0
  36. package/dist/config/paths.js +1 -1
  37. package/dist/daemon/server.d.ts +1 -0
  38. package/dist/daemon/server.js +31 -0
  39. package/dist/gateway/adapters/acp.d.ts +42 -0
  40. package/dist/gateway/adapters/acp.js +285 -0
  41. package/dist/gateway/adapters/echo.d.ts +12 -0
  42. package/dist/gateway/adapters/echo.js +51 -0
  43. package/dist/gateway/adapters/index.d.ts +9 -0
  44. package/dist/gateway/adapters/index.js +30 -0
  45. package/dist/gateway/assistantProjection.d.ts +32 -0
  46. package/dist/gateway/assistantProjection.js +193 -0
  47. package/dist/gateway/config.d.ts +50 -0
  48. package/dist/gateway/config.js +253 -0
  49. package/dist/gateway/detect.d.ts +40 -0
  50. package/dist/gateway/detect.js +145 -0
  51. package/dist/gateway/deviceHost.d.ts +82 -0
  52. package/dist/gateway/deviceHost.js +530 -0
  53. package/dist/gateway/mcpServer.d.ts +4 -0
  54. package/dist/gateway/mcpServer.js +118 -0
  55. package/dist/gateway/protocol.d.ts +29 -0
  56. package/dist/gateway/protocol.js +127 -0
  57. package/dist/gateway/runExecutor.d.ts +67 -0
  58. package/dist/gateway/runExecutor.js +316 -0
  59. package/dist/gateway/service.d.ts +26 -0
  60. package/dist/gateway/service.js +82 -0
  61. package/dist/gateway/streamPublisher.d.ts +33 -0
  62. package/dist/gateway/streamPublisher.js +93 -0
  63. package/dist/gateway/threadStore.d.ts +157 -0
  64. package/dist/gateway/threadStore.js +430 -0
  65. package/dist/gateway/types.d.ts +117 -0
  66. package/dist/gateway/types.js +9 -0
  67. package/dist/skills/registry.js +7 -0
  68. package/dist/utils/errorCodes.d.ts +212 -0
  69. package/dist/utils/errorCodes.js +53 -0
  70. package/dist/vendor/electron/domain/note-templates.d.ts +3 -0
  71. package/dist/vendor/electron/domain/note-templates.js +232 -0
  72. package/dist/vendor/electron/domain/properties.d.ts +10 -0
  73. package/dist/vendor/electron/domain/properties.js +1549 -0
  74. package/dist/vendor/electron/domain/saved-view-analytics.d.ts +45 -0
  75. package/dist/vendor/electron/domain/saved-view-analytics.js +365 -0
  76. package/dist/vendor/electron/domain/saved-view-relative-date.d.ts +21 -0
  77. package/dist/vendor/electron/domain/saved-view-relative-date.js +153 -0
  78. package/dist/vendor/electron/domain/saved-views.d.ts +61 -0
  79. package/dist/vendor/electron/domain/saved-views.js +1120 -0
  80. package/dist/vendor/electron/rpc.d.ts +22 -0
  81. package/dist/vendor/electron/rpc.js +27 -0
  82. package/dist/vendor/electron/shared/ai-errors.d.ts +11 -0
  83. package/dist/vendor/electron/shared/ai-errors.js +107 -0
  84. package/dist/vendor/electron/shared/external-types.d.ts +20 -0
  85. package/dist/vendor/electron/shared/external-types.js +1 -0
  86. package/dist/vendor/electron/shared/index.d.ts +9 -0
  87. package/dist/vendor/electron/shared/index.js +7 -0
  88. package/dist/vendor/electron/shared/note-properties.d.ts +328 -0
  89. package/dist/vendor/electron/shared/note-properties.js +1027 -0
  90. package/dist/vendor/electron/shared/note-templates.d.ts +82 -0
  91. package/dist/vendor/electron/shared/note-templates.js +84 -0
  92. package/dist/vendor/electron/shared/powersync-read-models.d.ts +1716 -0
  93. package/dist/vendor/electron/shared/powersync-read-models.js +1 -0
  94. package/dist/vendor/electron/shared/remote-device-agent.d.ts +373 -0
  95. package/dist/vendor/electron/shared/remote-device-agent.js +191 -0
  96. package/dist/vendor/electron/shared/saved-views.d.ts +394 -0
  97. package/dist/vendor/electron/shared/saved-views.js +981 -0
  98. package/dist/vendor/electron/sql-utils.d.ts +5 -0
  99. package/dist/vendor/electron/sql-utils.js +32 -0
  100. package/package.json +10 -4
  101. package/dist/commands/savedViews/semantics.d.ts +0 -46
  102. package/dist/commands/savedViews/semantics.js +0 -738
@@ -5,6 +5,10 @@ import { listCommandSchemas } from './spec.js';
5
5
  const SECTION_ORDER = [
6
6
  { title: 'Auth', paths: ['auth.login', 'auth.logout', 'auth.status'] },
7
7
  { title: 'Daemon', paths: ['daemon.start', 'daemon.status', 'daemon.restart', 'daemon.stop'] },
8
+ {
9
+ title: 'Agent Gateways',
10
+ paths: ['gateway.detect', 'gateway.add', 'gateway.list', 'gateway.remove', 'gateway.status', 'gateway.reload', 'gateway.mcp'],
11
+ },
8
12
  { title: 'Doctor', paths: ['doctor'] },
9
13
  { title: 'Sync', paths: ['sync'] },
10
14
  { title: 'Schema', paths: ['schema'] },
@@ -15,7 +19,9 @@ const SECTION_ORDER = [
15
19
  { title: 'Tags', paths: ['tag.list', 'tag.tree', 'tag.stats', 'tag.add', 'tag.rename', 'tag.move', 'tag.merge', 'tag.suggest', 'tag.cleanup'] },
16
20
  { title: 'Card Boxes (Zettel Boxes)', paths: ['box.list', 'box.add', 'box.tree', 'box.stats', 'box.rename', 'box.move', 'box.merge', 'box.cleanup'] },
17
21
  { title: 'Prompts', paths: ['prompt.list', 'prompt.add'] },
18
- { title: 'Saved Views', paths: ['view.list', 'view.get', 'view.fields', 'view.query', 'view.count', 'view.create', 'view.update', 'view.delete'] },
22
+ { title: 'Saved Views', paths: ['view.list', 'view.get', 'view.fields', 'view.query', 'view.count', 'view.analytics', 'view.note-detail', 'view.create', 'view.update', 'view.delete'] },
23
+ { title: 'Properties', paths: ['prop.list', 'prop.get', 'prop.create', 'prop.update', 'prop.delete', 'prop.migrate-options', 'prop.show', 'prop.set', 'prop.fill', 'prop.slots'] },
24
+ { title: 'Note Templates', paths: ['template.list', 'template.get', 'template.create', 'template.update', 'template.delete'] },
19
25
  { title: 'Storage', paths: ['storage.list', 'storage.test', 'storage.upload', 'storage.stats'] },
20
26
  { title: 'Config', paths: ['config.get', 'config.set'] },
21
27
  { title: 'Info', paths: ['info'] },
@@ -95,13 +101,34 @@ const GENERATED_COMMAND_SECTIONS = {
95
101
  },
96
102
  'skill:dino-manage-views': {
97
103
  title: 'Command Reference',
98
- paths: ['view.list', 'view.get', 'view.fields', 'view.query', 'view.count', 'view.create', 'view.update', 'view.delete'],
99
- intro: 'Use these commands as the canonical Dinox CLI interface for saved table view management and queries.',
104
+ paths: ['view.list', 'view.get', 'view.fields', 'view.query', 'view.count', 'view.analytics', 'view.note-detail', 'view.create', 'view.update', 'view.delete'],
105
+ intro: 'Use these commands as the canonical Dinox CLI interface for saved views (table, list, card), their analytics, and note-detail embeds.',
100
106
  notes: [
101
- 'Run `view fields` before authoring `prop.*` filters, columns, or sorts; select and multi-select filters store stable option IDs.',
102
- 'Pass canonical filter/config JSON through `@file` and run create, update, or delete with `--dry-run` first.',
107
+ 'Run `view fields` before authoring `prop.*` filters, columns, or sorts; select, status, and multi-select filters store stable option IDs.',
108
+ 'Pass filter/config JSON through `@file` and run create, update, or delete with `--dry-run` first; the dry run executes the real write in a rolled-back transaction.',
103
109
  'When executing an online command from this surface, add `--sync-timeout 20000` and keep the host timeout 5-10 seconds higher.',
104
110
  'Follow `nextOffset` while `hasMore` is true before claiming a query result is complete.',
111
+ 'A view with `valid: false` was written by a newer client; read its raw JSON from `data.view`, and change only metadata unless you rebuild a valid config.',
112
+ ],
113
+ },
114
+ 'skill:dino-manage-properties': {
115
+ title: 'Command Reference',
116
+ paths: ['prop.list', 'prop.get', 'prop.create', 'prop.update', 'prop.delete', 'prop.migrate-options', 'prop.show', 'prop.set', 'prop.fill', 'prop.slots'],
117
+ intro: 'Use these commands as the canonical Dinox CLI interface for property definitions and note property values.',
118
+ notes: [
119
+ 'Run `prop list` before writing values: types, option ids, and config decide how a value is coerced.',
120
+ 'Every write supports `--dry-run`, which runs the real write in a rolled-back transaction and shows the resulting properties.',
121
+ 'When executing an online command from this surface, add `--sync-timeout 20000` and keep the host timeout 5-10 seconds higher.',
122
+ '`prop migrate-options` rewrites notes and views; real writes require `--confirm --expected-count <n>` from the dry run.',
123
+ ],
124
+ },
125
+ 'skill:dino-manage-properties:templates': {
126
+ title: 'Template Command Reference',
127
+ paths: ['template.list', 'template.get', 'template.create', 'template.update', 'template.delete'],
128
+ intro: 'Note templates hold content plus the ordered property keys a new note declares; create notes from them with `dino note create --template <id>`.',
129
+ notes: [
130
+ 'Run template writes with `--dry-run` first.',
131
+ 'When executing an online command from this surface, add `--sync-timeout 20000` and keep the host timeout 5-10 seconds higher.',
105
132
  ],
106
133
  },
107
134
  'ref:dino-note:search-and-read': {
@@ -123,6 +150,7 @@ const GENERATED_COMMAND_SECTIONS = {
123
150
  intro: 'Use this generated command surface as the canonical interface for note creation.',
124
151
  notes: [
125
152
  'Use `--boxes` for public box input.',
153
+ '`--template <id>` hydrates the template content and declares its property keys as empty slots; `--properties` writes initial values atomically with the note.',
126
154
  'Prefer `--type note` unless the user explicitly wants a `crawl` note.',
127
155
  'Run the same command with `--sync-timeout 20000 --dry-run --format json` first and keep the host timeout 5-10 seconds higher.',
128
156
  ],
@@ -3,6 +3,7 @@ import { boxCommandGroup } from '../boxes/index.js';
3
3
  import { commandSchemasFromGroupDescriptor } from '../descriptor/register.js';
4
4
  import { standaloneCommandSchemaFromDescriptor } from '../descriptor/register.js';
5
5
  import { configCommandGroup } from '../config/index.js';
6
+ import { gatewayCommandGroup } from '../gateway/index.js';
6
7
  import { daemonCommandGroup } from '../../daemon/index.js';
7
8
  import { doctorCommandDescriptor } from '../doctor.js';
8
9
  import { graphCommandGroup } from '../graph/index.js';
@@ -10,17 +11,20 @@ import { infoCommandDescriptor } from '../info/index.js';
10
11
  import { metaCommandGroup } from '../meta/index.js';
11
12
  import { noteCommandGroup } from '../notes/index.js';
12
13
  import { promptCommandGroup } from '../prompt/index.js';
14
+ import { propertyCommandGroup } from '../properties/index.js';
13
15
  import { savedViewCommandGroup } from '../savedViews/index.js';
14
16
  import { schemaCommandMetadata } from './commandMetadata.js';
15
17
  import { skillsCommandGroup } from '../skills/index.js';
16
18
  import { syncCommandDescriptor } from '../sync.js';
17
19
  import { storageCommandGroup } from '../storage/index.js';
18
20
  import { tagCommandGroup } from '../tags/index.js';
21
+ import { templateCommandGroup } from '../templates/index.js';
19
22
  import { todoCommandGroup } from '../todo/index.js';
20
23
  import { updateCommandDescriptor } from '../update.js';
21
24
  export const COMMAND_SCHEMAS = [
22
25
  ...commandSchemasFromGroupDescriptor(authCommandGroup),
23
26
  ...commandSchemasFromGroupDescriptor(configCommandGroup),
27
+ ...commandSchemasFromGroupDescriptor(gatewayCommandGroup),
24
28
  ...commandSchemasFromGroupDescriptor(daemonCommandGroup),
25
29
  standaloneCommandSchemaFromDescriptor(doctorCommandDescriptor),
26
30
  standaloneCommandSchemaFromDescriptor(syncCommandDescriptor),
@@ -33,6 +37,8 @@ export const COMMAND_SCHEMAS = [
33
37
  ...commandSchemasFromGroupDescriptor(boxCommandGroup),
34
38
  ...commandSchemasFromGroupDescriptor(promptCommandGroup),
35
39
  ...commandSchemasFromGroupDescriptor(savedViewCommandGroup),
40
+ ...commandSchemasFromGroupDescriptor(propertyCommandGroup),
41
+ ...commandSchemasFromGroupDescriptor(templateCommandGroup),
36
42
  ...commandSchemasFromGroupDescriptor(storageCommandGroup),
37
43
  ...commandSchemasFromGroupDescriptor(noteCommandGroup),
38
44
  ...commandSchemasFromGroupDescriptor(graphCommandGroup),
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import type { CommandGroupDescriptor } from '../descriptor/types.js';
3
+ export declare const templateCommandGroup: CommandGroupDescriptor;
4
+ export declare function registerTemplateCommands(program: Command): void;
@@ -0,0 +1,290 @@
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 { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
7
+ import { isPropertyKeyValid } from '../../vendor/electron/shared/index.js';
8
+ import { registerCommandGroup } from '../descriptor/register.js';
9
+ import { assertExclusive, collectOption, parsePositiveIntegerOption, readJsonOption, requiredArgument, } from '../domain/options.js';
10
+ import { parseListInput } from '../notes/runtime.js';
11
+ import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
12
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, REQUIRE_SYNC_OPTIONS, SYNC_TIMEOUT_OPTIONS, } from '../schema/globalOptions.js';
13
+ import { WRITE_DURABILITY_OPTION } from '../writeDurability.js';
14
+ import { serializeNoteTemplateContent } from './model.js';
15
+ import { createNoteTemplate, deleteNoteTemplate, getNoteTemplate, listNoteTemplates, previewCreateNoteTemplate, previewDeleteNoteTemplate, previewUpdateNoteTemplate, updateNoteTemplate, } from './repo.js';
16
+ const TEMPLATE_GLOBAL_OPTIONS = [
17
+ ...FORMAT_OPTIONS,
18
+ ...OFFLINE_OPTIONS,
19
+ ...REQUIRE_SYNC_OPTIONS,
20
+ ...SYNC_TIMEOUT_OPTIONS,
21
+ ];
22
+ const DRY_RUN_OPTION = { flags: '--dry-run', description: 'Run the write in a rolled-back transaction and preview the result', type: 'boolean' };
23
+ function templateLine(template) {
24
+ return `${template.id}\t${template.name}\t${template.propertyKeys.join(',')}`;
25
+ }
26
+ /** Template output without the raw content string (the Tiptap `document` carries it). */
27
+ function templateOutput(template) {
28
+ const { content: _content, ...rest } = template;
29
+ return rest;
30
+ }
31
+ async function readTemplateContent(options) {
32
+ assertExclusive([
33
+ [options.content !== undefined && options.contentJson !== undefined, '--content cannot be used with --content-json'],
34
+ [Boolean(options.clearContent) && (options.content !== undefined || options.contentJson !== undefined), '--clear-content cannot be used with --content or --content-json'],
35
+ ]);
36
+ if (options.clearContent)
37
+ return '';
38
+ if (typeof options.content === 'string') {
39
+ const markdown = await readStringOrFile(options.content);
40
+ return serializeNoteTemplateContent(convertMarkdownToNoteContent(markdown).contentJson);
41
+ }
42
+ if (options.contentJson !== undefined) {
43
+ return serializeNoteTemplateContent(await readJsonOption(options.contentJson, '--content-json', 'template.create'));
44
+ }
45
+ return undefined;
46
+ }
47
+ async function readPropertyKeys(options) {
48
+ assertExclusive([[Boolean(options.clearPropertyKeys) && (options.propertyKey !== undefined || options.propertyKeys !== undefined), '--clear-property-keys cannot be combined with --property-key or --property-keys']]);
49
+ if (options.clearPropertyKeys)
50
+ return [];
51
+ const keys = [
52
+ ...(Array.isArray(options.propertyKey) ? options.propertyKey : []),
53
+ ...(typeof options.propertyKeys === 'string' ? parseListInput(await readStringOrFile(options.propertyKeys), '--property-keys') : []),
54
+ ];
55
+ if (options.propertyKey === undefined && options.propertyKeys === undefined)
56
+ return undefined;
57
+ const invalid = keys.find((key) => !isPropertyKeyValid(key));
58
+ if (invalid !== undefined)
59
+ throw new DinoxError(`Invalid property key: ${invalid}`, { code: 'USAGE_ERROR' });
60
+ return [...new Set(keys)];
61
+ }
62
+ async function readWriteInput(options) {
63
+ const [content, propertyKeys] = await Promise.all([readTemplateContent(options), readPropertyKeys(options)]);
64
+ return {
65
+ ...(typeof options.name === 'string' ? { name: options.name } : {}),
66
+ ...(propertyKeys === undefined ? {} : { propertyKeys }),
67
+ ...(content === undefined ? {} : { content }),
68
+ };
69
+ }
70
+ async function withTemplateRuntime(context, run) {
71
+ await withCommandRuntime(context, async (runtime) => {
72
+ const identity = await resolveAuthIdentity(runtime.config);
73
+ await syncBeforeCommand(runtime);
74
+ await run(runtime, identity.userId);
75
+ });
76
+ }
77
+ async function finishWrite(runtime, options, payload, line) {
78
+ const receipt = await waitForMutationUpload(runtime, { durability: options.durability });
79
+ if (getRequestedStructuredOutputFormat()) {
80
+ printSuccess({ ok: true, stale: commandStale(runtime), ...payload, ...receipt });
81
+ return;
82
+ }
83
+ printHumanReadableLine(line);
84
+ printStaleCacheWarning(runtime);
85
+ }
86
+ async function runTemplateList(context) {
87
+ const limit = context.options.limit;
88
+ await withTemplateRuntime(context, async (runtime, userId) => {
89
+ const templates = await listNoteTemplates(runtime.db, { userId, limit });
90
+ if (getRequestedStructuredOutputFormat()) {
91
+ printSuccess({
92
+ ok: true,
93
+ stale: commandStale(runtime),
94
+ count: templates.length,
95
+ templates: templates.map(({ content: _content, document: _document, ...summary }) => summary),
96
+ });
97
+ return;
98
+ }
99
+ for (const template of templates)
100
+ printHumanReadableLine(templateLine(template));
101
+ if (templates.length === 0)
102
+ printHumanReadableLine('No note templates found.');
103
+ printStaleCacheWarning(runtime);
104
+ });
105
+ }
106
+ async function runTemplateGet(context) {
107
+ const id = requiredArgument(context.args.id, 'Template id');
108
+ await withTemplateRuntime(context, async (runtime, userId) => {
109
+ const template = await getNoteTemplate(runtime.db, { userId, id });
110
+ printSuccess({ ok: true, stale: commandStale(runtime), template: templateOutput(template) });
111
+ });
112
+ }
113
+ async function runTemplateCreate(context) {
114
+ const options = context.options;
115
+ const name = typeof options.name === 'string' ? options.name.trim() : '';
116
+ if (!name)
117
+ throw new DinoxError('Template name is required. Use --name <string>.', { code: 'USAGE_ERROR' });
118
+ const patch = await readWriteInput(options);
119
+ await withTemplateRuntime(context, async (runtime, userId) => {
120
+ const input = { ...patch, userId, name };
121
+ if (options.dryRun) {
122
+ const preview = await previewCreateNoteTemplate(runtime.db, input);
123
+ printDryRunPreview({
124
+ dry_run: true,
125
+ command: 'dino template create',
126
+ action: 'create_note_template',
127
+ risk: 'write',
128
+ summary: `Create note template "${name}".`,
129
+ changes: { before: null, after: templateOutput(preview.after) },
130
+ }, runtime);
131
+ return;
132
+ }
133
+ const template = await createNoteTemplate(runtime.db, input);
134
+ await finishWrite(runtime, options, { template: templateOutput(template) }, template.id);
135
+ });
136
+ }
137
+ async function runTemplateUpdate(context) {
138
+ const id = requiredArgument(context.args.id, 'Template id');
139
+ const options = context.options;
140
+ const patch = await readWriteInput(options);
141
+ if (Object.keys(patch).length === 0) {
142
+ throw new DinoxError('At least one note-template field must be updated', { code: 'USAGE_ERROR' });
143
+ }
144
+ await withTemplateRuntime(context, async (runtime, userId) => {
145
+ const input = { ...patch, userId, id };
146
+ if (options.dryRun) {
147
+ const preview = await previewUpdateNoteTemplate(runtime.db, input);
148
+ printDryRunPreview({
149
+ dry_run: true,
150
+ command: 'dino template update',
151
+ action: 'update_note_template',
152
+ risk: 'write',
153
+ summary: `Update note template "${preview.before.name}".`,
154
+ targets: [{ id: preview.before.id, name: preview.before.name }],
155
+ changes: { before: templateOutput(preview.before), after: templateOutput(preview.after) },
156
+ }, runtime);
157
+ return;
158
+ }
159
+ const template = await updateNoteTemplate(runtime.db, input);
160
+ await finishWrite(runtime, options, { template: templateOutput(template) }, template.id);
161
+ });
162
+ }
163
+ async function runTemplateDelete(context) {
164
+ const id = requiredArgument(context.args.id, 'Template id');
165
+ const options = context.options;
166
+ await withTemplateRuntime(context, async (runtime, userId) => {
167
+ if (options.dryRun) {
168
+ const preview = await previewDeleteNoteTemplate(runtime.db, { userId, id });
169
+ printDryRunPreview({
170
+ dry_run: true,
171
+ command: 'dino template delete',
172
+ action: 'delete_note_template',
173
+ risk: 'write',
174
+ summary: `Soft-delete note template "${preview.before.name}".`,
175
+ targets: [{ id: preview.before.id, name: preview.before.name }],
176
+ }, runtime);
177
+ return;
178
+ }
179
+ const template = await deleteNoteTemplate(runtime.db, { userId, id });
180
+ await finishWrite(runtime, options, { deleted: true, template: templateOutput(template) }, `Deleted ${template.id}`);
181
+ });
182
+ }
183
+ const CONTENT_OPTIONS = [
184
+ { flags: '--content <string|@file>', description: 'Template content as Markdown; {{date}} {{time}} {{datetime}} {{location}} tokens are filled when a note is created', type: 'string' },
185
+ { flags: '--content-json <json|@file>', description: 'Template content as a Tiptap doc JSON', type: 'string' },
186
+ ];
187
+ const PROPERTY_KEY_OPTIONS = [
188
+ { flags: '--property-key <key>', description: 'Property key declared on notes created from the template (repeatable, ordered)', parser: collectOption, type: 'string[]' },
189
+ { flags: '--property-keys <string|@file>', description: 'Property keys as a JSON array or comma/newline-separated list', type: 'string' },
190
+ ];
191
+ const TEMPLATE_WRITE_FIELDS = ['template', 'durability', 'upload_queue_remaining', 'stale'];
192
+ export const templateCommandGroup = {
193
+ name: 'template',
194
+ description: 'Manage note templates (content plus the property keys new notes declare)',
195
+ commands: [
196
+ {
197
+ name: 'list',
198
+ description: 'List note templates',
199
+ options: [
200
+ { flags: '--limit <n>', description: 'Maximum returned templates (1-2000)', parser: parsePositiveIntegerOption, type: 'number', defaultValue: 500 },
201
+ ],
202
+ schema: {
203
+ path: 'template.list',
204
+ risk: 'read',
205
+ auth: 'required',
206
+ globalOptions: TEMPLATE_GLOBAL_OPTIONS,
207
+ output: { type: 'structured', summary: 'Template summaries in priority order', fields: ['stale', 'count', 'templates'] },
208
+ examples: ['dino template list --format json'],
209
+ },
210
+ action: runTemplateList,
211
+ },
212
+ {
213
+ name: 'get',
214
+ description: 'Get one note template with its Tiptap document',
215
+ arguments: [{ syntax: '<id>', description: 'Template id' }],
216
+ schema: {
217
+ path: 'template.get',
218
+ risk: 'read',
219
+ auth: 'required',
220
+ globalOptions: TEMPLATE_GLOBAL_OPTIONS,
221
+ output: { type: 'structured', summary: 'Template detail', fields: ['stale', 'template'] },
222
+ examples: ['dino template get <id> --format json'],
223
+ },
224
+ action: runTemplateGet,
225
+ },
226
+ {
227
+ name: 'create',
228
+ description: 'Create a note template',
229
+ options: [
230
+ { flags: '--name <string>', description: 'Template name', required: true, type: 'string' },
231
+ ...CONTENT_OPTIONS,
232
+ ...PROPERTY_KEY_OPTIONS,
233
+ WRITE_DURABILITY_OPTION,
234
+ DRY_RUN_OPTION,
235
+ ],
236
+ schema: {
237
+ path: 'template.create',
238
+ risk: 'write',
239
+ auth: 'required',
240
+ globalOptions: TEMPLATE_GLOBAL_OPTIONS,
241
+ output: { type: 'structured', summary: 'Created template', fields: TEMPLATE_WRITE_FIELDS },
242
+ examples: [
243
+ 'dino template create --name "Book" --property-key 作者 --property-key rating --content @book.md --format json --dry-run',
244
+ ],
245
+ },
246
+ action: runTemplateCreate,
247
+ },
248
+ {
249
+ name: 'update',
250
+ description: 'Update a note template; fields you do not pass are left as stored',
251
+ arguments: [{ syntax: '<id>', description: 'Template id' }],
252
+ options: [
253
+ { flags: '--name <string>', description: 'Rename the template', type: 'string' },
254
+ ...CONTENT_OPTIONS,
255
+ { flags: '--clear-content', description: 'Make the template property-only', type: 'boolean' },
256
+ ...PROPERTY_KEY_OPTIONS,
257
+ { flags: '--clear-property-keys', description: 'Remove all property keys', type: 'boolean' },
258
+ WRITE_DURABILITY_OPTION,
259
+ DRY_RUN_OPTION,
260
+ ],
261
+ schema: {
262
+ path: 'template.update',
263
+ risk: 'write',
264
+ auth: 'required',
265
+ globalOptions: TEMPLATE_GLOBAL_OPTIONS,
266
+ output: { type: 'structured', summary: 'Updated template', fields: TEMPLATE_WRITE_FIELDS },
267
+ examples: ['dino template update <id> --property-keys "作者,rating,状态" --format json'],
268
+ },
269
+ action: runTemplateUpdate,
270
+ },
271
+ {
272
+ name: 'delete',
273
+ description: 'Soft-delete a note template',
274
+ arguments: [{ syntax: '<id>', description: 'Template id' }],
275
+ options: [WRITE_DURABILITY_OPTION, DRY_RUN_OPTION],
276
+ schema: {
277
+ path: 'template.delete',
278
+ risk: 'write',
279
+ auth: 'required',
280
+ globalOptions: TEMPLATE_GLOBAL_OPTIONS,
281
+ output: { type: 'structured', summary: 'Deleted template', fields: ['deleted', ...TEMPLATE_WRITE_FIELDS] },
282
+ examples: ['dino template delete <id> --format json --dry-run'],
283
+ },
284
+ action: runTemplateDelete,
285
+ },
286
+ ],
287
+ };
288
+ export function registerTemplateCommands(program) {
289
+ registerCommandGroup(program, templateCommandGroup);
290
+ }
@@ -1,44 +1,13 @@
1
1
  import { type TiptapNode } from '../notes/contentEngine.js';
2
- export declare const NOTE_TEMPLATE_PROPERTY_DEF_VERSION = 1;
3
- export declare const MAX_NOTE_TEMPLATE_PROPERTY_KEYS = 50;
2
+ import { type NoteTemplateRow, type NoteTemplateSummary } from '../../vendor/electron/shared/index.js';
3
+ export { MAX_NOTE_TEMPLATE_PROPERTY_KEYS, NOTE_TEMPLATE_PROPERTY_DEF_VERSION, parseNoteTemplatePropertyKeys, sanitizeNoteTemplatePropertyKeys, serializeNoteTemplatePropertyKeys, type NoteTemplateRow, type NoteTemplateSummary, } from '../../vendor/electron/shared/index.js';
4
4
  export declare const MAX_NOTE_TEMPLATE_CONTENT_LENGTH = 200000;
5
- export type NoteTemplatePropertyDef = {
6
- version: typeof NOTE_TEMPLATE_PROPERTY_DEF_VERSION;
7
- keys: string[];
8
- };
9
- export type NoteTemplateRow = {
10
- id: string;
11
- user_id: string | null;
12
- name: string | null;
13
- content: string | null;
14
- priority: number | null;
15
- is_show: number | null;
16
- extra_data: string | null;
17
- property_def: string | null;
18
- insert_at: string | null;
19
- update_at: string | null;
20
- is_del: number | null;
21
- };
22
- export type NoteTemplateSummary = {
23
- id: string;
24
- name: string;
25
- propertyKeys: string[];
26
- content: string;
27
- hasContent: boolean;
28
- isShow: boolean;
29
- priority: number;
30
- updateAt: string | null;
31
- };
32
5
  export type NoteTemplateTokenValues = {
33
6
  date: string;
34
7
  datetime: string;
35
8
  time: string;
36
9
  location: string;
37
10
  };
38
- export declare function isNotePropertyKeyValid(value: unknown): value is string;
39
- export declare function validateNoteTemplatePropertyKeys(keys: readonly unknown[]): string[];
40
- export declare function parseNoteTemplatePropertyDef(raw: string | null | undefined): NoteTemplatePropertyDef | null;
41
- export declare function serializeNoteTemplatePropertyDef(keys: readonly string[]): string | null;
42
11
  export declare function isNoteTemplateDocumentEmpty(document: TiptapNode | null | undefined): boolean;
43
12
  export declare function parseNoteTemplateContent(raw: string | null | undefined): TiptapNode | null;
44
13
  export declare function serializeNoteTemplateContent(document: unknown): string;
@@ -47,4 +16,13 @@ export declare function hydrateNoteTemplateDocument(document: TiptapNode, option
47
16
  now?: Date;
48
17
  location?: string;
49
18
  }): TiptapNode;
50
- export declare function mapNoteTemplateRow(row: NoteTemplateRow): NoteTemplateSummary;
19
+ /**
20
+ * Resolves stored template content into a Tiptap doc the way desktop does: stringified doc
21
+ * JSON first, legacy Markdown second. Returns null for blank content.
22
+ */
23
+ export declare function resolveStoredTemplateDocument(raw: string | null | undefined): TiptapNode | null;
24
+ export type NoteTemplateDetail = NoteTemplateSummary & {
25
+ /** Content as a Tiptap doc (legacy Markdown converted); null when the template has none. */
26
+ document: TiptapNode | null;
27
+ };
28
+ export declare function mapNoteTemplateRow(row: NoteTemplateRow): NoteTemplateDetail;
@@ -1,7 +1,11 @@
1
+ // Note template content helpers. Rows and property-key envelopes follow the vendored desktop
2
+ // model (src/vendor/electron/shared/note-templates.ts); this module owns the Tiptap content
3
+ // contract and `{{date}}`-style token hydration used when a note is created from a template.
1
4
  import { projectNoteContent } from '../notes/contentEngine.js';
5
+ import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
2
6
  import { DinoxError } from '../../utils/errors.js';
3
- export const NOTE_TEMPLATE_PROPERTY_DEF_VERSION = 1;
4
- export const MAX_NOTE_TEMPLATE_PROPERTY_KEYS = 50;
7
+ import { mapNoteTemplateRow as mapDesktopNoteTemplateRow, } from '../../vendor/electron/shared/index.js';
8
+ export { MAX_NOTE_TEMPLATE_PROPERTY_KEYS, NOTE_TEMPLATE_PROPERTY_DEF_VERSION, parseNoteTemplatePropertyKeys, sanitizeNoteTemplatePropertyKeys, serializeNoteTemplatePropertyKeys, } from '../../vendor/electron/shared/index.js';
5
9
  export const MAX_NOTE_TEMPLATE_CONTENT_LENGTH = 200_000;
6
10
  const MAX_TIPTAP_NODE_DEPTH = 100;
7
11
  const MAX_TIPTAP_NODE_COUNT = 10_000;
@@ -24,74 +28,6 @@ function formatError(message, code, details) {
24
28
  function isPlainRecord(value) {
25
29
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
26
30
  }
27
- export function isNotePropertyKeyValid(value) {
28
- if (typeof value !== 'string')
29
- return false;
30
- const trimmed = value.trim();
31
- if (!trimmed || trimmed !== value || trimmed.length > 64)
32
- return false;
33
- if (trimmed === '__proto__')
34
- return false;
35
- return !trimmed.includes('.') && !/[\n\r\t]/.test(trimmed);
36
- }
37
- export function validateNoteTemplatePropertyKeys(keys) {
38
- if (keys.length > MAX_NOTE_TEMPLATE_PROPERTY_KEYS) {
39
- throw formatError(`Note templates support at most ${MAX_NOTE_TEMPLATE_PROPERTY_KEYS} property keys`, 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', { maxKeys: MAX_NOTE_TEMPLATE_PROPERTY_KEYS, receivedKeys: keys.length });
40
- }
41
- const seen = new Set();
42
- const validated = [];
43
- for (const key of keys) {
44
- if (!isNotePropertyKeyValid(key)) {
45
- throw formatError('Note template property_def contains an invalid property key', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', {
46
- key,
47
- });
48
- }
49
- if (seen.has(key)) {
50
- throw formatError('Note template property_def contains duplicate property keys', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', {
51
- key,
52
- });
53
- }
54
- seen.add(key);
55
- validated.push(key);
56
- }
57
- return validated;
58
- }
59
- export function parseNoteTemplatePropertyDef(raw) {
60
- const value = raw?.trim() ?? '';
61
- if (!value)
62
- return null;
63
- let parsed;
64
- try {
65
- parsed = JSON.parse(value);
66
- }
67
- catch (cause) {
68
- throw formatError('Note template property_def must be valid JSON', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF', {
69
- cause: cause instanceof Error ? cause.message : String(cause),
70
- });
71
- }
72
- if (!isPlainRecord(parsed)) {
73
- throw formatError('Note template property_def must be a versioned object', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF');
74
- }
75
- if (parsed.version !== NOTE_TEMPLATE_PROPERTY_DEF_VERSION) {
76
- throw formatError(`Unsupported note template property_def version: ${String(parsed.version)}`, 'NOTE_TEMPLATE_UNSUPPORTED_PROPERTY_DEF_VERSION', { supportedVersion: NOTE_TEMPLATE_PROPERTY_DEF_VERSION, receivedVersion: parsed.version });
77
- }
78
- if (!Array.isArray(parsed.keys)) {
79
- throw formatError('Note template property_def keys must be an array', 'NOTE_TEMPLATE_INVALID_PROPERTY_DEF');
80
- }
81
- return {
82
- version: NOTE_TEMPLATE_PROPERTY_DEF_VERSION,
83
- keys: validateNoteTemplatePropertyKeys(parsed.keys),
84
- };
85
- }
86
- export function serializeNoteTemplatePropertyDef(keys) {
87
- const validated = validateNoteTemplatePropertyKeys(keys);
88
- if (validated.length === 0)
89
- return null;
90
- return JSON.stringify({
91
- version: NOTE_TEMPLATE_PROPERTY_DEF_VERSION,
92
- keys: validated,
93
- });
94
- }
95
31
  function assertTiptapNode(value, state, depth) {
96
32
  if (depth > MAX_TIPTAP_NODE_DEPTH) {
97
33
  throw formatError(`Note template content exceeds the maximum node depth of ${MAX_TIPTAP_NODE_DEPTH}`, 'NOTE_TEMPLATE_INVALID_CONTENT');
@@ -222,18 +158,38 @@ export function hydrateNoteTemplateDocument(document, options = {}) {
222
158
  const values = buildNoteTemplateTokenValues(options.now, options.location ?? '');
223
159
  return hydrateTemplateNode(document, values) ?? { type: 'doc', content: [] };
224
160
  }
161
+ /**
162
+ * Resolves stored template content into a Tiptap doc the way desktop does: stringified doc
163
+ * JSON first, legacy Markdown second. Returns null for blank content.
164
+ */
165
+ export function resolveStoredTemplateDocument(raw) {
166
+ const value = raw?.trim() ?? '';
167
+ if (!value)
168
+ return null;
169
+ if (/^[{[]/.test(value)) {
170
+ try {
171
+ const parsed = JSON.parse(value);
172
+ if (isPlainRecord(parsed) && parsed.type === 'doc' && Array.isArray(parsed.content)) {
173
+ return parseNoteTemplateContent(value);
174
+ }
175
+ if (isPlainRecord(parsed) && Array.isArray(parsed.content)) {
176
+ return parseNoteTemplateContent(JSON.stringify({ type: 'doc', content: parsed.content }));
177
+ }
178
+ }
179
+ catch {
180
+ // Not JSON: fall through to the Markdown reading.
181
+ }
182
+ }
183
+ return convertMarkdownToNoteContent(value).contentJson;
184
+ }
225
185
  export function mapNoteTemplateRow(row) {
226
- const propertyDef = parseNoteTemplatePropertyDef(row.property_def);
227
- const document = parseNoteTemplateContent(row.content);
228
- const content = document ? serializeNoteTemplateContent(document) : '';
229
- return {
230
- id: row.id,
231
- name: (row.name ?? '').trim(),
232
- propertyKeys: propertyDef?.keys ?? [],
233
- content,
234
- hasContent: Boolean(content),
235
- isShow: Number(row.is_show ?? 0) === 1,
236
- priority: Number.isFinite(row.priority) ? Math.trunc(Number(row.priority)) : 0,
237
- updateAt: row.update_at ?? null,
238
- };
186
+ const summary = mapDesktopNoteTemplateRow(row);
187
+ let document = null;
188
+ try {
189
+ document = resolveStoredTemplateDocument(summary.content);
190
+ }
191
+ catch {
192
+ document = null;
193
+ }
194
+ return { ...summary, document };
239
195
  }
@@ -0,0 +1,53 @@
1
+ import type { AbstractPowerSyncDatabase } from '@powersync/common';
2
+ import { type NoteTemplateDetail } from './model.js';
3
+ export declare function listNoteTemplates(db: AbstractPowerSyncDatabase, input: {
4
+ userId: string;
5
+ limit?: number;
6
+ }): Promise<NoteTemplateDetail[]>;
7
+ export declare function getNoteTemplate(db: AbstractPowerSyncDatabase, input: {
8
+ userId: string;
9
+ id: string;
10
+ }): Promise<NoteTemplateDetail>;
11
+ export type NoteTemplateWriteInput = {
12
+ name?: string;
13
+ propertyKeys?: string[];
14
+ /** Serialized Tiptap doc JSON; '' clears the content. */
15
+ content?: string | null;
16
+ };
17
+ declare function runCreate(db: AbstractPowerSyncDatabase, input: NoteTemplateWriteInput & {
18
+ userId: string;
19
+ name: string;
20
+ }): Promise<NoteTemplateDetail>;
21
+ declare function runUpdate(db: AbstractPowerSyncDatabase, input: NoteTemplateWriteInput & {
22
+ userId: string;
23
+ id: string;
24
+ }): Promise<NoteTemplateDetail>;
25
+ declare function runDelete(db: AbstractPowerSyncDatabase, input: {
26
+ userId: string;
27
+ id: string;
28
+ }): Promise<NoteTemplateDetail>;
29
+ export declare const createNoteTemplate: typeof runCreate;
30
+ export declare const updateNoteTemplate: typeof runUpdate;
31
+ export declare const deleteNoteTemplate: typeof runDelete;
32
+ export declare function previewCreateNoteTemplate(db: AbstractPowerSyncDatabase, input: NoteTemplateWriteInput & {
33
+ userId: string;
34
+ name: string;
35
+ }): Promise<{
36
+ before: null;
37
+ after: NoteTemplateDetail;
38
+ }>;
39
+ export declare function previewUpdateNoteTemplate(db: AbstractPowerSyncDatabase, input: NoteTemplateWriteInput & {
40
+ userId: string;
41
+ id: string;
42
+ }): Promise<{
43
+ before: NoteTemplateDetail;
44
+ after: NoteTemplateDetail;
45
+ }>;
46
+ export declare function previewDeleteNoteTemplate(db: AbstractPowerSyncDatabase, input: {
47
+ userId: string;
48
+ id: string;
49
+ }): Promise<{
50
+ before: NoteTemplateDetail;
51
+ after: null;
52
+ }>;
53
+ export {};