@dinoxx/dinox-cli 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -3
- package/dist/cli.js +4 -0
- package/dist/commands/auth/index.js +1 -2
- package/dist/commands/boxes/index.js +37 -13
- package/dist/commands/boxes/operations.d.ts +3 -3
- package/dist/commands/boxes/operations.js +51 -28
- package/dist/commands/info/index.js +30 -5
- package/dist/commands/meta/repo.js +2 -1
- package/dist/commands/notes/actions.js +20 -37
- package/dist/commands/notes/bulk.d.ts +2 -2
- package/dist/commands/notes/bulk.js +3 -18
- package/dist/commands/notes/contentPatch.d.ts +5 -1
- package/dist/commands/notes/contentPatch.js +12 -1
- package/dist/commands/notes/contentPatchCommand.d.ts +3 -0
- package/dist/commands/notes/contentPatchCommand.js +29 -8
- package/dist/commands/notes/descriptors.js +19 -16
- package/dist/commands/notes/export.js +3 -0
- package/dist/commands/notes/repo.d.ts +9 -0
- package/dist/commands/notes/repo.js +65 -15
- package/dist/commands/notes/searchOutput.d.ts +2 -1
- package/dist/commands/notes/searchOutput.js +2 -1
- package/dist/commands/notes/searchSql.js +9 -3
- package/dist/commands/prompt/index.d.ts +1 -0
- package/dist/commands/prompt/index.js +26 -11
- package/dist/commands/savedViews/index.d.ts +7 -0
- package/dist/commands/savedViews/index.js +458 -0
- package/dist/commands/savedViews/model.d.ts +87 -0
- package/dist/commands/savedViews/model.js +306 -0
- package/dist/commands/savedViews/repo.d.ts +123 -0
- package/dist/commands/savedViews/repo.js +512 -0
- package/dist/commands/savedViews/semantics.d.ts +46 -0
- package/dist/commands/savedViews/semantics.js +738 -0
- package/dist/commands/schema/renderSkills.d.ts +2 -0
- package/dist/commands/schema/renderSkills.js +69 -10
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/skills/index.d.ts +54 -0
- package/dist/commands/skills/index.js +468 -0
- package/dist/commands/storage/index.js +2 -2
- package/dist/commands/sync.js +2 -1
- package/dist/commands/tags/index.js +30 -11
- package/dist/commands/tags/operations.d.ts +3 -3
- package/dist/commands/tags/operations.js +45 -20
- package/dist/commands/templates/model.d.ts +50 -0
- package/dist/commands/templates/model.js +239 -0
- package/dist/commands/todo/index.js +3 -3
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +46 -9
- package/dist/commands/writeConfirmation.d.ts +13 -0
- package/dist/commands/writeConfirmation.js +54 -0
- package/dist/config/resolve.js +5 -10
- package/dist/config/types.d.ts +3 -6
- package/dist/constants/links.d.ts +18 -1
- package/dist/constants/links.js +26 -1
- package/dist/daemon/client.d.ts +2 -2
- package/dist/daemon/client.js +106 -52
- package/dist/daemon/index.d.ts +5 -3
- package/dist/daemon/index.js +28 -5
- package/dist/daemon/lifecycle.d.ts +7 -2
- package/dist/daemon/lifecycle.js +53 -13
- package/dist/daemon/pid.d.ts +1 -1
- package/dist/daemon/pid.js +6 -3
- package/dist/dinox.js +9 -1
- package/dist/powersync/exclusiveRuntime.d.ts +1 -0
- package/dist/powersync/exclusiveRuntime.js +26 -8
- package/dist/powersync/runtime.d.ts +3 -2
- package/dist/powersync/runtime.js +19 -3
- package/dist/powersync/schema/content.d.ts +1 -78
- package/dist/powersync/schema/content.js +1 -78
- package/dist/powersync/schema/electronCanonical.d.ts +1561 -0
- package/dist/powersync/schema/electronCanonical.js +1154 -0
- package/dist/powersync/schema/index.d.ts +573 -129
- package/dist/powersync/schema/index.js +3 -32
- package/dist/powersync/schema/local.d.ts +1 -56
- package/dist/powersync/schema/local.js +1 -60
- package/dist/powersync/schema/note.d.ts +1 -35
- package/dist/powersync/schema/note.js +1 -35
- package/dist/powersync/schema/notesExtras.d.ts +1 -62
- package/dist/powersync/schema/notesExtras.js +1 -71
- package/dist/powersync/schema/projects.d.ts +1 -104
- package/dist/powersync/schema/projects.js +1 -104
- package/dist/powersync/schema/savedViews.d.ts +1 -0
- package/dist/powersync/schema/savedViews.js +1 -0
- package/dist/powersync/schema/storage.d.ts +1 -24
- package/dist/powersync/schema/storage.js +1 -24
- package/dist/powersync/schema/tags.d.ts +1 -37
- package/dist/powersync/schema/tags.js +1 -37
- package/dist/powersync/syncGate.d.ts +20 -2
- package/dist/powersync/uploader.d.ts +16 -2
- package/dist/powersync/uploader.js +152 -158
- package/dist/skills/check.d.ts +1 -0
- package/dist/skills/check.js +538 -70
- package/dist/skills/retired.d.ts +60 -0
- package/dist/skills/retired.js +58 -0
- package/dist/utils/deadline.d.ts +8 -0
- package/dist/utils/deadline.js +31 -0
- package/dist/utils/dryRun.d.ts +20 -1
- package/dist/utils/dryRun.js +22 -3
- package/dist/utils/errors.d.ts +2 -0
- package/dist/utils/errors.js +2 -0
- package/dist/utils/processExit.d.ts +1 -0
- package/dist/utils/processExit.js +33 -0
- package/dist/utils/updateNotice.d.ts +1 -0
- package/dist/utils/updateNotice.js +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -341,9 +341,11 @@ dino note get <note-id>
|
|
|
341
341
|
#### 只拿轻量上下文(适合 AI / 脚本)
|
|
342
342
|
|
|
343
343
|
```bash
|
|
344
|
-
dino note get <note-id> --
|
|
344
|
+
dino note get <note-id> --format json
|
|
345
345
|
```
|
|
346
346
|
|
|
347
|
+
`note get` 在 2.0 中始终是轻量读取;`--context-only` 仅保留为兼容参数,不会改变输出。
|
|
348
|
+
|
|
347
349
|
#### 预览正文前几行
|
|
348
350
|
|
|
349
351
|
```bash
|
|
@@ -450,11 +452,14 @@ dino note bulk --days 30 --boxes "Work/Inbox" --box-replace "Archive/Review" --e
|
|
|
450
452
|
#### `note patch`:结构化编辑正文
|
|
451
453
|
|
|
452
454
|
```bash
|
|
455
|
+
dino note patch <note-id> --append-section "复盘" --content @./section.md --format json --dry-run
|
|
453
456
|
dino note content-read <note-id> --format json
|
|
454
|
-
dino note patch <note-id> --append-section "复盘" --content @./section.md --read-token
|
|
457
|
+
dino note patch <note-id> --append-section "复盘" --content @./section.md --read-token @/tmp/dinox-read-token-<random> --format json
|
|
455
458
|
```
|
|
456
459
|
|
|
457
|
-
`note patch`
|
|
460
|
+
`note patch` 只用于正文结构化编辑。先 dry-run,再在真实写入前执行 `content-read` 并传回 `readToken`。默认 `content-read` 只返回 block 类型、范围、hash 和字符数,不返回 block 文本、预览或 attributes,同时返回 `readTokenScope: "structural-metadata"`。只有用户授权全文读取时才添加 `--include-content`;此时返回 `readTokenScope: "full-content"`。替换媒体、表格、容器、未知节点等受保护 block 时,必须使用已审阅全文后取得的 `full-content` token,并显式添加 `--allow-protected-replace`;默认 scope 会被拒绝。
|
|
461
|
+
|
|
462
|
+
token 短期有效(当前为 30 分钟,以 `readTokenExpiresAt` 为准),绑定生成它时当前账号对应的本地 PowerSync 数据库作用域,不能在切换账号或本地数据库后复用。token 也是单次使用的,任何真实 patch 尝试都会先消费它,失败后重试必须重新执行 `content-read`。Agent 应把 token 写入唯一、仅 owner 可读写(mode `0600`)的临时文件,通过 `--read-token @<file>` 传入,并在这一次真实 patch 尝试后立即删除;不要把 token 放进命令参数、日志、笔记或可复用文件。
|
|
458
463
|
|
|
459
464
|
#### 收藏状态
|
|
460
465
|
|
|
@@ -567,6 +572,19 @@ dino tag add --name "工作/项目A" --emoji "🧠"
|
|
|
567
572
|
- 支持层级标签,如 `工作/项目A`
|
|
568
573
|
- 已删除但同名的标签,CLI 会尽量做恢复而不是重复新建
|
|
569
574
|
|
|
575
|
+
#### 查看、调整和合并标签层级
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
dino tag tree --format json
|
|
579
|
+
dino tag stats --format json
|
|
580
|
+
dino tag rename "工作/旧项目" "新项目" --format json --dry-run
|
|
581
|
+
dino tag move "工作/新项目" --to "归档" --format json --dry-run
|
|
582
|
+
dino tag merge "重复标签" "规范标签" --format json --dry-run
|
|
583
|
+
dino tag merge "重复标签" "规范标签" --expected-count <affected-count> --confirm --format json
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`tag merge` 会更新笔记引用并软删除来源子树。真实写入必须使用新鲜 dry-run 返回的 `data.changes.affected_count`,同时提供 `--expected-count` 和 `--confirm`。
|
|
587
|
+
|
|
570
588
|
### 5.8 管理卡片盒
|
|
571
589
|
|
|
572
590
|
#### 查看全部卡片盒
|
|
@@ -587,6 +605,19 @@ dino box add --name "Inbox" --description "用于存放待整理的想法和资
|
|
|
587
605
|
- `--description` 很值得写
|
|
588
606
|
- 这段说明会帮助 AI 和你自己更准确地理解这个卡片盒的用途
|
|
589
607
|
|
|
608
|
+
#### 查看、调整和合并卡片盒层级
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
dino box tree --format json
|
|
612
|
+
dino box stats --format json
|
|
613
|
+
dino box rename "工作/Inbox" "待整理" --format json --dry-run
|
|
614
|
+
dino box move "工作/待整理" --to "归档" --format json --dry-run
|
|
615
|
+
dino box merge "工作/重复" "工作/规范" --format json --dry-run
|
|
616
|
+
dino box merge "工作/重复" "工作/规范" --expected-count <affected-count> --confirm --format json
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
`box merge` 会更新笔记归属并软删除来源子树,同样要求新鲜 dry-run、`--expected-count` 和 `--confirm`。
|
|
620
|
+
|
|
590
621
|
### 5.9 管理 Prompt
|
|
591
622
|
|
|
592
623
|
#### 查看全部 Prompt
|
|
@@ -599,6 +630,7 @@ dino prompt list
|
|
|
599
630
|
|
|
600
631
|
```bash
|
|
601
632
|
dino prompt add --name "周报助手" --prompt "请基于本周笔记输出一份简洁周报"
|
|
633
|
+
dino prompt add --name "周报助手" --prompt @./weekly-prompt.txt
|
|
602
634
|
```
|
|
603
635
|
|
|
604
636
|
### 5.10 查看版本与配置
|
|
@@ -933,6 +965,15 @@ dino todo search --status uncompleted --format json
|
|
|
933
965
|
|
|
934
966
|
`--format json` 会返回稳定的结构化 envelope,适合被脚本、自动化任务和 AI Agent 解析。
|
|
935
967
|
|
|
968
|
+
2.0 对几个读命令统一了 freshness envelope,升级脚本时需要迁移:
|
|
969
|
+
|
|
970
|
+
- `dino note get` 只返回轻量 `data.note`,不再默认暴露正文。
|
|
971
|
+
- `dino note detail` 始终返回 `data.stale`、`data.count` 和 `data.notes`,即使只查询一条笔记。
|
|
972
|
+
- `dino prompt list` 从 `data.prompts` 读取,`dino box list` 从 `data.boxes` 读取;两者同时返回 `data.stale` 和 `data.count`。
|
|
973
|
+
- 所有 structured dry-run 都会返回 `data.stale` 和 `data.sync.gate`,用于判断预览是否建立在新鲜数据上。
|
|
974
|
+
|
|
975
|
+
Agent 调用在线命令时,只要 schema 提供 `--sync-timeout`,建议传入有界预算(例如 `20000`),并把宿主工具超时设为高 5–10 秒。宿主先超时但没有收到 structured envelope 时,结果是未知,不要直接重试写入;先检查新鲜度或写入后置条件。
|
|
976
|
+
|
|
936
977
|
### 7.2 先读取命令 schema
|
|
937
978
|
|
|
938
979
|
不确定某个命令支持哪些参数、输出哪些字段时,先读取 schema:
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,8 @@ import { registerMetaCommands } from './commands/meta/index.js';
|
|
|
10
10
|
import { registerNoteCommands } from './commands/notes/index.js';
|
|
11
11
|
import { registerPromptCommands } from './commands/prompt/index.js';
|
|
12
12
|
import { registerSchemaCommands } from './commands/schema/index.js';
|
|
13
|
+
import { registerSavedViewCommands } from './commands/savedViews/index.js';
|
|
14
|
+
import { registerSkillsCommands } from './commands/skills/index.js';
|
|
13
15
|
import { registerSyncCommand } from './commands/sync.js';
|
|
14
16
|
import { registerStorageCommands } from './commands/storage/index.js';
|
|
15
17
|
import { registerUpdateCommand } from './commands/update.js';
|
|
@@ -82,7 +84,9 @@ export function createCli() {
|
|
|
82
84
|
registerSyncCommand(program);
|
|
83
85
|
registerUpdateCommand(program);
|
|
84
86
|
registerPromptCommands(program);
|
|
87
|
+
registerSavedViewCommands(program);
|
|
85
88
|
registerSchemaCommands(program);
|
|
89
|
+
registerSkillsCommands(program);
|
|
86
90
|
registerStorageCommands(program);
|
|
87
91
|
registerTagsCommand(program);
|
|
88
92
|
registerTodoCommands(program);
|
|
@@ -430,8 +430,7 @@ async function runAuthStatus(context) {
|
|
|
430
430
|
endpoint: resolved.powersync.endpoint ?? null,
|
|
431
431
|
tokenEndpoint: resolved.powersync.tokenEndpoint ?? null,
|
|
432
432
|
uploadBaseUrl: resolved.powersync.uploadBaseUrl ?? null,
|
|
433
|
-
|
|
434
|
-
uploadV2Path: resolved.powersync.uploadV2Path,
|
|
433
|
+
uploadPath: resolved.powersync.uploadPath,
|
|
435
434
|
},
|
|
436
435
|
dbPath,
|
|
437
436
|
};
|
|
@@ -6,6 +6,7 @@ import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
|
6
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
7
7
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
8
8
|
import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
9
|
+
import { assertWriteConfirmationProvided, parseExpectedCount } from '../writeConfirmation.js';
|
|
9
10
|
import { addBox, inspectAddBox, listBoxes } from './repo.js';
|
|
10
11
|
import { getBoxStats, getBoxTree, inspectBoxCleanup, mergeBox, moveBox, renameBox, } from './operations.js';
|
|
11
12
|
const BOX_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
@@ -13,7 +14,12 @@ async function runBoxList(context) {
|
|
|
13
14
|
await withCommandRuntime(context, async (runtime) => {
|
|
14
15
|
const identity = await resolveAuthIdentity(runtime.config);
|
|
15
16
|
await syncBeforeCommand(runtime, { syncTokenIndex: true });
|
|
16
|
-
|
|
17
|
+
const boxes = await listBoxes(runtime.db, { userId: identity.userId });
|
|
18
|
+
printSuccess({
|
|
19
|
+
stale: commandStale(runtime),
|
|
20
|
+
count: boxes.length,
|
|
21
|
+
boxes,
|
|
22
|
+
});
|
|
17
23
|
});
|
|
18
24
|
}
|
|
19
25
|
async function runBoxTree(context) {
|
|
@@ -62,7 +68,7 @@ async function runBoxAdd(context) {
|
|
|
62
68
|
description,
|
|
63
69
|
color,
|
|
64
70
|
})),
|
|
65
|
-
}));
|
|
71
|
+
}), runtime);
|
|
66
72
|
return;
|
|
67
73
|
}
|
|
68
74
|
const result = await addBox(runtime.db, {
|
|
@@ -110,7 +116,7 @@ async function runBoxRename(context) {
|
|
|
110
116
|
summary: `Rename zettel box "${result.fromPath}" to "${result.toPath}".`,
|
|
111
117
|
inputs: { path, new_name: newName },
|
|
112
118
|
changes: { updated_boxes: result.updatedBoxes },
|
|
113
|
-
}));
|
|
119
|
+
}), runtime);
|
|
114
120
|
return;
|
|
115
121
|
}
|
|
116
122
|
await waitForMutationUpload(runtime);
|
|
@@ -135,7 +141,7 @@ async function runBoxMove(context) {
|
|
|
135
141
|
summary: `Move zettel box "${result.fromPath}" to "${result.toPath}".`,
|
|
136
142
|
inputs: { path, to },
|
|
137
143
|
changes: { updated_boxes: result.updatedBoxes },
|
|
138
|
-
}));
|
|
144
|
+
}), runtime);
|
|
139
145
|
return;
|
|
140
146
|
}
|
|
141
147
|
await waitForMutationUpload(runtime);
|
|
@@ -145,9 +151,17 @@ async function runBoxMove(context) {
|
|
|
145
151
|
async function runBoxMerge(context) {
|
|
146
152
|
const from = requireArg(context.args.from, 'Source box path is required');
|
|
147
153
|
const to = requireArg(context.args.to, 'Target box path is required');
|
|
154
|
+
const dryRun = Boolean(context.options.dryRun);
|
|
155
|
+
const expectedCount = parseExpectedCount(context.options.expectedCount);
|
|
156
|
+
const writeOptions = {
|
|
157
|
+
dryRun,
|
|
158
|
+
confirm: Boolean(context.options.confirm),
|
|
159
|
+
expectedCount,
|
|
160
|
+
};
|
|
161
|
+
assertWriteConfirmationProvided('Box merge', writeOptions);
|
|
148
162
|
await withBoxWriteRuntime(context, async ({ runtime, userId }) => {
|
|
149
|
-
const result = await mergeBox(runtime.db, { from, to, userId },
|
|
150
|
-
if (
|
|
163
|
+
const result = await mergeBox(runtime.db, { from, to, userId }, writeOptions);
|
|
164
|
+
if (dryRun) {
|
|
151
165
|
printDryRunPreview(buildBoxMutationDryRunPreview({
|
|
152
166
|
config: runtime.config,
|
|
153
167
|
offline: runtime.offline,
|
|
@@ -155,12 +169,15 @@ async function runBoxMerge(context) {
|
|
|
155
169
|
action: 'merge_box',
|
|
156
170
|
risk: 'high-risk-write',
|
|
157
171
|
summary: `Merge zettel box subtree "${result.fromPath}" into "${result.toPath}".`,
|
|
158
|
-
inputs: { from, to },
|
|
172
|
+
inputs: { from, to, expected_count: expectedCount },
|
|
159
173
|
changes: {
|
|
174
|
+
affected_count: result.affectedCount,
|
|
175
|
+
soft_deleted_box_count: result.softDeletedBoxIds.length,
|
|
176
|
+
updated_note_count: result.updatedNotes.length,
|
|
160
177
|
soft_deleted_box_ids: result.softDeletedBoxIds,
|
|
161
178
|
updated_notes: result.updatedNotes,
|
|
162
179
|
},
|
|
163
|
-
}));
|
|
180
|
+
}), runtime);
|
|
164
181
|
return;
|
|
165
182
|
}
|
|
166
183
|
await waitForMutationUpload(runtime);
|
|
@@ -182,7 +199,7 @@ async function runBoxCleanup(context) {
|
|
|
182
199
|
summary: `Inspect zettel box cleanup candidates (${result.issues.length} issues).`,
|
|
183
200
|
inputs: {},
|
|
184
201
|
targets: result.issues,
|
|
185
|
-
}));
|
|
202
|
+
}), runtime);
|
|
186
203
|
return;
|
|
187
204
|
}
|
|
188
205
|
printSuccess({ stale: commandStale(runtime), ...result });
|
|
@@ -203,7 +220,7 @@ export const boxCommandGroup = {
|
|
|
203
220
|
output: {
|
|
204
221
|
type: 'structured',
|
|
205
222
|
summary: 'Box list payload',
|
|
206
|
-
fields: ['id', 'name', 'path', 'parentId', 'description', 'color'],
|
|
223
|
+
fields: ['stale', 'count', 'boxes.id', 'boxes.name', 'boxes.path', 'boxes.parentId', 'boxes.description', 'boxes.color'],
|
|
207
224
|
},
|
|
208
225
|
examples: ['dino box list --format json'],
|
|
209
226
|
},
|
|
@@ -318,7 +335,11 @@ export const boxCommandGroup = {
|
|
|
318
335
|
{ syntax: '<from>', description: 'Source box path' },
|
|
319
336
|
{ syntax: '<to>', description: 'Target box path' },
|
|
320
337
|
],
|
|
321
|
-
options: [
|
|
338
|
+
options: [
|
|
339
|
+
{ flags: '--expected-count <n>', description: 'Required for real writes; must equal source boxes plus notes affected', type: 'number' },
|
|
340
|
+
{ flags: '--confirm', description: 'Required for real writes after reviewing a dry run', type: 'boolean' },
|
|
341
|
+
{ flags: '--dry-run', description: 'Preview the write without executing it', type: 'boolean' },
|
|
342
|
+
],
|
|
322
343
|
schema: {
|
|
323
344
|
path: 'box.merge',
|
|
324
345
|
risk: 'high-risk-write',
|
|
@@ -327,9 +348,12 @@ export const boxCommandGroup = {
|
|
|
327
348
|
output: {
|
|
328
349
|
type: 'structured',
|
|
329
350
|
summary: 'Zettel box merge result',
|
|
330
|
-
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedBoxIds', 'updatedNotes', 'stale'],
|
|
351
|
+
fields: ['fromId', 'toId', 'fromPath', 'toPath', 'softDeletedBoxIds', 'updatedNotes', 'affectedCount', 'stale'],
|
|
331
352
|
},
|
|
332
|
-
examples: [
|
|
353
|
+
examples: [
|
|
354
|
+
'dino box merge "Work/Inbox" "Work/Archive" --format json --dry-run',
|
|
355
|
+
'dino box merge "Work/Inbox" "Work/Archive" --expected-count 6 --confirm --format json',
|
|
356
|
+
],
|
|
333
357
|
},
|
|
334
358
|
action: runBoxMerge,
|
|
335
359
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AbstractPowerSyncDatabase } from '@powersync/common';
|
|
2
|
+
import { type ConfirmedWriteOptions } from '../writeConfirmation.js';
|
|
2
3
|
import type { BoxListItem } from './model.js';
|
|
3
4
|
export type BoxTreeNode = BoxListItem & {
|
|
4
5
|
children: BoxTreeNode[];
|
|
@@ -71,6 +72,7 @@ export type BoxMergeResult = {
|
|
|
71
72
|
before: string[];
|
|
72
73
|
after: string[];
|
|
73
74
|
}>;
|
|
75
|
+
affectedCount: number;
|
|
74
76
|
};
|
|
75
77
|
export declare function getBoxTree(db: AbstractPowerSyncDatabase, input?: BoxReadInput): Promise<BoxTreeNode[]>;
|
|
76
78
|
export declare function getBoxStats(db: AbstractPowerSyncDatabase, input?: BoxReadInput): Promise<BoxStatsResult>;
|
|
@@ -81,6 +83,4 @@ export declare function renameBox(db: AbstractPowerSyncDatabase, input: BoxRenam
|
|
|
81
83
|
export declare function moveBox(db: AbstractPowerSyncDatabase, input: BoxMoveInput, options?: {
|
|
82
84
|
dryRun?: boolean;
|
|
83
85
|
}): Promise<BoxRenameMoveResult>;
|
|
84
|
-
export declare function mergeBox(db: AbstractPowerSyncDatabase, input: BoxMergeInput, options?:
|
|
85
|
-
dryRun?: boolean;
|
|
86
|
-
}): Promise<BoxMergeResult>;
|
|
86
|
+
export declare function mergeBox(db: AbstractPowerSyncDatabase, input: BoxMergeInput, options?: ConfirmedWriteOptions): Promise<BoxMergeResult>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { withPowerSyncWriteTransaction } from '../../powersync/runtime.js';
|
|
2
2
|
import { DinoxError } from '../../utils/errors.js';
|
|
3
3
|
import { nowIsoUtc } from '../../utils/time.js';
|
|
4
|
+
import { assertConfirmedWrite, assertWriteConfirmationProvided, } from '../writeConfirmation.js';
|
|
4
5
|
import { MAX_BOX_NAME_LENGTH, REQUIRED_NOTE_BOX_COLUMNS, assertBoxPathLimits, assertTextLimits, canonicalizeBoxPath, getLeafName, normalizeBoxLookupKey, parseStoredBoxIds, trimString, } from './model.js';
|
|
5
6
|
import { activeBoxNodes, activeNodeByPath, assertCanWriteBox, assertRequiredColumns, assertWritableBoxNodes, descendantsOf, ensureNoPathConflict, getTableColumns, isActiveBoxNode, isDescendantBoxPath, loadAllBoxNodes, requireActivePath, toBoxListItem, } from './store.js';
|
|
6
7
|
function buildSubtreeChanges(args) {
|
|
@@ -369,7 +370,7 @@ function sameIds(left, right) {
|
|
|
369
370
|
}
|
|
370
371
|
return left.every((value, index) => value === right[index]);
|
|
371
372
|
}
|
|
372
|
-
|
|
373
|
+
async function buildBoxMergePlan(db, input) {
|
|
373
374
|
const parsed = parseMergeInput(input);
|
|
374
375
|
const boxColumns = await getTableColumns(db, 'c_zettel_box');
|
|
375
376
|
const noteColumns = await getTableColumns(db, 'c_note');
|
|
@@ -393,33 +394,55 @@ export async function mergeBox(db, input, options = {}) {
|
|
|
393
394
|
})
|
|
394
395
|
.filter((row) => row.id && !sameIds(row.before, row.after));
|
|
395
396
|
const softDeletedBoxIds = sourceNodes.map((node) => node.id);
|
|
396
|
-
|
|
397
|
-
await withPowerSyncWriteTransaction(db, async (tx) => {
|
|
398
|
-
const timestamp = nowIsoUtc();
|
|
399
|
-
for (const note of updatedNotes) {
|
|
400
|
-
await tx.execute(`
|
|
401
|
-
UPDATE c_note
|
|
402
|
-
SET zettel_boxes = ?, updated_at = ?, version = COALESCE(version, 0) + 1
|
|
403
|
-
WHERE id = ?
|
|
404
|
-
AND TRIM(COALESCE(user_no, '')) = ?
|
|
405
|
-
`, [JSON.stringify(note.after), timestamp, note.id, parsed.userId]);
|
|
406
|
-
}
|
|
407
|
-
for (const boxId of softDeletedBoxIds) {
|
|
408
|
-
await tx.execute(`
|
|
409
|
-
UPDATE c_zettel_box
|
|
410
|
-
SET is_del = 1, updated_at = ?
|
|
411
|
-
WHERE id = ?
|
|
412
|
-
AND TRIM(COALESCE(user_no, '')) = ?
|
|
413
|
-
`, [timestamp, boxId, parsed.userId]);
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
}
|
|
397
|
+
const affectedCount = softDeletedBoxIds.length + updatedNotes.length;
|
|
417
398
|
return {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
399
|
+
result: {
|
|
400
|
+
fromId: source.id,
|
|
401
|
+
toId: target.id,
|
|
402
|
+
fromPath: source.path,
|
|
403
|
+
toPath: target.path,
|
|
404
|
+
softDeletedBoxIds,
|
|
405
|
+
updatedNotes,
|
|
406
|
+
affectedCount,
|
|
407
|
+
},
|
|
408
|
+
userId: parsed.userId,
|
|
424
409
|
};
|
|
425
410
|
}
|
|
411
|
+
function assertBoxMergeCount(plan, options) {
|
|
412
|
+
assertConfirmedWrite({
|
|
413
|
+
operation: 'Box merge',
|
|
414
|
+
schemaPath: 'box.merge',
|
|
415
|
+
actualCount: plan.result.affectedCount,
|
|
416
|
+
options,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
export async function mergeBox(db, input, options = {}) {
|
|
420
|
+
if (options.dryRun) {
|
|
421
|
+
const plan = await buildBoxMergePlan(db, input);
|
|
422
|
+
assertBoxMergeCount(plan, options);
|
|
423
|
+
return plan.result;
|
|
424
|
+
}
|
|
425
|
+
assertWriteConfirmationProvided('Box merge', options);
|
|
426
|
+
return withPowerSyncWriteTransaction(db, async (tx) => {
|
|
427
|
+
const plan = await buildBoxMergePlan(tx, input);
|
|
428
|
+
assertBoxMergeCount(plan, options);
|
|
429
|
+
const timestamp = nowIsoUtc();
|
|
430
|
+
for (const note of plan.result.updatedNotes) {
|
|
431
|
+
await tx.execute(`
|
|
432
|
+
UPDATE c_note
|
|
433
|
+
SET zettel_boxes = ?, updated_at = ?, version = COALESCE(version, 0) + 1
|
|
434
|
+
WHERE id = ?
|
|
435
|
+
AND TRIM(COALESCE(user_no, '')) = ?
|
|
436
|
+
`, [JSON.stringify(note.after), timestamp, note.id, plan.userId]);
|
|
437
|
+
}
|
|
438
|
+
for (const boxId of plan.result.softDeletedBoxIds) {
|
|
439
|
+
await tx.execute(`
|
|
440
|
+
UPDATE c_zettel_box
|
|
441
|
+
SET is_del = 1, updated_at = ?
|
|
442
|
+
WHERE id = ?
|
|
443
|
+
AND TRIM(COALESCE(user_no, '')) = ?
|
|
444
|
+
`, [timestamp, boxId, plan.userId]);
|
|
445
|
+
}
|
|
446
|
+
return plan.result;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { registerStandaloneCommand } from '../descriptor/register.js';
|
|
2
2
|
import { FORMAT_OPTIONS } from '../schema/globalOptions.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getSkillsReleaseMetadata } from '../../constants/links.js';
|
|
4
|
+
import { RETIRED_SKILLS, RETIRED_SKILLS_CLEANUP, formatRetiredSkillsSummary, } from '../../skills/retired.js';
|
|
4
5
|
import { clearPendingStructuredNoticeProvider, getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, setPendingStructuredNoticeProvider, } from '../../utils/output.js';
|
|
5
6
|
import { getUpdateNotice } from '../../utils/updateNotice.js';
|
|
6
7
|
import { getPackageVersion } from '../../utils/version.js';
|
|
7
8
|
export const infoCommandDescriptor = {
|
|
8
9
|
name: 'info',
|
|
9
|
-
description: 'Show CLI version and
|
|
10
|
+
description: 'Show CLI version and its matching standalone skills release',
|
|
10
11
|
schema: {
|
|
11
12
|
path: 'info',
|
|
12
13
|
risk: 'read',
|
|
@@ -14,17 +15,36 @@ export const infoCommandDescriptor = {
|
|
|
14
15
|
globalOptions: FORMAT_OPTIONS,
|
|
15
16
|
output: {
|
|
16
17
|
type: 'structured',
|
|
17
|
-
summary: 'CLI version metadata',
|
|
18
|
-
fields: [
|
|
18
|
+
summary: 'CLI version and matching standalone skills release metadata',
|
|
19
|
+
fields: [
|
|
20
|
+
'name',
|
|
21
|
+
'version',
|
|
22
|
+
'skills_repo',
|
|
23
|
+
'skills_version',
|
|
24
|
+
'skills_tag',
|
|
25
|
+
'skills_release',
|
|
26
|
+
'skills_installer',
|
|
27
|
+
'skills_install_command',
|
|
28
|
+
'retired_skills',
|
|
29
|
+
'retired_skills_cleanup',
|
|
30
|
+
],
|
|
19
31
|
},
|
|
20
32
|
examples: ['dino info', 'dino info --format json'],
|
|
21
33
|
},
|
|
22
34
|
action: async (_context) => {
|
|
23
35
|
const version = getPackageVersion();
|
|
36
|
+
const skills = getSkillsReleaseMetadata(version);
|
|
24
37
|
const payload = {
|
|
25
38
|
name: 'dino',
|
|
26
39
|
version,
|
|
27
|
-
skills_repo:
|
|
40
|
+
skills_repo: skills.repository,
|
|
41
|
+
skills_version: skills.version,
|
|
42
|
+
skills_tag: skills.tag,
|
|
43
|
+
skills_release: skills.release,
|
|
44
|
+
skills_installer: skills.installer,
|
|
45
|
+
skills_install_command: skills.installCommand,
|
|
46
|
+
retired_skills: RETIRED_SKILLS,
|
|
47
|
+
retired_skills_cleanup: RETIRED_SKILLS_CLEANUP,
|
|
28
48
|
};
|
|
29
49
|
if (getRequestedStructuredOutputFormat()) {
|
|
30
50
|
const updateNotice = await getUpdateNotice(version);
|
|
@@ -43,6 +63,11 @@ export const infoCommandDescriptor = {
|
|
|
43
63
|
}
|
|
44
64
|
printHumanReadableLine(payload.version);
|
|
45
65
|
printHumanReadableLine(`skills repo: ${payload.skills_repo}`);
|
|
66
|
+
printHumanReadableLine(`skills release: ${payload.skills_release}`);
|
|
67
|
+
printHumanReadableLine(`skills installer: ${payload.skills_installer.name}@${payload.skills_installer.version}`);
|
|
68
|
+
printHumanReadableLine(`skills install: ${payload.skills_install_command}`);
|
|
69
|
+
printHumanReadableLine(`retired skills: ${formatRetiredSkillsSummary()}`);
|
|
70
|
+
printHumanReadableLine(`retired skills cleanup: ${RETIRED_SKILLS_CLEANUP.guidance}`);
|
|
46
71
|
},
|
|
47
72
|
};
|
|
48
73
|
export function registerInfoCommand(program) {
|
|
@@ -33,7 +33,7 @@ export function getMetaSchema() {
|
|
|
33
33
|
version: 1,
|
|
34
34
|
entities: {
|
|
35
35
|
note_search_result: {
|
|
36
|
-
fields: ['id', 'title', 'summary', 'tags', 'created_at', 'boxes', 'is_starred'],
|
|
36
|
+
fields: ['id', 'title', 'summary', 'tags', 'created_at', 'starred_at', 'boxes', 'is_starred'],
|
|
37
37
|
meta_fields: [
|
|
38
38
|
'query',
|
|
39
39
|
'returned_count',
|
|
@@ -55,6 +55,7 @@ export function getMetaSchema() {
|
|
|
55
55
|
'created_at',
|
|
56
56
|
'updated_at',
|
|
57
57
|
'is_del',
|
|
58
|
+
'starred_at',
|
|
58
59
|
'is_starred',
|
|
59
60
|
'boxes',
|
|
60
61
|
'links.backlinks',
|
|
@@ -6,7 +6,7 @@ import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
|
|
|
6
6
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
7
7
|
import { DinoxError } from '../../utils/errors.js';
|
|
8
8
|
import { commandStale, printStaleCacheWarning, resolveWriteDurabilityForRuntime, waitForMutationUpload, } from '../runtime.js';
|
|
9
|
-
import { createNote, executeNoteSearch,
|
|
9
|
+
import { createNote, executeNoteSearch, getNoteContext, getNoteDetails, getNotePreview, assertNotesUpdatable, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForUpdate, softDeleteNote, updateNote, validateTagsForCreate, validateTagsForUpdate, } from './repo.js';
|
|
10
10
|
import { routeRead } from '../../daemon/route.js';
|
|
11
11
|
import { buildNoteCreateDryRunPreview, buildNoteDeleteDryRunPreview, buildNoteUpdateDryRunPreview, } from './dryRun.js';
|
|
12
12
|
import { applyNoteMetadataMutation, planNoteMetadataMutation, } from './metadataMutation.js';
|
|
@@ -32,16 +32,6 @@ function toPublicNoteDetail(note) {
|
|
|
32
32
|
boxes: zettel_boxes,
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
function toPublicNote(note) {
|
|
36
|
-
if (!note) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
const { zettel_boxes, ...rest } = note;
|
|
40
|
-
return {
|
|
41
|
-
...rest,
|
|
42
|
-
...(zettel_boxes !== undefined ? { boxes: zettel_boxes } : {}),
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
35
|
async function executeNoteUpdateOperation(args) {
|
|
46
36
|
const structuredOutput = getRequestedStructuredOutputFormat() !== null;
|
|
47
37
|
await withNoteDatabase(args.command, async (runtime) => {
|
|
@@ -84,7 +74,7 @@ async function executeNoteUpdateOperation(args) {
|
|
|
84
74
|
commandName: args.commandName ?? 'dino note update',
|
|
85
75
|
action: args.actionName ?? 'update_note',
|
|
86
76
|
});
|
|
87
|
-
printDryRunPreview(preview);
|
|
77
|
+
printDryRunPreview(preview, runtime);
|
|
88
78
|
return;
|
|
89
79
|
}
|
|
90
80
|
const results = [];
|
|
@@ -199,7 +189,7 @@ async function executeNoteMetadataListOperation(args) {
|
|
|
199
189
|
[`${args.field}_to`]: item.after,
|
|
200
190
|
changed: item.changed,
|
|
201
191
|
})),
|
|
202
|
-
});
|
|
192
|
+
}, runtime);
|
|
203
193
|
return;
|
|
204
194
|
}
|
|
205
195
|
await applyNoteMetadataMutation(db, plan);
|
|
@@ -299,40 +289,29 @@ export async function runNoteGet({ command, options, args }) {
|
|
|
299
289
|
await withNoteDatabase(command, async (runtime) => {
|
|
300
290
|
const { db } = runtime;
|
|
301
291
|
await syncBeforeNoteCommand(runtime);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (!context) {
|
|
305
|
-
throw new DinoxError(`Note not found: ${id}`, {
|
|
306
|
-
code: 'NOTE_NOT_FOUND',
|
|
307
|
-
suggestion: "Run 'dino note search <keyword>' to locate the note id.",
|
|
308
|
-
recoverable: true,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
printSuccess({ stale: commandStale(runtime), note: toPublicNoteContext(context) });
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const note = await getNote(db, id);
|
|
315
|
-
if (!note) {
|
|
292
|
+
const context = await getNoteContext(db, id);
|
|
293
|
+
if (!context) {
|
|
316
294
|
throw new DinoxError(`Note not found: ${id}`, {
|
|
317
295
|
code: 'NOTE_NOT_FOUND',
|
|
318
296
|
suggestion: "Run 'dino note search <keyword>' to locate the note id.",
|
|
319
297
|
recoverable: true,
|
|
320
298
|
});
|
|
321
299
|
}
|
|
322
|
-
|
|
323
|
-
|
|
300
|
+
const note = toPublicNoteContext(context);
|
|
301
|
+
if (structuredOutput || getOptions.contextOnly) {
|
|
302
|
+
printSuccess({ stale: commandStale(runtime), note });
|
|
324
303
|
return;
|
|
325
304
|
}
|
|
326
305
|
printStaleCacheWarning(runtime, 'warning: sync timed out; result may be stale');
|
|
327
|
-
printHumanReadableLine(
|
|
328
|
-
printHumanReadableLine(`title: ${note.title ?? ''}`);
|
|
306
|
+
printHumanReadableLine(String(note.id));
|
|
307
|
+
printHumanReadableLine(`title: ${String(note.title ?? '')}`);
|
|
329
308
|
if (note.updated_at) {
|
|
330
|
-
printHumanReadableLine(`updated_at: ${note.updated_at}`);
|
|
309
|
+
printHumanReadableLine(`updated_at: ${String(note.updated_at)}`);
|
|
331
310
|
}
|
|
332
|
-
if (note.is_starred) {
|
|
311
|
+
if (note.is_starred === true) {
|
|
333
312
|
printHumanReadableLine('is_starred: 1');
|
|
334
313
|
}
|
|
335
|
-
if (note.is_del) {
|
|
314
|
+
if (typeof note.is_del === 'number' && note.is_del !== 0) {
|
|
336
315
|
printHumanReadableLine('is_del: 1');
|
|
337
316
|
}
|
|
338
317
|
});
|
|
@@ -370,7 +349,11 @@ export async function runNoteDetail({ command, options, args }) {
|
|
|
370
349
|
throw new DinoxError(ids.length === 1 ? `Note not found: ${missing[0]}` : `Notes not found: ${missing.join(', ')}`);
|
|
371
350
|
}
|
|
372
351
|
const publicNotes = notes.map((note) => toPublicNoteDetail(note));
|
|
373
|
-
printSuccess(
|
|
352
|
+
printSuccess({
|
|
353
|
+
stale: commandStale(runtime),
|
|
354
|
+
count: publicNotes.length,
|
|
355
|
+
notes: publicNotes,
|
|
356
|
+
});
|
|
374
357
|
});
|
|
375
358
|
}
|
|
376
359
|
export async function runNoteCreate({ command, options }) {
|
|
@@ -402,7 +385,7 @@ export async function runNoteCreate({ command, options }) {
|
|
|
402
385
|
contentMd,
|
|
403
386
|
tagsInput,
|
|
404
387
|
zettelBoxNames: boxNames,
|
|
405
|
-
}));
|
|
388
|
+
}), runtime);
|
|
406
389
|
return;
|
|
407
390
|
}
|
|
408
391
|
const id = generateId();
|
|
@@ -447,7 +430,7 @@ export async function runNoteDelete({ command, options, args }) {
|
|
|
447
430
|
await syncBeforeNoteCommand(runtime);
|
|
448
431
|
const durability = resolveWriteDurabilityForRuntime(runtime, deleteOptions.durability);
|
|
449
432
|
if (Boolean(deleteOptions.dryRun)) {
|
|
450
|
-
printDryRunPreview(await buildNoteDeleteDryRunPreview({ db, config, offline, id }));
|
|
433
|
+
printDryRunPreview(await buildNoteDeleteDryRunPreview({ db, config, offline, id }), runtime);
|
|
451
434
|
return;
|
|
452
435
|
}
|
|
453
436
|
await softDeleteNote(db, id);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { parseExpectedCount } from '../writeConfirmation.js';
|
|
1
2
|
import type { CommandExecutionContext } from '../descriptor/types.js';
|
|
2
3
|
import type { NoteMetadataListField, NoteMetadataOperation } from './metadataMutation.js';
|
|
4
|
+
export { parseExpectedCount };
|
|
3
5
|
type NoteBulkOptions = {
|
|
4
6
|
tags?: string;
|
|
5
7
|
from?: string;
|
|
@@ -26,10 +28,8 @@ type NoteBulkMutationSelection = {
|
|
|
26
28
|
optionName: '--tag-add' | '--tag-remove' | '--tag-replace' | '--box-add' | '--box-remove' | '--box-replace';
|
|
27
29
|
};
|
|
28
30
|
export declare function selectBulkMutation(options: NoteBulkOptions): NoteBulkMutationSelection;
|
|
29
|
-
export declare function parseExpectedCount(value: unknown): number | null;
|
|
30
31
|
export declare function assertBulkTargetFilter(input: {
|
|
31
32
|
query: string;
|
|
32
33
|
options: NoteBulkOptions;
|
|
33
34
|
}): void;
|
|
34
35
|
export declare function runNoteBulk({ command, options, args }: CommandExecutionContext): Promise<void>;
|
|
35
|
-
export {};
|
|
@@ -3,12 +3,14 @@ import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
|
3
3
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
4
4
|
import { DinoxError } from '../../utils/errors.js';
|
|
5
5
|
import { commandStale, printStaleCacheWarning, resolveWriteDurabilityForRuntime, waitForMutationUpload, } from '../runtime.js';
|
|
6
|
+
import { parseExpectedCount } from '../writeConfirmation.js';
|
|
6
7
|
import { resolveZettelBoxIdsForBulk, resolveZettelBoxIdsForSearch, searchNotes, validateTagsForBulk, } from './repo.js';
|
|
7
8
|
import { applyNoteMetadataMutation, planNoteMetadataMutation, } from './metadataMutation.js';
|
|
8
9
|
import { parseCreatedAtRange } from './searchTime.js';
|
|
9
10
|
import { normalizeDaysForMeta, } from './searchResponse.js';
|
|
10
11
|
import { parseMutationListInput, parseSearchListInput, parseStarredOption, syncBeforeNoteCommand, withNoteDatabase, } from './runtime.js';
|
|
11
12
|
import { buildBatchWriteReceiptFields, readNoteRevisionReceipts, } from './writeReceipt.js';
|
|
13
|
+
export { parseExpectedCount };
|
|
12
14
|
const BULK_PREVIEW_LIMIT = 100;
|
|
13
15
|
export function selectBulkMutation(options) {
|
|
14
16
|
const candidates = [
|
|
@@ -25,23 +27,6 @@ export function selectBulkMutation(options) {
|
|
|
25
27
|
}
|
|
26
28
|
return selected[0];
|
|
27
29
|
}
|
|
28
|
-
export function parseExpectedCount(value) {
|
|
29
|
-
if (value === undefined || value === null || value === '') {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
if (typeof value !== 'string') {
|
|
33
|
-
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
34
|
-
}
|
|
35
|
-
const trimmed = value.trim();
|
|
36
|
-
if (!/^\d+$/.test(trimmed)) {
|
|
37
|
-
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
38
|
-
}
|
|
39
|
-
const parsed = Number(trimmed);
|
|
40
|
-
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
41
|
-
throw new DinoxError('--expected-count must be a non-negative integer');
|
|
42
|
-
}
|
|
43
|
-
return parsed;
|
|
44
|
-
}
|
|
45
30
|
function hasConcreteTargetFilter(input) {
|
|
46
31
|
const { query, options } = input;
|
|
47
32
|
const hasValue = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
@@ -169,7 +154,7 @@ export async function runNoteBulk({ command, options, args }) {
|
|
|
169
154
|
[`${selected.field}_to`]: item.after,
|
|
170
155
|
changed: item.changed,
|
|
171
156
|
})),
|
|
172
|
-
});
|
|
157
|
+
}, runtime);
|
|
173
158
|
return;
|
|
174
159
|
}
|
|
175
160
|
if (!bulkOptions.confirm) {
|