bikeshed-now 0.15.0 → 0.16.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 +20 -4
- package/package.json +1 -1
- package/src/index.mjs +95 -0
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
|
|
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -88,6 +88,17 @@ Organising
|
|
|
88
88
|
bikeshed mv <id> <folder|root> move a document
|
|
89
89
|
bikeshed rename <id> <title> change a document's title; its address stays
|
|
90
90
|
|
|
91
|
+
Management
|
|
92
|
+
bikeshed access <document|folder> <id> inspect direct and inherited permissions
|
|
93
|
+
--add <principals> --role <role> grant/change roles (emails or team:<id>, comma-separated)
|
|
94
|
+
--remove <principals> revoke direct grants; inherited access remains
|
|
95
|
+
bikeshed visibility <id> [private|unlisted|public]
|
|
96
|
+
--public-edit true|false change open editing
|
|
97
|
+
--expires <ISO timestamp|none> set or clear expiry; omitted settings stay unchanged
|
|
98
|
+
bikeshed rename-folder <id> <name> rename a folder you can edit
|
|
99
|
+
bikeshed move-folder <id> <folder|root> move a folder and recompute inherited access
|
|
100
|
+
bikeshed delete-folder <id> owner-only; reparent contents, retain documents
|
|
101
|
+
|
|
91
102
|
Skills
|
|
92
103
|
bikeshed skills skills this deployment publishes
|
|
93
104
|
bikeshed skills install <dir> write them into an agent's skills directory
|
|
@@ -788,6 +799,80 @@ async function folder(flags, [id]) {
|
|
|
788
799
|
});
|
|
789
800
|
}
|
|
790
801
|
|
|
802
|
+
/** Complete owner-visible grants, including why a removed direct grant may still read. */
|
|
803
|
+
function printManagedAccess(result) {
|
|
804
|
+
console.log(`${result.kind} ${result.id} — owner: ${result.owner}`);
|
|
805
|
+
for (const [role, people] of Object.entries(result.direct)) console.log(`${role}: ${people.join(", ") || "none"}`);
|
|
806
|
+
for (const grant of result.inherited) console.log(`inherited ${grant.role}: ${grant.principal} via ${grant.path}`);
|
|
807
|
+
for (const [role, people] of Object.entries(result.effective)) console.log(`${role}: ${people.join(", ") || "none"}`);
|
|
808
|
+
if (result.visibility !== undefined) printVisibility(result);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function printVisibility(result) {
|
|
812
|
+
console.log(`visibility: ${result.visibility}; open editing: ${result.publicEdit ? "on" : "off"}; expires: ${result.expiresAt === null ? "never" : new Date(result.expiresAt).toISOString()}`);
|
|
813
|
+
console.log(result.url);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async function access(flags, [kind, ref, ...rest]) {
|
|
817
|
+
if (!["document", "folder"].includes(kind) || !ref || rest.length) fail("usage: bikeshed access <document|folder> <id> [--add <principals> --role <role>] [--remove <principals>]");
|
|
818
|
+
const args = { id: kind === "document" ? docArgs(ref).id : ref };
|
|
819
|
+
const list = raw => {
|
|
820
|
+
if (typeof raw !== "string" || !raw.trim()) fail("principal list must not be empty");
|
|
821
|
+
const values = raw.split(",").map(p => p.trim());
|
|
822
|
+
if (values.some(p => !p)) fail("principal list contains an empty entry");
|
|
823
|
+
return values;
|
|
824
|
+
};
|
|
825
|
+
if (flags.add !== undefined) {
|
|
826
|
+
if (!["viewer", "suggester", "editor", "approver"].includes(flags.role)) fail("--add requires --role viewer|suggester|editor|approver");
|
|
827
|
+
args.add = list(flags.add).map(principal => ({ principal, role: flags.role }));
|
|
828
|
+
} else if (flags.role !== undefined) fail("--role requires --add");
|
|
829
|
+
if (flags.remove !== undefined) args.remove = list(flags.remove);
|
|
830
|
+
const { origin, token } = resolveAuth(flags);
|
|
831
|
+
const result = await callTool(origin, token, `${args.add || args.remove ? "set" : "get"}_${kind}_access`, args);
|
|
832
|
+
out(flags.json, result, () => printManagedAccess(result));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function visibility(flags, [ref, level, ...rest]) {
|
|
836
|
+
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>]");
|
|
837
|
+
const args = { id: docArgs(ref).id };
|
|
838
|
+
if (level !== undefined) args.visibility = level;
|
|
839
|
+
if (flags["public-edit"] !== undefined) {
|
|
840
|
+
if (!["true", "false"].includes(flags["public-edit"])) fail("--public-edit must be true or false");
|
|
841
|
+
args.publicEdit = flags["public-edit"] === "true";
|
|
842
|
+
}
|
|
843
|
+
if (flags.expires !== undefined) {
|
|
844
|
+
if (flags.expires === "none") args.expiresAt = null;
|
|
845
|
+
else {
|
|
846
|
+
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");
|
|
847
|
+
args.expiresAt = Date.parse(flags.expires);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const { origin, token } = resolveAuth(flags);
|
|
851
|
+
const result = await callTool(origin, token, Object.keys(args).length > 1 ? "set_document_visibility" : "get_document_access", args);
|
|
852
|
+
out(flags.json, result, () => printVisibility(result));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function renameFolder(flags, [id, name, ...rest]) {
|
|
856
|
+
if (!id || !name || rest.length) fail("usage: bikeshed rename-folder <id> <name>");
|
|
857
|
+
const { origin, token } = resolveAuth(flags);
|
|
858
|
+
const result = await callTool(origin, token, "rename_folder", { id, name });
|
|
859
|
+
out(flags.json, result, () => console.log(`renamed ${id}: ${result.path}`));
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
async function moveFolder(flags, [id, parent, ...rest]) {
|
|
863
|
+
if (!id || !parent || rest.length) fail("usage: bikeshed move-folder <id> <folder|root>");
|
|
864
|
+
const { origin, token } = resolveAuth(flags);
|
|
865
|
+
const result = await callTool(origin, token, "move_folder", { id, ...(parent === "root" ? {} : { parent }) });
|
|
866
|
+
out(flags.json, result, () => console.log(`moved ${id}: ${result.path}; ${result.access.readers} can read it`));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
async function deleteFolder(flags, [id, ...rest]) {
|
|
870
|
+
if (!id || rest.length) fail("usage: bikeshed delete-folder <id>");
|
|
871
|
+
const { origin, token } = resolveAuth(flags);
|
|
872
|
+
const result = await callTool(origin, token, "delete_folder", { id });
|
|
873
|
+
out(flags.json, result, () => console.log(`deleted ${id}; moved ${result.reparented.folders} folders and ${result.reparented.documents} documents to ${result.parent ?? "root"}`));
|
|
874
|
+
}
|
|
875
|
+
|
|
791
876
|
/** Exit status when `changes --wait` gives up: distinct from 1, an error. */
|
|
792
877
|
const WAIT_TIMED_OUT = 3;
|
|
793
878
|
/** The shortest wait between checks: each one reads the log and the document. */
|
|
@@ -1062,6 +1147,11 @@ const COMMANDS = {
|
|
|
1062
1147
|
reject,
|
|
1063
1148
|
commit,
|
|
1064
1149
|
open,
|
|
1150
|
+
access,
|
|
1151
|
+
visibility,
|
|
1152
|
+
"rename-folder": renameFolder,
|
|
1153
|
+
"move-folder": moveFolder,
|
|
1154
|
+
"delete-folder": deleteFolder,
|
|
1065
1155
|
folders,
|
|
1066
1156
|
folder,
|
|
1067
1157
|
mkdir,
|
|
@@ -1079,6 +1169,11 @@ async function main() {
|
|
|
1079
1169
|
allowPositionals: true,
|
|
1080
1170
|
strict: false,
|
|
1081
1171
|
options: {
|
|
1172
|
+
add: { type: "string" },
|
|
1173
|
+
remove: { type: "string" },
|
|
1174
|
+
role: { type: "string" },
|
|
1175
|
+
"public-edit": { type: "string" },
|
|
1176
|
+
expires: { type: "string" },
|
|
1082
1177
|
json: { type: "boolean", default: false },
|
|
1083
1178
|
watch: { type: "boolean", default: false },
|
|
1084
1179
|
all: { type: "boolean", default: false },
|