@dinoxx/dinox-cli 1.0.2 → 1.0.4

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 CHANGED
@@ -88,7 +88,7 @@ dino info
88
88
  ## 第 1 步:登录
89
89
 
90
90
  ```bash
91
- dino auth login "Bearer <你的token>"
91
+ dino auth login "<你的token>"
92
92
  ```
93
93
 
94
94
  ## 第 2 步:同步
@@ -176,6 +176,7 @@ dino box add --name "Inbox" --description "用于存放待整理的想法和资
176
176
 
177
177
  ```bash
178
178
  dino prompt list
179
+ dino prompt add --name "周报助手" --cmd "请基于本周笔记输出一份简洁周报"
179
180
  ```
180
181
 
181
182
  ## 5.5 配置
@@ -252,7 +253,7 @@ dino note create --title "测试" --content "@.\note.md"
252
253
  你还没有完成登录。执行:
253
254
 
254
255
  ```bash
255
- dino auth login "Bearer <token>"
256
+ dino auth login "<token>"
256
257
  ```
257
258
 
258
259
  ## Q2:创建笔记时报 `Unknown tags` 或 `Unknown zettel box names`
@@ -281,17 +282,13 @@ dino box add "你的卡片盒"
281
282
 
282
283
  ## 8.2 `auth` 命令
283
284
 
284
- ### `dino auth login <authorization>`
285
+ ### `dino auth login <token>`
285
286
 
286
287
  保存授权信息并验证连接。
287
288
 
288
289
  | 参数 | 必填 | 说明 |
289
290
  | --- | --- | --- |
290
- | `<authorization>` | 是 | 完整 Authorization 头值,例如:`"Bearer <token>"` |
291
-
292
- | 选项 | 必填 | 说明 |
293
- | --- | --- | --- |
294
- | `--no-verify` | 否 | 只保存登录信息,不做凭证交换与初始同步验证。 |
291
+ | `<token>` | 是 | 登录 token,直接传 token 字符串(不需要 `Bearer` 前缀)。 |
295
292
 
296
293
  支持全局参数:`--json`、`--sync-timeout`
297
294
 
@@ -454,6 +451,18 @@ dino box add "你的卡片盒"
454
451
  | `--offline` | 否 | 跳过 connect/sync,仅用本地缓存。 |
455
452
  | `--sync-timeout <ms>` | 否 | 覆盖连接/同步超时毫秒。 |
456
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
+
457
466
  ## 8.8 `config` 命令
458
467
 
459
468
  ### `dino config get [key]`
