@dinoxx/dinox-cli 1.0.1 → 1.0.3
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 +241 -6
- package/dist/commands/auth/index.js +10 -17
- package/dist/commands/config/index.d.ts +1 -0
- package/dist/commands/config/index.js +4 -17
- package/dist/commands/notes/index.js +9 -1
- package/dist/commands/notes/repo.d.ts +3 -0
- package/dist/commands/notes/repo.js +64 -4
- package/dist/commands/notes/searchSql.d.ts +11 -0
- package/dist/commands/notes/searchSql.js +599 -0
- package/dist/commands/prompt/index.js +54 -1
- package/dist/commands/prompt/repo.d.ts +12 -0
- package/dist/commands/prompt/repo.js +146 -0
- package/dist/commands/sync.js +5 -6
- package/dist/config/keys.d.ts +3 -3
- package/dist/config/keys.js +1 -4
- package/dist/config/paths.js +2 -1
- package/dist/powersync/syncPass.d.ts +7 -0
- package/dist/powersync/syncPass.js +10 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -115,6 +115,8 @@ dino auth status
|
|
|
115
115
|
dino note search "AI"
|
|
116
116
|
dino note search "AI" --days 7
|
|
117
117
|
dino note search "AI" --from 2026-02-01 --to 2026-02-28
|
|
118
|
+
dino note search "AI" --box "Inbox"
|
|
119
|
+
dino note search --sql 'type = "crawl" AND zettel_boxes IN ("Inbox","Project")'
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
创建笔记:
|
|
@@ -174,6 +176,7 @@ dino box add --name "Inbox" --description "用于存放待整理的想法和资
|
|
|
174
176
|
|
|
175
177
|
```bash
|
|
176
178
|
dino prompt list
|
|
179
|
+
dino prompt add --name "周报助手" --cmd "请基于本周笔记输出一份简洁周报"
|
|
177
180
|
```
|
|
178
181
|
|
|
179
182
|
## 5.5 配置
|
|
@@ -207,11 +210,6 @@ dino config set sync.timeoutMs 20000
|
|
|
207
210
|
1. macOS: `~/Library/Application Support/dinox/config.json`
|
|
208
211
|
2. Windows: `%APPDATA%\dinox\config.json`
|
|
209
212
|
|
|
210
|
-
本地数据库:
|
|
211
|
-
|
|
212
|
-
1. macOS: `~/Library/Application Support/dinox/powersync.sqlite`
|
|
213
|
-
2. Windows: `%LOCALAPPDATA%\dinox\powersync.sqlite`
|
|
214
|
-
|
|
215
213
|
## 6.2 多行命令写法不同
|
|
216
214
|
|
|
217
215
|
macOS / Linux(bash/zsh)用 `\` 续行:
|
|
@@ -269,7 +267,244 @@ dino box add "你的卡片盒"
|
|
|
269
267
|
|
|
270
268
|
---
|
|
271
269
|
|
|
272
|
-
## 8.
|
|
270
|
+
## 8. 完整命令参数参考
|
|
271
|
+
|
|
272
|
+
## 8.1 全局参数(适用于 CLI)
|
|
273
|
+
|
|
274
|
+
以下参数可放在命令前后使用,例如:`dino --json auth status`。
|
|
275
|
+
|
|
276
|
+
| 参数 | 类型 | 说明 |
|
|
277
|
+
| --- | --- | --- |
|
|
278
|
+
| `--json` | flag | 输出 machine-readable YAML(仅被支持该参数的命令读取)。 |
|
|
279
|
+
| `--offline` | flag | 跳过 connect/sync,仅用本地缓存(仅被支持该参数的命令读取)。 |
|
|
280
|
+
| `--sync-timeout <ms>` | integer | 覆盖连接/同步超时毫秒,必须是正整数。 |
|
|
281
|
+
| `--verbose` | flag | 预留 verbose 开关。 |
|
|
282
|
+
|
|
283
|
+
## 8.2 `auth` 命令
|
|
284
|
+
|
|
285
|
+
### `dino auth login <authorization>`
|
|
286
|
+
|
|
287
|
+
保存授权信息并验证连接。
|
|
288
|
+
|
|
289
|
+
| 参数 | 必填 | 说明 |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| `<authorization>` | 是 | 完整 Authorization 头值,例如:`"Bearer <token>"` |
|
|
292
|
+
|
|
293
|
+
支持全局参数:`--json`、`--sync-timeout`
|
|
294
|
+
|
|
295
|
+
### `dino auth logout`
|
|
296
|
+
|
|
297
|
+
清理已保存登录信息。
|
|
298
|
+
|
|
299
|
+
| 选项 | 必填 | 说明 |
|
|
300
|
+
| --- | --- | --- |
|
|
301
|
+
| `--clear-local-db` | 否 | 同时删除本地 SQLite 数据库。 |
|
|
302
|
+
|
|
303
|
+
### `dino auth status`
|
|
304
|
+
|
|
305
|
+
查看当前登录与同步状态。
|
|
306
|
+
|
|
307
|
+
支持全局参数:`--json`、`--offline`、`--sync-timeout`
|
|
308
|
+
|
|
309
|
+
## 8.3 `sync` 命令
|
|
310
|
+
|
|
311
|
+
### `dino sync`
|
|
312
|
+
|
|
313
|
+
连接并执行同步。
|
|
314
|
+
|
|
315
|
+
支持全局参数:`--json`、`--sync-timeout`
|
|
316
|
+
|
|
317
|
+
## 8.4 `note` 命令
|
|
318
|
+
|
|
319
|
+
### `dino note search [query]`
|
|
320
|
+
|
|
321
|
+
按关键词/标签/创建时间搜索笔记。
|
|
322
|
+
|
|
323
|
+
| 参数 | 必填 | 说明 |
|
|
324
|
+
| --- | --- | --- |
|
|
325
|
+
| `[query]` | 否 | 搜索关键词。可为空。 |
|
|
326
|
+
|
|
327
|
+
| 选项 | 必填 | 说明 |
|
|
328
|
+
| --- | --- | --- |
|
|
329
|
+
| `--tags <expr>` | 否 | 标签表达式,支持 `AND` / `OR` / `NOT` 与括号。 |
|
|
330
|
+
| `--from <date>` | 否 | 创建时间起点,支持 `YYYY-MM-DD` 或 ISO datetime。 |
|
|
331
|
+
| `--to <date>` | 否 | 创建时间终点,支持 `YYYY-MM-DD` 或 ISO datetime。 |
|
|
332
|
+
| `--days <n>` | 否 | 最近 N 天(按 `created_at`),不能与 `--from/--to` 同时使用。 |
|
|
333
|
+
| `--box <string\|@file>` | 否 | 按卡片盒名称筛选(支持 JSON 数组或逗号/换行分隔);会自动解析为 box id 查询。 |
|
|
334
|
+
| `--sql <expr>` | 否 | SQL 风格条件表达式;仅支持字段 `id/content_md/summary/tags/zettel_boxes/created_at/type`,其中 `zettel_boxes` 按名称自动转 id。只支持只读 WHERE 条件(禁止 `INSERT/UPDATE/DELETE`、注释和多语句)。 |
|
|
335
|
+
| `--include-deleted` | 否 | 包含软删除笔记。 |
|
|
336
|
+
|
|
337
|
+
支持全局参数:`--offline`、`--sync-timeout`
|
|
338
|
+
|
|
339
|
+
### `dino note get <id>`
|
|
340
|
+
|
|
341
|
+
按 ID 获取笔记基础信息。
|
|
342
|
+
|
|
343
|
+
| 参数 | 必填 | 说明 |
|
|
344
|
+
| --- | --- | --- |
|
|
345
|
+
| `<id>` | 是 | 笔记 ID。 |
|
|
346
|
+
|
|
347
|
+
支持全局参数:`--json`、`--offline`、`--sync-timeout`
|
|
348
|
+
|
|
349
|
+
### `dino note detail <id>`
|
|
350
|
+
|
|
351
|
+
按 ID 获取笔记完整详情。
|
|
352
|
+
|
|
353
|
+
| 参数 | 必填 | 说明 |
|
|
354
|
+
| --- | --- | --- |
|
|
355
|
+
| `<id>` | 是 | 笔记 ID。 |
|
|
356
|
+
|
|
357
|
+
支持全局参数:`--offline`、`--sync-timeout`
|
|
358
|
+
|
|
359
|
+
### `dino note create --title <string> --content <string|@file> [options]`
|
|
360
|
+
|
|
361
|
+
创建笔记。
|
|
362
|
+
|
|
363
|
+
| 选项 | 必填 | 说明 |
|
|
364
|
+
| --- | --- | --- |
|
|
365
|
+
| `--title <string>` | 是 | 笔记标题。 |
|
|
366
|
+
| `--content <string|@file>` | 是 | Markdown 内容,可传 `@文件路径`。 |
|
|
367
|
+
| `--type <note\|crawl>` | 否 | 笔记类型,默认 `crawl`。 |
|
|
368
|
+
| `--tags <string\|@file>` | 否 | 标签列表(JSON 数组或逗号/换行分隔)。 |
|
|
369
|
+
| `--zettel_boxes <string\|@file>` | 否 | 卡片盒名称列表(JSON 数组或逗号/换行分隔)。 |
|
|
370
|
+
|
|
371
|
+
支持全局参数:`--json`、`--offline`、`--sync-timeout`
|
|
372
|
+
|
|
373
|
+
### `dino note delete <id>`
|
|
374
|
+
|
|
375
|
+
软删除笔记(`is_del=1`)。
|
|
376
|
+
|
|
377
|
+
| 参数 | 必填 | 说明 |
|
|
378
|
+
| --- | --- | --- |
|
|
379
|
+
| `<id>` | 是 | 笔记 ID。 |
|
|
380
|
+
|
|
381
|
+
支持全局参数:`--json`、`--offline`、`--sync-timeout`
|
|
382
|
+
|
|
383
|
+
## 8.5 `tag` 命令
|
|
384
|
+
|
|
385
|
+
### `dino tag list`
|
|
386
|
+
|
|
387
|
+
列出标签。
|
|
388
|
+
|
|
389
|
+
| 选项 | 必填 | 说明 |
|
|
390
|
+
| --- | --- | --- |
|
|
391
|
+
| `--json` | 否 | 输出 machine-readable YAML。 |
|
|
392
|
+
| `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
|
|
393
|
+
| `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
|
|
394
|
+
|
|
395
|
+
### `dino tag add [name] [options]`
|
|
396
|
+
|
|
397
|
+
新增标签。
|
|
398
|
+
|
|
399
|
+
| 参数 | 必填 | 说明 |
|
|
400
|
+
| --- | --- | --- |
|
|
401
|
+
| `[name]` | 否 | 标签名/路径(支持斜杠层级)。 |
|
|
402
|
+
|
|
403
|
+
| 选项 | 必填 | 说明 |
|
|
404
|
+
| --- | --- | --- |
|
|
405
|
+
| `--name <string>` | 否 | 标签名/路径,可替代位置参数 `[name]`。 |
|
|
406
|
+
| `--emoji <string>` | 否 | 标签 emoji。 |
|
|
407
|
+
| `--json` | 否 | 输出 machine-readable YAML。 |
|
|
408
|
+
| `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
|
|
409
|
+
| `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
|
|
410
|
+
|
|
411
|
+
说明:`[name]` 与 `--name` 至少提供一个;若两者都提供且值不同会报错。
|
|
412
|
+
|
|
413
|
+
## 8.6 `box` 命令
|
|
414
|
+
|
|
415
|
+
### `dino box list`
|
|
416
|
+
|
|
417
|
+
列出卡片盒。
|
|
418
|
+
|
|
419
|
+
| 选项 | 必填 | 说明 |
|
|
420
|
+
| --- | --- | --- |
|
|
421
|
+
| `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
|
|
422
|
+
| `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
|
|
423
|
+
|
|
424
|
+
### `dino box add [name] [options]`
|
|
425
|
+
|
|
426
|
+
新增卡片盒。
|
|
427
|
+
|
|
428
|
+
| 参数 | 必填 | 说明 |
|
|
429
|
+
| --- | --- | --- |
|
|
430
|
+
| `[name]` | 否 | 卡片盒名称。 |
|
|
431
|
+
|
|
432
|
+
| 选项 | 必填 | 说明 |
|
|
433
|
+
| --- | --- | --- |
|
|
434
|
+
| `--name <string>` | 否 | 卡片盒名称,可替代位置参数 `[name]`。 |
|
|
435
|
+
| `--description <string>` | 否 | 用途说明(可帮助 AI 路由笔记)。 |
|
|
436
|
+
| `--color <string>` | 否 | 卡片盒颜色。 |
|
|
437
|
+
| `--json` | 否 | 输出 machine-readable YAML。 |
|
|
438
|
+
| `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
|
|
439
|
+
| `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
|
|
440
|
+
|
|
441
|
+
说明:`[name]` 与 `--name` 至少提供一个;若两者都提供且值不同会报错。
|
|
442
|
+
|
|
443
|
+
## 8.7 `prompt` 命令
|
|
444
|
+
|
|
445
|
+
### `dino prompt list`
|
|
446
|
+
|
|
447
|
+
列出 Prompt(`name` 与 `cmd`)。
|
|
448
|
+
|
|
449
|
+
| 选项 | 必填 | 说明 |
|
|
450
|
+
| --- | --- | --- |
|
|
451
|
+
| `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
|
|
452
|
+
| `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
|
|
453
|
+
|
|
454
|
+
### `dino prompt add [options]`
|
|
455
|
+
|
|
456
|
+
新增 Prompt(写入 `c_cmd`)。
|
|
457
|
+
|
|
458
|
+
| 选项 | 必填 | 说明 |
|
|
459
|
+
| --- | --- | --- |
|
|
460
|
+
| `--name <string>` | 是 | Prompt 名称。 |
|
|
461
|
+
| `--cmd <string>` | 是 | Prompt 指令内容。 |
|
|
462
|
+
| `--json` | 否 | 输出 machine-readable YAML。 |
|
|
463
|
+
| `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
|
|
464
|
+
| `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
|
|
465
|
+
|
|
466
|
+
## 8.8 `config` 命令
|
|
467
|
+
|
|
468
|
+
### `dino config get [key]`
|
|
469
|
+
|
|
470
|
+
读取配置(输出 YAML)。
|
|
471
|
+
|
|
472
|
+
| 参数 | 必填 | 说明 |
|
|
473
|
+
| --- | --- | --- |
|
|
474
|
+
| `[key]` | 否 | 配置键(点路径);不传则返回全部配置。 |
|
|
475
|
+
|
|
476
|
+
可读 key:
|
|
477
|
+
|
|
478
|
+
```text
|
|
479
|
+
sync.timeoutMs
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### `dino config set <key> <value>`
|
|
483
|
+
|
|
484
|
+
写入配置。
|
|
485
|
+
|
|
486
|
+
| 参数 | 必填 | 说明 |
|
|
487
|
+
| --- | --- | --- |
|
|
488
|
+
| `<key>` | 是 | 配置键(点路径)。 |
|
|
489
|
+
| `<value>` | 是 | 配置值。 |
|
|
490
|
+
|
|
491
|
+
可写 key:
|
|
492
|
+
|
|
493
|
+
```text
|
|
494
|
+
sync.timeoutMs (必须为正整数)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## 8.9 `info` 命令
|
|
498
|
+
|
|
499
|
+
### `dino info`
|
|
500
|
+
|
|
501
|
+
显示 CLI 版本信息。
|
|
502
|
+
|
|
503
|
+
支持全局参数:`--json`
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## 9. 查看帮助
|
|
273
508
|
|
|
274
509
|
随时可以看帮助:
|
|
275
510
|
|
|
@@ -8,6 +8,7 @@ import { printYaml } from '../../utils/output.js';
|
|
|
8
8
|
import { refreshAndPersistAuthIdentity } from '../../auth/userInfo.js';
|
|
9
9
|
import { connectPowerSync, getStatusSnapshot } from '../../powersync/runtime.js';
|
|
10
10
|
import { fetchCredentialsWithAuthorization } from '../../powersync/connector.js';
|
|
11
|
+
import { runSingleSyncPass } from '../../powersync/syncPass.js';
|
|
11
12
|
function parseSyncTimeoutMs(value) {
|
|
12
13
|
if (typeof value !== 'string') {
|
|
13
14
|
return undefined;
|
|
@@ -18,6 +19,12 @@ function parseSyncTimeoutMs(value) {
|
|
|
18
19
|
}
|
|
19
20
|
return Math.trunc(parsed);
|
|
20
21
|
}
|
|
22
|
+
async function removeSqliteArtifacts(dbPath) {
|
|
23
|
+
const artifacts = ['', '-wal', '-shm'];
|
|
24
|
+
for (const suffix of artifacts) {
|
|
25
|
+
await fs.rm(`${dbPath}${suffix}`, { force: true }).catch(() => undefined);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
21
28
|
export function registerAuthCommands(program) {
|
|
22
29
|
const auth = program
|
|
23
30
|
.command('auth')
|
|
@@ -26,8 +33,7 @@ export function registerAuthCommands(program) {
|
|
|
26
33
|
.command('login')
|
|
27
34
|
.description('Save authorization token and verify PowerSync connectivity')
|
|
28
35
|
.argument('<authorization>', 'full Authorization header value (e.g. "Bearer <token>")')
|
|
29
|
-
.
|
|
30
|
-
.action(async (authorization, options, command) => {
|
|
36
|
+
.action(async (authorization, _options, command) => {
|
|
31
37
|
const auth = authorization?.trim();
|
|
32
38
|
if (!auth) {
|
|
33
39
|
throw new DinoxError('Authorization token is required');
|
|
@@ -39,20 +45,6 @@ export function registerAuthCommands(program) {
|
|
|
39
45
|
const globals = command.optsWithGlobals?.() ?? {};
|
|
40
46
|
const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? resolved.sync.timeoutMs;
|
|
41
47
|
const jsonOutput = Boolean(globals.json);
|
|
42
|
-
if (!options.verify) {
|
|
43
|
-
const result = {
|
|
44
|
-
ok: true,
|
|
45
|
-
verified: false,
|
|
46
|
-
authorization: redactAuthorization(auth),
|
|
47
|
-
};
|
|
48
|
-
if (jsonOutput) {
|
|
49
|
-
printYaml(result);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
console.log('Login saved (verification skipped).');
|
|
53
|
-
}
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
48
|
// 1) Token exchange sanity check.
|
|
57
49
|
await fetchCredentialsWithAuthorization({
|
|
58
50
|
authorization: auth,
|
|
@@ -68,6 +60,7 @@ export function registerAuthCommands(program) {
|
|
|
68
60
|
timeoutMs,
|
|
69
61
|
});
|
|
70
62
|
try {
|
|
63
|
+
await runSingleSyncPass(db, timeoutMs);
|
|
71
64
|
const status = getStatusSnapshot(db);
|
|
72
65
|
const result = {
|
|
73
66
|
ok: true,
|
|
@@ -110,7 +103,7 @@ export function registerAuthCommands(program) {
|
|
|
110
103
|
await saveConfig(config);
|
|
111
104
|
if (options.clearLocalDb) {
|
|
112
105
|
const dbPath = getPowerSyncDbPath();
|
|
113
|
-
await
|
|
106
|
+
await removeSqliteArtifacts(dbPath);
|
|
114
107
|
}
|
|
115
108
|
});
|
|
116
109
|
auth
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { isConfigKey, isFixedConfigKey, isReadableConfigKey } from '../../config/keys.js';
|
|
2
|
-
import { getConfigValue,
|
|
3
|
-
import { resolveConfig } from '../../config/resolve.js';
|
|
2
|
+
import { getConfigValue, setConfigValue } from '../../config/store.js';
|
|
4
3
|
import { redactAuthorization } from '../../utils/redact.js';
|
|
5
4
|
import { DinoxError } from '../../utils/errors.js';
|
|
6
5
|
import { printYaml } from '../../utils/output.js';
|
|
7
|
-
function
|
|
6
|
+
export function sanitizeConfigForDisplay(config) {
|
|
8
7
|
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
9
8
|
return config;
|
|
10
9
|
}
|
|
@@ -13,16 +12,9 @@ function redactConfigForDisplay(config) {
|
|
|
13
12
|
if (auth && typeof auth === 'object') {
|
|
14
13
|
auth.authorization = redactAuthorization(auth.authorization);
|
|
15
14
|
}
|
|
15
|
+
delete clone.powersync;
|
|
16
16
|
return clone;
|
|
17
17
|
}
|
|
18
|
-
function getByPath(value, keyPath) {
|
|
19
|
-
return keyPath.split('.').reduce((current, segment) => {
|
|
20
|
-
if (!current || typeof current !== 'object') {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
return current[segment];
|
|
24
|
-
}, value);
|
|
25
|
-
}
|
|
26
18
|
export function registerConfigCommands(program) {
|
|
27
19
|
const configCmd = program.command('config').description('Read and write Dinox configuration');
|
|
28
20
|
configCmd
|
|
@@ -33,13 +25,8 @@ export function registerConfigCommands(program) {
|
|
|
33
25
|
if (key && !isReadableConfigKey(key)) {
|
|
34
26
|
throw new DinoxError(`Unknown config key: ${key}`);
|
|
35
27
|
}
|
|
36
|
-
if (key && isFixedConfigKey(key)) {
|
|
37
|
-
const resolved = resolveConfig(await loadConfig());
|
|
38
|
-
printYaml(getByPath(resolved, key));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
28
|
const value = await getConfigValue(key);
|
|
42
|
-
const redacted = key ? value :
|
|
29
|
+
const redacted = key ? value : sanitizeConfigForDisplay(value);
|
|
43
30
|
printYaml(redacted);
|
|
44
31
|
});
|
|
45
32
|
configCmd
|
|
@@ -8,7 +8,7 @@ import { printYaml } from '../../utils/output.js';
|
|
|
8
8
|
import { resolveAuthIdentity } from '../../auth/userInfo.js';
|
|
9
9
|
import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
|
|
10
10
|
import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
|
|
11
|
-
import { createNote, getNote, getNoteDetail, resolveZettelBoxIdsForCreate, searchNotes, softDeleteNote, validateTagsForCreate, } from './repo.js';
|
|
11
|
+
import { createNote, getNote, getNoteDetail, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, searchNotes, softDeleteNote, validateTagsForCreate, } from './repo.js';
|
|
12
12
|
import { parseCreatedAtRange } from './searchTime.js';
|
|
13
13
|
function parseSyncTimeoutMs(value) {
|
|
14
14
|
if (typeof value !== 'string') {
|
|
@@ -78,6 +78,8 @@ export function registerNoteCommands(program) {
|
|
|
78
78
|
.option('--from <date>', 'created_at start (YYYY-MM-DD or ISO datetime)')
|
|
79
79
|
.option('--to <date>', 'created_at end (YYYY-MM-DD or ISO datetime)')
|
|
80
80
|
.option('--days <n>', 'recent N days by created_at (cannot be used with --from/--to)')
|
|
81
|
+
.option('--box <string|@file>', 'zettel box names (JSON array or comma/newline-separated)')
|
|
82
|
+
.option('--sql <expr>', 'SQL-like expression over id/content_md/summary/tags/zettel_boxes/created_at/type')
|
|
81
83
|
.option('--include-deleted', 'include soft-deleted notes')
|
|
82
84
|
.action(async (query, options, command) => {
|
|
83
85
|
const globals = command.optsWithGlobals?.() ?? {};
|
|
@@ -94,11 +96,17 @@ export function registerNoteCommands(program) {
|
|
|
94
96
|
to: options.to,
|
|
95
97
|
days: options.days,
|
|
96
98
|
});
|
|
99
|
+
const boxInput = typeof options.box === 'string' ? options.box : '';
|
|
100
|
+
const zettelBoxNames = boxInput ? await parseStringListInput(boxInput, '--box') : [];
|
|
101
|
+
const zettelBoxIds = await resolveZettelBoxIdsForSearch(db, zettelBoxNames);
|
|
102
|
+
const sqlExpression = typeof options.sql === 'string' ? options.sql.trim() : '';
|
|
97
103
|
const rows = await searchNotes(db, keyword, {
|
|
98
104
|
includeDeleted: Boolean(options.includeDeleted),
|
|
99
105
|
tagsExpression: tagsExpression || undefined,
|
|
100
106
|
createdAtFrom,
|
|
101
107
|
createdAtTo,
|
|
108
|
+
zettelBoxIds,
|
|
109
|
+
sqlExpression: sqlExpression || undefined,
|
|
102
110
|
});
|
|
103
111
|
printYaml(rows);
|
|
104
112
|
}
|
|
@@ -18,6 +18,8 @@ type SearchNotesOptions = {
|
|
|
18
18
|
tagsExpression?: string;
|
|
19
19
|
createdAtFrom?: string;
|
|
20
20
|
createdAtTo?: string;
|
|
21
|
+
zettelBoxIds?: string[];
|
|
22
|
+
sqlExpression?: string;
|
|
21
23
|
};
|
|
22
24
|
export type SearchedNote = {
|
|
23
25
|
id: string;
|
|
@@ -66,5 +68,6 @@ export declare function createNote(db: AbstractPowerSyncDatabase, input: {
|
|
|
66
68
|
}>;
|
|
67
69
|
export declare function validateTagsForCreate(db: AbstractPowerSyncDatabase, requestedTags: string[]): Promise<string[]>;
|
|
68
70
|
export declare function resolveZettelBoxIdsForCreate(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
71
|
+
export declare function resolveZettelBoxIdsForSearch(db: AbstractPowerSyncDatabase, requestedNames: string[]): Promise<string[]>;
|
|
69
72
|
export declare function softDeleteNote(db: AbstractPowerSyncDatabase, id: string): Promise<void>;
|
|
70
73
|
export {};
|
|
@@ -3,16 +3,23 @@ import { nowIsoUtc } from '../../utils/time.js';
|
|
|
3
3
|
import { tokenizeWithJieba } from '../../utils/tokenize.js';
|
|
4
4
|
import { listBoxes } from '../boxes/repo.js';
|
|
5
5
|
import { listTags } from '../tags/repo.js';
|
|
6
|
+
import { buildNoteSearchSqlFilter } from './searchSql.js';
|
|
6
7
|
export async function searchNotes(db, query, options) {
|
|
7
8
|
const normalizedQuery = query.trim();
|
|
8
9
|
const tagFilter = buildTagFilter(options.tagsExpression, 'n');
|
|
9
10
|
const createdAtFilter = buildCreatedAtFilter(options.createdAtFrom, options.createdAtTo, 'n');
|
|
11
|
+
const zettelBoxFilter = buildZettelBoxFilter(options.zettelBoxIds, 'n');
|
|
12
|
+
const sqlFilter = await buildNoteSearchSqlFilter({
|
|
13
|
+
expression: options.sqlExpression,
|
|
14
|
+
noteAlias: 'n',
|
|
15
|
+
resolveZettelBoxNames: (names) => resolveZettelBoxIdsForSearch(db, names),
|
|
16
|
+
});
|
|
10
17
|
let rows = [];
|
|
11
18
|
if (!options.includeDeleted) {
|
|
12
19
|
const ftsMatch = buildFtsMatchQuery(normalizedQuery);
|
|
13
20
|
if (ftsMatch) {
|
|
14
21
|
try {
|
|
15
|
-
rows = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter);
|
|
22
|
+
rows = await searchNotesWithFts(db, ftsMatch, tagFilter, createdAtFilter, zettelBoxFilter, sqlFilter);
|
|
16
23
|
return enrichSearchRows(db, rows);
|
|
17
24
|
}
|
|
18
25
|
catch (error) {
|
|
@@ -53,6 +60,14 @@ export async function searchNotes(db, query, options) {
|
|
|
53
60
|
whereClauses.push(createdAtFilter.sql);
|
|
54
61
|
params.push(...createdAtFilter.params);
|
|
55
62
|
}
|
|
63
|
+
if (zettelBoxFilter) {
|
|
64
|
+
whereClauses.push(zettelBoxFilter.sql);
|
|
65
|
+
params.push(...zettelBoxFilter.params);
|
|
66
|
+
}
|
|
67
|
+
if (sqlFilter) {
|
|
68
|
+
whereClauses.push(sqlFilter.sql);
|
|
69
|
+
params.push(...sqlFilter.params);
|
|
70
|
+
}
|
|
56
71
|
rows = await db.getAll(`
|
|
57
72
|
SELECT
|
|
58
73
|
n.id,
|
|
@@ -69,7 +84,7 @@ export async function searchNotes(db, query, options) {
|
|
|
69
84
|
`, params);
|
|
70
85
|
return enrichSearchRows(db, rows);
|
|
71
86
|
}
|
|
72
|
-
async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter) {
|
|
87
|
+
async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter, zettelBoxFilter, sqlFilter) {
|
|
73
88
|
const whereClauses = ['(n.is_del IS NULL OR n.is_del = 0)', 'note_local_fts MATCH ?'];
|
|
74
89
|
const params = [matchQuery];
|
|
75
90
|
if (tagFilter) {
|
|
@@ -80,6 +95,14 @@ async function searchNotesWithFts(db, matchQuery, tagFilter, createdAtFilter) {
|
|
|
80
95
|
whereClauses.push(createdAtFilter.sql);
|
|
81
96
|
params.push(...createdAtFilter.params);
|
|
82
97
|
}
|
|
98
|
+
if (zettelBoxFilter) {
|
|
99
|
+
whereClauses.push(zettelBoxFilter.sql);
|
|
100
|
+
params.push(...zettelBoxFilter.params);
|
|
101
|
+
}
|
|
102
|
+
if (sqlFilter) {
|
|
103
|
+
whereClauses.push(sqlFilter.sql);
|
|
104
|
+
params.push(...sqlFilter.params);
|
|
105
|
+
}
|
|
83
106
|
return db.getAll(`
|
|
84
107
|
SELECT
|
|
85
108
|
n.id,
|
|
@@ -214,6 +237,30 @@ function buildCreatedAtFilter(createdAtFrom, createdAtTo, noteAlias) {
|
|
|
214
237
|
params,
|
|
215
238
|
};
|
|
216
239
|
}
|
|
240
|
+
function buildZettelBoxFilter(zettelBoxIds, noteAlias) {
|
|
241
|
+
const normalizedIds = Array.from(new Set((zettelBoxIds ?? [])
|
|
242
|
+
.map((id) => id.trim())
|
|
243
|
+
.filter((id) => id.length > 0)));
|
|
244
|
+
if (normalizedIds.length === 0) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const placeholders = normalizedIds.map(() => '?').join(', ');
|
|
248
|
+
return {
|
|
249
|
+
sql: `
|
|
250
|
+
EXISTS (
|
|
251
|
+
SELECT 1
|
|
252
|
+
FROM json_each(
|
|
253
|
+
CASE
|
|
254
|
+
WHEN json_valid(COALESCE(${noteAlias}.zettel_boxes, '')) THEN ${noteAlias}.zettel_boxes
|
|
255
|
+
ELSE '[]'
|
|
256
|
+
END
|
|
257
|
+
) box_item
|
|
258
|
+
WHERE TRIM(CAST(box_item.value AS TEXT)) IN (${placeholders})
|
|
259
|
+
)
|
|
260
|
+
`,
|
|
261
|
+
params: normalizedIds,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
217
264
|
function parseTagExpression(expression) {
|
|
218
265
|
const tokens = tokenizeTagExpression(expression);
|
|
219
266
|
if (tokens.length === 0) {
|
|
@@ -537,6 +584,13 @@ export async function createNote(db, input) {
|
|
|
537
584
|
function normalizeLookupKey(value) {
|
|
538
585
|
return value.trim().toLowerCase();
|
|
539
586
|
}
|
|
587
|
+
function buildZettelBoxRetryQuestion(intent, type) {
|
|
588
|
+
const command = intent === 'search' ? 'dino note search' : 'dino note create';
|
|
589
|
+
if (type === 'ambiguous') {
|
|
590
|
+
return `Do you want to specify unique box names (or IDs) and retry \`${command}\`?`;
|
|
591
|
+
}
|
|
592
|
+
return `Do you want to add these missing boxes and retry \`${command}\`?`;
|
|
593
|
+
}
|
|
540
594
|
export async function validateTagsForCreate(db, requestedTags) {
|
|
541
595
|
if (requestedTags.length === 0) {
|
|
542
596
|
return [];
|
|
@@ -580,6 +634,12 @@ export async function validateTagsForCreate(db, requestedTags) {
|
|
|
580
634
|
return resolved;
|
|
581
635
|
}
|
|
582
636
|
export async function resolveZettelBoxIdsForCreate(db, requestedNames) {
|
|
637
|
+
return resolveZettelBoxIdsByName(db, requestedNames, 'create');
|
|
638
|
+
}
|
|
639
|
+
export async function resolveZettelBoxIdsForSearch(db, requestedNames) {
|
|
640
|
+
return resolveZettelBoxIdsByName(db, requestedNames, 'search');
|
|
641
|
+
}
|
|
642
|
+
async function resolveZettelBoxIdsByName(db, requestedNames, intent) {
|
|
583
643
|
if (requestedNames.length === 0) {
|
|
584
644
|
return [];
|
|
585
645
|
}
|
|
@@ -642,7 +702,7 @@ export async function resolveZettelBoxIdsForCreate(db, requestedNames) {
|
|
|
642
702
|
type: 'ambiguous_zettel_boxes',
|
|
643
703
|
ambiguous,
|
|
644
704
|
availableNamesSample: availableNames.slice(0, 20),
|
|
645
|
-
question:
|
|
705
|
+
question: buildZettelBoxRetryQuestion(intent, 'ambiguous'),
|
|
646
706
|
},
|
|
647
707
|
});
|
|
648
708
|
}
|
|
@@ -653,7 +713,7 @@ export async function resolveZettelBoxIdsForCreate(db, requestedNames) {
|
|
|
653
713
|
missing,
|
|
654
714
|
availableCount: availableNames.length,
|
|
655
715
|
availableNamesSample: availableNames.slice(0, 20),
|
|
656
|
-
question: '
|
|
716
|
+
question: buildZettelBoxRetryQuestion(intent, 'missing'),
|
|
657
717
|
},
|
|
658
718
|
});
|
|
659
719
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SqlFilter = {
|
|
2
|
+
sql: string;
|
|
3
|
+
params: string[];
|
|
4
|
+
};
|
|
5
|
+
type BuildNoteSearchSqlFilterOptions = {
|
|
6
|
+
expression?: string;
|
|
7
|
+
noteAlias: string;
|
|
8
|
+
resolveZettelBoxNames: (names: string[]) => Promise<string[]>;
|
|
9
|
+
};
|
|
10
|
+
export declare function buildNoteSearchSqlFilter(options: BuildNoteSearchSqlFilterOptions): Promise<SqlFilter | null>;
|
|
11
|
+
export {};
|