@dinoxx/dinox-cli 1.0.17 → 1.0.19

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 (111) hide show
  1. package/README.md +72 -34
  2. package/dist/cli.js +15 -1
  3. package/dist/commands/auth/index.d.ts +2 -0
  4. package/dist/commands/auth/index.js +209 -163
  5. package/dist/commands/boxes/index.d.ts +2 -0
  6. package/dist/commands/boxes/index.js +119 -75
  7. package/dist/commands/boxes/repo.d.ts +8 -0
  8. package/dist/commands/boxes/repo.js +41 -0
  9. package/dist/commands/config/index.d.ts +2 -0
  10. package/dist/commands/config/index.js +85 -36
  11. package/dist/commands/descriptor/register.d.ts +8 -0
  12. package/dist/commands/descriptor/register.js +113 -0
  13. package/dist/commands/descriptor/types.d.ts +37 -0
  14. package/dist/commands/descriptor/types.js +17 -0
  15. package/dist/commands/graph/index.d.ts +2 -0
  16. package/dist/commands/graph/index.js +143 -73
  17. package/dist/commands/info/index.d.ts +2 -0
  18. package/dist/commands/info/index.js +41 -14
  19. package/dist/commands/mcp/index.js +11 -10
  20. package/dist/commands/mcp/server.js +6 -3
  21. package/dist/commands/mcp/tools.js +30 -104
  22. package/dist/commands/mcp/types.d.ts +8 -1
  23. package/dist/commands/meta/index.d.ts +2 -0
  24. package/dist/commands/meta/index.js +64 -33
  25. package/dist/commands/meta/repo.d.ts +6 -0
  26. package/dist/commands/meta/repo.js +6 -0
  27. package/dist/commands/notes/actions.d.ts +10 -0
  28. package/dist/commands/notes/actions.js +403 -0
  29. package/dist/commands/notes/descriptors.d.ts +4 -0
  30. package/dist/commands/notes/descriptors.js +250 -0
  31. package/dist/commands/notes/dryRun.d.ts +31 -0
  32. package/dist/commands/notes/dryRun.js +130 -0
  33. package/dist/commands/notes/index.d.ts +2 -51
  34. package/dist/commands/notes/index.js +2 -581
  35. package/dist/commands/notes/repo.d.ts +9 -0
  36. package/dist/commands/notes/repo.js +56 -10
  37. package/dist/commands/notes/runtime.d.ts +28 -0
  38. package/dist/commands/notes/runtime.js +185 -0
  39. package/dist/commands/notes/searchOutput.d.ts +10 -2
  40. package/dist/commands/notes/searchOutput.js +14 -2
  41. package/dist/commands/notes/searchResponse.d.ts +51 -0
  42. package/dist/commands/notes/searchResponse.js +56 -0
  43. package/dist/commands/notes/searchSql.js +2 -0
  44. package/dist/commands/prompt/index.d.ts +2 -0
  45. package/dist/commands/prompt/index.js +122 -72
  46. package/dist/commands/prompt/repo.d.ts +7 -0
  47. package/dist/commands/prompt/repo.js +46 -0
  48. package/dist/commands/schema/commandMetadata.d.ts +2 -0
  49. package/dist/commands/schema/commandMetadata.js +18 -0
  50. package/dist/commands/schema/globalOptions.d.ts +3 -0
  51. package/dist/commands/schema/globalOptions.js +3 -0
  52. package/dist/commands/schema/index.d.ts +4 -0
  53. package/dist/commands/schema/index.js +109 -0
  54. package/dist/commands/schema/renderSkills.d.ts +6 -0
  55. package/dist/commands/schema/renderSkills.js +216 -0
  56. package/dist/commands/schema/spec.d.ts +6 -0
  57. package/dist/commands/schema/spec.js +40 -0
  58. package/dist/commands/schema/types.d.ts +47 -0
  59. package/dist/commands/schema/types.js +1 -0
  60. package/dist/commands/storage/index.d.ts +4 -0
  61. package/dist/commands/storage/index.js +292 -0
  62. package/dist/commands/storage/repo.d.ts +162 -0
  63. package/dist/commands/storage/repo.js +751 -0
  64. package/dist/commands/sync.d.ts +2 -0
  65. package/dist/commands/sync.js +41 -27
  66. package/dist/commands/tags/index.d.ts +2 -0
  67. package/dist/commands/tags/index.js +121 -79
  68. package/dist/commands/tags/repo.d.ts +7 -0
  69. package/dist/commands/tags/repo.js +52 -0
  70. package/dist/commands/todo/dryRun.d.ts +23 -0
  71. package/dist/commands/todo/dryRun.js +133 -0
  72. package/dist/commands/todo/index.d.ts +2 -0
  73. package/dist/commands/todo/index.js +285 -135
  74. package/dist/commands/todo/repo.d.ts +11 -0
  75. package/dist/commands/todo/repo.js +37 -9
  76. package/dist/commands/update.d.ts +3 -0
  77. package/dist/commands/update.js +35 -13
  78. package/dist/commands/writeDryRun.d.ts +62 -0
  79. package/dist/commands/writeDryRun.js +131 -0
  80. package/dist/constants/links.d.ts +1 -1
  81. package/dist/constants/links.js +1 -1
  82. package/dist/daemon/ensure.js +3 -2
  83. package/dist/daemon/index.js +17 -15
  84. package/dist/dinox.js +9 -13
  85. package/dist/powersync/schema/index.d.ts +27 -0
  86. package/dist/powersync/schema/index.js +2 -0
  87. package/dist/powersync/schema/note.d.ts +1 -0
  88. package/dist/powersync/schema/note.js +1 -0
  89. package/dist/powersync/schema/projects.d.ts +3 -0
  90. package/dist/powersync/schema/projects.js +3 -0
  91. package/dist/powersync/schema/storage.d.ts +24 -0
  92. package/dist/powersync/schema/storage.js +24 -0
  93. package/dist/skills/check.d.ts +6 -0
  94. package/dist/skills/check.js +254 -0
  95. package/dist/utils/bidlinkCanonicalization.d.ts +2 -0
  96. package/dist/utils/bidlinkCanonicalization.js +161 -0
  97. package/dist/utils/dryRun.d.ts +13 -0
  98. package/dist/utils/dryRun.js +69 -0
  99. package/dist/utils/output.d.ts +19 -0
  100. package/dist/utils/output.js +155 -1
  101. package/dist/utils/terminalSanitization.d.ts +2 -0
  102. package/dist/utils/terminalSanitization.js +33 -0
  103. package/dist/utils/tiptapMarkdown.d.ts +4 -1
  104. package/dist/utils/tiptapMarkdown.js +285 -113
  105. package/dist/utils/tiptapMarkdownSchema.d.ts +37 -0
  106. package/dist/utils/tiptapMarkdownSchema.js +577 -0
  107. package/dist/utils/tiptapMarkdownSchemaUtils.d.ts +74 -0
  108. package/dist/utils/tiptapMarkdownSchemaUtils.js +394 -0
  109. package/dist/utils/updateNotice.d.ts +7 -0
  110. package/dist/utils/updateNotice.js +64 -0
  111. package/package.json +25 -3
