@dinoxx/dinox-cli 1.0.20 → 1.0.21

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 (84) hide show
  1. package/README.md +72 -139
  2. package/dist/auth/userInfo.d.ts +8 -1
  3. package/dist/auth/userInfo.js +38 -6
  4. package/dist/cli.js +0 -2
  5. package/dist/commands/auth/index.js +11 -4
  6. package/dist/commands/boxes/index.js +272 -52
  7. package/dist/commands/boxes/model.d.ts +34 -0
  8. package/dist/commands/boxes/model.js +78 -0
  9. package/dist/commands/boxes/operations.d.ts +86 -0
  10. package/dist/commands/boxes/operations.js +415 -0
  11. package/dist/commands/boxes/repo.d.ts +13 -6
  12. package/dist/commands/boxes/repo.js +272 -149
  13. package/dist/commands/boxes/store.d.ts +22 -0
  14. package/dist/commands/boxes/store.js +143 -0
  15. package/dist/commands/graph/index.js +2 -36
  16. package/dist/commands/mcp/noteContentTools.d.ts +222 -0
  17. package/dist/commands/mcp/noteContentTools.js +390 -0
  18. package/dist/commands/mcp/server.d.ts +3 -0
  19. package/dist/commands/mcp/server.js +43 -15
  20. package/dist/commands/mcp/tools.js +3 -1
  21. package/dist/commands/mcp/types.d.ts +6 -2
  22. package/dist/commands/meta/index.js +6 -22
  23. package/dist/commands/meta/repo.d.ts +0 -3
  24. package/dist/commands/meta/repo.js +1 -4
  25. package/dist/commands/notes/actions.d.ts +2 -0
  26. package/dist/commands/notes/actions.js +149 -55
  27. package/dist/commands/notes/bulk.d.ts +34 -0
  28. package/dist/commands/notes/bulk.js +204 -0
  29. package/dist/commands/notes/contentEngine.d.ts +95 -0
  30. package/dist/commands/notes/contentEngine.js +652 -0
  31. package/dist/commands/notes/contentPatch.d.ts +84 -0
  32. package/dist/commands/notes/contentPatch.js +337 -0
  33. package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
  34. package/dist/commands/notes/contentPatchCommand.js +126 -0
  35. package/dist/commands/notes/descriptors.js +192 -40
  36. package/dist/commands/notes/export.d.ts +21 -0
  37. package/dist/commands/notes/export.js +224 -0
  38. package/dist/commands/notes/metadataMutation.d.ts +28 -0
  39. package/dist/commands/notes/metadataMutation.js +127 -0
  40. package/dist/commands/notes/repo.d.ts +20 -0
  41. package/dist/commands/notes/repo.js +135 -18
  42. package/dist/commands/notes/runtime.d.ts +3 -12
  43. package/dist/commands/notes/runtime.js +4 -30
  44. package/dist/commands/notes/searchSql.js +1 -1
  45. package/dist/commands/prompt/index.js +15 -45
  46. package/dist/commands/prompt/repo.js +2 -2
  47. package/dist/commands/runtime.d.ts +26 -0
  48. package/dist/commands/runtime.js +76 -0
  49. package/dist/commands/schema/renderSkills.js +20 -7
  50. package/dist/commands/schema/spec.d.ts +1 -2
  51. package/dist/commands/schema/spec.js +2 -3
  52. package/dist/commands/schema/types.d.ts +0 -13
  53. package/dist/commands/storage/index.js +30 -60
  54. package/dist/commands/sync.js +4 -2
  55. package/dist/commands/tags/content.d.ts +32 -0
  56. package/dist/commands/tags/content.js +243 -0
  57. package/dist/commands/tags/index.js +285 -49
  58. package/dist/commands/tags/model.d.ts +38 -0
  59. package/dist/commands/tags/model.js +100 -0
  60. package/dist/commands/tags/operations.d.ts +105 -0
  61. package/dist/commands/tags/operations.js +438 -0
  62. package/dist/commands/tags/repo.d.ts +5 -1
  63. package/dist/commands/tags/repo.js +142 -218
  64. package/dist/commands/tags/store.d.ts +28 -0
  65. package/dist/commands/tags/store.js +166 -0
  66. package/dist/commands/todo/index.js +30 -61
  67. package/dist/commands/writeDryRun.d.ts +25 -0
  68. package/dist/commands/writeDryRun.js +31 -1
  69. package/dist/config/resolve.js +2 -2
  70. package/dist/daemon/index.d.ts +2 -0
  71. package/dist/daemon/index.js +176 -58
  72. package/dist/powersync/runtime.d.ts +1 -0
  73. package/dist/powersync/runtime.js +10 -0
  74. package/dist/powersync/schema/content.d.ts +2 -0
  75. package/dist/powersync/schema/content.js +2 -0
  76. package/dist/powersync/schema/index.d.ts +2 -0
  77. package/dist/skills/check.d.ts +1 -1
  78. package/dist/skills/check.js +27 -2
  79. package/dist/utils/authToken.d.ts +2 -0
  80. package/dist/utils/authToken.js +7 -2
  81. package/dist/utils/noteContentEngine.d.ts +333 -0
  82. package/dist/utils/noteContentEngine.js +740 -0
  83. package/dist/utils/tiptapMarkdownSchema.js +31 -0
  84. package/package.json +4 -8
