docz-cli 0.4.0 → 0.6.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 +10 -8
- package/dist/{chunk-WH5WLKFJ.js → chunk-XUT76ACQ.js} +314 -65
- package/dist/chunk-XUT76ACQ.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-OHEL4DCQ.js +685 -0
- package/dist/mcp-OHEL4DCQ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-WH5WLKFJ.js.map +0 -1
- package/dist/mcp-PAPGDW5K.js +0 -367
- package/dist/mcp-PAPGDW5K.js.map +0 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ npx docz-cli@latest write 吴鹏飞:notes/todo.md '# TODO List'
|
|
|
29
29
|
## Features
|
|
30
30
|
|
|
31
31
|
- **Simple addressing** — `<space>:<path>` format, Space supports name or ID
|
|
32
|
-
- **Short URL support** — paste `https://docz.xxx.com/s/slug/f/fileId` directly into cat/ls/log
|
|
32
|
+
- **Short URL support** — paste `https://docz.xxx.com/s/slug/f/fileId` directly into cat/ls/log, or generate with `shortlink`
|
|
33
33
|
- **Full file operations** — ls, cat, upload, write, mkdir, rm, mv
|
|
34
34
|
- **Share links** — create, list, update, access, delete share links from CLI
|
|
35
35
|
- **File diff** — view file-level unified diff or space-level change summary
|
|
@@ -84,6 +84,7 @@ export DOCSYNC_API_TOKEN=<your-token>
|
|
|
84
84
|
| `rm <space>:<path>` | Delete file/folder (30-day trash) |
|
|
85
85
|
| `mv <space>:<from> <to>` | Rename or move |
|
|
86
86
|
| `log <space>[:<path>]` | Show change history |
|
|
87
|
+
| `shortlink <space>:<path>` | Get short URL for file |
|
|
87
88
|
| `trash <space>` | Show deleted files |
|
|
88
89
|
| `diff <space>[:<path>] <commit> [<from>]` | Show changes (file or space level) |
|
|
89
90
|
| `share create <space>:<path>` | Create share link |
|
|
@@ -107,17 +108,16 @@ docz-cli cat 研发:docs/guide.md # Read file content
|
|
|
107
108
|
|
|
108
109
|
### Short URL
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
Generate a short URL for any file, or paste existing short URLs into cat/ls/log:
|
|
111
112
|
|
|
112
113
|
```bash
|
|
113
|
-
#
|
|
114
|
-
docz-cli
|
|
115
|
-
|
|
114
|
+
# Generate short URL
|
|
115
|
+
docz-cli shortlink 闫洪康:AI-Coding技巧总结12.md
|
|
116
|
+
# → https://docz.zhenguanyu.com/s/yanhongkang/f/NNjrcj8c
|
|
116
117
|
|
|
117
|
-
#
|
|
118
|
+
# Short URLs work with cat, ls, log
|
|
119
|
+
docz-cli cat https://docz.zhenguanyu.com/s/yanhongkang/f/NNjrcj8c
|
|
118
120
|
docz-cli ls https://docz.zhenguanyu.com/s/yanfa
|
|
119
|
-
|
|
120
|
-
# File history via short URL
|
|
121
121
|
docz-cli log https://docz.zhenguanyu.com/s/yanhongkang/f/NNjrcj8c
|
|
122
122
|
```
|
|
123
123
|
|
|
@@ -239,6 +239,7 @@ Add to your MCP settings:
|
|
|
239
239
|
| `docz_share_read` | Read shared file by token |
|
|
240
240
|
| `docz_share_info` | View share link info |
|
|
241
241
|
| `docz_share_delete` | Delete share link |
|
|
242
|
+
| `docz_shortlink` | Get short URL for file |
|
|
242
243
|
| `docz_diff` | View file or space diff |
|
|
243
244
|
|
|
244
245
|
## AI Agent Skill
|
|
@@ -273,6 +274,7 @@ docz-cli wraps the DocSync REST API:
|
|
|
273
274
|
| `share cat` | `GET /api/share/{token}` |
|
|
274
275
|
| `share info` | `GET /api/share/{token}/info` |
|
|
275
276
|
| `share rm` | `DELETE /api/spaces/{id}/share-links/{linkId}` |
|
|
277
|
+
| `shortlink` | `GET /api/spaces/{id}/file-ref?path=` |
|
|
276
278
|
| Short URL resolve | `GET /api/spaces/by-slug/{slug}` + `GET /api/file-refs/{fileId}` |
|
|
277
279
|
|
|
278
280
|
Auth: `Authorization: Bearer <token>`. Backend is Git — every write is a commit.
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/client.ts
|
|
4
|
+
var ConflictError = class extends Error {
|
|
5
|
+
constructor(detail) {
|
|
6
|
+
super(
|
|
7
|
+
"Conflict: file has been modified by others. Please re-read the latest content and re-apply your changes."
|
|
8
|
+
);
|
|
9
|
+
this.detail = detail;
|
|
10
|
+
this.name = "ConflictError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
4
13
|
var DocSyncClient = class {
|
|
5
14
|
constructor(baseUrl, token) {
|
|
6
15
|
this.baseUrl = baseUrl;
|
|
@@ -48,12 +57,28 @@ var DocSyncClient = class {
|
|
|
48
57
|
`/api/spaces/${spaceId}/tree?path=${encodeURIComponent(path)}`
|
|
49
58
|
);
|
|
50
59
|
}
|
|
60
|
+
async treeFull(spaceId) {
|
|
61
|
+
return this.request(`/api/spaces/${spaceId}/tree/full`);
|
|
62
|
+
}
|
|
51
63
|
// --- Blob ---
|
|
52
64
|
async cat(spaceId, filepath) {
|
|
53
65
|
return this.request(
|
|
54
66
|
`/api/spaces/${spaceId}/blob/${encodeURIComponent(filepath)}`
|
|
55
67
|
);
|
|
56
68
|
}
|
|
69
|
+
async catWithRef(spaceId, filepath) {
|
|
70
|
+
const res = await fetch(
|
|
71
|
+
`${this.baseUrl}/api/spaces/${spaceId}/blob/${encodeURIComponent(filepath)}`,
|
|
72
|
+
{ headers: { Authorization: `Bearer ${this.token}` } }
|
|
73
|
+
);
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
const body = await res.text().catch(() => "");
|
|
76
|
+
throw new Error(`${res.status} ${res.statusText}: ${body}`.trim());
|
|
77
|
+
}
|
|
78
|
+
const content = await res.text();
|
|
79
|
+
const ref = res.headers.get("X-Git-Ref") ?? "";
|
|
80
|
+
return { content, ref };
|
|
81
|
+
}
|
|
57
82
|
// --- Write ---
|
|
58
83
|
async upload(spaceId, dir, filename, content) {
|
|
59
84
|
const blob = typeof content === "string" ? new Blob([content], { type: "application/octet-stream" }) : new Blob([content], { type: "application/octet-stream" });
|
|
@@ -65,6 +90,31 @@ var DocSyncClient = class {
|
|
|
65
90
|
body: form
|
|
66
91
|
});
|
|
67
92
|
}
|
|
93
|
+
async save(spaceId, path, content, opts) {
|
|
94
|
+
const body = { path, content };
|
|
95
|
+
if (opts?.baseRef) body.base_ref = opts.baseRef;
|
|
96
|
+
if (opts?.message) body.message = opts.message;
|
|
97
|
+
const res = await fetch(
|
|
98
|
+
`${this.baseUrl}/api/spaces/${spaceId}/files/save`,
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${this.token}`,
|
|
103
|
+
"Content-Type": "application/json"
|
|
104
|
+
},
|
|
105
|
+
body: JSON.stringify(body)
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
if (res.status === 409) {
|
|
109
|
+
const data = await res.json();
|
|
110
|
+
throw new ConflictError(data);
|
|
111
|
+
}
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
const text = await res.text().catch(() => "");
|
|
114
|
+
throw new Error(`${res.status} ${res.statusText}: ${text}`.trim());
|
|
115
|
+
}
|
|
116
|
+
return res.json();
|
|
117
|
+
}
|
|
68
118
|
async mkdir(spaceId, path) {
|
|
69
119
|
await this.request(`/api/spaces/${spaceId}/files/mkdir`, {
|
|
70
120
|
method: "POST",
|
|
@@ -86,6 +136,13 @@ var DocSyncClient = class {
|
|
|
86
136
|
body: JSON.stringify({ old_path: from, new_path: to })
|
|
87
137
|
});
|
|
88
138
|
}
|
|
139
|
+
async rollback(spaceId, filePath, commitHash) {
|
|
140
|
+
await this.request(`/api/spaces/${spaceId}/files/rollback`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: { "Content-Type": "application/json" },
|
|
143
|
+
body: JSON.stringify({ file_path: filePath, commit_hash: commitHash })
|
|
144
|
+
});
|
|
145
|
+
}
|
|
89
146
|
// --- History ---
|
|
90
147
|
async log(spaceId, filepath) {
|
|
91
148
|
const path = filepath ? `/api/spaces/${spaceId}/log/${encodeURIComponent(filepath)}` : `/api/spaces/${spaceId}/log/`;
|
|
@@ -95,6 +152,48 @@ var DocSyncClient = class {
|
|
|
95
152
|
async trash(spaceId) {
|
|
96
153
|
return this.request(`/api/spaces/${spaceId}/trash`);
|
|
97
154
|
}
|
|
155
|
+
async restore(spaceId, path, commit) {
|
|
156
|
+
await this.request(`/api/spaces/${spaceId}/trash/restore`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "Content-Type": "application/json" },
|
|
159
|
+
body: JSON.stringify({ path, commit })
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// --- Comments ---
|
|
163
|
+
async listComments(spaceId, filePath) {
|
|
164
|
+
return this.request(
|
|
165
|
+
`/api/spaces/${spaceId}/comments?path=${encodeURIComponent(filePath)}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
async createComment(spaceId, filePath, content) {
|
|
169
|
+
return this.request(`/api/spaces/${spaceId}/comments`, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "Content-Type": "application/json" },
|
|
172
|
+
body: JSON.stringify({ file_path: filePath, content })
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async replyComment(spaceId, commentId, content) {
|
|
176
|
+
return this.request(
|
|
177
|
+
`/api/spaces/${spaceId}/comments/${commentId}/replies`,
|
|
178
|
+
{
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json" },
|
|
181
|
+
body: JSON.stringify({ content })
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
async closeComment(spaceId, commentId) {
|
|
186
|
+
return this.request(`/api/spaces/${spaceId}/comments/${commentId}`, {
|
|
187
|
+
method: "PUT",
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: JSON.stringify({ is_closed: true })
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async deleteComment(spaceId, commentId) {
|
|
193
|
+
await this.request(`/api/spaces/${spaceId}/comments/${commentId}`, {
|
|
194
|
+
method: "DELETE"
|
|
195
|
+
});
|
|
196
|
+
}
|
|
98
197
|
// --- Share Links ---
|
|
99
198
|
async createShareLink(spaceId, filePath, opts) {
|
|
100
199
|
const body = { file_path: filePath };
|
|
@@ -137,13 +236,21 @@ var DocSyncClient = class {
|
|
|
137
236
|
async diffFile(spaceId, filePath, to, from) {
|
|
138
237
|
const params = new URLSearchParams({ to });
|
|
139
238
|
if (from) params.set("from", from);
|
|
140
|
-
return this.request(
|
|
239
|
+
return this.request(
|
|
240
|
+
`/api/spaces/${spaceId}/diff/${encodeURIComponent(filePath)}?${params}`
|
|
241
|
+
);
|
|
141
242
|
}
|
|
142
243
|
async diffSummary(spaceId, to, from) {
|
|
143
244
|
const params = new URLSearchParams({ to });
|
|
144
245
|
if (from) params.set("from", from);
|
|
145
246
|
return this.request(`/api/spaces/${spaceId}/diff?${params}`);
|
|
146
247
|
}
|
|
248
|
+
// --- File Ref ---
|
|
249
|
+
async getFileRef(spaceId, path) {
|
|
250
|
+
return this.request(
|
|
251
|
+
`/api/spaces/${spaceId}/file-ref?path=${encodeURIComponent(path)}`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
147
254
|
// --- Resolve short URLs ---
|
|
148
255
|
async resolveBySlug(slug) {
|
|
149
256
|
return this.request(`/api/spaces/by-slug/${encodeURIComponent(slug)}`);
|
|
@@ -186,7 +293,7 @@ function getConfigPath() {
|
|
|
186
293
|
|
|
187
294
|
// src/commands.ts
|
|
188
295
|
import { readFileSync as readFileSync2 } from "fs";
|
|
189
|
-
import { basename
|
|
296
|
+
import { basename } from "path";
|
|
190
297
|
function getClient() {
|
|
191
298
|
const token = getToken();
|
|
192
299
|
if (!token) {
|
|
@@ -256,7 +363,10 @@ async function resolveTarget(client, args) {
|
|
|
256
363
|
}
|
|
257
364
|
function parseExpires(value) {
|
|
258
365
|
const match = value.match(/^(\d+)([dh])$/);
|
|
259
|
-
if (!match)
|
|
366
|
+
if (!match)
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Invalid expires format: "${value}". Use e.g. 7d, 24h, 30d`
|
|
369
|
+
);
|
|
260
370
|
const [, num, unit] = match;
|
|
261
371
|
const ms = unit === "d" ? Number(num) * 864e5 : Number(num) * 36e5;
|
|
262
372
|
return new Date(Date.now() + ms).toISOString();
|
|
@@ -266,6 +376,14 @@ function extractShareToken(input) {
|
|
|
266
376
|
if (m) return m[1];
|
|
267
377
|
return input;
|
|
268
378
|
}
|
|
379
|
+
async function readStdin() {
|
|
380
|
+
const chunks = [];
|
|
381
|
+
for await (const chunk of process.stdin) {
|
|
382
|
+
chunks.push(chunk);
|
|
383
|
+
}
|
|
384
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
385
|
+
}
|
|
386
|
+
var MAX_SAVE_SIZE = 2 * 1024 * 1024;
|
|
269
387
|
function registerCommands(program) {
|
|
270
388
|
program.command("login").description("Configure DocSync credentials").option("-u, --url <url>", "DocSync server URL").option("-t, --token <token>", "API token").action(async (opts) => {
|
|
271
389
|
const url = opts.url ?? getBaseUrl();
|
|
@@ -302,10 +420,10 @@ function registerCommands(program) {
|
|
|
302
420
|
console.log(`${s.name} ${tag} ${s.member_count} members ${s.id}`);
|
|
303
421
|
}
|
|
304
422
|
});
|
|
305
|
-
program.command("ls").description("List files \u2014 docz ls <space>[:<path>] or <url>").argument("<target...>").action(async (args) => {
|
|
423
|
+
program.command("ls").description("List files \u2014 docz ls <space>[:<path>] or <url>").argument("<target...>").option("-R, --recursive", "Recursively list all files").action(async (args, opts) => {
|
|
306
424
|
const client = getClient();
|
|
307
425
|
const { spaceId, path } = await resolveTarget(client, args);
|
|
308
|
-
const entries = await client.ls(spaceId, path);
|
|
426
|
+
const entries = opts.recursive ? await client.treeFull(spaceId) : await client.ls(spaceId, path);
|
|
309
427
|
if (entries.length === 0) {
|
|
310
428
|
console.log("(empty)");
|
|
311
429
|
return;
|
|
@@ -318,7 +436,7 @@ function registerCommands(program) {
|
|
|
318
436
|
}
|
|
319
437
|
}
|
|
320
438
|
});
|
|
321
|
-
program.command("cat").description("Read file content \u2014 docz cat <space>:<path> or <url>").argument("<target...>").action(async (args) => {
|
|
439
|
+
program.command("cat").description("Read file content \u2014 docz cat <space>:<path> or <url>").argument("<target...>").option("--ref", "Also output Git ref to stderr").action(async (args, opts) => {
|
|
322
440
|
const client = getClient();
|
|
323
441
|
const { spaceId, path } = await resolveTarget(client, args);
|
|
324
442
|
if (!path) {
|
|
@@ -327,8 +445,14 @@ function registerCommands(program) {
|
|
|
327
445
|
);
|
|
328
446
|
process.exit(1);
|
|
329
447
|
}
|
|
330
|
-
|
|
331
|
-
|
|
448
|
+
if (opts.ref) {
|
|
449
|
+
const result = await client.catWithRef(spaceId, path);
|
|
450
|
+
console.error(`ref: ${result.ref}`);
|
|
451
|
+
process.stdout.write(result.content);
|
|
452
|
+
} else {
|
|
453
|
+
const content = await client.cat(spaceId, path);
|
|
454
|
+
process.stdout.write(content);
|
|
455
|
+
}
|
|
332
456
|
});
|
|
333
457
|
program.command("upload").description("Upload file \u2014 docz upload <local-file> <space>[:<dir>]").argument("<file>", "Local file to upload").argument("<target...>").action(async (file, args) => {
|
|
334
458
|
const { space, path: dir } = parseTarget(args);
|
|
@@ -340,36 +464,51 @@ function registerCommands(program) {
|
|
|
340
464
|
const result = await client.upload(s.id, targetDir, filename, content);
|
|
341
465
|
console.log(`Uploaded: ${result.path}`);
|
|
342
466
|
});
|
|
343
|
-
program.command("write").description("Write content to file \u2014 docz write <space>:<path> <content>").argument("<target>", "space:dir/filename.md").argument("<content>", "File content (or - for stdin)").
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
467
|
+
program.command("write").description("Write content to file \u2014 docz write <space>:<path> <content>").argument("<target>", "space:dir/filename.md").argument("<content>", "File content (or - for stdin)").option("--force", "Skip conflict detection").option("-m, --message <msg>", "Custom commit message").action(
|
|
468
|
+
async (target, content, opts) => {
|
|
469
|
+
const { space, path } = parseTarget([target]);
|
|
470
|
+
if (!path) {
|
|
471
|
+
console.error(
|
|
472
|
+
"Error: path is required. Usage: docz write <space>:<dir/filename> <content>"
|
|
473
|
+
);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
const client = getClient();
|
|
477
|
+
const s = await client.resolveSpace(space);
|
|
478
|
+
const body = content === "-" ? await readStdin() : content;
|
|
479
|
+
if (Buffer.byteLength(body, "utf-8") > MAX_SAVE_SIZE) {
|
|
480
|
+
console.error(
|
|
481
|
+
"Error: content exceeds 2MB limit. Use `docz upload` for large files."
|
|
482
|
+
);
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
let baseRef;
|
|
486
|
+
if (!opts.force) {
|
|
487
|
+
try {
|
|
488
|
+
const existing = await client.catWithRef(s.id, path);
|
|
489
|
+
baseRef = existing.ref;
|
|
490
|
+
} catch (err) {
|
|
491
|
+
const msg = err instanceof Error ? err.message : "";
|
|
492
|
+
if (!msg.includes("404")) throw err;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const result = await client.save(s.id, path, body, {
|
|
497
|
+
baseRef,
|
|
498
|
+
message: opts.message
|
|
499
|
+
});
|
|
500
|
+
console.log(`Written: ${result.path} (ref: ${result.ref})`);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
if (err instanceof ConflictError) {
|
|
503
|
+
console.error(
|
|
504
|
+
"Error: file was modified by someone else. Please re-read the latest content and try again."
|
|
505
|
+
);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
throw err;
|
|
360
509
|
}
|
|
361
|
-
body = Buffer.concat(chunks).toString("utf-8");
|
|
362
|
-
} else {
|
|
363
|
-
body = content;
|
|
364
510
|
}
|
|
365
|
-
|
|
366
|
-
s.id,
|
|
367
|
-
dir === "." ? "" : dir,
|
|
368
|
-
filename,
|
|
369
|
-
body
|
|
370
|
-
);
|
|
371
|
-
console.log(`Written: ${result.path}`);
|
|
372
|
-
});
|
|
511
|
+
);
|
|
373
512
|
program.command("mkdir").description("Create folder \u2014 docz mkdir <space>:<path>").argument("<target...>").action(async (args) => {
|
|
374
513
|
const { space, path } = parseTarget(args);
|
|
375
514
|
if (!path) {
|
|
@@ -415,6 +554,19 @@ function registerCommands(program) {
|
|
|
415
554
|
console.log(`${l.hash} ${l.date} ${l.message}`);
|
|
416
555
|
}
|
|
417
556
|
});
|
|
557
|
+
program.command("rollback").description(
|
|
558
|
+
"Rollback file to a previous version \u2014 docz rollback <space>:<path> <commit>"
|
|
559
|
+
).argument("<target>", "space:path").argument("<commit>", "Commit hash to rollback to").action(async (target, commit) => {
|
|
560
|
+
const { space, path } = parseTarget([target]);
|
|
561
|
+
if (!path) {
|
|
562
|
+
console.error("Error: file path is required.");
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
const client = getClient();
|
|
566
|
+
const s = await client.resolveSpace(space);
|
|
567
|
+
await client.rollback(s.id, path, commit);
|
|
568
|
+
console.log(`Rolled back: ${path} \u2192 ${commit.substring(0, 7)}`);
|
|
569
|
+
});
|
|
418
570
|
program.command("trash").description("Show deleted files \u2014 docz trash <space>").argument("<space>").action(async (spaceName) => {
|
|
419
571
|
const client = getClient();
|
|
420
572
|
const s = await client.resolveSpace(spaceName);
|
|
@@ -429,29 +581,105 @@ function registerCommands(program) {
|
|
|
429
581
|
);
|
|
430
582
|
}
|
|
431
583
|
});
|
|
432
|
-
|
|
433
|
-
|
|
584
|
+
program.command("restore").description(
|
|
585
|
+
"Restore file from trash \u2014 docz restore <space>:<path> <commit>"
|
|
586
|
+
).argument("<target>", "space:path").argument("<commit>", "Commit hash from trash listing").action(async (target, commit) => {
|
|
434
587
|
const { space, path } = parseTarget([target]);
|
|
435
588
|
if (!path) {
|
|
436
|
-
console.error("Error:
|
|
589
|
+
console.error("Error: path is required.");
|
|
437
590
|
process.exit(1);
|
|
438
591
|
}
|
|
439
592
|
const client = getClient();
|
|
440
593
|
const s = await client.resolveSpace(space);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (opts.users) apiOpts.userIds = opts.users.split(",").map((s2) => s2.trim());
|
|
444
|
-
if (opts.groups) apiOpts.groupIds = opts.groups.split(",").map((s2) => s2.trim());
|
|
445
|
-
const link = await client.createShareLink(s.id, path, apiOpts);
|
|
446
|
-
const baseUrl = getBaseUrl();
|
|
447
|
-
console.log(`Created share link:`);
|
|
448
|
-
console.log(` id: ${link.id}`);
|
|
449
|
-
console.log(` token: ${link.token}`);
|
|
450
|
-
console.log(` url: ${baseUrl}/share/${link.token}`);
|
|
451
|
-
console.log(` expires: ${link.expires_at ?? "never"}`);
|
|
452
|
-
if (link.user_ids?.length) console.log(` users: ${link.user_ids.join(", ")}`);
|
|
453
|
-
if (link.group_ids?.length) console.log(` groups: ${link.group_ids.length}`);
|
|
594
|
+
await client.restore(s.id, path, commit);
|
|
595
|
+
console.log(`Restored: ${path}`);
|
|
454
596
|
});
|
|
597
|
+
const comment = program.command("comment").description("Manage file comments");
|
|
598
|
+
comment.command("list").description("List comments \u2014 docz comment list <space>:<path>").argument("<target...>").action(async (args) => {
|
|
599
|
+
const client = getClient();
|
|
600
|
+
const { spaceId, path } = await resolveTarget(client, args);
|
|
601
|
+
if (!path) {
|
|
602
|
+
console.error("Error: file path is required.");
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
const comments = await client.listComments(spaceId, path);
|
|
606
|
+
if (comments.length === 0) {
|
|
607
|
+
console.log("No comments.");
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
for (const c of comments) {
|
|
611
|
+
const status = c.is_closed ? " [closed]" : "";
|
|
612
|
+
console.log(`#${c.id} ${c.user_name} (${c.created_at})${status}`);
|
|
613
|
+
console.log(` ${c.content}`);
|
|
614
|
+
for (const r of c.replies) {
|
|
615
|
+
console.log(` \u2192 ${r.user_name}: ${r.content}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
comment.command("add").description("Add comment \u2014 docz comment add <space>:<path> <content>").argument("<target>", "space:path").argument("<content>", "Comment text (or - for stdin)").action(async (target, content) => {
|
|
620
|
+
const { space, path } = parseTarget([target]);
|
|
621
|
+
if (!path) {
|
|
622
|
+
console.error("Error: file path is required.");
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
const client = getClient();
|
|
626
|
+
const s = await client.resolveSpace(space);
|
|
627
|
+
const body = content === "-" ? await readStdin() : content;
|
|
628
|
+
const c = await client.createComment(s.id, path, body);
|
|
629
|
+
console.log(`Comment #${c.id} created.`);
|
|
630
|
+
});
|
|
631
|
+
comment.command("reply").description(
|
|
632
|
+
"Reply to comment \u2014 docz comment reply <space> <commentId> <content>"
|
|
633
|
+
).argument("<space>").argument("<commentId>").argument("<content>", "Reply text (or - for stdin)").action(async (spaceName, commentId, content) => {
|
|
634
|
+
const client = getClient();
|
|
635
|
+
const s = await client.resolveSpace(spaceName);
|
|
636
|
+
const body = content === "-" ? await readStdin() : content;
|
|
637
|
+
const r = await client.replyComment(s.id, Number(commentId), body);
|
|
638
|
+
console.log(`Reply #${r.id} created.`);
|
|
639
|
+
});
|
|
640
|
+
comment.command("close").description("Close comment \u2014 docz comment close <space> <commentId>").argument("<space>").argument("<commentId>").action(async (spaceName, commentId) => {
|
|
641
|
+
const client = getClient();
|
|
642
|
+
const s = await client.resolveSpace(spaceName);
|
|
643
|
+
await client.closeComment(s.id, Number(commentId));
|
|
644
|
+
console.log(`Comment #${commentId} closed.`);
|
|
645
|
+
});
|
|
646
|
+
comment.command("rm").description("Delete comment \u2014 docz comment rm <space> <commentId>").argument("<space>").argument("<commentId>").action(async (spaceName, commentId) => {
|
|
647
|
+
const client = getClient();
|
|
648
|
+
const s = await client.resolveSpace(spaceName);
|
|
649
|
+
await client.deleteComment(s.id, Number(commentId));
|
|
650
|
+
console.log(`Comment #${commentId} deleted.`);
|
|
651
|
+
});
|
|
652
|
+
const share = program.command("share").description("Manage share links");
|
|
653
|
+
share.command("create").description("Create share link \u2014 docz share create <space>:<path>").argument("<target>", "space:path").option("--expires <duration>", "Expiry duration (e.g. 7d, 24h)").option("--users <emails>", "Comma-separated user emails or IDs").option("--groups <ids>", "Comma-separated group IDs").action(
|
|
654
|
+
async (target, opts) => {
|
|
655
|
+
const { space, path } = parseTarget([target]);
|
|
656
|
+
if (!path) {
|
|
657
|
+
console.error(
|
|
658
|
+
"Error: file path is required. Usage: docz share create <space>:<path>"
|
|
659
|
+
);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
const client = getClient();
|
|
663
|
+
const s = await client.resolveSpace(space);
|
|
664
|
+
const apiOpts = {};
|
|
665
|
+
if (opts.expires) apiOpts.expiresAt = parseExpires(opts.expires);
|
|
666
|
+
if (opts.users)
|
|
667
|
+
apiOpts.userIds = opts.users.split(",").map((s2) => s2.trim());
|
|
668
|
+
if (opts.groups)
|
|
669
|
+
apiOpts.groupIds = opts.groups.split(",").map((s2) => s2.trim());
|
|
670
|
+
const link = await client.createShareLink(s.id, path, apiOpts);
|
|
671
|
+
const baseUrl = getBaseUrl();
|
|
672
|
+
console.log("Created share link:");
|
|
673
|
+
console.log(` id: ${link.id}`);
|
|
674
|
+
console.log(` token: ${link.token}`);
|
|
675
|
+
console.log(` url: ${baseUrl}/share/${link.token}`);
|
|
676
|
+
console.log(` expires: ${link.expires_at ?? "never"}`);
|
|
677
|
+
if (link.user_ids?.length)
|
|
678
|
+
console.log(` users: ${link.user_ids.join(", ")}`);
|
|
679
|
+
if (link.group_ids?.length)
|
|
680
|
+
console.log(` groups: ${link.group_ids.length}`);
|
|
681
|
+
}
|
|
682
|
+
);
|
|
455
683
|
share.command("list").description("List share links \u2014 docz share list <space>").argument("<space>").option("--file <path>", "Filter by file path").action(async (spaceName, opts) => {
|
|
456
684
|
const client = getClient();
|
|
457
685
|
const s = await client.resolveSpace(spaceName);
|
|
@@ -463,19 +691,25 @@ function registerCommands(program) {
|
|
|
463
691
|
for (const l of links) {
|
|
464
692
|
const expires = l.expires_at ?? "never";
|
|
465
693
|
const creator = l.created_by_name ?? l.created_by_email ?? l.created_by;
|
|
466
|
-
console.log(
|
|
694
|
+
console.log(
|
|
695
|
+
`${l.id} ${l.token} ${l.file_path} ${expires} ${creator}`
|
|
696
|
+
);
|
|
467
697
|
}
|
|
468
698
|
});
|
|
469
|
-
share.command("update").description("Update share link \u2014 docz share update <space> <link-id>").argument("<space>").argument("<linkId>").option("--expires <duration>", "New expiry duration (e.g. 30d)").option("--users <emails>", "Comma-separated user emails or IDs").option("--groups <ids>", "Comma-separated group IDs").action(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
699
|
+
share.command("update").description("Update share link \u2014 docz share update <space> <link-id>").argument("<space>").argument("<linkId>").option("--expires <duration>", "New expiry duration (e.g. 30d)").option("--users <emails>", "Comma-separated user emails or IDs").option("--groups <ids>", "Comma-separated group IDs").action(
|
|
700
|
+
async (spaceName, linkId, opts) => {
|
|
701
|
+
const client = getClient();
|
|
702
|
+
const s = await client.resolveSpace(spaceName);
|
|
703
|
+
const apiOpts = {};
|
|
704
|
+
if (opts.expires) apiOpts.expiresAt = parseExpires(opts.expires);
|
|
705
|
+
if (opts.users)
|
|
706
|
+
apiOpts.userIds = opts.users.split(",").map((s2) => s2.trim());
|
|
707
|
+
if (opts.groups)
|
|
708
|
+
apiOpts.groupIds = opts.groups.split(",").map((s2) => s2.trim());
|
|
709
|
+
await client.updateShareLink(s.id, linkId, apiOpts);
|
|
710
|
+
console.log(`Updated share link: ${linkId}`);
|
|
711
|
+
}
|
|
712
|
+
);
|
|
479
713
|
share.command("cat").description("Read shared file \u2014 docz share cat <token-or-url>").argument("<token>", "Share token or full URL").option("--raw", "Output raw content only").action(async (tokenArg, opts) => {
|
|
480
714
|
const client = getClient();
|
|
481
715
|
const token = extractShareToken(tokenArg);
|
|
@@ -483,7 +717,9 @@ function registerCommands(program) {
|
|
|
483
717
|
try {
|
|
484
718
|
const info = await client.getSharedFileInfo(token);
|
|
485
719
|
console.log(`File: ${info.file_path} (${info.space_name})`);
|
|
486
|
-
console.log(
|
|
720
|
+
console.log(
|
|
721
|
+
`Shared by: ${info.created_by_name} | Expires: ${info.expires_at ?? "never"}`
|
|
722
|
+
);
|
|
487
723
|
console.log("---");
|
|
488
724
|
} catch (err) {
|
|
489
725
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -510,6 +746,18 @@ function registerCommands(program) {
|
|
|
510
746
|
await client.deleteShareLink(s.id, linkId);
|
|
511
747
|
console.log(`Deleted share link: ${linkId}`);
|
|
512
748
|
});
|
|
749
|
+
program.command("shortlink").description("Get short URL \u2014 docz shortlink <space>:<path> or <url>").argument("<target...>").action(async (args) => {
|
|
750
|
+
const client = getClient();
|
|
751
|
+
const { spaceId, path } = await resolveTarget(client, args);
|
|
752
|
+
if (!path) {
|
|
753
|
+
console.error(
|
|
754
|
+
"Error: file path is required. Usage: docz shortlink <space>:<path>"
|
|
755
|
+
);
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
const ref = await client.getFileRef(spaceId, path);
|
|
759
|
+
console.log(ref.url);
|
|
760
|
+
});
|
|
513
761
|
program.command("diff").description("Show changes \u2014 docz diff <space>[:<path>] <commit> [<from>]").argument("<target>", "space or space:path").argument("<to>", "Commit hash").argument("[from]", "From commit hash (default: to^)").action(async (target, to, from) => {
|
|
514
762
|
const client = getClient();
|
|
515
763
|
const { space, path } = parseTarget([target]);
|
|
@@ -535,10 +783,11 @@ function registerCommands(program) {
|
|
|
535
783
|
}
|
|
536
784
|
|
|
537
785
|
export {
|
|
786
|
+
ConflictError,
|
|
538
787
|
DocSyncClient,
|
|
539
788
|
getBaseUrl,
|
|
540
789
|
getToken,
|
|
541
790
|
parseExpires,
|
|
542
791
|
registerCommands
|
|
543
792
|
};
|
|
544
|
-
//# sourceMappingURL=chunk-
|
|
793
|
+
//# sourceMappingURL=chunk-XUT76ACQ.js.map
|