bikeshed-now 0.1.0 → 0.3.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
@@ -34,6 +34,16 @@ Publishing identical bytes is a no-op: the bundle is hashed, and a matching hash
34
34
  mints no new version. So `update` is safe in a loop, in CI, or after a change you
35
35
  are not sure you made.
36
36
 
37
+ The address comes from the title. To choose it, pass `--id`:
38
+
39
+ ```bash
40
+ bikeshed publish platform.md --id 01-platform
41
+ # published 01-platform v1 — https://<deployment>/d/01-platform
42
+ ```
43
+
44
+ If the id is taken, the document is still published, at the id plus a few
45
+ random characters, and the CLI says so on stderr.
46
+
37
47
  `bikeshed --help` lists the rest — listing documents, folders, and installing the
38
48
  agent skills a deployment serves.
39
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bikeshed-now",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Publish and revise documents on a Bikeshed deployment from the terminal.",
@@ -23,7 +23,7 @@
23
23
  "url": "https://github.com/DeputyHumans/bikeshed-now/issues"
24
24
  },
25
25
  "bin": {
26
- "bikeshed": "./src/index.mjs"
26
+ "bikeshed": "src/index.mjs"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=20"
package/src/client.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  import { readFileSync } from "node:fs";
5
5
  import { basename } from "node:path";
6
6
 
7
- export const CLIENT_NAME = "bikeshed-cli/0.1.0";
7
+ export const CLIENT_NAME = "bikeshed-cli/0.3.0";
8
8
 
9
9
  /**
10
10
  * Two surfaces, and the split is deliberate rather than untidy.
@@ -80,11 +80,12 @@ export async function callTool(origin, token, name, args = {}) {
80
80
  }
81
81
 
82
82
  /** Upload a file as a NEW document, exactly as the browser uploader does. */
83
- export async function uploadFile(origin, token, path, { title, folder } = {}) {
83
+ export async function uploadFile(origin, token, path, { title, folder, id } = {}) {
84
84
  const bytes = readFileSync(path);
85
85
  const params = new URLSearchParams({ filename: basename(path) });
86
86
  if (title) params.set("title", title);
87
87
  if (folder) params.set("folderId", folder);
88
+ if (id) params.set("id", id);
88
89
 
89
90
  const res = await fetch(`${origin}/publish?${params}`, {
90
91
  method: "POST",
package/src/index.mjs CHANGED
@@ -49,8 +49,12 @@ Skills
49
49
  Options
50
50
  --json machine-readable output
51
51
  --title <text> title for publish
52
+ --id <id> with publish: the address, /d/<id> (default: from the title)
52
53
  --folder <id> destination folder for publish
53
54
  --parent <id> parent folder for mkdir
55
+ --limit <n> with list: rows per page (default 20, max 50)
56
+ --cursor <id> with list: the page after the one that printed it
57
+ --all with list: every page; with comments: resolved too
54
58
  --watch with publish/update: republish on every save
55
59
  --origin <url> the deployment (default: the one you logged in to)
56
60
  --token <token> override the stored token
@@ -104,9 +108,28 @@ async function login(flags) {
104
108
 
105
109
  async function list(flags) {
106
110
  const { origin, token } = resolveAuth(flags);
107
- const result = await callTool(origin, token, "list_documents", {
108
- limit: Number(flags.limit) || 20,
109
- });
111
+ const page = (limit, cursor) => {
112
+ const args = { limit };
113
+ if (cursor) args.cursor = cursor;
114
+ return callTool(origin, token, "list_documents", args);
115
+ };
116
+
117
+ let result;
118
+ if (flags.all) {
119
+ // Pages at the server's cap (LIST_LIMIT), which is the fewest round trips.
120
+ // The result keeps one page's shape, so `--json` needs no second parser.
121
+ const documents = [];
122
+ let cursor = flags.cursor;
123
+ do {
124
+ const next = await page(50, cursor);
125
+ documents.push(...next.documents);
126
+ cursor = next.nextCursor;
127
+ } while (cursor);
128
+ result = { documents, nextCursor: null };
129
+ } else {
130
+ result = await page(Number(flags.limit) || 20, flags.cursor);
131
+ }
132
+
110
133
  out(flags.json, result, () => {
111
134
  if (result.documents.length === 0) {
112
135
  console.log("Nothing yet. `bikeshed publish <file>` to start.");
@@ -122,6 +145,7 @@ async function list(flags) {
122
145
 
123
146
  async function publish(flags, [path]) {
124
147
  if (!path) fail("usage: bikeshed publish <file>");
148
+ if (flags.id === true) fail("usage: bikeshed publish <file> --id <id>");
125
149
  const { origin, token } = resolveAuth(flags);
126
150
  const file = resolve(path);
127
151
 
@@ -129,7 +153,14 @@ async function publish(flags, [path]) {
129
153
  const result = await uploadFile(origin, token, file, {
130
154
  title: flags.title,
131
155
  folder: flags.folder,
156
+ id: flags.id,
132
157
  });
158
+ // A taken id still publishes, under a suffix. Said on stderr so `--json`
159
+ // stays parseable; "not available" also covers a deployment too old to
160
+ // know the parameter, which ignores it.
161
+ if (flags.id && result.docId !== flags.id) {
162
+ console.error(`${flags.id} was not available; published at ${result.docId}`);
163
+ }
133
164
  // The address is in the human line already; `--json` carried only the id, so
134
165
  // anything reading it had to know how to build a URL out of a docId.
135
166
  out(flags.json, { ...result, url: result.url ?? `${origin}/d/${result.docId}` }, () =>
@@ -369,6 +400,7 @@ async function main() {
369
400
  all: { type: "boolean", default: false },
370
401
  help: { type: "boolean", default: false },
371
402
  title: { type: "string" },
403
+ id: { type: "string" },
372
404
  folder: { type: "string" },
373
405
  parent: { type: "string" },
374
406
  origin: { type: "string" },