@@ -1,183 +1,229 @@
1
1
  import fs from 'node:fs/promises';
2
+ import { refreshAndPersistAuthIdentity } from '../../auth/userInfo.js';
3
+ import { getPowerSyncDbPath } from '../../config/paths.js';
2
4
  import { loadConfig, saveConfig } from '../../config/store.js';
3
5
  import { resolveConfig } from '../../config/resolve.js';
4
- import { getPowerSyncDbPath } from '../../config/paths.js';
5
- import { redactAuthorization } from '../../utils/redact.js';
6
- import { DinoxError } from '../../utils/errors.js';
7
- import { printYaml } from '../../utils/output.js';
8
- import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
9
- import { normalizeAuthToken } from '../../utils/authToken.js';
10
- import { refreshAndPersistAuthIdentity } from '../../auth/userInfo.js';
11
- import { connectPowerSync, getStatusSnapshot } from '../../powersync/runtime.js';
12
6
  import { fetchCredentialsWithAuthorization } from '../../powersync/connector.js';
7
+ import { connectPowerSync, getStatusSnapshot } from '../../powersync/runtime.js';
13
8
  import { runSingleSyncPass } from '../../powersync/syncPass.js';
9
+ import { registerCommandGroup } from '../descriptor/register.js';
10
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
11
+ import { normalizeAuthToken } from '../../utils/authToken.js';
12
+ import { DinoxError } from '../../utils/errors.js';
13
+ import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
14
+ import { redactAuthorization } from '../../utils/redact.js';
15
+ import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
16
+ const AUTH_LOGIN_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
17
+ const AUTH_STATUS_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
14
18
  async function removeSqliteArtifacts(dbPath) {
15
- const artifacts = ['', '-wal', '-shm'];
16
- for (const suffix of artifacts) {
19
+ for (const suffix of ['', '-wal', '-shm']) {
17
20
  await fs.rm(`${dbPath}${suffix}`, { force: true }).catch(() => undefined);
18
21
  }
19
22
  }
20
- export function registerAuthCommands(program) {
21
- const auth = program
22
- .command('auth')
23
- .description('Authentication and PowerSync connectivity commands');
24
- auth
25
- .command('login')
26
- .description('Save login token and verify PowerSync connectivity')
27
- .argument('<token>', 'login token (pass the raw token string, without Bearer prefix)')
28
- .action(async (token, _options, command) => {
29
- const rawToken = normalizeAuthToken(token);
30
- if (!rawToken) {
31
- throw new DinoxError('Token is required');
32
- }
33
- const config = await loadConfig();
34
- config.auth = { ...(config.auth ?? {}), authorization: rawToken };
35
- await saveConfig(config);
36
- const resolved = resolveConfig(config);
37
- const globals = command.optsWithGlobals?.() ?? {};
38
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? resolved.sync.timeoutMs;
39
- const jsonOutput = Boolean(globals.json);
40
- // 1) Token exchange sanity check.
41
- await fetchCredentialsWithAuthorization({
42
- authorization: rawToken,
43
- endpoint: resolved.powersync.endpoint,
44
- credentialsUrl: resolved.powersync.tokenEndpoint,
45
- timeoutMs,
46
- });
47
- // 1.5) Fetch and persist user identity.
48
- const identity = await refreshAndPersistAuthIdentity(config, resolved);
49
- // 2) Connect + initial sync.
50
- const { db, dbPath, stale } = await connectPowerSync({
51
- config: resolved,
52
- offline: false,
53
- timeoutMs,
54
- });
55
- try {
56
- await runSingleSyncPass(db, timeoutMs);
57
- const status = getStatusSnapshot(db);
58
- const result = {
59
- ok: true,
60
- verified: true,
61
- endpoint: resolved.powersync.endpoint,
62
- dbPath,
63
- stale,
64
- lastSyncedAt: status.lastSyncedAt,
65
- userId: identity.userId,
66
- };
67
- if (jsonOutput) {
68
- printYaml(result);
69
- }
70
- else {
71
- console.log('Login verified.');
72
- console.log(`- endpoint: ${result.endpoint}`);
73
- console.log(`- db: ${result.dbPath}`);
74
- console.log(`- userId: ${result.userId}`);
75
- if (result.lastSyncedAt) {
76
- console.log(`- lastSyncedAt: ${result.lastSyncedAt}`);
77
- }
78
- if (stale) {
79
- console.log('- warning: initial sync timed out; local cache may be stale');
80
- }
81
- }
82
- }
83
- finally {
84
- await db.close().catch(() => undefined);
85
- }
23
+ async function runAuthLogin(context) {
24
+ const token = context.args.token;
25
+ const rawToken = normalizeAuthToken(token);
26
+ if (!rawToken) {
27
+ throw new DinoxError('Token is required');
28
+ }
29
+ const config = await loadConfig();
30
+ config.auth = { ...(config.auth ?? {}), authorization: rawToken };
31
+ await saveConfig(config);
32
+ const resolved = resolveConfig(config);
33
+ const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? resolved.sync.timeoutMs;
34
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
35
+ await fetchCredentialsWithAuthorization({
36
+ authorization: rawToken,
37
+ endpoint: resolved.powersync.endpoint,
38
+ credentialsUrl: resolved.powersync.tokenEndpoint,
39
+ timeoutMs,
86
40
  });
87
- auth
88
- .command('logout')
89
- .description('Clear saved login token')
90
- .option('--clear-local-db', 'delete the local PowerSync SQLite database')
91
- .action(async (options) => {
92
- const config = await loadConfig();
93
- if (config.auth) {
94
- config.auth.authorization = undefined;
95
- config.auth.userId = undefined;
96
- config.auth.username = undefined;
97
- config.auth.email = undefined;
98
- config.auth.userInfoSyncedAt = undefined;
99
- }
100
- await saveConfig(config);
101
- if (options.clearLocalDb) {
102
- const dbPath = getPowerSyncDbPath();
103
- await removeSqliteArtifacts(dbPath);
104
- }
41
+ const identity = await refreshAndPersistAuthIdentity(config, resolved);
42
+ const { db, dbPath, stale } = await connectPowerSync({
43
+ config: resolved,
44
+ offline: false,
45
+ timeoutMs,
105
46
  });
106
- auth
107
- .command('status')
108
- .description('Show current login and sync status')
109
- .action(async (_options, command) => {
110
- const config = await loadConfig();
111
- const resolved = resolveConfig(config);
112
- const globals = command.optsWithGlobals?.() ?? {};
113
- const offline = Boolean(globals.offline);
114
- const jsonOutput = Boolean(globals.json);
115
- const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? resolved.sync.timeoutMs;
116
- const dbPath = getPowerSyncDbPath();
117
- const base = {
118
- loggedIn: Boolean(resolved.auth.authorization),
119
- authorization: redactAuthorization(resolved.auth.authorization),
120
- userId: resolved.auth.userId ?? null,
121
- username: resolved.auth.username ?? null,
122
- email: resolved.auth.email ?? null,
123
- powersync: {
124
- endpoint: resolved.powersync.endpoint ?? null,
125
- tokenEndpoint: resolved.powersync.tokenEndpoint ?? null,
126
- uploadBaseUrl: resolved.powersync.uploadBaseUrl ?? null,
127
- uploadV4Path: resolved.powersync.uploadV4Path,
128
- uploadV2Path: resolved.powersync.uploadV2Path,
129
- },
47
+ try {
48
+ await runSingleSyncPass(db, timeoutMs);
49
+ const status = getStatusSnapshot(db);
50
+ const result = {
51
+ ok: true,
52
+ verified: true,
53
+ endpoint: resolved.powersync.endpoint,
130
54
  dbPath,
55
+ stale,
56
+ lastSyncedAt: status.lastSyncedAt,
57
+ userId: identity.userId,
131
58
  };
132
- if (offline || !resolved.auth.authorization || !resolved.powersync.endpoint || !resolved.powersync.tokenEndpoint) {
133
- const payload = { ...base, offline: true };
134
- if (jsonOutput) {
135
- printYaml(payload);
136
- }
137
- else {
138
- console.log(`loggedIn: ${payload.loggedIn ? 'yes' : 'no'}`);
139
- if (payload.userId) {
140
- console.log(`userId: ${payload.userId}`);
141
- }
142
- console.log(`db: ${payload.dbPath}`);
143
- console.log(`powersync.endpoint: ${payload.powersync.endpoint ?? '(unset)'}`);
144
- console.log(`powersync.tokenEndpoint: ${payload.powersync.tokenEndpoint ?? '(unset)'}`);
145
- console.log(`powersync.uploadBaseUrl: ${payload.powersync.uploadBaseUrl ?? '(unset)'}`);
146
- }
59
+ if (structuredOutput) {
60
+ printSuccess(result);
147
61
  return;
148
62
  }
149
- const { db, stale } = await connectPowerSync({
150
- config: resolved,
151
- offline: false,
152
- timeoutMs,
63
+ printHumanReadableLine('Login verified.');
64
+ printHumanReadableLine(`- endpoint: ${result.endpoint}`);
65
+ printHumanReadableLine(`- db: ${result.dbPath}`);
66
+ printHumanReadableLine(`- userId: ${result.userId}`);
67
+ if (result.lastSyncedAt) {
68
+ printHumanReadableLine(`- lastSyncedAt: ${result.lastSyncedAt}`);
69
+ }
70
+ if (stale) {
71
+ printHumanReadableLine('- warning: initial sync timed out; local cache may be stale');
72
+ }
73
+ }
74
+ finally {
75
+ await db.close().catch(() => undefined);
76
+ }
77
+ }
78
+ async function runAuthLogout(context) {
79
+ const options = context.options;
80
+ const config = await loadConfig();
81
+ if (config.auth) {
82
+ config.auth.authorization = undefined;
83
+ config.auth.userId = undefined;
84
+ config.auth.username = undefined;
85
+ config.auth.email = undefined;
86
+ config.auth.userInfoSyncedAt = undefined;
87
+ }
88
+ await saveConfig(config);
89
+ if (options.clearLocalDb) {
90
+ await removeSqliteArtifacts(getPowerSyncDbPath());
91
+ }
92
+ if (getRequestedStructuredOutputFormat()) {
93
+ printSuccess({
94
+ ok: true,
95
+ loggedOut: true,
96
+ clearedLocalDb: Boolean(options.clearLocalDb),
153
97
  });
154
- try {
155
- const status = getStatusSnapshot(db);
156
- const payload = { ...base, offline: false, stale, status };
157
- if (jsonOutput) {
158
- printYaml(payload);
159
- }
160
- else {
161
- console.log(`loggedIn: ${payload.loggedIn ? 'yes' : 'no'}`);
162
- if (payload.userId) {
163
- console.log(`userId: ${payload.userId}`);
164
- }
165
- console.log(`db: ${payload.dbPath}`);
166
- console.log(`connected: ${payload.status.connected ? 'yes' : 'no'}`);
167
- if (payload.status.lastSyncedAt) {
168
- console.log(`lastSyncedAt: ${payload.status.lastSyncedAt}`);
169
- }
170
- console.log(`dataFlow: downloading=${payload.status.dataFlowStatus.downloading} uploading=${payload.status.dataFlowStatus.uploading}`);
171
- if (payload.stale) {
172
- console.log('warning: sync timed out; local cache may be stale');
173
- }
174
- }
98
+ }
99
+ }
100
+ async function runAuthStatus(context) {
101
+ const config = await loadConfig();
102
+ const resolved = resolveConfig(config);
103
+ const offline = Boolean(context.globals.offline);
104
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
105
+ const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? resolved.sync.timeoutMs;
106
+ const dbPath = getPowerSyncDbPath();
107
+ const base = {
108
+ loggedIn: Boolean(resolved.auth.authorization),
109
+ authorization: redactAuthorization(resolved.auth.authorization),
110
+ userId: resolved.auth.userId ?? null,
111
+ username: resolved.auth.username ?? null,
112
+ email: resolved.auth.email ?? null,
113
+ powersync: {
114
+ endpoint: resolved.powersync.endpoint ?? null,
115
+ tokenEndpoint: resolved.powersync.tokenEndpoint ?? null,
116
+ uploadBaseUrl: resolved.powersync.uploadBaseUrl ?? null,
117
+ uploadV4Path: resolved.powersync.uploadV4Path,
118
+ uploadV2Path: resolved.powersync.uploadV2Path,
119
+ },
120
+ dbPath,
121
+ };
122
+ if (offline || !resolved.auth.authorization || !resolved.powersync.endpoint || !resolved.powersync.tokenEndpoint) {
123
+ const payload = { ...base, offline: true };
124
+ if (structuredOutput) {
125
+ printSuccess(payload);
126
+ return;
175
127
  }
176
- finally {
177
- await db.close().catch(() => undefined);
128
+ printHumanReadableLine(`loggedIn: ${payload.loggedIn ? 'yes' : 'no'}`);
129
+ if (payload.userId) {
130
+ printHumanReadableLine(`userId: ${payload.userId}`);
178
131
  }
132
+ printHumanReadableLine(`db: ${payload.dbPath}`);
133
+ printHumanReadableLine(`powersync.endpoint: ${payload.powersync.endpoint ?? '(unset)'}`);
134
+ printHumanReadableLine(`powersync.tokenEndpoint: ${payload.powersync.tokenEndpoint ?? '(unset)'}`);
135
+ printHumanReadableLine(`powersync.uploadBaseUrl: ${payload.powersync.uploadBaseUrl ?? '(unset)'}`);
136
+ return;
137
+ }
138
+ const { db, stale } = await connectPowerSync({
139
+ config: resolved,
140
+ offline: false,
141
+ timeoutMs,
179
142
  });
180
- auth.action((_options, command) => {
181
- command.outputHelp();
182
- });
143
+ try {
144
+ const status = getStatusSnapshot(db);
145
+ const payload = { ...base, offline: false, stale, status };
146
+ if (structuredOutput) {
147
+ printSuccess(payload);
148
+ return;
149
+ }
150
+ printHumanReadableLine(`loggedIn: ${payload.loggedIn ? 'yes' : 'no'}`);
151
+ if (payload.userId) {
152
+ printHumanReadableLine(`userId: ${payload.userId}`);
153
+ }
154
+ printHumanReadableLine(`db: ${payload.dbPath}`);
155
+ printHumanReadableLine(`connected: ${payload.status.connected ? 'yes' : 'no'}`);
156
+ if (payload.status.lastSyncedAt) {
157
+ printHumanReadableLine(`lastSyncedAt: ${payload.status.lastSyncedAt}`);
158
+ }
159
+ printHumanReadableLine(`dataFlow: downloading=${payload.status.dataFlowStatus.downloading} uploading=${payload.status.dataFlowStatus.uploading}`);
160
+ if (payload.stale) {
161
+ printHumanReadableLine('warning: sync timed out; local cache may be stale');
162
+ }
163
+ }
164
+ finally {
165
+ await db.close().catch(() => undefined);
166
+ }
167
+ }
168
+ export const authCommandGroup = {
169
+ name: 'auth',
170
+ description: 'Authentication and PowerSync connectivity commands',
171
+ commands: [
172
+ {
173
+ name: 'login',
174
+ description: 'Save login token and verify PowerSync connectivity',
175
+ arguments: [{ syntax: '<token>', description: 'Login token (without Bearer prefix)' }],
176
+ schema: {
177
+ path: 'auth.login',
178
+ risk: 'write',
179
+ auth: 'optional',
180
+ globalOptions: AUTH_LOGIN_GLOBAL_OPTIONS,
181
+ output: {
182
+ type: 'structured',
183
+ summary: 'Login verification result',
184
+ fields: ['verified', 'endpoint', 'dbPath', 'stale', 'lastSyncedAt', 'userId'],
185
+ },
186
+ examples: ['dino auth login "<token>" --format json'],
187
+ },
188
+ action: runAuthLogin,
189
+ },
190
+ {
191
+ name: 'logout',
192
+ description: 'Clear saved login token and optionally remove the local cache',
193
+ options: [{ flags: '--clear-local-db', description: 'Delete the local PowerSync SQLite database' }],
194
+ schema: {
195
+ path: 'auth.logout',
196
+ risk: 'write',
197
+ auth: 'optional',
198
+ globalOptions: FORMAT_OPTIONS,
199
+ output: {
200
+ type: 'structured',
201
+ summary: 'Logout acknowledgement',
202
+ fields: ['loggedOut', 'clearedLocalDb'],
203
+ },
204
+ examples: ['dino auth logout --format json', 'dino auth logout --clear-local-db --format json'],
205
+ },
206
+ action: runAuthLogout,
207
+ },
208
+ {
209
+ name: 'status',
210
+ description: 'Show current login, local cache, and sync status',
211
+ schema: {
212
+ path: 'auth.status',
213
+ risk: 'read',
214
+ auth: 'optional',
215
+ globalOptions: AUTH_STATUS_GLOBAL_OPTIONS,
216
+ output: {
217
+ type: 'structured',
218
+ summary: 'Auth and connectivity status envelope',
219
+ fields: ['loggedIn', 'authorization', 'userId', 'username', 'email', 'powersync', 'dbPath', 'offline', 'stale', 'status'],
220
+ },
221
+ examples: ['dino auth status --format json', 'dino auth status --offline --format json'],
222
+ },
223
+ action: runAuthStatus,
224
+ },
225
+ ],
226
+ };
227
+ export function registerAuthCommands(program) {
228
+ registerCommandGroup(program, authCommandGroup);
183
229
  }
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import type { CommandGroupDescriptor } from '../descriptor/types.js';
3
+ export declare const boxCommandGroup: CommandGroupDescriptor;
2
4
  export declare function registerBoxesCommand(program: Command): void;
@@ -1,12 +1,17 @@
1
+ import { resolveAuthIdentity } from '../../auth/userInfo.js';
1
2
  import { loadConfig } from '../../config/store.js';
2
3
  import { resolveConfig } from '../../config/resolve.js';
3
- import { DinoxError } from '../../utils/errors.js';
4
- import { printYaml } from '../../utils/output.js';
5
- import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
6
4
  import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
7
5
  import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
8
- import { resolveAuthIdentity } from '../../auth/userInfo.js';
9
- import { addBox, listBoxes } from './repo.js';
6
+ import { registerCommandGroup } from '../descriptor/register.js';
7
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
8
+ import { buildBoxAddDryRunPreview } from '../writeDryRun.js';
9
+ import { printDryRunPreview } from '../../utils/dryRun.js';
10
+ import { DinoxError } from '../../utils/errors.js';
11
+ import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
12
+ import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
13
+ import { addBox, inspectAddBox, listBoxes } from './repo.js';
14
+ const BOX_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
10
15
  async function syncBeforeBoxCommand(db, offline, timeoutMs) {
11
16
  if (offline) {
12
17
  return;
@@ -14,85 +19,124 @@ async function syncBeforeBoxCommand(db, offline, timeoutMs) {
14
19
  await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
15
20
  await syncNoteTokenIndex(db);
16
21
  }
17
- async function runBoxListCommand(options, command) {
18
- const globals = command.optsWithGlobals?.() ?? {};
19
- const offline = Boolean(options.offline ?? globals.offline);
20
- const config = resolveConfig(await loadConfig());
21
- const timeoutMs = parseSyncTimeoutMs(options.syncTimeout ?? globals.syncTimeout) ?? config.sync.timeoutMs;
22
- const { db } = await connectPowerSync({ config, offline, timeoutMs });
22
+ async function withBoxDb(context, run) {
23
+ const rawConfig = await loadConfig();
24
+ const config = resolveConfig(rawConfig);
25
+ const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? config.sync.timeoutMs;
26
+ const offline = Boolean(context.globals.offline);
27
+ const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
23
28
  try {
24
- await syncBeforeBoxCommand(db, offline, timeoutMs);
25
- const boxes = await listBoxes(db);
26
- printYaml(boxes);
29
+ await run({ db, config, rawConfig, offline, timeoutMs, stale });
27
30
  }
28
31
  finally {
29
32
  await db.close().catch(() => undefined);
30
33
  }
31
34
  }
32
- export function registerBoxesCommand(program) {
33
- const box = program.command('box').description('Manage zettel boxes');
34
- box
35
- .command('list')
36
- .description('List zettel boxes (id and name) from c_zettel_box')
37
- .option('--offline', 'skip connect/sync and only use local cache')
38
- .option('--sync-timeout <ms>', 'override sync/connect timeout (milliseconds)')
39
- .action(runBoxListCommand);
40
- box
41
- .command('add')
42
- .description('Create a new zettel box in c_zettel_box')
43
- .argument('[name]', 'box name')
44
- .option('--name <string>', 'box name (alternative to positional name)')
45
- .option('--description <string>', 'box purpose/usage description (helps AI route notes to this box)')
46
- .option('--color <string>', 'box color')
47
- .option('--json', 'output machine-readable YAML')
48
- .option('--offline', 'skip connect/sync and only use local cache')
49
- .option('--sync-timeout <ms>', 'override sync/connect timeout (milliseconds)')
50
- .action(async (nameArg, options, command) => {
51
- const globals = command.optsWithGlobals?.() ?? {};
52
- const offline = Boolean(options.offline ?? globals.offline);
53
- const jsonOutput = Boolean(options.json ?? globals.json);
54
- const optionName = typeof options.name === 'string' ? options.name : undefined;
55
- const name = (nameArg ?? optionName ?? '').trim();
56
- if (!name) {
57
- throw new DinoxError('Box name is required. Use `dino box add <name>` or `dino box add --name <name>`.');
35
+ async function runBoxList(context) {
36
+ await withBoxDb(context, async ({ db, offline, timeoutMs }) => {
37
+ await syncBeforeBoxCommand(db, offline, timeoutMs);
38
+ printSuccess(await listBoxes(db));
39
+ });
40
+ }
41
+ async function runBoxAdd(context) {
42
+ const nameArg = context.args.name;
43
+ const optionName = typeof context.options.name === 'string' ? context.options.name : undefined;
44
+ const name = (nameArg ?? optionName ?? '').trim();
45
+ if (!name) {
46
+ throw new DinoxError('Box name is required. Use `dino box add <name>` or `dino box add --name <name>`.');
47
+ }
48
+ if (nameArg && optionName && nameArg.trim() !== optionName.trim()) {
49
+ throw new DinoxError('Conflicting box name values from positional argument and --name option.');
50
+ }
51
+ const structuredOutput = getRequestedStructuredOutputFormat() !== null;
52
+ await withBoxDb(context, async ({ db, config, rawConfig, offline, timeoutMs, stale }) => {
53
+ const identity = await resolveAuthIdentity(rawConfig, config);
54
+ await syncBeforeBoxCommand(db, offline, timeoutMs);
55
+ const description = typeof context.options.description === 'string' ? context.options.description : undefined;
56
+ const color = typeof context.options.color === 'string' ? context.options.color : undefined;
57
+ if (Boolean(context.options.dryRun)) {
58
+ printDryRunPreview(buildBoxAddDryRunPreview({
59
+ config,
60
+ offline,
61
+ ...(await inspectAddBox(db, {
62
+ name,
63
+ userId: identity.userId,
64
+ description,
65
+ color,
66
+ })),
67
+ }));
68
+ return;
58
69
  }
59
- if (nameArg && optionName && nameArg.trim() !== optionName.trim()) {
60
- throw new DinoxError('Conflicting box name values from positional argument and --name option.');
70
+ const result = await addBox(db, {
71
+ name,
72
+ userId: identity.userId,
73
+ description,
74
+ color,
75
+ });
76
+ if (!offline && !config.powersync.uploadBaseUrl) {
77
+ printHumanReadableLine('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
61
78
  }
62
- const rawConfig = await loadConfig();
63
- const config = resolveConfig(rawConfig);
64
- const timeoutMs = parseSyncTimeoutMs(options.syncTimeout ?? globals.syncTimeout) ?? config.sync.timeoutMs;
65
- const identity = await resolveAuthIdentity(rawConfig, config);
66
- const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
67
- try {
68
- await syncBeforeBoxCommand(db, offline, timeoutMs);
69
- const result = await addBox(db, {
70
- name,
71
- userId: identity.userId,
72
- description: typeof options.description === 'string' ? options.description : undefined,
73
- color: typeof options.color === 'string' ? options.color : undefined,
74
- });
75
- if (!offline && !config.powersync.uploadBaseUrl) {
76
- console.log('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
77
- }
78
- if (!offline) {
79
- await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
80
- }
81
- const payload = { ok: true, stale: offline ? true : stale, ...result };
82
- if (jsonOutput) {
83
- printYaml(payload);
84
- return;
85
- }
86
- console.log(result.id);
87
- if (stale && !offline) {
88
- console.log('warning: sync timed out; results may be stale');
89
- }
79
+ if (!offline) {
80
+ await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
90
81
  }
91
- finally {
92
- await db.close().catch(() => undefined);
82
+ const payload = { ok: true, stale: offline ? true : stale, ...result };
83
+ if (structuredOutput) {
84
+ printSuccess(payload);
85
+ return;
86
+ }
87
+ printHumanReadableLine(result.id);
88
+ if (stale && !offline) {
89
+ printHumanReadableLine('warning: sync timed out; results may be stale');
93
90
  }
94
91
  });
95
- box.action((_options, command) => {
96
- command.outputHelp();
97
- });
92
+ }
93
+ export const boxCommandGroup = {
94
+ name: 'box',
95
+ description: 'Manage zettel boxes',
96
+ commands: [
97
+ {
98
+ name: 'list',
99
+ description: 'List all zettel boxes from c_zettel_box',
100
+ schema: {
101
+ path: 'box.list',
102
+ risk: 'read',
103
+ auth: 'required',
104
+ globalOptions: BOX_GLOBAL_OPTIONS,
105
+ output: {
106
+ type: 'structured',
107
+ summary: 'Box list payload',
108
+ fields: ['id', 'name', 'description'],
109
+ },
110
+ examples: ['dino box list --format json'],
111
+ },
112
+ action: runBoxList,
113
+ },
114
+ {
115
+ name: 'add',
116
+ description: 'Create a zettel box, restoring a deleted row when possible',
117
+ arguments: [{ syntax: '[name]', description: 'Box name' }],
118
+ options: [
119
+ { flags: '--name <string>', description: 'Box name (alternative to positional name)' },
120
+ { flags: '--description <string>', description: 'Box purpose/usage description' },
121
+ { flags: '--color <string>', description: 'Box color' },
122
+ { flags: '--dry-run', description: 'Preview the write without executing it' },
123
+ ],
124
+ schema: {
125
+ path: 'box.add',
126
+ risk: 'write',
127
+ auth: 'required',
128
+ globalOptions: BOX_GLOBAL_OPTIONS,
129
+ output: {
130
+ type: 'structured',
131
+ summary: 'Box creation acknowledgement',
132
+ fields: ['id', 'name', 'description', 'color', 'restored', 'stale'],
133
+ },
134
+ examples: ['dino box add "Inbox" --description "用于存放待整理的想法和资料" --format json --dry-run'],
135
+ },
136
+ action: runBoxAdd,
137
+ },
138
+ ],
139
+ };
140
+ export function registerBoxesCommand(program) {
141
+ registerCommandGroup(program, boxCommandGroup);
98
142
  }
@@ -17,5 +17,13 @@ export type AddBoxResult = {
17
17
  color: string;
18
18
  restored: boolean;
19
19
  };
20
+ export type InspectAddBoxResult = {
21
+ name: string;
22
+ description: string;
23
+ color: string;
24
+ restored: boolean;
25
+ existingId?: string;
26
+ };
20
27
  export declare function listBoxes(db: AbstractPowerSyncDatabase): Promise<BoxListItem[]>;
21
28
  export declare function addBox(db: AbstractPowerSyncDatabase, input: AddBoxInput): Promise<AddBoxResult>;
29
+ export declare function inspectAddBox(db: AbstractPowerSyncDatabase, input: AddBoxInput): Promise<InspectAddBoxResult>;