gudong-inbox-cli 1.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.
@@ -0,0 +1,274 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { NoteService, ConflictError, extractTagsFromContent } from './service.js';
3
+ const VALID_ID = 'b10a4576df11401c8673';
4
+ function makeNoteJson(id, overrides = {}) {
5
+ return Buffer.from(JSON.stringify({
6
+ id,
7
+ ver: 1,
8
+ meta: {
9
+ created_at: '2026-06-01T00:00:00.000Z',
10
+ updated_at: overrides.updated_at ?? '2026-06-02T00:00:00.000Z',
11
+ device_id: 'test',
12
+ },
13
+ content: {
14
+ content: overrides.content ?? 'hello world',
15
+ title: overrides.title ?? null,
16
+ tags: overrides.tags ?? [],
17
+ links: [],
18
+ assets: [],
19
+ },
20
+ flags: {
21
+ is_removed: overrides.is_removed ?? false,
22
+ is_top: false,
23
+ favorite: false,
24
+ },
25
+ parent_id: overrides.parent_id ?? null,
26
+ }));
27
+ }
28
+ function makeNoteFile(id) {
29
+ return { id, key: `inBox/notes/note-${id}.json` };
30
+ }
31
+ /**
32
+ * In-memory mock cloud provider for testing.
33
+ * Stores notes in a Map and simulates read/write/list operations.
34
+ */
35
+ class MockCloud {
36
+ type = 's3';
37
+ store = new Map();
38
+ constructor(notes = []) {
39
+ for (const n of notes) {
40
+ this.store.set(`inBox/notes/note-${n.id}.json`, n.data);
41
+ }
42
+ }
43
+ async listNotes(notesDir) {
44
+ const prefix = `${notesDir}/`;
45
+ const out = [];
46
+ for (const key of this.store.keys()) {
47
+ if (!key.startsWith(prefix))
48
+ continue;
49
+ const m = key.match(/note-([a-f0-9]{20})\.json$/);
50
+ if (m)
51
+ out.push({ id: m[1], key });
52
+ }
53
+ return out;
54
+ }
55
+ async readNote(key) {
56
+ const buf = this.store.get(key);
57
+ if (!buf)
58
+ throw new Error(`not found: ${key}`);
59
+ return buf;
60
+ }
61
+ async writeNote(key, data) {
62
+ this.store.set(key, data);
63
+ }
64
+ }
65
+ describe('extractTagsFromContent', () => {
66
+ it('extracts simple tags', () => {
67
+ expect(extractTagsFromContent('hello #idea world')).toEqual(['idea']);
68
+ });
69
+ it('extracts hierarchical tags', () => {
70
+ expect(extractTagsFromContent('#work/project/alpha')).toEqual([
71
+ 'work/project/alpha',
72
+ ]);
73
+ });
74
+ it('extracts Chinese tags', () => {
75
+ expect(extractTagsFromContent('#标签 测试')).toEqual(['标签']);
76
+ });
77
+ it('deduplicates tags', () => {
78
+ expect(extractTagsFromContent('#idea #idea #work')).toEqual(['idea', 'work']);
79
+ });
80
+ it('does not match email @ or plain hash', () => {
81
+ expect(extractTagsFromContent('test@example.com #')).toEqual([]);
82
+ });
83
+ });
84
+ describe('NoteService', () => {
85
+ let cloud;
86
+ let svc;
87
+ beforeEach(() => {
88
+ cloud = new MockCloud([
89
+ { id: VALID_ID, data: makeNoteJson(VALID_ID, { tags: ['old'] }) },
90
+ ]);
91
+ svc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 0 });
92
+ });
93
+ describe('listRecent', () => {
94
+ it('returns non-removed notes sorted by updated_at desc', async () => {
95
+ cloud = new MockCloud([
96
+ {
97
+ id: 'a'.repeat(20),
98
+ data: makeNoteJson('a'.repeat(20), { updated_at: '2026-06-01T00:00:00.000Z' }),
99
+ },
100
+ {
101
+ id: 'b'.repeat(20),
102
+ data: makeNoteJson('b'.repeat(20), { updated_at: '2026-06-03T00:00:00.000Z' }),
103
+ },
104
+ {
105
+ id: 'c'.repeat(20),
106
+ data: makeNoteJson('c'.repeat(20), { updated_at: '2026-06-02T00:00:00.000Z' }),
107
+ },
108
+ ]);
109
+ svc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 0 });
110
+ const notes = await svc.listRecent({ limit: 10 });
111
+ expect(notes).toHaveLength(3);
112
+ expect(notes[0].id).toBe('b'.repeat(20));
113
+ expect(notes[1].id).toBe('c'.repeat(20));
114
+ expect(notes[2].id).toBe('a'.repeat(20));
115
+ });
116
+ it('excludes removed notes', async () => {
117
+ cloud = new MockCloud([
118
+ { id: 'a'.repeat(20), data: makeNoteJson('a'.repeat(20), { is_removed: false }) },
119
+ { id: 'b'.repeat(20), data: makeNoteJson('b'.repeat(20), { is_removed: true }) },
120
+ ]);
121
+ svc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 0 });
122
+ const notes = await svc.listRecent({});
123
+ expect(notes).toHaveLength(1);
124
+ expect(notes[0].id).toBe('a'.repeat(20));
125
+ });
126
+ });
127
+ describe('search', () => {
128
+ it('matches in content', async () => {
129
+ cloud = new MockCloud([
130
+ { id: 'a'.repeat(20), data: makeNoteJson('a'.repeat(20), { content: 'Flutter tips' }) },
131
+ { id: 'b'.repeat(20), data: makeNoteJson('b'.repeat(20), { content: 'nothing here' }) },
132
+ ]);
133
+ svc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 0 });
134
+ const notes = await svc.search('flutter');
135
+ expect(notes).toHaveLength(1);
136
+ expect(notes[0].id).toBe('a'.repeat(20));
137
+ });
138
+ });
139
+ describe('listTags', () => {
140
+ it('counts tags across all non-removed notes', async () => {
141
+ cloud = new MockCloud([
142
+ { id: 'a'.repeat(20), data: makeNoteJson('a'.repeat(20), { tags: ['work', 'idea'] }) },
143
+ { id: 'b'.repeat(20), data: makeNoteJson('b'.repeat(20), { tags: ['idea', 'work'] }) },
144
+ { id: 'c'.repeat(20), data: makeNoteJson('c'.repeat(20), { tags: ['idea'], is_removed: true }) },
145
+ ]);
146
+ svc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 0 });
147
+ const tags = await svc.listTags();
148
+ const tagMap = new Map(tags.map((t) => [t.tag, t.count]));
149
+ expect(tagMap.get('idea')).toBe(2); // c is removed
150
+ expect(tagMap.get('work')).toBe(2);
151
+ });
152
+ });
153
+ describe('createNote', () => {
154
+ it('creates a note with merged tags from content', async () => {
155
+ const result = await svc.createNote({
156
+ content: 'hello #flutter world',
157
+ tags: ['explicit'],
158
+ });
159
+ // Read it back
160
+ const note = await svc.readById(result.id);
161
+ expect(note).not.toBeNull();
162
+ expect(note.tags).toContain('explicit');
163
+ expect(note.tags).toContain('flutter');
164
+ });
165
+ it('generates a 20-hex-char id', async () => {
166
+ const result = await svc.createNote({ content: 'test' });
167
+ expect(result.id).toMatch(/^[a-f0-9]{20}$/);
168
+ });
169
+ });
170
+ describe('updateNote', () => {
171
+ it('updates content and preserves existing tags when --tag not given', async () => {
172
+ await svc.updateNote({
173
+ id: VALID_ID,
174
+ content: 'updated #newtag content',
175
+ });
176
+ const note = await svc.readById(VALID_ID);
177
+ expect(note.content).toBe('updated #newtag content');
178
+ // Without explicit --tag, existing tags are preserved (#newtag is NOT auto-added)
179
+ expect(note.tags).toEqual(['old']);
180
+ });
181
+ it('merges explicit tags with content-extracted tags when --tag given', async () => {
182
+ await svc.updateNote({
183
+ id: VALID_ID,
184
+ content: 'updated #newtag content',
185
+ tags: ['explicit'],
186
+ });
187
+ const note = await svc.readById(VALID_ID);
188
+ expect(note.tags).toContain('explicit');
189
+ expect(note.tags).toContain('newtag');
190
+ });
191
+ it('preserves fields that are not provided', async () => {
192
+ await svc.updateNote({ id: VALID_ID, title: 'New Title' });
193
+ const note = await svc.readById(VALID_ID);
194
+ expect(note.title).toBe('New Title');
195
+ expect(note.content).toBe('hello world');
196
+ });
197
+ it('throws ConflictError when remote is newer', async () => {
198
+ // Use a service with cache enabled, so fetchAll returns the old cached version.
199
+ // We then modify the cloud store behind its back to simulate a remote write.
200
+ const cachedSvc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 60_000 });
201
+ await cachedSvc.fetchAll(); // prime cache with old data
202
+ const newerJson = makeNoteJson(VALID_ID, {
203
+ updated_at: '2026-06-10T00:00:00.000Z',
204
+ content: 'remote change',
205
+ });
206
+ cloud.store.set(`inBox/notes/note-${VALID_ID}.json`, newerJson);
207
+ await expect(cachedSvc.updateNote({ id: VALID_ID, content: 'local change' })).rejects.toThrow(ConflictError);
208
+ });
209
+ it('succeeds when remote is same or older', async () => {
210
+ // Remote updated_at is same as cached — should be fine
211
+ await svc.updateNote({ id: VALID_ID, content: 'safe update' });
212
+ const note = await svc.readById(VALID_ID);
213
+ expect(note.content).toBe('safe update');
214
+ });
215
+ });
216
+ describe('deleteNote', () => {
217
+ it('soft-deletes without clearing content', async () => {
218
+ await svc.deleteNote(VALID_ID);
219
+ const note = await svc.readById(VALID_ID);
220
+ expect(note.is_removed).toBe(true);
221
+ expect(note.content).toBe('hello world'); // content preserved
222
+ });
223
+ it('is idempotent for already-removed notes', async () => {
224
+ await svc.deleteNote(VALID_ID);
225
+ const result = await svc.deleteNote(VALID_ID);
226
+ expect(result.deleted).toBe(false);
227
+ });
228
+ it('throws ConflictError when remote is newer', async () => {
229
+ const cachedSvc2 = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 60_000 });
230
+ await cachedSvc2.fetchAll(); // prime cache with old data
231
+ cloud.store.set(`inBox/notes/note-${VALID_ID}.json`, makeNoteJson(VALID_ID, {
232
+ updated_at: '2026-06-10T00:00:00.000Z',
233
+ content: 'remote change',
234
+ }));
235
+ await expect(cachedSvc2.deleteNote(VALID_ID)).rejects.toThrow(ConflictError);
236
+ });
237
+ });
238
+ describe('cache stays sorted after writes', () => {
239
+ it('createNote puts the new note at the front of cache', async () => {
240
+ const cachedSvc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 60_000 });
241
+ await cachedSvc.fetchAll();
242
+ const result = await cachedSvc.createNote({ content: 'fresh note' });
243
+ // listRecent with limit 1 must return the freshly created note
244
+ const recent = await cachedSvc.listRecent({ limit: 1 });
245
+ expect(recent).toHaveLength(1);
246
+ expect(recent[0].id).toBe(result.id);
247
+ });
248
+ it('updateNote moves the updated note to the front of cache', async () => {
249
+ cloud = new MockCloud([
250
+ { id: 'a'.repeat(20), data: makeNoteJson('a'.repeat(20), { updated_at: '2026-06-01T00:00:00.000Z' }) },
251
+ { id: 'b'.repeat(20), data: makeNoteJson('b'.repeat(20), { updated_at: '2026-06-03T00:00:00.000Z' }) },
252
+ ]);
253
+ const cachedSvc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 60_000 });
254
+ await cachedSvc.fetchAll();
255
+ await cachedSvc.updateNote({ id: 'a'.repeat(20), content: 'updated' });
256
+ const recent = await cachedSvc.listRecent({ limit: 1 });
257
+ expect(recent[0].id).toBe('a'.repeat(20));
258
+ });
259
+ it('deleteNote moves the soft-deleted note to the front of cache', async () => {
260
+ cloud = new MockCloud([
261
+ { id: 'a'.repeat(20), data: makeNoteJson('a'.repeat(20), { updated_at: '2026-06-01T00:00:00.000Z' }) },
262
+ { id: 'b'.repeat(20), data: makeNoteJson('b'.repeat(20), { updated_at: '2026-06-03T00:00:00.000Z' }) },
263
+ ]);
264
+ const cachedSvc = new NoteService(cloud, 'inBox/notes', { cacheTtlMs: 60_000 });
265
+ await cachedSvc.fetchAll();
266
+ await cachedSvc.deleteNote('a'.repeat(20));
267
+ // Soft-deleted note is at front of cache array
268
+ const allCached = await cachedSvc.fetchAll();
269
+ expect(allCached[0].id).toBe('a'.repeat(20));
270
+ expect(allCached[0].flags.is_removed).toBe(true);
271
+ });
272
+ });
273
+ });
274
+ //# sourceMappingURL=service.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.test.js","sourceRoot":"","sources":["../../src/note/service.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ export type AssetType = 'image' | 'video' | 'audio' | 'attachment';
2
+ export type AssetStorageType = 'local' | 'webdav' | 's3' | 'minio' | 'aliyun' | 'tencent' | 'generic';
3
+ export interface NoteLink {
4
+ text?: string | null;
5
+ targetId: string;
6
+ }
7
+ export interface NoteAsset {
8
+ type: AssetType;
9
+ path: string;
10
+ storageType: AssetStorageType;
11
+ cloudUrl?: string | null;
12
+ mime_type: string;
13
+ size: number;
14
+ width?: number | null;
15
+ height?: number | null;
16
+ duration?: number | null;
17
+ }
18
+ export interface NoteMeta {
19
+ created_at: string;
20
+ updated_at: string;
21
+ device_id: string;
22
+ }
23
+ export interface NoteContent {
24
+ content: string;
25
+ title?: string | null;
26
+ tags: string[];
27
+ links: NoteLink[];
28
+ assets: NoteAsset[];
29
+ box?: string | null;
30
+ }
31
+ export interface NoteFlags {
32
+ is_removed: boolean;
33
+ is_top: boolean;
34
+ favorite: boolean;
35
+ }
36
+ export interface NoteJson {
37
+ id: string;
38
+ ver: number;
39
+ meta: NoteMeta;
40
+ content: NoteContent;
41
+ flags: NoteFlags;
42
+ parent_id?: string | null;
43
+ }
44
+ /** Format a UTC ISO string into a human-readable local time (Asia/Shanghai, UTC+8). */
45
+ export declare function toLocalTime(iso: string | null | undefined): string | null;
46
+ export interface NoteSummary {
47
+ id: string;
48
+ title: string | null;
49
+ updated_at: string;
50
+ updated_at_local: string | null;
51
+ tags: string[];
52
+ content_preview: string;
53
+ }
54
+ export interface NoteDetail {
55
+ id: string;
56
+ title: string | null;
57
+ content: string;
58
+ tags: string[];
59
+ links: NoteLink[];
60
+ assets: Array<Pick<NoteAsset, 'type' | 'path' | 'size' | 'mime_type' | 'cloudUrl' | 'storageType'>>;
61
+ created_at: string;
62
+ updated_at: string;
63
+ created_at_local: string | null;
64
+ updated_at_local: string | null;
65
+ is_top: boolean;
66
+ favorite: boolean;
67
+ is_removed: boolean;
68
+ }
69
+ export interface TagCount {
70
+ tag: string;
71
+ count: number;
72
+ }
@@ -0,0 +1,16 @@
1
+ /** Format a UTC ISO string into a human-readable local time (Asia/Shanghai, UTC+8). */
2
+ export function toLocalTime(iso) {
3
+ if (!iso)
4
+ return null;
5
+ const ms = Date.parse(iso);
6
+ if (!Number.isFinite(ms))
7
+ return null;
8
+ const dt = new Date(ms + 8 * 3600 * 1000); // shift to UTC+8
9
+ const y = dt.getUTCFullYear();
10
+ const m = String(dt.getUTCMonth() + 1).padStart(2, '0');
11
+ const d = String(dt.getUTCDate()).padStart(2, '0');
12
+ const h = String(dt.getUTCHours()).padStart(2, '0');
13
+ const min = String(dt.getUTCMinutes()).padStart(2, '0');
14
+ return `${y}-${m}-${d} ${h}:${min}`;
15
+ }
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/note/types.ts"],"names":[],"mappings":"AAyDA,uFAAuF;AACvF,MAAM,UAAU,WAAW,CAAC,GAA8B;IACxD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,iBAAiB;IAC5D,MAAM,CAAC,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC;IAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;AACtC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "gudong-inbox-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool that lets AI agents read and write your inBox notes from S3/WebDAV. No MCP needed, just a command.",
5
+ "type": "module",
6
+ "bin": {
7
+ "gudong-inbox": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE",
13
+ "skill"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "dev": "tsx src/index.ts",
18
+ "start": "node dist/index.js",
19
+ "test": "vitest run",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "inbox",
24
+ "notes",
25
+ "cli",
26
+ "s3",
27
+ "webdav",
28
+ "local-first",
29
+ "ai",
30
+ "agent"
31
+ ],
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "dependencies": {
37
+ "@aws-sdk/client-s3": "^3.700.0",
38
+ "webdav": "^5.8.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "prettier": "^3.3.0",
43
+ "tsx": "^4.19.0",
44
+ "typescript": "^5.6.0",
45
+ "vitest": "^2.1.0"
46
+ }
47
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: gudong-inbox
3
+ description: 读取和管理 inBox 笔记。当用户问笔记、记录、灵感、知识库相关问题时使用此 skill。如"看看笔记""今天记了什么""搜索关于XX的笔记"等。
4
+ version: 1.0.0
5
+ ---
6
+
7
+ # inBox 笔记读取器
8
+
9
+ 通过 `gudong-inbox` 命令行工具读取和管理用户的 inBox 笔记(本地优先笔记 App,通过 S3/WebDAV 云存储同步)。
10
+
11
+ ## 前置检查
12
+
13
+ 首次使用前,先检查 CLI 是否安装、配置是否就绪:
14
+
15
+ ```bash
16
+ gudong-inbox --version # 确认 CLI 已安装
17
+ gudong-inbox config show # 查看当前配置(密钥已脱敏)
18
+ ```
19
+
20
+ 如果提示命令找不到或配置不存在,走「安装和配置」流程。
21
+
22
+ ## 命令清单
23
+
24
+ | 操作 | 命令 | 用途 |
25
+ ------|------|------|
26
+ | 列出笔记 | `gudong-inbox list` | 按时间列出笔记摘要,支持过滤 |
27
+ | 读取笔记 | `gudong-inbox read --id <id>` | 读取单条笔记完整内容 |
28
+ | 搜索笔记 | `gudong-inbox search --query "关键词"` | 在标题、正文、标签中搜索 |
29
+ | 查看标签 | `gudong-inbox tags` | 列出所有标签及使用次数 |
30
+ | 创建笔记 | `gudong-inbox create --content "内容"` | 写入新笔记到用户的 inBox |
31
+ | 更新笔记 | `gudong-inbox update --id <id>` | 部分更新已有笔记 |
32
+ | 删除笔记 | `gudong-inbox delete --id <id> --confirm` | 软删除笔记(需确认) |
33
+ | 查看配置 | `gudong-inbox config show` | 查看当前配置(密钥已脱敏) |
34
+ | 测试连接 | `gudong-inbox config test` | 测试云存储连接是否正常 |
35
+
36
+ ## 参数速查
37
+
38
+ ### list
39
+ `--limit`(默认 20)、`--since`(ISO-8601)、`--until`(ISO-8601)、`--field`(`updated_at` 或 `created_at`,默认 `updated_at`)
40
+
41
+ **查今天创建的笔记**:
42
+ ```bash
43
+ gudong-inbox list --since 2026-06-25T00:00:00+08:00 --field created_at
44
+ ```
45
+
46
+ ### read
47
+ `--id`(必填,20 位 hex 格式)
48
+
49
+ 从 `list` 或 `search` 结果中拿到 `id` 字段后使用。
50
+
51
+ ### search
52
+ `--query`(必填)、`--limit`(默认 20)
53
+
54
+ ### create
55
+ `--content`(必填)、`--title`(可选)、`--tag`(可重复)、`--box`(可选)
56
+
57
+ ### update
58
+ `--id`(必填)、`--content`、`--title`、`--tag`、`--box`(只改传入的字段)
59
+
60
+ ### delete
61
+ `--id`(必填)、`--confirm`(必填,必须显式传 true)
62
+
63
+ ## 使用建议
64
+
65
+ 1. **先 list 再 read**:`list` 返回摘要(含 200 字预览),需要看全文再用 `read`。
66
+ 2. **时间过滤**:用户说"今天"时,用上海时区 0 点作为 `--since`。注意 ISO-8601 要带时区偏移(`+08:00`)。
67
+ 3. **搜索比 list 灵活**:不确定日期时,直接用 `search` 按关键词找。
68
+ 4. **所有命令输出都是 JSON**:直接解析 stdout 的 JSON 即可。
69
+
70
+ ## 输出格式
71
+
72
+ 所有命令成功时 exit code = 0,stdout 输出 JSON。失败时 exit code = 1,stderr 输出错误信息。
73
+
74
+ ## 安装和配置
75
+
76
+ 如果 `gudong-inbox` 未安装或未配置:
77
+
78
+ ```bash
79
+ # 安装 CLI
80
+ npm install -g gudong-inbox-cli
81
+
82
+ # 配置(S3 示例)
83
+ gudong-inbox config init \
84
+ --cloud-type s3 \
85
+ --endpoint https://s3.example.com \
86
+ --bucket my-bucket \
87
+ --access-key AKID... \
88
+ --secret-key SECRET... \
89
+ --region cn-south-1 \
90
+ --work-dir inBox
91
+
92
+ # 验证
93
+ gudong-inbox config test
94
+ ```
95
+
96
+ 配置保存在 `~/.gudong-inbox/config.json`,也支持环境变量(`CLOUD_TYPE`、`S3_ENDPOINT` 等)作为 fallback。
97
+
98
+ ## 创建笔记的两阶段确认
99
+
100
+ 创建、更新、删除笔记属于写操作。虽然 CLI 本身不需要 confirmation token,但建议在 AI 对话中遵循两阶段确认:
101
+
102
+ 1. 先向用户展示要写入的内容摘要
103
+ 2. 用户确认后再执行命令