@@ -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;
@@ -32,8 +33,7 @@ export function registerAuthCommands(program) {
32
33
  .command('login')
33
34
  .description('Save authorization token and verify PowerSync connectivity')
34
35
  .argument('<authorization>', 'full Authorization header value (e.g. "Bearer <token>")')
35
- .option('--no-verify', 'skip credential exchange and initial sync')
36
- .action(async (authorization, options, command) => {
36
+ .action(async (authorization, _options, command) => {
37
37
  const auth = authorization?.trim();
38
38
  if (!auth) {
39
39
  throw new DinoxError('Authorization token is required');
@@ -45,20 +45,6 @@ export function registerAuthCommands(program) {
45
45
  const globals = command.optsWithGlobals?.() ?? {};
46
46
  const timeoutMs = parseSyncTimeoutMs(globals.syncTimeout) ?? resolved.sync.timeoutMs;
47
47
  const jsonOutput = Boolean(globals.json);
48
- if (!options.verify) {
49
- const result = {
50
- ok: true,
51
- verified: false,
52
- authorization: redactAuthorization(auth),
53
- };
54
- if (jsonOutput) {
55
- printYaml(result);
56
- }
57
- else {
58
- console.log('Login saved (verification skipped).');
59
- }
60
- return;
61
- }
62
48
  // 1) Token exchange sanity check.
63
49
  await fetchCredentialsWithAuthorization({
64
50
  authorization: auth,
@@ -74,6 +60,7 @@ export function registerAuthCommands(program) {
74
60
  timeoutMs,
75
61
  });
76
62
  try {
63
+ await runSingleSyncPass(db, timeoutMs);
77
64
  const status = getStatusSnapshot(db);
78
65
  const result = {
79
66
  ok: true,
@@ -4,7 +4,8 @@ import { DinoxError } from '../../utils/errors.js';
4
4
  import { printYaml } from '../../utils/output.js';
5
5
  import { connectPowerSync, waitForIdleDataFlow } from '../../powersync/runtime.js';
6
6
  import { syncNoteTokenIndex } from '../../powersync/tokenIndex.js';
7
- import { listPrompts } from './repo.js';
7
+ import { resolveAuthIdentity } from '../../auth/userInfo.js';
8
+ import { addPrompt, listPrompts } from './repo.js';
8
9
  function parseSyncTimeoutMs(value) {
9
10
  if (typeof value !== 'string') {
10
11
  return undefined;
@@ -45,6 +46,58 @@ export function registerPromptCommands(program) {
45
46
  .option('--offline', 'skip connect/sync and only use local cache')
46
47
  .option('--sync-timeout <ms>', 'override sync/connect timeout (milliseconds)')
47
48
  .action(runPromptListCommand);
49
+ prompt
50
+ .command('add')
51
+ .description('Create a new prompt in c_cmd')
52
+ .option('--name <string>', 'prompt name')
53
+ .option('--cmd <string>', 'prompt command/prompt text')
54
+ .option('--json', 'output machine-readable YAML')
55
+ .option('--offline', 'skip connect/sync and only use local cache')
56
+ .option('--sync-timeout <ms>', 'override sync/connect timeout (milliseconds)')
57
+ .action(async (options, command) => {
58
+ const globals = command.optsWithGlobals?.() ?? {};
59
+ const offline = Boolean(options.offline ?? globals.offline);
60
+ const jsonOutput = Boolean(options.json ?? globals.json);
61
+ const name = typeof options.name === 'string' ? options.name.trim() : '';
62
+ const cmd = typeof options.cmd === 'string' ? options.cmd.trim() : '';
63
+ if (!name) {
64
+ throw new DinoxError('Prompt name is required. Use `dino prompt add --name <name> --cmd <cmd>`.');
65
+ }
66
+ if (!cmd) {
67
+ throw new DinoxError('Prompt cmd is required. Use `dino prompt add --name <name> --cmd <cmd>`.');
68
+ }
69
+ const rawConfig = await loadConfig();
70
+ const config = resolveConfig(rawConfig);
71
+ const timeoutMs = parseSyncTimeoutMs(options.syncTimeout ?? globals.syncTimeout) ?? config.sync.timeoutMs;
72
+ const identity = await resolveAuthIdentity(rawConfig, config);
73
+ const { db, stale } = await connectPowerSync({ config, offline, timeoutMs });
74
+ try {
75
+ await syncBeforePromptCommand(db, offline, timeoutMs);
76
+ const result = await addPrompt(db, {
77
+ name,
78
+ cmd,
79
+ userId: identity.userId,
80
+ });
81
+ if (!offline && !config.powersync.uploadBaseUrl) {
82
+ console.log('warning: upload disabled (powersync.uploadBaseUrl is unset); changes are local-only for now');
83
+ }
84
+ if (!offline) {
85
+ await waitForIdleDataFlow(db, Math.min(timeoutMs, 5_000));
86
+ }
87
+ const payload = { ok: true, stale: offline ? true : stale, ...result };
88
+ if (jsonOutput) {
89
+ printYaml(payload);
90
+ return;
91
+ }
92
+ console.log(result.id);
93
+ if (stale && !offline) {
94
+ console.log('warning: sync timed out; results may be stale');
95
+ }
96
+ }
97
+ finally {
98
+ await db.close().catch(() => undefined);
99
+ }
100
+ });
48
101
  prompt.action((_options, command) => {
49
102
  command.outputHelp();
50
103
  });
@@ -3,4 +3,16 @@ export type PromptListItem = {
3
3
  name: string;
4
4
  cmd: string;
5
5
  };
6
+ export type AddPromptInput = {
7
+ name: string;
8
+ cmd: string;
9
+ userId: string;
10
+ };
11
+ export type AddPromptResult = {
12
+ id: string;
13
+ name: string;
14
+ cmd: string;
15
+ restored: boolean;
16
+ };
6
17
  export declare function listPrompts(db: AbstractPowerSyncDatabase): Promise<PromptListItem[]>;
18
+ export declare function addPrompt(db: AbstractPowerSyncDatabase, input: AddPromptInput): Promise<AddPromptResult>;
@@ -1,3 +1,39 @@
1
+ import { generateId } from '../../utils/id.js';
2
+ import { DinoxError } from '../../utils/errors.js';
3
+ function normalizeText(value) {
4
+ return value.trim();
5
+ }
6
+ function normalizeLookup(value) {
7
+ return normalizeText(value).toLowerCase();
8
+ }
9
+ async function getPromptColumns(db) {
10
+ const rows = await db.getAll('PRAGMA table_info(c_cmd)', []);
11
+ return new Set(rows.map((row) => row.name).filter(Boolean));
12
+ }
13
+ async function listPromptRowsForWrite(db, columnNames) {
14
+ const isDelExpr = columnNames.has('is_del') ? 'is_del' : 'NULL';
15
+ const userIdExpr = columnNames.has('user_id') ? `TRIM(COALESCE(user_id, ''))` : `''`;
16
+ const activeFirstExpr = columnNames.has('is_del')
17
+ ? 'CASE WHEN is_del IS NULL OR is_del = 0 THEN 0 ELSE 1 END'
18
+ : '0';
19
+ const updateOrderExpr = columnNames.has('update_at') ? 'update_at DESC,' : '';
20
+ const insertOrderExpr = columnNames.has('insert_at') ? 'insert_at DESC,' : '';
21
+ return db.getAll(`
22
+ SELECT
23
+ TRIM(COALESCE(id, '')) AS id,
24
+ TRIM(COALESCE(name, '')) AS name,
25
+ TRIM(COALESCE(cmd, '')) AS cmd,
26
+ ${isDelExpr} AS is_del,
27
+ ${userIdExpr} AS user_id
28
+ FROM c_cmd
29
+ WHERE TRIM(COALESCE(id, '')) <> ''
30
+ ORDER BY
31
+ ${activeFirstExpr} ASC,
32
+ ${updateOrderExpr}
33
+ ${insertOrderExpr}
34
+ id DESC
35
+ `, []);
36
+ }
1
37
  export async function listPrompts(db) {
2
38
  const rows = await db.getAll(`
3
39
  SELECT DISTINCT
@@ -16,3 +52,113 @@ export async function listPrompts(db) {
16
52
  }))
17
53
  .filter((row) => row.name.length > 0 && row.cmd.length > 0);
18
54
  }
55
+ export async function addPrompt(db, input) {
56
+ const name = normalizeText(input.name);
57
+ if (!name) {
58
+ throw new DinoxError('Prompt name is required');
59
+ }
60
+ const cmd = normalizeText(input.cmd);
61
+ if (!cmd) {
62
+ throw new DinoxError('Prompt cmd is required');
63
+ }
64
+ const userId = normalizeText(input.userId);
65
+ const nameLookup = normalizeLookup(name);
66
+ const cmdLookup = normalizeLookup(cmd);
67
+ const columnNames = await getPromptColumns(db);
68
+ const requiredColumns = ['id', 'name', 'cmd'];
69
+ const missingRequired = requiredColumns.filter((column) => !columnNames.has(column));
70
+ if (missingRequired.length > 0) {
71
+ throw new DinoxError(`c_cmd table is missing required columns: ${missingRequired.join(', ')}`);
72
+ }
73
+ if (columnNames.has('user_id') && !userId) {
74
+ throw new DinoxError('c_cmd insert requires user_id. Missing persisted userId. Run `dino auth login` first.');
75
+ }
76
+ const rows = await listPromptRowsForWrite(db, columnNames);
77
+ const samePromptRows = rows.filter((row) => {
78
+ return normalizeLookup(row.name ?? '') === nameLookup && normalizeLookup(row.cmd ?? '') === cmdLookup;
79
+ });
80
+ const activeRows = samePromptRows.filter((row) => row.is_del !== 1);
81
+ if (activeRows.length > 0) {
82
+ const ids = activeRows.map((row) => row.id?.trim() ?? '').filter(Boolean);
83
+ throw new DinoxError(`Prompt already exists: ${name}`, {
84
+ details: {
85
+ type: 'prompt_exists',
86
+ name,
87
+ cmd,
88
+ ids,
89
+ question: 'This prompt already exists. Do you want to keep using the existing prompt?',
90
+ },
91
+ });
92
+ }
93
+ const timestamp = new Date().toISOString();
94
+ const deletedRow = samePromptRows.find((row) => (row.id?.trim() ?? '').length > 0);
95
+ if (deletedRow?.id) {
96
+ const setParts = [];
97
+ const params = [];
98
+ const setValue = (column, value) => {
99
+ if (!columnNames.has(column)) {
100
+ return;
101
+ }
102
+ setParts.push(`${column} = ?`);
103
+ params.push(value);
104
+ };
105
+ setValue('name', name);
106
+ setValue('cmd', cmd);
107
+ setValue('is_del', 0);
108
+ setValue('update_at', timestamp);
109
+ setValue('user_id', userId);
110
+ if (setParts.length > 0) {
111
+ params.push(deletedRow.id);
112
+ await db.execute(`
113
+ UPDATE c_cmd
114
+ SET ${setParts.join(', ')}
115
+ WHERE id = ?
116
+ `, params);
117
+ }
118
+ return {
119
+ id: deletedRow.id,
120
+ name,
121
+ cmd,
122
+ restored: true,
123
+ };
124
+ }
125
+ const id = generateId();
126
+ const insertEntries = [];
127
+ const pushInsert = (column, value) => {
128
+ if (!columnNames.has(column)) {
129
+ return;
130
+ }
131
+ insertEntries.push([column, value]);
132
+ };
133
+ pushInsert('id', id);
134
+ pushInsert('name', name);
135
+ pushInsert('cmd', cmd);
136
+ pushInsert('zone', '');
137
+ pushInsert('type', 'custom');
138
+ pushInsert('priority', 0);
139
+ pushInsert('is_show', 1);
140
+ pushInsert('description', '');
141
+ pushInsert('extra_data', '{}');
142
+ pushInsert('user_id', userId);
143
+ pushInsert('insert_at', timestamp);
144
+ pushInsert('update_at', timestamp);
145
+ pushInsert('is_del', 0);
146
+ pushInsert('template_id', '');
147
+ pushInsert('meta_data', '{}');
148
+ pushInsert('output', 'markdown');
149
+ pushInsert('output_param', '{}');
150
+ pushInsert('category', '');
151
+ const insertColumns = insertEntries.map(([column]) => column);
152
+ const placeholders = insertColumns.map(() => '?').join(', ');
153
+ const insertParams = insertEntries.map(([, value]) => value);
154
+ await db.execute(`
155
+ INSERT INTO c_cmd (${insertColumns.join(', ')})
156
+ VALUES (${placeholders})
157
+ `, insertParams);
158
+ return {
159
+ id,
160
+ name,
161
+ cmd,
162
+ restored: false,
163
+ };
164
+ }
@@ -2,8 +2,8 @@ import { loadConfig } from '../config/store.js';
2
2
  import { resolveConfig } from '../config/resolve.js';
3
3
  import { DinoxError } from '../utils/errors.js';
4
4
  import { printYaml } from '../utils/output.js';
5
- import { connectPowerSync, getStatusSnapshot, waitForIdleDataFlow } from '../powersync/runtime.js';
6
- import { syncNoteTokenIndex } from '../powersync/tokenIndex.js';
5
+ import { connectPowerSync, getStatusSnapshot } from '../powersync/runtime.js';
6
+ import { runSingleSyncPass } from '../powersync/syncPass.js';
7
7
  function parseSyncTimeoutMs(value) {
8
8
  if (typeof value !== 'string') {
9
9
  return undefined;
@@ -29,16 +29,15 @@ export function registerSyncCommand(program) {
29
29
  timeoutMs,
30
30
  });
31
31
  try {
32
- const idle = await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
33
- const tokenIndex = await syncNoteTokenIndex(db);
32
+ const syncPass = await runSingleSyncPass(db, timeoutMs);
34
33
  const status = getStatusSnapshot(db);
35
34
  const payload = {
36
35
  ok: true,
37
36
  dbPath,
38
37
  stale,
39
- idle,
38
+ idle: syncPass.idle,
40
39
  uploadEnabled: Boolean(config.powersync.uploadBaseUrl),
41
- tokenIndex,
40
+ tokenIndex: syncPass.tokenIndex,
42
41
  status,
43
42
  };
44
43
  if (jsonOutput) {
@@ -0,0 +1,7 @@
1
+ import type { AbstractPowerSyncDatabase } from '@powersync/common';
2
+ import { type NoteTokenIndexResult } from './tokenIndex.js';
3
+ export type SyncPassResult = {
4
+ idle: boolean;
5
+ tokenIndex: NoteTokenIndexResult;
6
+ };
7
+ export declare function runSingleSyncPass(db: AbstractPowerSyncDatabase, timeoutMs: number): Promise<SyncPassResult>;
@@ -0,0 +1,10 @@
1
+ import { waitForIdleDataFlow } from './runtime.js';
2
+ import { syncNoteTokenIndex } from './tokenIndex.js';
3
+ export async function runSingleSyncPass(db, timeoutMs) {
4
+ const idle = await waitForIdleDataFlow(db, Math.min(timeoutMs, 10_000));
5
+ const tokenIndex = await syncNoteTokenIndex(db);
6
+ return {
7
+ idle,
8
+ tokenIndex,
9
+ };
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dinoxx/dinox-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Dinox CLI",
5
5
  "main": "dist/dinox.js",
6
6
  "scripts": {