@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 +17 -8
- package/dist/commands/auth/index.js +3 -16
- 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/powersync/syncPass.d.ts +7 -0
- package/dist/powersync/syncPass.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ dino info
|
|
|
88
88
|
## 第 1 步:登录
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
dino auth login "
|
|
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 "
|
|
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 <
|
|
285
|
+
### `dino auth login <token>`
|
|
285
286
|
|
|
286
287
|
保存授权信息并验证连接。
|
|
287
288
|
|
|
288
289
|
| 参数 | 必填 | 说明 |
|
|
289
290
|
| --- | --- | --- |
|
|
290
|
-
| `<
|
|
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
|
-
.
|
|
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 {
|
|
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
|
+
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
|
6
|
-
import {
|
|
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
|
|
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
|
+
}
|