bikeshed-now 0.15.0 → 0.17.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 +49 -4
- package/package.json +1 -1
- package/src/index.mjs +171 -36
package/README.md
CHANGED
|
@@ -131,10 +131,26 @@ nearest has changed since you forked it. On a document you own it also counts
|
|
|
131
131
|
its forks. The author of what you fork sees that it was forked — a count, never
|
|
132
132
|
who.
|
|
133
133
|
|
|
134
|
-
##
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
## Manage access and folders
|
|
135
|
+
|
|
136
|
+
`bikeshed access document <id>` and `bikeshed access folder <id>` show the
|
|
137
|
+
owner, direct grants, inherited grants and effective membership. Add or change
|
|
138
|
+
a direct role with `--add email@example.com,team:design --role editor`; revoke
|
|
139
|
+
with `--remove email@example.com`. All four roles are supported. The owner
|
|
140
|
+
cannot be removed, and inherited grants must be changed at their source.
|
|
141
|
+
Newly effective readers receive an invitation when email is enabled.
|
|
142
|
+
|
|
143
|
+
`bikeshed visibility <id>` shows settings. Add `private`, `unlisted` or `public`
|
|
144
|
+
to change visibility, `--public-edit true|false` to change open editing, or
|
|
145
|
+
`--expires <ISO-8601 timestamp with timezone|none>` to set or clear expiry.
|
|
146
|
+
Omitted settings stay unchanged. These commands are owner-only, including on
|
|
147
|
+
expired documents; the returned URL includes an unlisted key when needed.
|
|
148
|
+
|
|
149
|
+
`bikeshed rename-folder <id> <name>` and
|
|
150
|
+
`bikeshed move-folder <id> <folder|root>` require edit rights.
|
|
151
|
+
`bikeshed delete-folder <id>` is owner-only: contents move to its parent or the
|
|
152
|
+
root, and access is recomputed. No documents are deleted. All commands support
|
|
153
|
+
`--json`. Existing sign-ins work without reconnecting.
|
|
138
154
|
|
|
139
155
|
## For agents
|
|
140
156
|
|
|
@@ -150,3 +166,32 @@ reviewer can always tell which feedback came from a person.
|
|
|
150
166
|
|
|
151
167
|
Apache-2.0. The deployment it talks to is the same repository:
|
|
152
168
|
[DeputyHumans/bikeshed-now](https://github.com/DeputyHumans/bikeshed-now).
|
|
169
|
+
|
|
170
|
+
### Previous versions and write guards
|
|
171
|
+
|
|
172
|
+
Use `list_versions({id, limit:20, cursor})` or `bikeshed versions <id|link>
|
|
173
|
+
--all --json` to discover published history. Pages are newest first, default 20,
|
|
174
|
+
maximum 50; pass `nextCursor` as `cursor` until null. Each row has a pinned URL.
|
|
175
|
+
History uses the document's current access rules and expiry. A missing or
|
|
176
|
+
withdrawn version is an error, never a request for the latest version.
|
|
177
|
+
|
|
178
|
+
Read a chosen version with `get_document({id, version:"v2"})` and
|
|
179
|
+
`get_comments({id, version:"v2", includeResolved:true})`, or
|
|
180
|
+
`bikeshed get <id> --version v2` and `bikeshed comments <id> --version v2 --all`.
|
|
181
|
+
Read results name `currentVersion` and `isCurrent`; historical write capabilities
|
|
182
|
+
are false. Existing comments and suggestions remain readable as an archive.
|
|
183
|
+
Older versions cannot be edited or receive, revise, delete, reply to, resolve,
|
|
184
|
+
reopen, accept, reject, claim or withdraw feedback. Fork one to make a new,
|
|
185
|
+
independent document with `fork_document({id, version:"v2"})`.
|
|
186
|
+
|
|
187
|
+
CLI `get`, `comments`, `fork`, and `open` preserve a version in a pasted
|
|
188
|
+
`/d/<id>/<version>?k=...` URL. Feedback commands also preserve it as a write
|
|
189
|
+
guard. `--version` must agree with the URL; conflicts fail. Keys are retained.
|
|
190
|
+
Document-wide sharing, folder and lineage operations still address the document.
|
|
191
|
+
|
|
192
|
+
Before changing feedback, read the latest version and its feedback, then pass
|
|
193
|
+
that `version` to every feedback tool (`--version` in the CLI). Pass
|
|
194
|
+
`baseVersion` to `update_document` and `commit_draft` (`--base` on `bikeshed
|
|
195
|
+
update` and `bikeshed commit`). A versioned URL supplies the CLI base too.
|
|
196
|
+
If the head changes, the server refuses the write. Read the new head and
|
|
197
|
+
reassess the request; never silently redirect a historical request to it.
|
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -51,7 +51,8 @@ const USAGE = `bikeshed ${VERSION} — publish and revise documents
|
|
|
51
51
|
bikeshed update <id> <file> publish a new version
|
|
52
52
|
bikeshed fork <id> your own copy, which remembers its original
|
|
53
53
|
bikeshed lineage <id> what it was forked from; for its owner, its forks
|
|
54
|
-
bikeshed get <id>
|
|
54
|
+
bikeshed get <id> [--version <v>] print a published version's source
|
|
55
|
+
bikeshed versions <id> list published history (--all for every page)
|
|
55
56
|
bikeshed comments <id> unresolved feedback, with its anchored text
|
|
56
57
|
(--version <v> for an earlier version's)
|
|
57
58
|
bikeshed changes <id> --since <cursor|last|all>
|
|
@@ -88,6 +89,17 @@ Organising
|
|
|
88
89
|
bikeshed mv <id> <folder|root> move a document
|
|
89
90
|
bikeshed rename <id> <title> change a document's title; its address stays
|
|
90
91
|
|
|
92
|
+
Management
|
|
93
|
+
bikeshed access <document|folder> <id> inspect direct and inherited permissions
|
|
94
|
+
--add <principals> --role <role> grant/change roles (emails or team:<id>, comma-separated)
|
|
95
|
+
--remove <principals> revoke direct grants; inherited access remains
|
|
96
|
+
bikeshed visibility <id> [private|unlisted|public]
|
|
97
|
+
--public-edit true|false change open editing
|
|
98
|
+
--expires <ISO timestamp|none> set or clear expiry; omitted settings stay unchanged
|
|
99
|
+
bikeshed rename-folder <id> <name> rename a folder you can edit
|
|
100
|
+
bikeshed move-folder <id> <folder|root> move a folder and recompute inherited access
|
|
101
|
+
bikeshed delete-folder <id> owner-only; reparent contents, retain documents
|
|
102
|
+
|
|
91
103
|
Skills
|
|
92
104
|
bikeshed skills skills this deployment publishes
|
|
93
105
|
bikeshed skills install <dir> write them into an agent's skills directory
|
|
@@ -111,9 +123,9 @@ Options
|
|
|
111
123
|
--title <text> title for publish or fork (fork default: the original's)
|
|
112
124
|
--id <id> with publish: the address, /d/<id> (default: from the title)
|
|
113
125
|
--folder <id> destination folder for publish or fork
|
|
114
|
-
--version <v>
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
--version <v> get/comments/fork/open: the published version to read
|
|
127
|
+
or copy; feedback commands: require that version to
|
|
128
|
+
still be current. On its own: the CLI version
|
|
117
129
|
--parent <id> parent folder for mkdir
|
|
118
130
|
--limit <n> with list: rows per page (default 20, max 50)
|
|
119
131
|
--cursor <id> with list: the page after the one that printed it
|
|
@@ -125,7 +137,7 @@ Options
|
|
|
125
137
|
60, at least 30)
|
|
126
138
|
--timeout <s> with changes --wait: give up after this long (default
|
|
127
139
|
3600), exit 3, and print the cursor to resume from
|
|
128
|
-
--base <v> with update: the version you revised; refused if
|
|
140
|
+
--base <v> with update/commit: the version you revised; refused if
|
|
129
141
|
somebody has published since
|
|
130
142
|
--all with list: every page; with comments: resolved too
|
|
131
143
|
--with <text> with suggest or revise: what the passage should say instead
|
|
@@ -164,18 +176,31 @@ function out(json, value, human) {
|
|
|
164
176
|
* for both — `…/d/<id>[/<version>]?k=<key>` — and the key goes along as the
|
|
165
177
|
* tools' `k` argument (SPEC §17).
|
|
166
178
|
*/
|
|
167
|
-
function docArgs(ref) {
|
|
168
|
-
|
|
169
|
-
let
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
fail(`not a document link: ${ref}`);
|
|
179
|
+
function docArgs(ref, flags) {
|
|
180
|
+
let id = ref;
|
|
181
|
+
let k;
|
|
182
|
+
let linkedVersion;
|
|
183
|
+
if (/^https?:\/\//i.test(ref)) {
|
|
184
|
+
let url;
|
|
185
|
+
try { url = new URL(ref); } catch { fail(`not a document link: ${ref}`); }
|
|
186
|
+
const match = url.pathname.match(/^\/d\/([^/]+)(?:\/([^/]+))?\/?$/);
|
|
187
|
+
if (!match) fail(`not a document link: ${ref}`);
|
|
188
|
+
id = decodeURIComponent(match[1]);
|
|
189
|
+
linkedVersion = match[2] ? decodeURIComponent(match[2]) : undefined;
|
|
190
|
+
k = url.searchParams.get("k");
|
|
191
|
+
}
|
|
192
|
+
// Document-wide operations deliberately use only the document id/key.
|
|
193
|
+
if (flags === undefined) return { id, ...(k ? { k } : {}) };
|
|
194
|
+
const explicit = flags.version;
|
|
195
|
+
if (explicit !== undefined && (typeof explicit !== "string" || !explicit || /[\/\\?#\s]/.test(explicit))) {
|
|
196
|
+
fail("invalid version");
|
|
197
|
+
}
|
|
198
|
+
if (explicit && linkedVersion && explicit !== linkedVersion) {
|
|
199
|
+
fail(`conflicting versions: the link names ${linkedVersion}, the flag names ${explicit}`);
|
|
174
200
|
}
|
|
175
|
-
const
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
return { id: decodeURIComponent(match[1]), ...(k ? { k } : {}) };
|
|
201
|
+
const version = explicit ?? linkedVersion;
|
|
202
|
+
if (version && (version === "." || version === ".." || /[\/\\?#\s]/.test(version))) fail("invalid version");
|
|
203
|
+
return { id, ...(k ? { k } : {}), ...(version ? { version } : {}) };
|
|
179
204
|
}
|
|
180
205
|
|
|
181
206
|
const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
|
|
@@ -485,7 +510,7 @@ async function update(flags, [ref, path]) {
|
|
|
485
510
|
if (!ref || !path) fail("usage: bikeshed update <id> <file>");
|
|
486
511
|
const { origin, token } = resolveAuth(flags);
|
|
487
512
|
const file = resolve(path);
|
|
488
|
-
const doc = docArgs(ref);
|
|
513
|
+
const { version: selectedBase, ...doc } = docArgs(ref, { version: flags.base });
|
|
489
514
|
const { id } = doc;
|
|
490
515
|
|
|
491
516
|
const once = async () => {
|
|
@@ -494,7 +519,7 @@ async function update(flags, [ref, path]) {
|
|
|
494
519
|
// text, every byte above 0x7f in it would become U+FFFD on the way.
|
|
495
520
|
// --base: the version this was revised from, so an update over somebody's
|
|
496
521
|
// newer save is refused instead of replacing it (SPEC §19).
|
|
497
|
-
const base =
|
|
522
|
+
const base = selectedBase;
|
|
498
523
|
const result = isArchive(file, bytes)
|
|
499
524
|
? await uploadFile(origin, token, file, { update: id, k: doc.k, base })
|
|
500
525
|
: await callTool(origin, token, "update_document", {
|
|
@@ -516,10 +541,30 @@ async function update(flags, [ref, path]) {
|
|
|
516
541
|
if (flags.watch) await watchFile(file, once);
|
|
517
542
|
}
|
|
518
543
|
|
|
544
|
+
/** The published history, paged by the deployment rather than guessed from ids. */
|
|
545
|
+
async function versions(flags, [ref, ...rest]) {
|
|
546
|
+
if (!ref || rest.length) fail("usage: bikeshed versions <id> [--limit N] [--cursor …] [--all] [--json]");
|
|
547
|
+
const { origin, token } = resolveAuth(flags);
|
|
548
|
+
const limit = flags.limit === undefined ? 20 : Number(flags.limit);
|
|
549
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) fail("--limit must be an integer from 1 to 50");
|
|
550
|
+
let cursor = flags.cursor;
|
|
551
|
+
const rows = [];
|
|
552
|
+
let result;
|
|
553
|
+
do {
|
|
554
|
+
result = await callTool(origin, token, "list_versions", { ...docArgs(ref), limit, ...(cursor ? { cursor } : {}) });
|
|
555
|
+
rows.push(...result.versions);
|
|
556
|
+
cursor = result.nextCursor;
|
|
557
|
+
} while (flags.all && cursor);
|
|
558
|
+
out(flags.json, { ...result, versions: rows }, () => {
|
|
559
|
+
for (const row of rows) console.log(`${row.isCurrent ? "*" : " "} ${row.version} ${row.publishedAt ?? ""} ${row.label} ${row.url ?? ""}`);
|
|
560
|
+
if (cursor) console.log(`… more: bikeshed versions ${ref} --cursor ${cursor}`);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
519
564
|
async function get(flags, [ref]) {
|
|
520
565
|
if (!ref) fail("usage: bikeshed get <id>");
|
|
521
566
|
const { origin, token } = resolveAuth(flags);
|
|
522
|
-
const doc = docArgs(ref);
|
|
567
|
+
const doc = docArgs(ref, flags);
|
|
523
568
|
const result = await callTool(origin, token, "get_document", doc);
|
|
524
569
|
// The tool cuts a long source at a fixed length. Printed as it came, the cut
|
|
525
570
|
// copy exited 0 and looked complete, and `update` with an edit of it
|
|
@@ -544,9 +589,7 @@ async function comments(flags, [ref]) {
|
|
|
544
589
|
if (!ref) fail("usage: bikeshed comments <id>");
|
|
545
590
|
const { origin, token } = resolveAuth(flags);
|
|
546
591
|
const result = await callTool(origin, token, "get_comments", {
|
|
547
|
-
...docArgs(ref),
|
|
548
|
-
// Threads belong to the version they were opened on; a version in a link
|
|
549
|
-
// is not taken as a request for one, only the flag is.
|
|
592
|
+
...docArgs(ref, flags),
|
|
550
593
|
...(typeof flags.version === "string" ? { version: flags.version } : {}),
|
|
551
594
|
includeResolved: Boolean(flags.all),
|
|
552
595
|
});
|
|
@@ -592,7 +635,7 @@ async function comment(flags, [ref, ...rest]) {
|
|
|
592
635
|
if (typeof flags.body !== "string" || !flags.body.trim()) fail(usage);
|
|
593
636
|
const { origin, token } = resolveAuth(flags);
|
|
594
637
|
const result = await callTool(origin, token, "add_comment", {
|
|
595
|
-
...docArgs(ref),
|
|
638
|
+
...docArgs(ref, flags),
|
|
596
639
|
exact,
|
|
597
640
|
body: flags.body,
|
|
598
641
|
...(typeof flags.prefix === "string" ? { prefix: flags.prefix } : {}),
|
|
@@ -606,7 +649,7 @@ async function reply(flags, [ref, threadId, ...rest]) {
|
|
|
606
649
|
if (!ref || !threadId || !body) fail("usage: bikeshed reply <id> <thread> <text>");
|
|
607
650
|
const { origin, token } = resolveAuth(flags);
|
|
608
651
|
const result = await callTool(origin, token, "reply_to_comment", {
|
|
609
|
-
...docArgs(ref),
|
|
652
|
+
...docArgs(ref, flags),
|
|
610
653
|
threadId,
|
|
611
654
|
body,
|
|
612
655
|
});
|
|
@@ -630,7 +673,7 @@ async function reopenThread(flags, args) {
|
|
|
630
673
|
async function setResolved(flags, [ref, threadId, ...rest], verb) {
|
|
631
674
|
if (!ref || !threadId || rest.length) fail(`usage: bikeshed ${verb} <id> <thread>`);
|
|
632
675
|
const { origin, token } = resolveAuth(flags);
|
|
633
|
-
const result = await callTool(origin, token, `${verb}_thread`, { ...docArgs(ref), threadId });
|
|
676
|
+
const result = await callTool(origin, token, `${verb}_thread`, { ...docArgs(ref, flags), threadId });
|
|
634
677
|
out(flags.json, result, () => console.log(`${verb === "resolve" ? "resolved" : "reopened"} ${threadId}`));
|
|
635
678
|
}
|
|
636
679
|
|
|
@@ -646,7 +689,7 @@ async function suggest(flags, [ref, ...rest]) {
|
|
|
646
689
|
const replacement = replacementFrom(flags, usage);
|
|
647
690
|
const { origin, token } = resolveAuth(flags);
|
|
648
691
|
const result = await callTool(origin, token, "suggest_edit", {
|
|
649
|
-
...docArgs(ref),
|
|
692
|
+
...docArgs(ref, flags),
|
|
650
693
|
exact,
|
|
651
694
|
replacement,
|
|
652
695
|
...(typeof flags.note === "string" ? { note: flags.note } : {}),
|
|
@@ -678,7 +721,7 @@ async function revise(flags, [ref, threadId, ...rest]) {
|
|
|
678
721
|
const replacement = replacementFrom(flags, usage);
|
|
679
722
|
const { origin, token } = resolveAuth(flags);
|
|
680
723
|
const result = await callTool(origin, token, "revise_suggestion", {
|
|
681
|
-
...docArgs(ref),
|
|
724
|
+
...docArgs(ref, flags),
|
|
682
725
|
threadId,
|
|
683
726
|
replacement,
|
|
684
727
|
...(typeof flags.note === "string" ? { note: flags.note } : {}),
|
|
@@ -694,8 +737,9 @@ async function revise(flags, [ref, threadId, ...rest]) {
|
|
|
694
737
|
async function accept(flags, [ref, threadId, ...rest]) {
|
|
695
738
|
if (!ref || !threadId || rest.length) fail("usage: bikeshed accept <id> <thread>");
|
|
696
739
|
const { origin, token } = resolveAuth(flags);
|
|
697
|
-
|
|
698
|
-
|
|
740
|
+
const { id, version } = docArgs(ref, flags);
|
|
741
|
+
// Deciding still takes a real role; a version is only a precondition.
|
|
742
|
+
const result = await callTool(origin, token, "accept_suggestion", { id, ...(version ? { version } : {}), threadId });
|
|
699
743
|
out(flags.json, result, () => console.log(`accepted ${threadId}; the change is in the draft — \`bikeshed commit\` publishes it`));
|
|
700
744
|
}
|
|
701
745
|
|
|
@@ -703,7 +747,8 @@ async function accept(flags, [ref, threadId, ...rest]) {
|
|
|
703
747
|
async function commit(flags, [ref, ...rest]) {
|
|
704
748
|
if (!ref || rest.length) fail("usage: bikeshed commit <id>");
|
|
705
749
|
const { origin, token } = resolveAuth(flags);
|
|
706
|
-
const
|
|
750
|
+
const { id, version } = docArgs(ref, { version: flags.base });
|
|
751
|
+
const result = await callTool(origin, token, "commit_draft", { id, ...(version ? { baseVersion: version } : {}) });
|
|
707
752
|
out(flags.json, result, () =>
|
|
708
753
|
console.log(result.deduped ? `nothing new: the draft is ${result.versionId} already` : `published ${result.versionId}`),
|
|
709
754
|
);
|
|
@@ -717,9 +762,10 @@ async function commit(flags, [ref, ...rest]) {
|
|
|
717
762
|
async function reject(flags, [ref, threadId, ...rest]) {
|
|
718
763
|
if (!ref || !threadId || rest.length) fail("usage: bikeshed reject <id> <thread> [--reason <why>]");
|
|
719
764
|
const { origin, token } = resolveAuth(flags);
|
|
720
|
-
|
|
765
|
+
const { id, version } = docArgs(ref, flags);
|
|
766
|
+
// Deciding still takes a real role; a version is only a precondition.
|
|
721
767
|
const result = await callTool(origin, token, "reject_suggestion", {
|
|
722
|
-
id:
|
|
768
|
+
id, ...(version ? { version } : {}),
|
|
723
769
|
threadId,
|
|
724
770
|
...(typeof flags.reason === "string" ? { reason: flags.reason } : {}),
|
|
725
771
|
});
|
|
@@ -730,7 +776,7 @@ async function reject(flags, [ref, threadId, ...rest]) {
|
|
|
730
776
|
async function withdraw(flags, [ref, threadId, ...rest]) {
|
|
731
777
|
if (!ref || !threadId || rest.length) fail("usage: bikeshed withdraw <id> <thread>");
|
|
732
778
|
const { origin, token } = resolveAuth(flags);
|
|
733
|
-
const result = await callTool(origin, token, "withdraw_suggestion", { ...docArgs(ref), threadId });
|
|
779
|
+
const result = await callTool(origin, token, "withdraw_suggestion", { ...docArgs(ref, flags), threadId });
|
|
734
780
|
out(flags.json, result, () => console.log(`withdrew ${threadId}`));
|
|
735
781
|
}
|
|
736
782
|
|
|
@@ -788,6 +834,80 @@ async function folder(flags, [id]) {
|
|
|
788
834
|
});
|
|
789
835
|
}
|
|
790
836
|
|
|
837
|
+
/** Complete owner-visible grants, including why a removed direct grant may still read. */
|
|
838
|
+
function printManagedAccess(result) {
|
|
839
|
+
console.log(`${result.kind} ${result.id} — owner: ${result.owner}`);
|
|
840
|
+
for (const [role, people] of Object.entries(result.direct)) console.log(`${role}: ${people.join(", ") || "none"}`);
|
|
841
|
+
for (const grant of result.inherited) console.log(`inherited ${grant.role}: ${grant.principal} via ${grant.path}`);
|
|
842
|
+
for (const [role, people] of Object.entries(result.effective)) console.log(`${role}: ${people.join(", ") || "none"}`);
|
|
843
|
+
if (result.visibility !== undefined) printVisibility(result);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function printVisibility(result) {
|
|
847
|
+
console.log(`visibility: ${result.visibility}; open editing: ${result.publicEdit ? "on" : "off"}; expires: ${result.expiresAt === null ? "never" : new Date(result.expiresAt).toISOString()}`);
|
|
848
|
+
console.log(result.url);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
async function access(flags, [kind, ref, ...rest]) {
|
|
852
|
+
if (!["document", "folder"].includes(kind) || !ref || rest.length) fail("usage: bikeshed access <document|folder> <id> [--add <principals> --role <role>] [--remove <principals>]");
|
|
853
|
+
const args = { id: kind === "document" ? docArgs(ref).id : ref };
|
|
854
|
+
const list = raw => {
|
|
855
|
+
if (typeof raw !== "string" || !raw.trim()) fail("principal list must not be empty");
|
|
856
|
+
const values = raw.split(",").map(p => p.trim());
|
|
857
|
+
if (values.some(p => !p)) fail("principal list contains an empty entry");
|
|
858
|
+
return values;
|
|
859
|
+
};
|
|
860
|
+
if (flags.add !== undefined) {
|
|
861
|
+
if (!["viewer", "suggester", "editor", "approver"].includes(flags.role)) fail("--add requires --role viewer|suggester|editor|approver");
|
|
862
|
+
args.add = list(flags.add).map(principal => ({ principal, role: flags.role }));
|
|
863
|
+
} else if (flags.role !== undefined) fail("--role requires --add");
|
|
864
|
+
if (flags.remove !== undefined) args.remove = list(flags.remove);
|
|
865
|
+
const { origin, token } = resolveAuth(flags);
|
|
866
|
+
const result = await callTool(origin, token, `${args.add || args.remove ? "set" : "get"}_${kind}_access`, args);
|
|
867
|
+
out(flags.json, result, () => printManagedAccess(result));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function visibility(flags, [ref, level, ...rest]) {
|
|
871
|
+
if (!ref || rest.length || (level !== undefined && !["private", "unlisted", "public"].includes(level))) fail("usage: bikeshed visibility <id> [private|unlisted|public] [--public-edit true|false] [--expires <ISO timestamp|none>]");
|
|
872
|
+
const args = { id: docArgs(ref).id };
|
|
873
|
+
if (level !== undefined) args.visibility = level;
|
|
874
|
+
if (flags["public-edit"] !== undefined) {
|
|
875
|
+
if (!["true", "false"].includes(flags["public-edit"])) fail("--public-edit must be true or false");
|
|
876
|
+
args.publicEdit = flags["public-edit"] === "true";
|
|
877
|
+
}
|
|
878
|
+
if (flags.expires !== undefined) {
|
|
879
|
+
if (flags.expires === "none") args.expiresAt = null;
|
|
880
|
+
else {
|
|
881
|
+
if (typeof flags.expires !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/.test(flags.expires) || !Number.isFinite(Date.parse(flags.expires))) fail("--expires must be an ISO-8601 timestamp with a timezone, or none");
|
|
882
|
+
args.expiresAt = Date.parse(flags.expires);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const { origin, token } = resolveAuth(flags);
|
|
886
|
+
const result = await callTool(origin, token, Object.keys(args).length > 1 ? "set_document_visibility" : "get_document_access", args);
|
|
887
|
+
out(flags.json, result, () => printVisibility(result));
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
async function renameFolder(flags, [id, name, ...rest]) {
|
|
891
|
+
if (!id || !name || rest.length) fail("usage: bikeshed rename-folder <id> <name>");
|
|
892
|
+
const { origin, token } = resolveAuth(flags);
|
|
893
|
+
const result = await callTool(origin, token, "rename_folder", { id, name });
|
|
894
|
+
out(flags.json, result, () => console.log(`renamed ${id}: ${result.path}`));
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async function moveFolder(flags, [id, parent, ...rest]) {
|
|
898
|
+
if (!id || !parent || rest.length) fail("usage: bikeshed move-folder <id> <folder|root>");
|
|
899
|
+
const { origin, token } = resolveAuth(flags);
|
|
900
|
+
const result = await callTool(origin, token, "move_folder", { id, ...(parent === "root" ? {} : { parent }) });
|
|
901
|
+
out(flags.json, result, () => console.log(`moved ${id}: ${result.path}; ${result.access.readers} can read it`));
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
async function deleteFolder(flags, [id, ...rest]) {
|
|
905
|
+
if (!id || rest.length) fail("usage: bikeshed delete-folder <id>");
|
|
906
|
+
const { origin, token } = resolveAuth(flags);
|
|
907
|
+
const result = await callTool(origin, token, "delete_folder", { id });
|
|
908
|
+
out(flags.json, result, () => console.log(`deleted ${id}; moved ${result.reparented.folders} folders and ${result.reparented.documents} documents to ${result.parent ?? "root"}`));
|
|
909
|
+
}
|
|
910
|
+
|
|
791
911
|
/** Exit status when `changes --wait` gives up: distinct from 1, an error. */
|
|
792
912
|
const WAIT_TIMED_OUT = 3;
|
|
793
913
|
/** The shortest wait between checks: each one reads the log and the document. */
|
|
@@ -857,7 +977,7 @@ function printChanges(flags, result) {
|
|
|
857
977
|
async function fork(flags, [ref]) {
|
|
858
978
|
if (!ref) fail("usage: bikeshed fork <id> [--version <v>] [--folder <id>] [--title <text>]");
|
|
859
979
|
const { origin, token } = resolveAuth(flags);
|
|
860
|
-
const args = docArgs(ref);
|
|
980
|
+
const args = docArgs(ref, flags);
|
|
861
981
|
if (typeof flags.version === "string") args.version = flags.version;
|
|
862
982
|
if (typeof flags.folder === "string") args.folder = flags.folder;
|
|
863
983
|
if (typeof flags.title === "string") args.title = flags.title;
|
|
@@ -1009,8 +1129,8 @@ async function open(flags, [ref]) {
|
|
|
1009
1129
|
const { origin } = resolveAuth(flags);
|
|
1010
1130
|
// An unlisted document opens only with its key, and the link is the one place
|
|
1011
1131
|
// the key lives, so a link keeps it.
|
|
1012
|
-
const { id, k } = docArgs(ref);
|
|
1013
|
-
const url = `${origin}/d/${encodeURIComponent(id)}${k ? `?k=${encodeURIComponent(k)}` : ""}`;
|
|
1132
|
+
const { id, k, version } = docArgs(ref, flags);
|
|
1133
|
+
const url = `${origin}/d/${encodeURIComponent(id)}${version ? `/${encodeURIComponent(version)}` : ""}${k ? `?k=${encodeURIComponent(k)}` : ""}`;
|
|
1014
1134
|
// Best effort, and the address is printed either way. This used to spawn
|
|
1015
1135
|
// `start`, which is a cmd.exe builtin rather than a program, so on Windows
|
|
1016
1136
|
// the spawn failed and, with nothing listening for the error, crashed.
|
|
@@ -1049,6 +1169,7 @@ const COMMANDS = {
|
|
|
1049
1169
|
fork,
|
|
1050
1170
|
lineage,
|
|
1051
1171
|
get,
|
|
1172
|
+
versions,
|
|
1052
1173
|
comments,
|
|
1053
1174
|
changes,
|
|
1054
1175
|
comment,
|
|
@@ -1062,6 +1183,11 @@ const COMMANDS = {
|
|
|
1062
1183
|
reject,
|
|
1063
1184
|
commit,
|
|
1064
1185
|
open,
|
|
1186
|
+
access,
|
|
1187
|
+
visibility,
|
|
1188
|
+
"rename-folder": renameFolder,
|
|
1189
|
+
"move-folder": moveFolder,
|
|
1190
|
+
"delete-folder": deleteFolder,
|
|
1065
1191
|
folders,
|
|
1066
1192
|
folder,
|
|
1067
1193
|
mkdir,
|
|
@@ -1079,6 +1205,11 @@ async function main() {
|
|
|
1079
1205
|
allowPositionals: true,
|
|
1080
1206
|
strict: false,
|
|
1081
1207
|
options: {
|
|
1208
|
+
add: { type: "string" },
|
|
1209
|
+
remove: { type: "string" },
|
|
1210
|
+
role: { type: "string" },
|
|
1211
|
+
"public-edit": { type: "string" },
|
|
1212
|
+
expires: { type: "string" },
|
|
1082
1213
|
json: { type: "boolean", default: false },
|
|
1083
1214
|
watch: { type: "boolean", default: false },
|
|
1084
1215
|
all: { type: "boolean", default: false },
|
|
@@ -1123,6 +1254,10 @@ async function main() {
|
|
|
1123
1254
|
return;
|
|
1124
1255
|
}
|
|
1125
1256
|
|
|
1257
|
+
const versionCommands = new Set(["get", "comments", "fork", "open", "comment", "reply", "resolve", "reopen", "suggest", "revise", "withdraw", "accept", "reject"]);
|
|
1258
|
+
if (values.version !== undefined && !versionCommands.has(name)) fail(`--version is not supported by ${name}; update and commit use --base`);
|
|
1259
|
+
if (values.base !== undefined && !["update", "commit"].includes(name)) fail(`--base is not supported by ${name}`);
|
|
1260
|
+
|
|
1126
1261
|
const command = COMMANDS[name];
|
|
1127
1262
|
if (!command) fail(`unknown command: ${name}\n\n${USAGE}`);
|
|
1128
1263
|
|