@@ -0,0 +1,76 @@
1
+ import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
2
+ import { loadConfig } from '../config/store.js';
3
+ import { resolveConfig } from '../config/resolve.js';
4
+ import { connectPowerSync, waitForIdleDataFlow } from '../powersync/runtime.js';
5
+ import { syncNoteTokenIndex } from '../powersync/tokenIndex.js';
6
+ import { printHumanReadableLine } from '../utils/output.js';
7
+ import { parseSyncTimeoutMs } from '../utils/syncTimeout.js';
8
+ const PRE_SYNC_TIMEOUT_MS = 10_000;
9
+ const POST_WRITE_IDLE_TIMEOUT_MS = 5_000;
10
+ function isCommandExecutionContext(source) {
11
+ return typeof source === 'object' && source !== null && 'globals' in source;
12
+ }
13
+ function isCommanderCommand(source) {
14
+ return typeof source.optsWithGlobals === 'function';
15
+ }
16
+ function resolveGlobals(source) {
17
+ if (isCommandExecutionContext(source)) {
18
+ return source.globals;
19
+ }
20
+ if (isCommanderCommand(source)) {
21
+ return (source.optsWithGlobals?.() ?? {});
22
+ }
23
+ return source;
24
+ }
25
+ export function commandStale(runtime) {
26
+ return runtime.offline ? true : runtime.stale;
27
+ }
28
+ export async function withCommandRuntime(source, run) {
29
+ const globals = resolveGlobals(source);
30
+ const rawConfig = await loadConfig();
31
+ const baseConfig = resolveConfig(rawConfig);
32
+ const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? baseConfig.sync.timeoutMs;
33
+ const offline = Boolean(globals.offline);
34
+ const { config } = offline
35
+ ? { config: baseConfig }
36
+ : await hydrateResolvedAuthIdentity(baseConfig, { timeoutMs });
37
+ const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
38
+ try {
39
+ return await run({
40
+ db,
41
+ config,
42
+ rawConfig,
43
+ offline,
44
+ timeoutMs,
45
+ stale,
46
+ });
47
+ }
48
+ finally {
49
+ await db.close().catch(() => undefined);
50
+ }
51
+ }
52
+ export async function syncBeforeCommand(runtime, options = {}) {
53
+ if (runtime.offline) {
54
+ return;
55
+ }
56
+ await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? PRE_SYNC_TIMEOUT_MS));
57
+ if (options.syncTokenIndex) {
58
+ await syncNoteTokenIndex(runtime.db);
59
+ }
60
+ }
61
+ export async function waitForMutationUpload(runtime, options = {}) {
62
+ if (runtime.offline) {
63
+ return;
64
+ }
65
+ await waitForIdleDataFlow(runtime.db, Math.min(runtime.timeoutMs, options.timeoutMs ?? POST_WRITE_IDLE_TIMEOUT_MS));
66
+ }
67
+ export function printUploadDisabledWarning(runtime, message = 'warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now') {
68
+ if (!runtime.offline && !runtime.config.powersync.uploadBaseUrl) {
69
+ printHumanReadableLine(message);
70
+ }
71
+ }
72
+ export function printStaleCacheWarning(runtime, message = 'warning: sync timed out; local cache may be stale') {
73
+ if (runtime.stale && !runtime.offline) {
74
+ printHumanReadableLine(message);
75
+ }
76
+ }
@@ -3,13 +3,14 @@ import path from 'node:path';
3
3
  import { listCommandSchemas } from './spec.js';
