@dinoxx/dinox-cli 1.0.19 → 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.
- package/README.md +756 -472
- package/dist/auth/userInfo.d.ts +10 -1
- package/dist/auth/userInfo.js +66 -17
- package/dist/cli.js +0 -2
- package/dist/commands/auth/index.js +29 -14
- package/dist/commands/boxes/index.js +272 -52
- package/dist/commands/boxes/model.d.ts +34 -0
- package/dist/commands/boxes/model.js +78 -0
- package/dist/commands/boxes/operations.d.ts +86 -0
- package/dist/commands/boxes/operations.js +415 -0
- package/dist/commands/boxes/repo.d.ts +13 -6
- package/dist/commands/boxes/repo.js +272 -149
- package/dist/commands/boxes/store.d.ts +22 -0
- package/dist/commands/boxes/store.js +143 -0
- package/dist/commands/graph/index.js +2 -36
- package/dist/commands/mcp/noteContentTools.d.ts +222 -0
- package/dist/commands/mcp/noteContentTools.js +390 -0
- package/dist/commands/mcp/server.d.ts +3 -0
- package/dist/commands/mcp/server.js +45 -16
- package/dist/commands/mcp/tools.js +3 -1
- package/dist/commands/mcp/types.d.ts +6 -2
- package/dist/commands/meta/index.js +6 -22
- package/dist/commands/meta/repo.d.ts +0 -3
- package/dist/commands/meta/repo.js +1 -4
- package/dist/commands/notes/actions.d.ts +2 -0
- package/dist/commands/notes/actions.js +149 -55
- package/dist/commands/notes/bulk.d.ts +34 -0
- package/dist/commands/notes/bulk.js +204 -0
- package/dist/commands/notes/contentEngine.d.ts +95 -0
- package/dist/commands/notes/contentEngine.js +652 -0
- package/dist/commands/notes/contentPatch.d.ts +84 -0
- package/dist/commands/notes/contentPatch.js +337 -0
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +126 -0
- package/dist/commands/notes/descriptors.js +192 -40
- package/dist/commands/notes/export.d.ts +21 -0
- package/dist/commands/notes/export.js +224 -0
- package/dist/commands/notes/metadataMutation.d.ts +28 -0
- package/dist/commands/notes/metadataMutation.js +127 -0
- package/dist/commands/notes/repo.d.ts +20 -0
- package/dist/commands/notes/repo.js +135 -18
- package/dist/commands/notes/runtime.d.ts +3 -12
- package/dist/commands/notes/runtime.js +4 -30
- package/dist/commands/notes/searchSql.js +1 -1
- package/dist/commands/prompt/index.js +15 -45
- package/dist/commands/prompt/repo.js +2 -2
- package/dist/commands/runtime.d.ts +26 -0
- package/dist/commands/runtime.js +76 -0
- package/dist/commands/schema/renderSkills.js +20 -7
- package/dist/commands/schema/spec.d.ts +1 -2
- package/dist/commands/schema/spec.js +2 -3
- package/dist/commands/schema/types.d.ts +0 -13
- package/dist/commands/storage/index.js +30 -60
- package/dist/commands/storage/repo.d.ts +1 -1
- package/dist/commands/storage/repo.js +20 -1
- package/dist/commands/sync.js +4 -2
- package/dist/commands/tags/content.d.ts +32 -0
- package/dist/commands/tags/content.js +243 -0
- package/dist/commands/tags/index.js +285 -49
- package/dist/commands/tags/model.d.ts +38 -0
- package/dist/commands/tags/model.js +100 -0
- package/dist/commands/tags/operations.d.ts +105 -0
- package/dist/commands/tags/operations.js +438 -0
- package/dist/commands/tags/repo.d.ts +5 -1
- package/dist/commands/tags/repo.js +142 -218
- package/dist/commands/tags/store.d.ts +28 -0
- package/dist/commands/tags/store.js +166 -0
- package/dist/commands/todo/index.js +30 -61
- package/dist/commands/writeDryRun.d.ts +25 -0
- package/dist/commands/writeDryRun.js +31 -1
- package/dist/config/paths.d.ts +2 -1
- package/dist/config/paths.js +11 -1
- package/dist/config/resolve.js +9 -7
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +176 -58
- package/dist/powersync/runtime.d.ts +2 -1
- package/dist/powersync/runtime.js +15 -3
- package/dist/powersync/schema/content.d.ts +2 -0
- package/dist/powersync/schema/content.js +2 -0
- package/dist/powersync/schema/index.d.ts +2 -0
- package/dist/skills/check.d.ts +1 -1
- package/dist/skills/check.js +27 -2
- package/dist/utils/authToken.d.ts +2 -0
- package/dist/utils/authToken.js +7 -2
- package/dist/utils/noteContentEngine.d.ts +333 -0
- package/dist/utils/noteContentEngine.js +740 -0
- package/dist/utils/tiptapMarkdownSchema.js +31 -0
- package/package.json +4 -8
package/dist/auth/userInfo.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export type AuthIdentity = {
|
|
|
5
5
|
email: string | null;
|
|
6
6
|
syncedAt: string;
|
|
7
7
|
};
|
|
8
|
+
export declare function buildPendingAuthConfig(rawConfig: DinoxConfig, authorization: string): DinoxConfig;
|
|
9
|
+
export declare function mergeAuthIdentityIntoConfig(rawConfig: DinoxConfig, authorization: string, identity: AuthIdentity): DinoxConfig;
|
|
8
10
|
export declare function fetchUserInfoWithAuthorization(input: {
|
|
9
11
|
authorization: string;
|
|
10
12
|
config: ResolvedDinoxConfig;
|
|
@@ -12,4 +14,11 @@ export declare function fetchUserInfoWithAuthorization(input: {
|
|
|
12
14
|
signal?: AbortSignal;
|
|
13
15
|
}): Promise<AuthIdentity>;
|
|
14
16
|
export declare function refreshAndPersistAuthIdentity(rawConfig: DinoxConfig, resolvedConfig: ResolvedDinoxConfig): Promise<AuthIdentity>;
|
|
15
|
-
export declare function
|
|
17
|
+
export declare function hydrateResolvedAuthIdentity(resolvedConfig: ResolvedDinoxConfig, options?: {
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
config: ResolvedDinoxConfig;
|
|
21
|
+
identity: AuthIdentity | null;
|
|
22
|
+
hydrated: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function resolveAuthIdentity(resolvedConfig: ResolvedDinoxConfig): Promise<AuthIdentity>;
|
package/dist/auth/userInfo.js
CHANGED
|
@@ -3,6 +3,33 @@ import { DinoxError } from '../utils/errors.js';
|
|
|
3
3
|
import { createTimeoutSignal, mergeAbortSignals } from '../utils/abort.js';
|
|
4
4
|
import { toAuthorizationHeaderValue } from '../utils/authToken.js';
|
|
5
5
|
const AUTH_CODE_OK = '000000';
|
|
6
|
+
export function buildPendingAuthConfig(rawConfig, authorization) {
|
|
7
|
+
return {
|
|
8
|
+
...rawConfig,
|
|
9
|
+
auth: {
|
|
10
|
+
...(rawConfig.auth ?? {}),
|
|
11
|
+
authorization,
|
|
12
|
+
userId: undefined,
|
|
13
|
+
username: undefined,
|
|
14
|
+
email: undefined,
|
|
15
|
+
userInfoSyncedAt: undefined,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function mergeAuthIdentityIntoConfig(rawConfig, authorization, identity) {
|
|
20
|
+
const pendingConfig = buildPendingAuthConfig(rawConfig, authorization);
|
|
21
|
+
return {
|
|
22
|
+
...pendingConfig,
|
|
23
|
+
auth: {
|
|
24
|
+
...(pendingConfig.auth ?? {}),
|
|
25
|
+
authorization,
|
|
26
|
+
userId: identity.userId,
|
|
27
|
+
username: identity.username,
|
|
28
|
+
email: identity.email,
|
|
29
|
+
userInfoSyncedAt: identity.syncedAt,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
6
33
|
function normalizeOptionalString(value) {
|
|
7
34
|
if (typeof value !== 'string') {
|
|
8
35
|
return undefined;
|
|
@@ -105,30 +132,52 @@ export async function refreshAndPersistAuthIdentity(rawConfig, resolvedConfig) {
|
|
|
105
132
|
authorization,
|
|
106
133
|
config: resolvedConfig,
|
|
107
134
|
});
|
|
108
|
-
const nextConfig =
|
|
109
|
-
...rawConfig,
|
|
110
|
-
auth: {
|
|
111
|
-
...(rawConfig.auth ?? {}),
|
|
112
|
-
authorization: rawConfig.auth?.authorization ?? authorization,
|
|
113
|
-
userId: identity.userId,
|
|
114
|
-
username: identity.username,
|
|
115
|
-
email: identity.email,
|
|
116
|
-
userInfoSyncedAt: identity.syncedAt,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
135
|
+
const nextConfig = mergeAuthIdentityIntoConfig(rawConfig, authorization, identity);
|
|
119
136
|
await saveConfig(nextConfig);
|
|
120
137
|
return identity;
|
|
121
138
|
}
|
|
122
|
-
|
|
123
|
-
const savedUserId = normalizeOptionalString(
|
|
124
|
-
const syncedAt = normalizeOptionalString(
|
|
139
|
+
function authIdentityFromResolvedConfig(resolvedConfig) {
|
|
140
|
+
const savedUserId = normalizeOptionalString(resolvedConfig.auth.userId);
|
|
141
|
+
const syncedAt = normalizeOptionalString(resolvedConfig.auth.userInfoSyncedAt);
|
|
125
142
|
if (savedUserId) {
|
|
126
143
|
return {
|
|
127
144
|
userId: savedUserId,
|
|
128
|
-
username: normalizeOptionalNullableString(
|
|
129
|
-
email: normalizeOptionalNullableString(
|
|
145
|
+
username: normalizeOptionalNullableString(resolvedConfig.auth.username),
|
|
146
|
+
email: normalizeOptionalNullableString(resolvedConfig.auth.email),
|
|
130
147
|
syncedAt: syncedAt ?? new Date().toISOString(),
|
|
131
148
|
};
|
|
132
149
|
}
|
|
133
|
-
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
export async function hydrateResolvedAuthIdentity(resolvedConfig, options = {}) {
|
|
153
|
+
const existing = authIdentityFromResolvedConfig(resolvedConfig);
|
|
154
|
+
if (existing || !resolvedConfig.auth.authorization) {
|
|
155
|
+
return { config: resolvedConfig, identity: existing, hydrated: false };
|
|
156
|
+
}
|
|
157
|
+
const identity = await fetchUserInfoWithAuthorization({
|
|
158
|
+
authorization: resolvedConfig.auth.authorization,
|
|
159
|
+
config: resolvedConfig,
|
|
160
|
+
timeoutMs: options.timeoutMs,
|
|
161
|
+
});
|
|
162
|
+
return {
|
|
163
|
+
config: {
|
|
164
|
+
...resolvedConfig,
|
|
165
|
+
auth: {
|
|
166
|
+
...resolvedConfig.auth,
|
|
167
|
+
userId: identity.userId,
|
|
168
|
+
username: identity.username,
|
|
169
|
+
email: identity.email,
|
|
170
|
+
userInfoSyncedAt: identity.syncedAt,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
identity,
|
|
174
|
+
hydrated: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export async function resolveAuthIdentity(resolvedConfig) {
|
|
178
|
+
const identity = authIdentityFromResolvedConfig(resolvedConfig);
|
|
179
|
+
if (identity) {
|
|
180
|
+
return identity;
|
|
181
|
+
}
|
|
182
|
+
throw new DinoxError('Missing resolved userId for the current authorization. Run `dino auth login` first, or set DINOX_TOKEN and run an online command so the CLI can resolve the token identity.');
|
|
134
183
|
}
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,6 @@ import { registerConfigCommands } from './commands/config/index.js';
|
|
|
5
5
|
import { registerDaemonCommands } from './daemon/index.js';
|
|
6
6
|
import { registerGraphCommands } from './commands/graph/index.js';
|
|
7
7
|
import { registerInfoCommand } from './commands/info/index.js';
|
|
8
|
-
import { registerMCPCommands } from './commands/mcp/index.js';
|
|
9
8
|
import { registerMetaCommands } from './commands/meta/index.js';
|
|
10
9
|
import { registerNoteCommands } from './commands/notes/index.js';
|
|
11
10
|
import { registerPromptCommands } from './commands/prompt/index.js';
|
|
@@ -44,7 +43,6 @@ export function createCli() {
|
|
|
44
43
|
registerDaemonCommands(program);
|
|
45
44
|
registerGraphCommands(program);
|
|
46
45
|
registerInfoCommand(program);
|
|
47
|
-
registerMCPCommands(program);
|
|
48
46
|
registerMetaCommands(program);
|
|
49
47
|
registerSyncCommand(program);
|
|
50
48
|
registerUpdateCommand(program);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import {
|
|
3
|
-
import { getPowerSyncDbPath } from '../../config/paths.js';
|
|
2
|
+
import { buildPendingAuthConfig, fetchUserInfoWithAuthorization, hydrateResolvedAuthIdentity, mergeAuthIdentityIntoConfig, } from '../../auth/userInfo.js';
|
|
3
|
+
import { getLegacyPowerSyncDbPath, getPowerSyncDbPath } from '../../config/paths.js';
|
|
4
4
|
import { loadConfig, saveConfig } from '../../config/store.js';
|
|
5
5
|
import { resolveConfig } from '../../config/resolve.js';
|
|
6
6
|
import { fetchCredentialsWithAuthorization } from '../../powersync/connector.js';
|
|
@@ -8,7 +8,7 @@ import { connectPowerSync, getStatusSnapshot } from '../../powersync/runtime.js'
|
|
|
8
8
|
import { runSingleSyncPass } from '../../powersync/syncPass.js';
|
|
9
9
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
10
10
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
11
|
-
import { normalizeAuthToken } from '../../utils/authToken.js';
|
|
11
|
+
import { normalizeAuthToken, readEnvAuthToken } from '../../utils/authToken.js';
|
|
12
12
|
import { DinoxError } from '../../utils/errors.js';
|
|
13
13
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
14
14
|
import { redactAuthorization } from '../../utils/redact.js';
|
|
@@ -26,10 +26,13 @@ async function runAuthLogin(context) {
|
|
|
26
26
|
if (!rawToken) {
|
|
27
27
|
throw new DinoxError('Token is required');
|
|
28
28
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const envToken = readEnvAuthToken();
|
|
30
|
+
if (envToken && envToken !== rawToken) {
|
|
31
|
+
throw new DinoxError('DINOX_TOKEN is set and differs from the login token. Unset DINOX_TOKEN before running `dino auth login`, or use the same token.');
|
|
32
|
+
}
|
|
33
|
+
const previousConfig = await loadConfig();
|
|
34
|
+
const pendingConfig = buildPendingAuthConfig(previousConfig, rawToken);
|
|
35
|
+
const resolved = resolveConfig(pendingConfig);
|
|
33
36
|
const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? resolved.sync.timeoutMs;
|
|
34
37
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
35
38
|
await fetchCredentialsWithAuthorization({
|
|
@@ -38,19 +41,26 @@ async function runAuthLogin(context) {
|
|
|
38
41
|
credentialsUrl: resolved.powersync.tokenEndpoint,
|
|
39
42
|
timeoutMs,
|
|
40
43
|
});
|
|
41
|
-
const identity = await
|
|
42
|
-
|
|
44
|
+
const identity = await fetchUserInfoWithAuthorization({
|
|
45
|
+
authorization: rawToken,
|
|
43
46
|
config: resolved,
|
|
47
|
+
timeoutMs,
|
|
48
|
+
});
|
|
49
|
+
const verifiedConfig = mergeAuthIdentityIntoConfig(previousConfig, rawToken, identity);
|
|
50
|
+
const verifiedResolved = resolveConfig(verifiedConfig);
|
|
51
|
+
const { db, dbPath, stale } = await connectPowerSync({
|
|
52
|
+
config: verifiedResolved,
|
|
44
53
|
offline: false,
|
|
45
54
|
timeoutMs,
|
|
46
55
|
});
|
|
47
56
|
try {
|
|
48
57
|
await runSingleSyncPass(db, timeoutMs);
|
|
58
|
+
await saveConfig(verifiedConfig);
|
|
49
59
|
const status = getStatusSnapshot(db);
|
|
50
60
|
const result = {
|
|
51
61
|
ok: true,
|
|
52
62
|
verified: true,
|
|
53
|
-
endpoint:
|
|
63
|
+
endpoint: verifiedResolved.powersync.endpoint,
|
|
54
64
|
dbPath,
|
|
55
65
|
stale,
|
|
56
66
|
lastSyncedAt: status.lastSyncedAt,
|
|
@@ -78,6 +88,7 @@ async function runAuthLogin(context) {
|
|
|
78
88
|
async function runAuthLogout(context) {
|
|
79
89
|
const options = context.options;
|
|
80
90
|
const config = await loadConfig();
|
|
91
|
+
const currentUserId = config.auth?.userId;
|
|
81
92
|
if (config.auth) {
|
|
82
93
|
config.auth.authorization = undefined;
|
|
83
94
|
config.auth.userId = undefined;
|
|
@@ -87,7 +98,8 @@ async function runAuthLogout(context) {
|
|
|
87
98
|
}
|
|
88
99
|
await saveConfig(config);
|
|
89
100
|
if (options.clearLocalDb) {
|
|
90
|
-
await removeSqliteArtifacts(getPowerSyncDbPath());
|
|
101
|
+
await removeSqliteArtifacts(getPowerSyncDbPath(currentUserId));
|
|
102
|
+
await removeSqliteArtifacts(getLegacyPowerSyncDbPath());
|
|
91
103
|
}
|
|
92
104
|
if (getRequestedStructuredOutputFormat()) {
|
|
93
105
|
printSuccess({
|
|
@@ -99,11 +111,14 @@ async function runAuthLogout(context) {
|
|
|
99
111
|
}
|
|
100
112
|
async function runAuthStatus(context) {
|
|
101
113
|
const config = await loadConfig();
|
|
102
|
-
const
|
|
114
|
+
const baseResolved = resolveConfig(config);
|
|
103
115
|
const offline = Boolean(context.globals.offline);
|
|
104
116
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
105
|
-
const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ??
|
|
106
|
-
const
|
|
117
|
+
const timeoutMs = parseSyncTimeoutMs(context.globals.syncTimeout) ?? baseResolved.sync.timeoutMs;
|
|
118
|
+
const { config: resolved } = offline
|
|
119
|
+
? { config: baseResolved }
|
|
120
|
+
: await hydrateResolvedAuthIdentity(baseResolved, { timeoutMs });
|
|
121
|
+
const dbPath = getPowerSyncDbPath(resolved.auth.userId);
|
|
107
122
|
const base = {
|
|
108
123
|
loggedIn: Boolean(resolved.auth.authorization),
|
|
109
124
|
authorization: redactAuthorization(resolved.auth.authorization),
|
|
@@ -1,41 +1,39 @@
|
|
|
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
|
-
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
6
2
|
import { registerCommandGroup } from '../descriptor/register.js';
|
|
7
3
|
import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
|
|
8
|
-
import { buildBoxAddDryRunPreview } from '../writeDryRun.js';
|
|
4
|
+
import { buildBoxAddDryRunPreview, buildBoxMutationDryRunPreview } from '../writeDryRun.js';
|
|
9
5
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
10
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
11
7
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
12
|
-
import {
|
|
8
|
+
import { commandStale, printStaleCacheWarning, printUploadDisabledWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
13
9
|
import { addBox, inspectAddBox, listBoxes } from './repo.js';
|
|
10
|
+
import { getBoxStats, getBoxTree, inspectBoxCleanup, mergeBox, moveBox, renameBox, } from './operations.js';
|
|
14
11
|
const BOX_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
15
|
-
async function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
async function runBoxList(context) {
|
|
13
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
14
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
15
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
16
|
+
printSuccess(await listBoxes(runtime.db, { userId: identity.userId }));
|
|
17
|
+
});
|
|
21
18
|
}
|
|
22
|
-
async function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
await db.close().catch(() => undefined);
|
|
33
|
-
}
|
|
19
|
+
async function runBoxTree(context) {
|
|
20
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
21
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
22
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
23
|
+
printSuccess({
|
|
24
|
+
stale: commandStale(runtime),
|
|
25
|
+
tree: await getBoxTree(runtime.db, { userId: identity.userId }),
|
|
26
|
+
});
|
|
27
|
+
});
|
|
34
28
|
}
|
|
35
|
-
async function
|
|
36
|
-
await
|
|
37
|
-
await
|
|
38
|
-
|
|
29
|
+
async function runBoxStats(context) {
|
|
30
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
31
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
32
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
33
|
+
printSuccess({
|
|
34
|
+
stale: commandStale(runtime),
|
|
35
|
+
...(await getBoxStats(runtime.db, { userId: identity.userId })),
|
|
36
|
+
});
|
|
39
37
|
});
|
|
40
38
|
}
|
|
41
39
|
async function runBoxAdd(context) {
|
|
@@ -43,22 +41,22 @@ async function runBoxAdd(context) {
|
|
|
43
41
|
const optionName = typeof context.options.name === 'string' ? context.options.name : undefined;
|
|
44
42
|
const name = (nameArg ?? optionName ?? '').trim();
|
|
45
43
|
if (!name) {
|
|
46
|
-
throw new DinoxError('Box
|
|
44
|
+
throw new DinoxError('Box path is required. Use `dino box add <path>` or `dino box add --name <path>`.');
|
|
47
45
|
}
|
|
48
46
|
if (nameArg && optionName && nameArg.trim() !== optionName.trim()) {
|
|
49
|
-
throw new DinoxError('Conflicting box
|
|
47
|
+
throw new DinoxError('Conflicting box path values from positional argument and --name option.');
|
|
50
48
|
}
|
|
51
49
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
52
|
-
await
|
|
53
|
-
const identity = await resolveAuthIdentity(
|
|
54
|
-
await
|
|
50
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
51
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
52
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
55
53
|
const description = typeof context.options.description === 'string' ? context.options.description : undefined;
|
|
56
54
|
const color = typeof context.options.color === 'string' ? context.options.color : undefined;
|
|
57
55
|
if (Boolean(context.options.dryRun)) {
|
|
58
56
|
printDryRunPreview(buildBoxAddDryRunPreview({
|
|
59
|
-
config,
|
|
60
|
-
offline,
|
|
61
|
-
...(await inspectAddBox(db, {
|
|
57
|
+
config: runtime.config,
|
|
58
|
+
offline: runtime.offline,
|
|
59
|
+
...(await inspectAddBox(runtime.db, {
|
|
62
60
|
name,
|
|
63
61
|
userId: identity.userId,
|
|
64
62
|
description,
|
|
@@ -67,27 +65,131 @@ async function runBoxAdd(context) {
|
|
|
67
65
|
}));
|
|
68
66
|
return;
|
|
69
67
|
}
|
|
70
|
-
const result = await addBox(db, {
|
|
68
|
+
const result = await addBox(runtime.db, {
|
|
71
69
|
name,
|
|
72
70
|
userId: identity.userId,
|
|
73
71
|
description,
|
|
74
72
|
color,
|
|
75
73
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
if (!offline) {
|
|
80
|
-
await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
|
|
81
|
-
}
|
|
82
|
-
const payload = { ok: true, stale: offline ? true : stale, ...result };
|
|
74
|
+
printUploadDisabledWarning(runtime);
|
|
75
|
+
await waitForMutationUpload(runtime);
|
|
76
|
+
const payload = { ok: true, stale: commandStale(runtime), ...result };
|
|
83
77
|
if (structuredOutput) {
|
|
84
78
|
printSuccess(payload);
|
|
85
79
|
return;
|
|
86
80
|
}
|
|
87
81
|
printHumanReadableLine(result.id);
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
printStaleCacheWarning(runtime, 'warning: sync timed out; results may be stale');
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function withBoxWriteRuntime(context, run) {
|
|
86
|
+
return withCommandRuntime(context, async (runtime) => {
|
|
87
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
88
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
89
|
+
return run({ runtime, userId: identity.userId });
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function requireArg(value, message) {
|
|
93
|
+
const trimmed = value?.trim() ?? '';
|
|
94
|
+
if (!trimmed) {
|
|
95
|
+
throw new DinoxError(message);
|
|
96
|
+
}
|
|
97
|
+
return trimmed;
|
|
98
|
+
}
|
|
99
|
+
async function runBoxRename(context) {
|
|
100
|
+
const path = requireArg(context.args.path, 'Box path is required');
|
|
101
|
+
const newName = requireArg(context.args.newName, 'New box name is required');
|
|
102
|
+
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
103
|
+
const result = await renameBox(runtime.db, { path, newName, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
104
|
+
if (Boolean(context.options.dryRun)) {
|
|
105
|
+
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
106
|
+
config: runtime.config,
|
|
107
|
+
offline: runtime.offline,
|
|
108
|
+
command: 'dino box rename',
|
|
109
|
+
action: 'rename_box',
|
|
110
|
+
risk: 'write',
|
|
111
|
+
summary: `Rename zettel box "${result.fromPath}" to "${result.toPath}".`,
|
|
112
|
+
inputs: { path, new_name: newName },
|
|
113
|
+
changes: { updated_boxes: result.updatedBoxes },
|
|
114
|
+
}));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
printUploadDisabledWarning(runtime);
|
|
118
|
+
await waitForMutationUpload(runtime);
|
|
119
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function runBoxMove(context) {
|
|
123
|
+
const path = requireArg(context.args.path, 'Box path is required');
|
|
124
|
+
const to = typeof context.options.to === 'string' ? context.options.to.trim() : '';
|
|
125
|
+
if (!to) {
|
|
126
|
+
throw new DinoxError('--to is required');
|
|
127
|
+
}
|
|
128
|
+
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
129
|
+
const result = await moveBox(runtime.db, { path, to, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
130
|
+
if (Boolean(context.options.dryRun)) {
|
|
131
|
+
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
132
|
+
config: runtime.config,
|
|
133
|
+
offline: runtime.offline,
|
|
134
|
+
command: 'dino box move',
|
|
135
|
+
action: 'move_box',
|
|
136
|
+
risk: 'write',
|
|
137
|
+
summary: `Move zettel box "${result.fromPath}" to "${result.toPath}".`,
|
|
138
|
+
inputs: { path, to },
|
|
139
|
+
changes: { updated_boxes: result.updatedBoxes },
|
|
140
|
+
}));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
printUploadDisabledWarning(runtime);
|
|
144
|
+
await waitForMutationUpload(runtime);
|
|
145
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function runBoxMerge(context) {
|
|
149
|
+
const from = requireArg(context.args.from, 'Source box path is required');
|
|
150
|
+
const to = requireArg(context.args.to, 'Target box path is required');
|
|
151
|
+
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
152
|
+
const result = await mergeBox(runtime.db, { from, to, userId }, { dryRun: Boolean(context.options.dryRun) });
|
|
153
|
+
if (Boolean(context.options.dryRun)) {
|
|
154
|
+
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
155
|
+
config: runtime.config,
|
|
156
|
+
offline: runtime.offline,
|
|
157
|
+
command: 'dino box merge',
|
|
158
|
+
action: 'merge_box',
|
|
159
|
+
risk: 'high-risk-write',
|
|
160
|
+
summary: `Merge zettel box subtree "${result.fromPath}" into "${result.toPath}".`,
|
|
161
|
+
inputs: { from, to },
|
|
162
|
+
changes: {
|
|
163
|
+
soft_deleted_box_ids: result.softDeletedBoxIds,
|
|
164
|
+
updated_notes: result.updatedNotes,
|
|
165
|
+
},
|
|
166
|
+
}));
|
|
167
|
+
return;
|
|
90
168
|
}
|
|
169
|
+
printUploadDisabledWarning(runtime);
|
|
170
|
+
await waitForMutationUpload(runtime);
|
|
171
|
+
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async function runBoxCleanup(context) {
|
|
175
|
+
await withCommandRuntime(context, async (runtime) => {
|
|
176
|
+
const identity = await resolveAuthIdentity(runtime.config);
|
|
177
|
+
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
178
|
+
const result = await inspectBoxCleanup(runtime.db, { userId: identity.userId });
|
|
179
|
+
if (Boolean(context.options.dryRun)) {
|
|
180
|
+
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
181
|
+
config: runtime.config,
|
|
182
|
+
offline: runtime.offline,
|
|
183
|
+
command: 'dino box cleanup',
|
|
184
|
+
action: 'cleanup_boxes',
|
|
185
|
+
risk: 'write',
|
|
186
|
+
summary: `Inspect zettel box cleanup candidates (${result.issues.length} issues).`,
|
|
187
|
+
inputs: {},
|
|
188
|
+
targets: result.issues,
|
|
189
|
+
}));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
printSuccess({ stale: commandStale(runtime), ...result });
|
|
91
193
|
});
|
|
92
194
|
}
|
|
93
195
|
export const boxCommandGroup = {
|
|
@@ -105,7 +207,7 @@ export const boxCommandGroup = {
|
|
|
105
207
|
output: {
|
|
106
208
|
type: 'structured',
|
|
107
209
|
summary: 'Box list payload',
|
|
108
|
-
fields: ['id', 'name', 'description'],
|
|
210
|
+
fields: ['id', 'name', 'path', 'parentId', 'description', 'color'],
|
|
109
211
|
},
|
|
110
212
|
examples: ['dino box list --format json'],
|
|
111
213
|
},
|
|
@@ -113,10 +215,10 @@ export const boxCommandGroup = {
|
|
|
113
215
|
},
|
|
114
216
|
{
|
|
115
217
|
name: 'add',
|
|
116
|
-
description: 'Create a zettel box, restoring
|
|
117
|
-
arguments: [{ syntax: '[
|
|
218
|
+
description: 'Create a zettel box path, restoring deleted path nodes when possible',
|
|
219
|
+
arguments: [{ syntax: '[path]', description: 'Box path, for example Work/Inbox' }],
|
|
118
220
|
options: [
|
|
119
|
-
{ flags: '--name <string>', description: 'Box
|
|
221
|
+
{ flags: '--name <string>', description: 'Box path (alternative to positional path)' },
|
|
120
222
|
{ flags: '--description <string>', description: 'Box purpose/usage description' },
|
|
121
223
|
{ flags: '--color <string>', description: 'Box color' },
|
|
122
224
|
{ flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
@@ -129,12 +231,130 @@ export const boxCommandGroup = {
|
|
|
129
231
|
output: {
|
|
130
232
|
type: 'structured',
|
|
131
233
|
summary: 'Box creation acknowledgement',
|
|
132
|
-
fields: ['id', 'name', 'description', 'color', 'restored', 'stale'],
|
|
234
|
+
fields: ['id', 'name', 'path', 'parentId', 'description', 'color', 'restored', 'createdNodes', 'restoredNodes', 'stale'],
|
|
133
235
|
},
|
|
134
|
-
examples: ['dino box add "Inbox" --description "用于存放待整理的想法和资料" --format json --dry-run'],
|
|
236
|
+
examples: ['dino box add "Work/Inbox" --description "用于存放待整理的想法和资料" --format json --dry-run'],
|
|
135
237
|
},
|
|
136
238
|
action: runBoxAdd,
|
|
137
239
|
},
|
|
240
|
+
{
|
|
241
|
+
name: 'tree',
|
|
242
|
+
description: 'Show zettel boxes as a hierarchy tree',
|
|
243
|
+
schema: {
|
|
244
|
+
path: 'box.tree',
|
|
245
|
+
risk: 'read',
|
|
246
|
+
auth: 'required',
|
|
247
|
+
globalOptions: BOX_GLOBAL_OPTIONS,
|
|
248
|
+
output: {
|
|
249
|
+
type: 'structured',
|
|
250
|
+
summary: 'Nested zettel box tree payload',
|
|
251
|
+
fields: ['tree.id', 'tree.name', 'tree.path', 'tree.parentId', 'tree.children', 'stale'],
|
|
252
|
+
},
|
|
253
|
+
examples: ['dino box tree --format json'],
|
|
254
|
+
},
|
|
255
|
+
action: runBoxTree,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'stats',
|
|
259
|
+
description: 'Show zettel box note counts and empty-box status',
|
|
260
|
+
schema: {
|
|
261
|
+
path: 'box.stats',
|
|
262
|
+
risk: 'read',
|
|
263
|
+
auth: 'required',
|
|
264
|
+
globalOptions: BOX_GLOBAL_OPTIONS,
|
|
265
|
+
output: {
|
|
266
|
+
type: 'structured',
|
|
267
|
+
summary: 'Zettel box statistics payload',
|
|
268
|
+
fields: ['totalBoxes', 'emptyBoxes', 'boxes.id', 'boxes.path', 'boxes.directNoteCount', 'boxes.descendantNoteCount', 'boxes.latestNoteUpdatedAt', 'boxes.isEmpty', 'stale'],
|
|
269
|
+
},
|
|
270
|
+
examples: ['dino box stats --format json'],
|
|
271
|
+
},
|
|
272
|
+
action: runBoxStats,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'rename',
|
|
276
|
+
description: 'Rename a zettel box and cascade descendant paths',
|
|
277
|
+
arguments: [
|
|
278
|
+
{ syntax: '<path>', description: 'Existing box path' },
|
|
279
|
+
{ syntax: '<new-name>', description: 'New leaf name' },
|
|
280
|
+
],
|
|
281
|
+
options: [{ flags: '--dry-run', description: 'Preview the write without executing it' }],
|
|
282
|
+
schema: {
|
|
283
|
+
path: 'box.rename',
|
|
284
|
+
risk: 'write',
|
|
285
|
+
auth: 'required',
|
|
286
|
+
globalOptions: BOX_GLOBAL_OPTIONS,
|
|
287
|
+
output: {
|
|
288
|
+
type: 'structured',
|
|
289
|
+
summary: 'Zettel box rename result',
|
|
290
|
+
fields: ['id', 'fromPath', 'toPath', 'updatedBoxes', 'stale'],
|
|
291
|
+
},
|
|
292
|
+
examples: ['dino box rename "Work/Inbox" "Ideas" --format json --dry-run'],
|
|
293
|
+
},
|
|
294
|
+
action: runBoxRename,
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'move',
|
|
298
|
+
description: 'Move a zettel box under another parent and cascade descendant paths',
|
|
299
|
+
arguments: [{ syntax: '<path>', description: 'Existing box path' }],
|
|
300
|
+
options: [
|
|
301
|
+
{ flags: '--to <parent-path>', description: 'Target parent box path', required: true, type: 'string' },
|
|
302
|
+
{ flags: '--dry-run', description: 'Preview the write without executing it' },
|
|
303
|
+
],
|
|
304
|
+
schema: {
|
|
305
|
+
path: 'box.move',
|
|
306
|
+
risk: 'write',
|
|
307
|
+
auth: 'required',
|
|
308
|
+
globalOptions: BOX_GLOBAL_OPTIONS,
|
|
309
|
+
output: {
|
|
310
|
+
type: 'structured',
|
|
311
|
+
summary: 'Zettel box move result',
|
|
312
|
+
fields: ['id', 'fromPath', 'toPath', 'updatedBoxes', 'stale'],
|
|
313
|
+
},
|
|
314
|
+
examples: ['dino box move "Inbox" --to "Work" --format json --dry-run'],
|
|
315
|
+
},
|
|
316
|
+
action: runBoxMove,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: 'merge',
|
|
320
|
+
description: 'Merge a zettel box subtree into another box and soft-delete the source subtree',
|
|
321
|
+
arguments: [
|
|
322
|
+
{ syntax: '<from>', description: 'Source box path' },
|
|
323
|
+
{ syntax: '<to>', description: 'Target box path' },
|
|
324
|
+
],
|
|
325
|
+
options: [{ flags: '--dry-run', description: 'Preview the write without executing it' }],
|
|
326
|
+
schema: {
|
|
327
|
+
path: 'box.merge',
|
|
328
|
+
risk: 'high-risk-write',
|
|
329
|
+
auth: 'required',
|
|
330
|
+
globalOptions: BOX_GLOBAL_OPTIONS,
|
|
331
|
+
output: {
|
|
332
|
+
type: 'structured',
|
|
333
|
+
summary: 'Zettel box merge result',
|
|
334
|
+
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedBoxIds', 'updatedNotes', 'stale'],
|
|
335
|
+
},
|
|
336
|
+
examples: ['dino box merge "Work/Inbox" "Work/Archive" --format json --dry-run'],
|
|
337
|
+
},
|
|
338
|
+
action: runBoxMerge,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'cleanup',
|
|
342
|
+
description: 'Inspect zettel box cleanup candidates',
|
|
343
|
+
options: [{ flags: '--dry-run', description: 'Preview cleanup candidates without writing' }],
|
|
344
|
+
schema: {
|
|
345
|
+
path: 'box.cleanup',
|
|
346
|
+
risk: 'read',
|
|
347
|
+
auth: 'required',
|
|
348
|
+
globalOptions: BOX_GLOBAL_OPTIONS,
|
|
349
|
+
output: {
|
|
350
|
+
type: 'structured',
|
|
351
|
+
summary: 'Zettel box cleanup inspection payload',
|
|
352
|
+
fields: ['issues', 'emptyBoxes', 'duplicatePaths', 'orphanParents', 'deletedWithActiveChildren', 'stale'],
|
|
353
|
+
},
|
|
354
|
+
examples: ['dino box cleanup --format json --dry-run'],
|
|
355
|
+
},
|
|
356
|
+
action: runBoxCleanup,
|
|
357
|
+
},
|
|
138
358
|
],
|
|
139
359
|
};
|
|
140
360
|
export function registerBoxesCommand(program) {
|