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 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
- `cat`, `ls`, `log` accept DocSync short URLs directly:
111
+ Generate a short URL for any file, or paste existing short URLs into cat/ls/log:
111
112
 
112
113
  ```bash
113
- # These are equivalent
114
- docz-cli cat 闫洪康:AI-Coding技巧总结12.md
115
- docz-cli cat https://docz.zhenguanyu.com/s/yanhongkang/f/NNjrcj8c
114
+ # Generate short URL
115
+ docz-cli shortlink 闫洪康:AI-Coding技巧总结12.md
116
+ # https://docz.zhenguanyu.com/s/yanhongkang/f/NNjrcj8c
116
117
 
117
- # List space root via slug
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(`/api/spaces/${spaceId}/diff/${encodeURIComponent(filePath)}?${params}`);
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, dirname } from "path";
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) throw new Error(`Invalid expires format: "${value}". Use e.g. 7d, 24h, 30d`);
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
- const content = await client.cat(spaceId, path);
331
- process.stdout.write(content);
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)").action(async (target, content) => {
344
- const { space, path } = parseTarget([target]);
345
- if (!path) {
346
- console.error(
347
- "Error: path is required. Usage: docz write <space>:<dir/filename> <content>"
348
- );
349
- process.exit(1);
350
- }
351
- const client = getClient();
352
- const s = await client.resolveSpace(space);
353
- const filename = basename(path);
354
- const dir = dirname(path);
355
- let body;
356
- if (content === "-") {
357
- const chunks = [];
358
- for await (const chunk of process.stdin) {
359
- chunks.push(chunk);
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
- const result = await client.upload(
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
- const share = program.command("share").description("Manage share links");
433
- 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(async (target, opts) => {
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: file path is required. Usage: docz share create <space>:<path>");
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
- const apiOpts = {};
442
- if (opts.expires) apiOpts.expiresAt = parseExpires(opts.expires);
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(`${l.id} ${l.token} ${l.file_path} ${expires} ${creator}`);
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(async (spaceName, linkId, opts) => {
470
- const client = getClient();
471
- const s = await client.resolveSpace(spaceName);
472
- const apiOpts = {};
473
- if (opts.expires) apiOpts.expiresAt = parseExpires(opts.expires);
474
- if (opts.users) apiOpts.userIds = opts.users.split(",").map((s2) => s2.trim());
475
- if (opts.groups) apiOpts.groupIds = opts.groups.split(",").map((s2) => s2.trim());
476
- await client.updateShareLink(s.id, linkId, apiOpts);
477
- console.log(`Updated share link: ${linkId}`);
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(`Shared by: ${info.created_by_name} | Expires: ${info.expires_at ?? "never"}`);
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-WH5WLKFJ.js.map
793
+ //# sourceMappingURL=chunk-XUT76ACQ.js.map