4
4
  const SECTION_ORDER = [
5
5
  { title: 'Auth', paths: ['auth.login', 'auth.logout', 'auth.status'] },
6
+ { title: 'Daemon', paths: ['daemon.start', 'daemon.status', 'daemon.restart', 'daemon.stop'] },
6
7
  { title: 'Sync', paths: ['sync'] },
7
8
  { title: 'Schema', paths: ['schema'] },
8
9
  { title: 'Update CLI', paths: ['update'] },
9
- { title: 'Notes', paths: ['note.search', 'note.get', 'note.preview', 'note.detail', 'note.create', 'note.update', 'note.star', 'note.unstar', 'note.delete'] },
10
+ { title: 'Notes', paths: ['note.search', 'note.get', 'note.preview', 'note.detail', 'note.export', 'note.content-read', 'note.create', 'note.update', 'note.tag', 'note.move', 'note.patch', 'note.bulk', 'note.star', 'note.unstar', 'note.delete'] },
10
11
  { title: 'Todo', paths: ['todo.search', 'todo.append', 'todo.create', 'todo.update'] },
11
- { title: 'Tags', paths: ['tag.list', 'tag.add'] },
12
- { title: 'Card Boxes (Zettel Boxes)', paths: ['box.list', 'box.add'] },
12
+ { title: 'Tags', paths: ['tag.list', 'tag.tree', 'tag.stats', 'tag.add', 'tag.rename', 'tag.move', 'tag.merge', 'tag.suggest', 'tag.cleanup'] },
13
+ { title: 'Card Boxes (Zettel Boxes)', paths: ['box.list', 'box.add', 'box.tree', 'box.stats', 'box.rename', 'box.move', 'box.merge', 'box.cleanup'] },
13
14
  { title: 'Prompts', paths: ['prompt.list', 'prompt.add'] },
14
15
  { title: 'Storage', paths: ['storage.list', 'storage.test', 'storage.upload', 'storage.stats'] },
15
16
  { title: 'Config', paths: ['config.get', 'config.set'] },
@@ -52,18 +53,23 @@ const GENERATED_COMMAND_SECTIONS = {
52
53
  },
53
54
  'skill:dino-manage-tags': {
54
55
  title: 'Command Reference',
55
- paths: ['tag.list', 'tag.add'],
56
+ paths: ['tag.list', 'tag.tree', 'tag.stats', 'tag.add', 'tag.rename', 'tag.move', 'tag.merge', 'tag.suggest', 'tag.cleanup'],
56
57
  intro: 'Use these commands as the canonical Dinox CLI interface for tag management.',
57
58
  notes: [
58
59
  'For writes, run the same command with `--dry-run` first.',
60
+ '`tag rename` and `tag move` cascade descendant paths and note tag references inside one transaction.',
61
+ '`tag merge` remaps note references into the target tag and soft-deletes the source subtree.',
62
+ 'Inline `hashtag` nodes keep `attrs.id` as `c_tag_node.id`; `attrs.label` stores the canonical path.',
59
63
  ],
60
64
  },
61
65
  'skill:dino-manage-boxes': {
62
66
  title: 'Command Reference',
63
- paths: ['box.list', 'box.add'],
67
+ paths: ['box.list', 'box.add', 'box.tree', 'box.stats', 'box.rename', 'box.move', 'box.merge', 'box.cleanup'],
64
68
  intro: 'Use these commands as the canonical Dinox CLI interface for box management.',
65
69
  notes: [
66
70
  'For writes, run the same command with `--dry-run` first.',
71
+ '`box rename` and `box move` cascade descendant paths inside one transaction.',
72
+ '`box merge` remaps note references into the target box and soft-deletes the source subtree.',
67
73
  ],
68
74
  },
69
75
  'skill:dino-storage': {
@@ -77,11 +83,13 @@ const GENERATED_COMMAND_SECTIONS = {
77
83
  },
78
84
  'ref:dino-note:search-and-read': {
79
85
  title: 'Command Surface',
80
- paths: ['note.search', 'note.get', 'note.preview', 'note.detail'],
86
+ paths: ['note.search', 'note.get', 'note.preview', 'note.detail', 'note.export', 'note.content-read'],
81
87
  intro: 'Use these generated commands as the canonical interfaces for note search and read workflows.',
82
88
  notes: [
83
89
  'Use `--boxes` for public box filters.',
84
90
  'Prefer `--fields id,title,summary,tags,created_at,boxes,is_starred` when the user only needs search metadata.',
91
+ 'Use `note export` for backup or migration; Markdown exports include frontmatter, JSON exports preserve note metadata and content JSON.',
92
+ 'Use `note content-read` immediately before `note patch`; the returned readToken, content hash, block index, outline, and resolved hashtag paths are the required edit context.',
85
93
  '`--sql` remains storage-oriented and still uses the field name `zettel_boxes`.',
86
94
  ],
87
95
  },
@@ -97,10 +105,15 @@ const GENERATED_COMMAND_SECTIONS = {
97
105
  },
98
106
  'ref:dino-note:update-and-delete': {
99
107
  title: 'Command Surface',
100
- paths: ['note.update', 'note.star', 'note.unstar', 'note.delete'],
108
+ paths: ['note.update', 'note.tag', 'note.move', 'note.patch', 'note.bulk', 'note.star', 'note.unstar', 'note.delete'],
101
109
  intro: 'Use these generated commands as the canonical interfaces for note mutation workflows.',
102
110
  notes: [
103
111
  '`--tags` and `--boxes` are full-replacement inputs.',
112
+ '`note update` is for explicit note ids and full metadata replacement. It is not an append command.',
113
+ '`note tag` and `note move` are explicit-id incremental organizers: add/remove preserves existing values, replace overwrites the whole list.',
114
+ '`note move` changes zettel box membership only; it does not move files or note content.',
115
+ '`note patch` is only for structured content edits. Run `note content-read` immediately before it, pass `--read-token` for real writes, and use `--allow-protected-replace` only after reviewing protected blocks.',
116
+ '`note bulk` is for filter-based batch metadata organization; it never accepts `--sql`, and real writes require `--confirm --expected-count <n>`.',
104
117
  'Prefer `note star` / `note unstar` for pure starring changes, and `note update` when multiple fields change together.',
105
118
  'Run the same command with `--dry-run --format json` first.',
106
119
  ],
@@ -1,6 +1,5 @@
1
1
  import type { CommandSchema } from './types.js';
2
- export type { CommandSchema, CommandSchemaAuth, CommandSchemaMcp, CommandSchemaMcpProperty, CommandSchemaOutput, CommandSchemaRisk, } from './types.js';
2
+ export type { CommandSchema, CommandSchemaAuth, CommandSchemaOutput, CommandSchemaRisk, } from './types.js';
3
3
  export declare const COMMAND_SCHEMAS: CommandSchema[];
4
4
  export declare function listCommandSchemas(): CommandSchema[];
5
5
  export declare function findCommandSchema(path: string): CommandSchema | null;
6
- export declare function listMcpCommandSchemas(): CommandSchema[];
@@ -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 { daemonCommandGroup } from '../../daemon/index.js';
6
7
  import { graphCommandGroup } from '../graph/index.js';
7
8
  import { infoCommandDescriptor } from '../info/index.js';
8
9
  import { noteCommandGroup } from '../notes/index.js';
@@ -16,6 +17,7 @@ import { updateCommandDescriptor } from '../update.js';
16
17
  export const COMMAND_SCHEMAS = [
17
18
  ...commandSchemasFromGroupDescriptor(authCommandGroup),
18
19
  ...commandSchemasFromGroupDescriptor(configCommandGroup),
20
+ ...commandSchemasFromGroupDescriptor(daemonCommandGroup),
19
21
  standaloneCommandSchemaFromDescriptor(syncCommandDescriptor),
20
22
  standaloneCommandSchemaFromDescriptor({ ...schemaCommandMetadata, action: async () => undefined }),
21
23
  standaloneCommandSchemaFromDescriptor(infoCommandDescriptor),
@@ -35,6 +37,3 @@ export function listCommandSchemas() {
35
37
  export function findCommandSchema(path) {
36
38
  return COMMAND_SCHEMA_MAP.get(path) ?? null;
37
39
  }
38
- export function listMcpCommandSchemas() {
39
- return COMMAND_SCHEMAS.filter((schema) => schema.mcp);
40
- }
@@ -20,18 +20,6 @@ export type CommandSchemaOutput = {
20
20
  fields?: string[];
21
21
  metaFields?: string[];
22
22
  };
23
- export type CommandSchemaMcpProperty = {
24
- name: string;
25
- type: 'string' | 'number' | 'boolean' | 'string[]';
26
- description: string;
27
- required?: boolean;
28
- enum?: string[];
29
- };
30
- export type CommandSchemaMcp = {
31
- toolName: string;
32
- description: string;
33
- properties: CommandSchemaMcpProperty[];
34
- };
35
23
  export type CommandSchema = {
36
24
  path: string;
37
25
  command: string;
@@ -42,6 +30,5 @@ export type CommandSchema = {
42
30
  arguments?: CommandSchemaArgument[];
43
31
  options?: CommandSchemaOption[];
44
32
  output: CommandSchemaOutput;
45
- mcp?: CommandSchemaMcp;
46
33
  examples: string[];
47
34
  };
@@ -1,42 +1,20 @@
1
1
  import { resolveAuthIdentity } from '../../auth/userInfo.js';
2
- import { loadConfig } from '../../config/store.js';
3
- import { resolveConfig } from '../../config/resolve.js';
4
- import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
5
2
  import { registerCommandGroup } from '../descriptor/register.js';
6
3
  import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
7
4
  import { buildStorageTestDryRunPreview, buildStorageUploadDryRunPreview } from '../writeDryRun.js';
8
5
  import { printDryRunPreview } from '../../utils/dryRun.js';
9
6
  import { DinoxError } from '../../utils/errors.js';
10
7
  import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
11
- import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
8
+ import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
12
9
  import { getStorageStats, inspectStorageTest, inspectStorageUpload, listStorageConfigs, parseStorageUploadCategory, testStorageUpload, uploadFileToStorage, } from './repo.js';
13
10
  const STORAGE_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
14
- async function syncBeforeStorageCommand(db, offline, timeoutMs) {
15
- if (offline) {
16
- return;
17
- }
18
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
19
- }
20
- async function withStorageDb(context, run) {
21
- const rawConfig = await loadConfig();
22
- const config = resolveConfig(rawConfig);
23
- const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? config.sync.timeoutMs;
24
- const offline = Boolean(context.globals.offline);
25
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
26
- try {
27
- await run({ db, config, rawConfig, offline, timeoutMs, stale });
28
- }
29
- finally {
30
- await db.close().catch(() => undefined);
31
- }
32
- }
33
11
  async function runStorageList(context) {
34
- await withStorageDb(context, async ({ db, config, rawConfig, offline, timeoutMs, stale }) => {
35
- const identity = await resolveAuthIdentity(rawConfig, config);
36
- await syncBeforeStorageCommand(db, offline, timeoutMs);
12
+ await withCommandRuntime(context, async (runtime) => {
13
+ const identity = await resolveAuthIdentity(runtime.config);
14
+ await syncBeforeCommand(runtime);
37
15
  printSuccess({
38
- stale: offline ? true : stale,
39
- ...(await listStorageConfigs(db, identity.userId)),
16
+ stale: commandStale(runtime),
17
+ ...(await listStorageConfigs(runtime.db, identity.userId)),
40
18
  });
41
19
  });
42
20
  }
@@ -48,9 +26,10 @@ async function runStorageUpload(context) {
48
26
  const options = context.options;
49
27
  const category = parseStorageUploadCategory(options.category ?? 'files');
50
28
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
51
- await withStorageDb(context, async ({ db, config, rawConfig, offline, timeoutMs, stale }) => {
52
- const identity = await resolveAuthIdentity(rawConfig, config);
53
- await syncBeforeStorageCommand(db, offline, timeoutMs);
29
+ await withCommandRuntime(context, async (runtime) => {
30
+ const { db } = runtime;
31
+ const identity = await resolveAuthIdentity(runtime.config);
32
+ await syncBeforeCommand(runtime);
54
33
  const plan = await inspectStorageUpload(db, {
55
34
  userId: identity.userId,
56
35
  filePath,
@@ -60,8 +39,8 @@ async function runStorageUpload(context) {
60
39
  });
61
40
  if (options.dryRun) {
62
41
  printDryRunPreview(buildStorageUploadDryRunPreview({
63
- config,
64
- offline,
42
+ config: runtime.config,
43
+ offline: runtime.offline,
65
44
  filePath: plan.filePath,
66
45
  fileName: plan.fileName,
67
46
  sizeBytes: plan.sizeBytes,
@@ -80,15 +59,11 @@ async function runStorageUpload(context) {
80
59
  storageId: options.storageId,
81
60
  key: options.key,
82
61
  });
83
- if (!offline && !config.powersync.uploadBaseUrl) {
84
- printHumanReadableLine('warning: upload disabled (powersync.uploadBaseUrl is unset); resource metadata is local-only for now');
85
- }
86
- if (!offline) {
87
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
88
- }
62
+ printUploadDisabledWarning(runtime, 'warning: upload disabled (powersync.uploadBaseUrl is unset); resource metadata is local-only for now');
63
+ await waitForMutationUpload(runtime);
89
64
  const payload = {
90
65
  ok: true,
91
- stale: offline ? true : stale,
66
+ stale: commandStale(runtime),
92
67
  ...result,
93
68
  };
94
69
  if (structuredOutput) {
@@ -104,9 +79,7 @@ async function runStorageUpload(context) {
104
79
  if (result.thumbnailUrl) {
105
80
  printHumanReadableLine(`- thumbnail: ${result.thumbnailUrl}`);
106
81
  }
107
- if (stale && !offline) {
108
- printHumanReadableLine('warning: sync timed out; local cache may be stale');
109
- }
82
+ printStaleCacheWarning(runtime);
110
83
  });
111
84
  }
112
85
  function formatBytes(bytes) {
@@ -123,17 +96,18 @@ function formatBytes(bytes) {
123
96
  async function runStorageTest(context) {
124
97
  const options = context.options;
125
98
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
126
- await withStorageDb(context, async ({ db, config, rawConfig, offline, timeoutMs, stale }) => {
127
- const identity = await resolveAuthIdentity(rawConfig, config);
128
- await syncBeforeStorageCommand(db, offline, timeoutMs);
99
+ await withCommandRuntime(context, async (runtime) => {
100
+ const { db } = runtime;
101
+ const identity = await resolveAuthIdentity(runtime.config);
102
+ await syncBeforeCommand(runtime);
129
103
  const plan = await inspectStorageTest(db, {
130
104
  userId: identity.userId,
131
105
  storageId: options.storageId,
132
106
  });
133
107
  if (options.dryRun) {
134
108
  printDryRunPreview(buildStorageTestDryRunPreview({
135
- config,
136
- offline,
109
+ config: runtime.config,
110
+ offline: runtime.offline,
137
111
  storage: plan.storage,
138
112
  storageKey: plan.storageKey,
139
113
  storageUrl: plan.storageUrl,
@@ -148,7 +122,7 @@ async function runStorageTest(context) {
148
122
  });
149
123
  const payload = {
150
124
  ok: true,
151
- stale: offline ? true : stale,
125
+ stale: commandStale(runtime),
152
126
  ...result,
153
127
  };
154
128
  if (structuredOutput) {
@@ -162,20 +136,18 @@ async function runStorageTest(context) {
162
136
  if (result.isPrivate) {
163
137
  printHumanReadableLine('- note: the target bucket is private; direct reads may still require signed access.');
164
138
  }
165
- if (stale && !offline) {
166
- printHumanReadableLine('warning: sync timed out; local cache may be stale');
167
- }
139
+ printStaleCacheWarning(runtime);
168
140
  });
169
141
  }
170
142
  async function runStorageStats(context) {
171
143
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
172
- await withStorageDb(context, async ({ db, config, rawConfig, offline, timeoutMs, stale }) => {
173
- const identity = await resolveAuthIdentity(rawConfig, config);
174
- await syncBeforeStorageCommand(db, offline, timeoutMs);
175
- const stats = await getStorageStats(db, identity.userId);
144
+ await withCommandRuntime(context, async (runtime) => {
145
+ const identity = await resolveAuthIdentity(runtime.config);
146
+ await syncBeforeCommand(runtime);
147
+ const stats = await getStorageStats(runtime.db, identity.userId);
176
148
  const payload = {
177
149
  ok: true,
178
- stale: offline ? true : stale,
150
+ stale: commandStale(runtime),
179
151
  ...stats,
180
152
  };
181
153
  if (structuredOutput) {
@@ -191,9 +163,7 @@ async function runStorageStats(context) {
191
163
  printHumanReadableLine(`- ${entry.label}: ${entry.fileCount} files, ${formatBytes(entry.totalBytes)}`);
192
164
  }
193
165
  }
194
- if (stale && !offline) {
195
- printHumanReadableLine('warning: sync timed out; local cache may be stale');
196
- }
166
+ printStaleCacheWarning(runtime);
197
167
  });
198
168
  }
199
169
  export const storageCommandGroup = {
@@ -1,5 +1,6 @@
1
1
  import { loadConfig } from '../config/store.js';
2
2
  import { resolveConfig } from '../config/resolve.js';
3
+ import { hydrateResolvedAuthIdentity } from '../auth/userInfo.js';
3
4
  import { registerStandaloneCommand } from './descriptor/register.js';
4
5
  import { FORMAT_OPTIONS, SYNC_TIMEOUT_OPTIONS } from './schema/globalOptions.js';
5
6
  import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../utils/output.js';
@@ -22,9 +23,10 @@ export const syncCommandDescriptor = {
22
23
  examples: ['dino sync --format json', 'dino sync --sync-timeout 600000 --format json'],
23
24
  },
24
25
  action: async (context) => {
25
- const config = resolveConfig(await loadConfig());
26
+ const baseConfig = resolveConfig(await loadConfig());
26
27
  const structuredOutput = getRequestedStructuredOutputFormat() !== null;
27
- const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? config.sync.timeoutMs;
28
+ const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? baseConfig.sync.timeoutMs;
29
+ const { config } = await hydrateResolvedAuthIdentity(baseConfig, { timeoutMs });
28
30
  const { db, dbPath, stale } = await connectPowerSync({
29
31
  config,
30
32
  offline: false,
@@ -0,0 +1,32 @@
1
+ export type NoteTagContentUpdate = {
2
+ tagsChanged: boolean;
3
+ contentJsonChanged: boolean;
4
+ contentMdChanged: boolean;
5
+ contentTextChanged: boolean;
6
+ tags: string[];
7
+ contentJson: string | null;
8
+ contentMd: string | null;
9
+ contentText: string | null;
10
+ };
11
+ export type TagReferenceLookup = {
12
+ idByPath: ReadonlyMap<string, string>;
13
+ pathById: ReadonlyMap<string, string>;
14
+ };
15
+ export declare function renameTagReferencesInNote(args: {
16
+ tags: string[];
17
+ contentJson: string | null;
18
+ contentMd: string | null;
19
+ contentText: string | null;
20
+ oldPrefix: string;
21
+ newPrefix: string;
22
+ lookup?: TagReferenceLookup;
23
+ }): NoteTagContentUpdate;
24
+ export declare function mergeTagReferencesInNote(args: {
25
+ tags: string[];
26
+ contentJson: string | null;
27
+ contentMd: string | null;
28
+ contentText: string | null;
29
+ oldPrefix: string;
30
+ newPrefix: string;
31
+ lookup?: TagReferenceLookup;
32
+ }): NoteTagContentUpdate;