@buildinternet/uploads 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 +49 -14
- package/dist/agent.js +1 -1
- package/dist/cli-args.d.ts +2 -0
- package/dist/cli-args.js +5 -0
- package/dist/cli.js +70 -20
- package/dist/client.d.ts +91 -0
- package/dist/client.js +93 -11
- package/dist/commands/admin-enrollment.d.ts +2 -0
- package/dist/commands/admin-enrollment.js +60 -8
- package/dist/commands/login.js +6 -0
- package/dist/commands/mcp.js +2 -5
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +299 -8
- package/dist/github.d.ts +23 -1
- package/dist/github.js +65 -2
- package/dist/index.d.ts +1 -1
- package/dist/mcp/tools.js +140 -2
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +19 -0
- package/dist/provenance.js +1 -15
- package/dist/update-check.d.ts +30 -0
- package/dist/update-check.js +134 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,15 +4,16 @@ CLI and client for **uploads.sh** — upload files, get public URLs, and produce
|
|
|
4
4
|
|
|
5
5
|
## CLI
|
|
6
6
|
|
|
7
|
-
Binary: **`uploads`**. Install globally (or use
|
|
7
|
+
Binary: **`uploads`**. Install globally (or use an `npx` one-shot):
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install --global @buildinternet/uploads
|
|
11
|
-
npx @buildinternet/uploads
|
|
11
|
+
npx @buildinternet/uploads --help
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
uploads setup
|
|
16
|
+
uploads --version
|
|
16
17
|
uploads attach ./before.png ./after.png
|
|
17
18
|
uploads put ./shot.png
|
|
18
19
|
uploads put ./shot.png --destination screenshots
|
|
@@ -20,6 +21,8 @@ uploads put ./shot.png --no-optimize
|
|
|
20
21
|
uploads put ./mobile.png --frame phone
|
|
21
22
|
uploads put ./ui.png --frame browser --frame-url "https://app.example"
|
|
22
23
|
uploads put ./after.png --pr 123 --comment
|
|
24
|
+
uploads gallery create --title "Release screenshots"
|
|
25
|
+
uploads put ./after.png --gallery gal_example
|
|
23
26
|
uploads doctor
|
|
24
27
|
```
|
|
25
28
|
|
|
@@ -27,12 +30,20 @@ Inside this monorepo only, `pnpm uploads …` builds the package first so you pi
|
|
|
27
30
|
up local source; product docs and PR “how to try it” examples should use the
|
|
28
31
|
global `uploads` form above.
|
|
29
32
|
|
|
30
|
-
Commands: `attach`, `put`, `comment`, `list`, `delete`, `usage`, `reconcile`,
|
|
33
|
+
Commands: `attach`, `put`, `gallery`, `comment`, `list`, `delete`, `usage`, `reconcile`,
|
|
31
34
|
`purge-expired`, `setup`, `install`, `config`, `doctor`, `health`, `mcp`.
|
|
32
35
|
|
|
36
|
+
**Globals (before the command):** `--api-url`, `--token`, `--workspace` / `-w`,
|
|
37
|
+
`--env-file`, `--json`, `--quiet`, `--version` / `-V`, `-h` / `--help`.
|
|
38
|
+
|
|
39
|
+
**Update hints:** after a successful run the CLI may print one stderr line when a
|
|
40
|
+
newer npm release is available (at most once/day, `~/.cache/uploads/`). Silence
|
|
41
|
+
with `--quiet`, `--json`, `UPLOADS_NO_UPDATE=1`, or `NO_UPDATE_NOTIFIER=1`. Not used
|
|
42
|
+
for `uploads mcp`.
|
|
43
|
+
|
|
33
44
|
`attach` is the agent-friendly default for GitHub media. It accepts one or more files,
|
|
34
45
|
infers the pull request for the current branch via `gh`, uploads stable URLs, and creates
|
|
35
|
-
or updates one
|
|
46
|
+
or updates one marker-owned GitHub comment. It keeps loose `gh/...` attachments and linked public galleries in distinct sections, shows up to three available gallery images inline, and updates that same comment in place on every sync. Use `--pr`, `--issue`, and `--repo` to select
|
|
36
47
|
the target explicitly, or `--no-comment` to upload without changing GitHub comments.
|
|
37
48
|
|
|
38
49
|
**Keys / destinations:** default put uses the `screenshots` layout. Typed destinations
|
|
@@ -50,11 +61,33 @@ stripped**. Pass `--keep-exif` / `UPLOADS_KEEP_EXIF=1` to preserve image metadat
|
|
|
50
61
|
community art from [device-frames-media](https://github.com/jonnyjackson26/device-frames-media)
|
|
51
62
|
into `~/.cache/uploads/frames` (not bundled).
|
|
52
63
|
|
|
64
|
+
## Public galleries
|
|
65
|
+
|
|
66
|
+
Create an ordered gallery, then add existing uploads by key. The API returns the canonical
|
|
67
|
+
public URL; the CLI never constructs it. **Anyone who knows that URL can view the gallery and
|
|
68
|
+
its media**—GitHub or repository visibility does not restrict it. Deleting a gallery removes
|
|
69
|
+
only the gallery record, not its uploaded objects or their retention policy. A workspace can hold up to 100 active galleries, each with up to 100 items and 20 external references.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uploads gallery create --title "Release screenshots"
|
|
73
|
+
uploads gallery add gal_example screenshots/myapp/42/after.webp --alt "Updated dashboard"
|
|
74
|
+
uploads put ./before.png --gallery gal_example
|
|
75
|
+
uploads gallery show gal_example
|
|
76
|
+
uploads gallery link gal_example --github buildinternet/uploads#58
|
|
77
|
+
uploads gallery list --github https://github.com/buildinternet/uploads/pull/58
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
When adding several keys, `uploads gallery add` processes them sequentially and reports any
|
|
81
|
+
individual failures in `--json` output. Gallery item updates use the API's current version to
|
|
82
|
+
avoid overwriting concurrent changes.
|
|
83
|
+
|
|
84
|
+
Link a gallery to a GitHub issue or pull request with `gallery link --github`. Run `uploads comment --pr <number>` (or use `put --comment`) to refresh that target’s one managed comment with every linked gallery and loose attachment. Coordinates and strict `https://github.com/<owner>/<repo>/issues|pull/<number>` URLs are accepted; `gallery list --github` performs the authenticated reverse lookup. Links never change gallery identity, and GitHub repository visibility does not make the public gallery private.
|
|
85
|
+
|
|
53
86
|
Config layers (first match wins): CLI flags → env vars → `--env-file` → `~/.config/buildinternet/config`. See `config.example` for keys.
|
|
54
87
|
|
|
55
88
|
## MCP server
|
|
56
89
|
|
|
57
|
-
`uploads mcp` serves the Model Context Protocol over stdio (newline-delimited JSON-RPC, no extra dependencies). Tools
|
|
90
|
+
`uploads mcp` serves the Model Context Protocol over stdio (newline-delimited JSON-RPC, no extra dependencies). Tools include file operations plus public gallery workflows: `gallery_create`, `gallery_get`, `gallery_add`, `gallery_link`, and `gallery_find_by_reference`. Gallery tools return API-provided canonical URLs and never need GitHub credentials. The remaining stdio tools are `put`, `attach`, `list`, `delete`, `usage`, `reconcile`, `purge_expired`, `comment`, `health`, and `doctor` — with the same config resolution and defaults, plus a per-call `workspace` argument. Interactive/credential commands (`setup`, `login`, `admin`, `config`) are not exposed. A token isn't required to start the server; auth errors surface per tool call (`health` needs no auth).
|
|
58
91
|
|
|
59
92
|
```json
|
|
60
93
|
{ "command": "uploads", "args": ["--env-file", "/path/to/.env", "mcp"] }
|
|
@@ -62,7 +95,7 @@ Config layers (first match wins): CLI flags → env vars → `--env-file` → `~
|
|
|
62
95
|
|
|
63
96
|
Or with `UPLOADS_TOKEN`/`UPLOADS_WORKSPACE` in the environment or user config. Claude Code: `claude mcp add uploads -- uploads --env-file /path/to/.env mcp`.
|
|
64
97
|
|
|
65
|
-
For HTTP clients there's also a hosted variant at `https://agents.uploads.sh/mcp` — the workspace is inferred from the bearer token, so only the URL and token are needed (`https://agents.uploads.sh/<workspace>/mcp` and the `mcp.uploads.sh` hostname also work). Tools:
|
|
98
|
+
For HTTP clients there's also a hosted variant at `https://agents.uploads.sh/mcp` — the workspace is inferred from the bearer token, so only the URL and token are needed (`https://agents.uploads.sh/<workspace>/mcp` and the `mcp.uploads.sh` hostname also work). Tools: file operations plus `gallery_create`, `gallery_get`, `gallery_add`, `gallery_link`, and `gallery_find_by_reference`; all use the same bearer-token workspace scopes and gallery URLs come from the API — see `apps/mcp` in the repo. `uploads install` registers it with Claude Code (and installs the agent skill) in one step. Its `put` takes no content type: the stored type is sniffed server-side from the bytes and checked against the workspace allowlist, and writes are rate limited per workspace.
|
|
66
99
|
|
|
67
100
|
## Programmatic use
|
|
68
101
|
|
|
@@ -76,14 +109,16 @@ Agent/MCP helpers: `@buildinternet/uploads/agent` (`createUploadsWorkerFileTools
|
|
|
76
109
|
|
|
77
110
|
```
|
|
78
111
|
src/
|
|
79
|
-
cli.ts
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
cli.ts Entry + help
|
|
113
|
+
package-version.ts Shared version for --version / doctor / headers
|
|
114
|
+
update-check.ts Optional npm update hint (stderr)
|
|
115
|
+
commands.ts put, list, delete, comment, …
|
|
116
|
+
commands/mcp.ts `mcp` command entry
|
|
117
|
+
mcp/ Stdio MCP server (server.ts, tools.ts)
|
|
118
|
+
client.ts HTTP client for the API
|
|
119
|
+
github.ts PR/issue key paths + attachment comments
|
|
120
|
+
embed.ts Markdown image output
|
|
121
|
+
bin/uploads.js Bin shim
|
|
87
122
|
```
|
|
88
123
|
|
|
89
124
|
## Commands
|
package/dist/agent.js
CHANGED
|
@@ -15,7 +15,7 @@ export function createUploadsWorkerFileTools(files, opts = {}) {
|
|
|
15
15
|
},
|
|
16
16
|
overrides: {
|
|
17
17
|
uploadFile: {
|
|
18
|
-
description: "Upload a file for public hosting (e.g. GitHub embeds).
|
|
18
|
+
description: "Upload a non-sensitive file for public hosting (e.g. GitHub embeds). GitHub repository visibility does not restrict access; prefer keys under screenshots/.",
|
|
19
19
|
},
|
|
20
20
|
...overrides,
|
|
21
21
|
},
|
package/dist/cli-args.d.ts
CHANGED
package/dist/cli-args.js
CHANGED
|
@@ -28,6 +28,11 @@ export function parseArgv(argv) {
|
|
|
28
28
|
i++;
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
|
+
if (arg === "--version" || arg === "-V") {
|
|
32
|
+
globals.version = true;
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
31
36
|
if (VALUE_GLOBALS.has(arg)) {
|
|
32
37
|
const value = args[i + 1];
|
|
33
38
|
if (!value || value.startsWith("-")) {
|
package/dist/cli.js
CHANGED
|
@@ -2,13 +2,15 @@ import { createUploadsClient } from "./client.js";
|
|
|
2
2
|
import { resolveApiUrl, resolveConfig } from "./config.js";
|
|
3
3
|
import { UploadsError } from "./errors.js";
|
|
4
4
|
import { commandWorkspace, isHelpFlag, parseArgv, parseCommandArgs, UsageError, } from "./cli-args.js";
|
|
5
|
-
import { runPut, runAttach, runList, runDelete, runHealth, runDoctor, runComment, runUsage, runReconcile, runPurgeExpired, } from "./commands.js";
|
|
5
|
+
import { runPut, runAttach, runList, runDelete, runHealth, runDoctor, runComment, runUsage, runReconcile, runPurgeExpired, runGallery, } from "./commands.js";
|
|
6
6
|
import { runConfig } from "./commands/config.js";
|
|
7
7
|
import { runSetup } from "./commands/setup.js";
|
|
8
8
|
import { runLogin } from "./commands/login.js";
|
|
9
9
|
import { runAdmin } from "./commands/admin-enrollment.js";
|
|
10
10
|
import { runMcp } from "./commands/mcp.js";
|
|
11
11
|
import { runInstall } from "./commands/install.js";
|
|
12
|
+
import { packageVersion } from "./package-version.js";
|
|
13
|
+
import { maybeHintUpdate } from "./update-check.js";
|
|
12
14
|
const ROOT_HELP = `uploads — CLI for uploads.sh (GitHub image embeds)
|
|
13
15
|
|
|
14
16
|
Usage:
|
|
@@ -31,11 +33,13 @@ Other globals (before command):
|
|
|
31
33
|
--token <token> or UPLOADS_TOKEN
|
|
32
34
|
--env-file <path>
|
|
33
35
|
--json JSON on stdout
|
|
34
|
-
--quiet
|
|
36
|
+
--quiet Suppress stderr progress and update hints
|
|
37
|
+
--version, -V Print package version and exit
|
|
35
38
|
|
|
36
39
|
Commands:
|
|
37
40
|
attach <file...> Attach media to the current PR (stable URLs + managed comment)
|
|
38
41
|
put <file> Upload (+ URL + markdown for GitHub)
|
|
42
|
+
gallery Create and organize public media galleries
|
|
39
43
|
comment Create/update a PR/issue attachments comment (via gh)
|
|
40
44
|
list List objects
|
|
41
45
|
delete <key> Delete object
|
|
@@ -45,7 +49,7 @@ Commands:
|
|
|
45
49
|
setup Inspect/configure advanced CLI settings
|
|
46
50
|
install Install the agent skill + register the remote MCP server
|
|
47
51
|
login Exchange an enrollment code and configure credentials
|
|
48
|
-
admin Admin
|
|
52
|
+
admin Admin invitation management
|
|
49
53
|
config Show path, init, or set shared config
|
|
50
54
|
doctor Health + auth + workspace checks
|
|
51
55
|
health API liveness (no auth)
|
|
@@ -55,12 +59,16 @@ Put/list defaults (config file or env):
|
|
|
55
59
|
UPLOADS_DEFAULT_PREFIX, UPLOADS_DEFAULT_REPO, UPLOADS_DEFAULT_REF
|
|
56
60
|
UPLOADS_DEFAULT_WIDTH, UPLOADS_NO_GIT
|
|
57
61
|
|
|
62
|
+
Update hints (stderr, once/day): silence with --quiet / UPLOADS_NO_UPDATE=1 / NO_UPDATE_NOTIFIER=1
|
|
63
|
+
|
|
58
64
|
Examples:
|
|
59
65
|
uploads setup
|
|
60
66
|
uploads setup --token up_default_… --repo myorg/myapp
|
|
61
67
|
uploads attach ./before.png ./after.png
|
|
62
68
|
uploads put ./shot.png --ref 42
|
|
69
|
+
uploads gallery create --title "Release screenshots"
|
|
63
70
|
uploads doctor
|
|
71
|
+
uploads --version
|
|
64
72
|
|
|
65
73
|
Agent/MCP: \`uploads install\` sets up the agent skill and the hosted MCP server
|
|
66
74
|
(https://agents.uploads.sh/mcp, workspace inferred from the token). Run
|
|
@@ -133,33 +141,57 @@ function errorOut(err, json) {
|
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
143
|
}
|
|
144
|
+
/** Point agents at layered --help instead of dumping the full root manual. */
|
|
145
|
+
function usageHint(argv) {
|
|
146
|
+
try {
|
|
147
|
+
const cmd = parseArgv(argv).command;
|
|
148
|
+
process.stderr.write(cmd ? `hint: uploads ${cmd} --help\n` : "hint: uploads --help\n");
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
process.stderr.write("hint: uploads --help\n");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
136
154
|
export async function runCli(argv) {
|
|
137
155
|
try {
|
|
138
156
|
const parsed = parseArgv(argv);
|
|
139
157
|
const json = parsed.globals.json ?? false;
|
|
158
|
+
const quiet = parsed.globals.quiet ?? false;
|
|
159
|
+
if (parsed.globals.version) {
|
|
160
|
+
process.stdout.write(`${packageVersion()}\n`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
140
163
|
if (!parsed.command) {
|
|
141
164
|
process.stderr.write(ROOT_HELP);
|
|
142
165
|
return parsed.help ? 0 : 2;
|
|
143
166
|
}
|
|
144
167
|
const cmdArgs = parsed.rest.slice(1);
|
|
145
168
|
const showHelp = parsed.help || cmdArgs.some(isHelpFlag);
|
|
169
|
+
let code;
|
|
146
170
|
switch (parsed.command) {
|
|
147
171
|
case "health":
|
|
148
|
-
|
|
172
|
+
code = await runHealth({ apiUrl: resolveApiUrl(parsed.globals), json }, cmdArgs, showHelp);
|
|
173
|
+
break;
|
|
149
174
|
case "config":
|
|
150
|
-
|
|
175
|
+
code = await runConfig(cmdArgs, { json, envFile: parsed.globals.envFile }, showHelp);
|
|
176
|
+
break;
|
|
151
177
|
case "setup":
|
|
152
|
-
|
|
178
|
+
code = await runSetup(cmdArgs, { json, envFile: parsed.globals.envFile }, showHelp);
|
|
179
|
+
break;
|
|
153
180
|
case "login":
|
|
154
|
-
|
|
181
|
+
code = await runLogin(cmdArgs, { json, apiUrl: resolveApiUrl(parsed.globals) }, showHelp);
|
|
182
|
+
break;
|
|
155
183
|
case "admin":
|
|
156
|
-
|
|
184
|
+
code = await runAdmin(cmdArgs, { json, apiUrl: resolveApiUrl(parsed.globals) }, showHelp);
|
|
185
|
+
break;
|
|
157
186
|
case "mcp":
|
|
158
|
-
|
|
187
|
+
code = await runMcp(cmdArgs, { globals: parsed.globals }, showHelp);
|
|
188
|
+
break;
|
|
159
189
|
case "install":
|
|
160
|
-
|
|
190
|
+
code = await runInstall(cmdArgs, { globals: parsed.globals, json }, showHelp);
|
|
191
|
+
break;
|
|
161
192
|
case "attach":
|
|
162
193
|
case "put":
|
|
194
|
+
case "gallery":
|
|
163
195
|
case "list":
|
|
164
196
|
case "delete":
|
|
165
197
|
case "usage":
|
|
@@ -170,34 +202,52 @@ export async function runCli(argv) {
|
|
|
170
202
|
const ctx = createContext(parsed.globals, !showHelp, cmdArgs);
|
|
171
203
|
switch (parsed.command) {
|
|
172
204
|
case "attach":
|
|
173
|
-
|
|
205
|
+
code = await runAttach(ctx, cmdArgs, showHelp);
|
|
206
|
+
break;
|
|
174
207
|
case "put":
|
|
175
|
-
|
|
208
|
+
code = await runPut(ctx, cmdArgs, showHelp);
|
|
209
|
+
break;
|
|
210
|
+
case "gallery":
|
|
211
|
+
code = await runGallery(ctx, cmdArgs, showHelp);
|
|
212
|
+
break;
|
|
176
213
|
case "comment":
|
|
177
|
-
|
|
214
|
+
code = await runComment(ctx, cmdArgs, showHelp);
|
|
215
|
+
break;
|
|
178
216
|
case "list":
|
|
179
|
-
|
|
217
|
+
code = await runList(ctx, cmdArgs, showHelp);
|
|
218
|
+
break;
|
|
180
219
|
case "delete":
|
|
181
|
-
|
|
220
|
+
code = await runDelete(ctx, cmdArgs, showHelp);
|
|
221
|
+
break;
|
|
182
222
|
case "usage":
|
|
183
|
-
|
|
223
|
+
code = await runUsage(ctx, cmdArgs, showHelp);
|
|
224
|
+
break;
|
|
184
225
|
case "reconcile":
|
|
185
|
-
|
|
226
|
+
code = await runReconcile(ctx, cmdArgs, showHelp);
|
|
227
|
+
break;
|
|
186
228
|
case "purge-expired":
|
|
187
|
-
|
|
229
|
+
code = await runPurgeExpired(ctx, cmdArgs, showHelp);
|
|
230
|
+
break;
|
|
188
231
|
case "doctor":
|
|
189
|
-
|
|
232
|
+
code = await runDoctor(ctx, cmdArgs, showHelp);
|
|
233
|
+
break;
|
|
190
234
|
}
|
|
235
|
+
break;
|
|
191
236
|
}
|
|
192
237
|
default:
|
|
193
238
|
process.stderr.write(`unknown command: ${parsed.command}\n\n${ROOT_HELP}`);
|
|
194
239
|
return 2;
|
|
195
240
|
}
|
|
241
|
+
// Best-effort; skipped for mcp, --quiet/--json, and opt-out env vars.
|
|
242
|
+
if (code === 0 && !showHelp) {
|
|
243
|
+
await maybeHintUpdate({ quiet: quiet || json, command: parsed.command });
|
|
244
|
+
}
|
|
245
|
+
return code;
|
|
196
246
|
}
|
|
197
247
|
catch (err) {
|
|
198
248
|
errorOut(err, argv.includes("--json"));
|
|
199
249
|
if (err instanceof UsageError && !argv.includes("--json"))
|
|
200
|
-
|
|
250
|
+
usageHint(argv);
|
|
201
251
|
return exitCode(err);
|
|
202
252
|
}
|
|
203
253
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -53,6 +53,79 @@ export interface DeleteResult {
|
|
|
53
53
|
key: string;
|
|
54
54
|
deleted: boolean;
|
|
55
55
|
}
|
|
56
|
+
/** A workspace-owned, publicly visible ordered media gallery. */
|
|
57
|
+
export interface GalleryItem {
|
|
58
|
+
id: string;
|
|
59
|
+
objectKey: string;
|
|
60
|
+
position: number;
|
|
61
|
+
caption: string | null;
|
|
62
|
+
altText: string | null;
|
|
63
|
+
createdAt: string;
|
|
64
|
+
status: "available" | "missing";
|
|
65
|
+
url: string | null;
|
|
66
|
+
contentType: string | null;
|
|
67
|
+
size: number | null;
|
|
68
|
+
}
|
|
69
|
+
export interface Gallery {
|
|
70
|
+
id: string;
|
|
71
|
+
/** Canonical public URL returned by the API; clients must not construct it. */
|
|
72
|
+
url: string;
|
|
73
|
+
workspace: string;
|
|
74
|
+
title: string;
|
|
75
|
+
description: string | null;
|
|
76
|
+
visibility: "public";
|
|
77
|
+
coverItemId: string | null;
|
|
78
|
+
version: number;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
updatedAt: string;
|
|
81
|
+
items: GalleryItem[];
|
|
82
|
+
}
|
|
83
|
+
export type GallerySummary = Omit<Gallery, "items">;
|
|
84
|
+
export interface GalleryListOptions {
|
|
85
|
+
limit?: number;
|
|
86
|
+
cursor?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface GalleryListResult {
|
|
89
|
+
galleries: GallerySummary[];
|
|
90
|
+
nextCursor: string | null;
|
|
91
|
+
}
|
|
92
|
+
export interface CreateGalleryOptions {
|
|
93
|
+
title: string;
|
|
94
|
+
description?: string | null;
|
|
95
|
+
}
|
|
96
|
+
export interface AddGalleryItemOptions {
|
|
97
|
+
expectedVersion: number;
|
|
98
|
+
caption?: string | null;
|
|
99
|
+
altText?: string | null;
|
|
100
|
+
}
|
|
101
|
+
export interface DeleteGalleryOptions {
|
|
102
|
+
expectedVersion: number;
|
|
103
|
+
}
|
|
104
|
+
export interface GalleryExternalReference {
|
|
105
|
+
id: string;
|
|
106
|
+
provider: "github";
|
|
107
|
+
resourceType: "item";
|
|
108
|
+
coordinate: string;
|
|
109
|
+
canonicalUrl: string | null;
|
|
110
|
+
createdAt: string;
|
|
111
|
+
}
|
|
112
|
+
export interface GalleryExternalReferenceListResult {
|
|
113
|
+
references: GalleryExternalReference[];
|
|
114
|
+
}
|
|
115
|
+
export interface LinkGalleryExternalReferenceOptions {
|
|
116
|
+
expectedVersion: number;
|
|
117
|
+
provider: "github";
|
|
118
|
+
coordinate: string;
|
|
119
|
+
}
|
|
120
|
+
export interface UnlinkGalleryExternalReferenceOptions {
|
|
121
|
+
expectedVersion: number;
|
|
122
|
+
}
|
|
123
|
+
export interface FindGalleriesByReferenceOptions {
|
|
124
|
+
provider: "github";
|
|
125
|
+
coordinate: string;
|
|
126
|
+
limit?: number;
|
|
127
|
+
cursor?: string;
|
|
128
|
+
}
|
|
56
129
|
export interface HealthResult {
|
|
57
130
|
ok: boolean;
|
|
58
131
|
}
|
|
@@ -101,14 +174,17 @@ export interface EnrollmentExchangeResult {
|
|
|
101
174
|
expiresAt?: string;
|
|
102
175
|
}
|
|
103
176
|
export interface EnrollmentCreateResult {
|
|
177
|
+
pageId: string;
|
|
104
178
|
code: string;
|
|
105
179
|
expiresAt: string;
|
|
106
180
|
tokenExpiresAt: string;
|
|
181
|
+
emailed?: boolean;
|
|
107
182
|
}
|
|
108
183
|
export declare function exchangeEnrollment(apiUrl: string, code: string): Promise<EnrollmentExchangeResult>;
|
|
109
184
|
export declare function createEnrollment(apiUrl: string, adminToken: string, input: {
|
|
110
185
|
workspace?: string;
|
|
111
186
|
label?: string;
|
|
187
|
+
email?: string;
|
|
112
188
|
enrollmentSeconds?: number;
|
|
113
189
|
tokenExpiresInSeconds?: number;
|
|
114
190
|
scopes?: Array<"files:read" | "files:write" | "files:delete">;
|
|
@@ -124,6 +200,21 @@ export declare function createUploadsClient(config: UploadsClientConfig): {
|
|
|
124
200
|
}): Promise<ListItem[]>;
|
|
125
201
|
delete(key: string): Promise<DeleteResult>;
|
|
126
202
|
head(key: string): Promise<HeadResult>;
|
|
203
|
+
createGallery(opts: CreateGalleryOptions): Promise<Gallery>;
|
|
204
|
+
getGallery(id: string): Promise<Gallery>;
|
|
205
|
+
listGalleries(opts?: GalleryListOptions): Promise<GalleryListResult>;
|
|
206
|
+
deleteGallery(id: string, opts: DeleteGalleryOptions): Promise<{
|
|
207
|
+
deleted: boolean;
|
|
208
|
+
id: string;
|
|
209
|
+
}>;
|
|
210
|
+
addGalleryItem(id: string, objectKey: string, opts: AddGalleryItemOptions): Promise<GalleryItem>;
|
|
211
|
+
listGalleryExternalReferences(id: string): Promise<GalleryExternalReferenceListResult>;
|
|
212
|
+
linkGalleryExternalReference(id: string, opts: LinkGalleryExternalReferenceOptions): Promise<GalleryExternalReference>;
|
|
213
|
+
unlinkGalleryExternalReference(id: string, referenceId: string, opts: UnlinkGalleryExternalReferenceOptions): Promise<{
|
|
214
|
+
deleted: boolean;
|
|
215
|
+
id: string;
|
|
216
|
+
}>;
|
|
217
|
+
findGalleriesByReference(opts: FindGalleriesByReferenceOptions): Promise<GalleryListResult>;
|
|
127
218
|
health(): Promise<HealthResult>;
|
|
128
219
|
/** Workspace storage / upload counters (+ limits when configured). */
|
|
129
220
|
usage(): Promise<UsageResult>;
|
package/dist/client.js
CHANGED
|
@@ -36,21 +36,24 @@ function filesBase(config) {
|
|
|
36
36
|
function usageBase(config) {
|
|
37
37
|
return `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/usage`;
|
|
38
38
|
}
|
|
39
|
+
function galleriesBase(config) {
|
|
40
|
+
return `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/galleries`;
|
|
41
|
+
}
|
|
39
42
|
function mapApiError(status, error, code) {
|
|
40
43
|
const normalized = error.toLowerCase();
|
|
41
|
-
if (status === 401 || normalized === "unauthorized") {
|
|
44
|
+
if (status === 401 || code === "unauthorized" || normalized === "unauthorized") {
|
|
42
45
|
return new UploadsError(error, "UNAUTHORIZED", status);
|
|
43
46
|
}
|
|
44
|
-
if (status === 404 || normalized === "not found") {
|
|
47
|
+
if (status === 404 || code === "not_found" || normalized === "not found") {
|
|
45
48
|
return new UploadsError(error, "NOT_FOUND", status);
|
|
46
49
|
}
|
|
47
|
-
if (status === 400 && normalized === "invalid key") {
|
|
50
|
+
if (code === "invalid_key" || (status === 400 && normalized === "invalid key")) {
|
|
48
51
|
return new UploadsError(error, "INVALID_KEY", status);
|
|
49
52
|
}
|
|
50
53
|
if (code === "key_prefix_not_allowed" || code === "key_too_deep") {
|
|
51
54
|
return new UploadsError(error, "KEY_POLICY", status);
|
|
52
55
|
}
|
|
53
|
-
// Prefer stable body
|
|
56
|
+
// Prefer stable body code — bare 429 is also used for write rate limits.
|
|
54
57
|
if (status === 507 || code === "storage_quota_exceeded") {
|
|
55
58
|
return new UploadsError(error, "STORAGE_QUOTA", status);
|
|
56
59
|
}
|
|
@@ -59,15 +62,34 @@ function mapApiError(status, error, code) {
|
|
|
59
62
|
}
|
|
60
63
|
return new UploadsError(error, "API_ERROR", status);
|
|
61
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Parse API error bodies. Prefers the nested envelope
|
|
67
|
+
* `{ error: { code, type, message, details? } }`; still accepts the legacy
|
|
68
|
+
* flat `{ error: string, code?: string }` shape.
|
|
69
|
+
*/
|
|
70
|
+
function extractErrorFields(body) {
|
|
71
|
+
if (typeof body === "object" && body && "error" in body) {
|
|
72
|
+
const err = body.error;
|
|
73
|
+
if (typeof err === "object" && err && "message" in err) {
|
|
74
|
+
const nested = err;
|
|
75
|
+
return {
|
|
76
|
+
message: typeof nested.message === "string" ? nested.message : "request failed",
|
|
77
|
+
code: typeof nested.code === "string" ? nested.code : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (typeof err === "string") {
|
|
81
|
+
const code = "code" in body && typeof body.code === "string"
|
|
82
|
+
? body.code
|
|
83
|
+
: undefined;
|
|
84
|
+
return { message: err, code };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { message: "request failed" };
|
|
88
|
+
}
|
|
62
89
|
async function parseErrorResponse(res) {
|
|
63
90
|
const body = await res.json().catch(() => ({}));
|
|
64
|
-
const message
|
|
65
|
-
|
|
66
|
-
: res.statusText || "request failed";
|
|
67
|
-
const code = typeof body === "object" && body && "code" in body && typeof body.code === "string"
|
|
68
|
-
? body.code
|
|
69
|
-
: undefined;
|
|
70
|
-
return mapApiError(res.status, message, code);
|
|
91
|
+
const { message, code } = extractErrorFields(body);
|
|
92
|
+
return mapApiError(res.status, message || res.statusText || "request failed", code);
|
|
71
93
|
}
|
|
72
94
|
export function createUploadsClient(config) {
|
|
73
95
|
async function request(method, path, opts) {
|
|
@@ -105,6 +127,9 @@ export function createUploadsClient(config) {
|
|
|
105
127
|
const qs = params.toString();
|
|
106
128
|
return request("GET", `${filesBase(config)}${qs ? `?${qs}` : ""}`);
|
|
107
129
|
}
|
|
130
|
+
async function getGallery(id) {
|
|
131
|
+
return request("GET", `${galleriesBase(config)}/${encodeURIComponent(id)}`);
|
|
132
|
+
}
|
|
108
133
|
return {
|
|
109
134
|
async put(body, opts) {
|
|
110
135
|
const key = opts.key ??
|
|
@@ -151,6 +176,63 @@ export function createUploadsClient(config) {
|
|
|
151
176
|
async head(key) {
|
|
152
177
|
return request("GET", `${filesBase(config)}/${encodeKeyPath(key)}`);
|
|
153
178
|
},
|
|
179
|
+
async createGallery(opts) {
|
|
180
|
+
return request("POST", galleriesBase(config), {
|
|
181
|
+
body: new TextEncoder().encode(JSON.stringify(opts)),
|
|
182
|
+
headers: { "Content-Type": "application/json" },
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
async getGallery(id) {
|
|
186
|
+
return getGallery(id);
|
|
187
|
+
},
|
|
188
|
+
async listGalleries(opts = {}) {
|
|
189
|
+
const params = new URLSearchParams();
|
|
190
|
+
if (opts.limit != null)
|
|
191
|
+
params.set("limit", String(opts.limit));
|
|
192
|
+
if (opts.cursor)
|
|
193
|
+
params.set("cursor", opts.cursor);
|
|
194
|
+
const qs = params.toString();
|
|
195
|
+
return request("GET", `${galleriesBase(config)}${qs ? `?${qs}` : ""}`);
|
|
196
|
+
},
|
|
197
|
+
async deleteGallery(id, opts) {
|
|
198
|
+
return request("DELETE", `${galleriesBase(config)}/${encodeURIComponent(id)}`, {
|
|
199
|
+
body: new TextEncoder().encode(JSON.stringify(opts)),
|
|
200
|
+
headers: { "Content-Type": "application/json" },
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
async addGalleryItem(id, objectKey, opts) {
|
|
204
|
+
return request("POST", `${galleriesBase(config)}/${encodeURIComponent(id)}/items`, {
|
|
205
|
+
body: new TextEncoder().encode(JSON.stringify({ objectKey, ...opts })),
|
|
206
|
+
headers: { "Content-Type": "application/json" },
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
async listGalleryExternalReferences(id) {
|
|
210
|
+
return request("GET", galleriesBase(config) + "/" + encodeURIComponent(id) + "/external-references");
|
|
211
|
+
},
|
|
212
|
+
async linkGalleryExternalReference(id, opts) {
|
|
213
|
+
return request("POST", galleriesBase(config) + "/" + encodeURIComponent(id) + "/external-references", {
|
|
214
|
+
body: new TextEncoder().encode(JSON.stringify(opts)),
|
|
215
|
+
headers: { "Content-Type": "application/json" },
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
async unlinkGalleryExternalReference(id, referenceId, opts) {
|
|
219
|
+
return request("DELETE", galleriesBase(config) +
|
|
220
|
+
"/" +
|
|
221
|
+
encodeURIComponent(id) +
|
|
222
|
+
"/external-references/" +
|
|
223
|
+
encodeURIComponent(referenceId), {
|
|
224
|
+
body: new TextEncoder().encode(JSON.stringify(opts)),
|
|
225
|
+
headers: { "Content-Type": "application/json" },
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
async findGalleriesByReference(opts) {
|
|
229
|
+
const params = new URLSearchParams({ provider: opts.provider, coordinate: opts.coordinate });
|
|
230
|
+
if (opts.limit != null)
|
|
231
|
+
params.set("limit", String(opts.limit));
|
|
232
|
+
if (opts.cursor)
|
|
233
|
+
params.set("cursor", opts.cursor);
|
|
234
|
+
return request("GET", galleriesBase(config) + "/by-reference?" + params);
|
|
235
|
+
},
|
|
154
236
|
async health() {
|
|
155
237
|
return request("GET", `${config.apiUrl}/health`, { auth: false });
|
|
156
238
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
type FileScope = "files:read" | "files:write" | "files:delete";
|
|
2
|
+
export declare function invitePageUrl(apiUrl: string, pageId: string, webUrl?: string): string;
|
|
3
|
+
export declare function inviteMagicLink(pageUrl: string, code: string): string;
|
|
2
4
|
export declare function parseScopes(raw: string | undefined): FileScope[] | undefined;
|
|
3
5
|
export declare function runAdmin(args: string[], opts: {
|
|
4
6
|
json?: boolean;
|