@buildinternet/uploads 0.8.0 → 0.10.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
@@ -25,10 +25,11 @@ uploads put ./capture-2026-…Z.png --pr 123 --name hero.png # clean leaf, sta
25
25
  uploads put ./shot.png --pr 123 --name hero.png --dry-run --format url # preview URL, no upload
26
26
  uploads gallery create --title "Release screenshots"
27
27
  uploads put ./after.png --gallery gal_example
28
- uploads put ./shot.png --meta app=myapp --meta page=settings # queryable custom metadata
29
- uploads meta get screenshots/myapp/42/shot.png
30
- uploads meta set screenshots/myapp/42/shot.png page=onboarding --delete device
31
- uploads find app=myapp page=settings # or: list --meta app=myapp
28
+ # custom metadata (queryable): page URL, in-app path, which surface
29
+ uploads put ./shot.png --meta url=https://app.example/settings --meta path=/settings --meta app=web
30
+ uploads meta get screenshots/myapp/42/shot.webp
31
+ uploads meta set screenshots/myapp/42/shot.webp path=/onboarding --delete url
32
+ uploads find app=web path=/settings # or: list --meta app=web
32
33
  uploads doctor
33
34
  ```
34
35
 
@@ -37,10 +38,19 @@ up local source; product docs and PR “how to try it” examples should use the
37
38
  global `uploads` form above.
38
39
 
39
40
  Commands: `attach`, `put`, `gallery`, `comment`, `list`, `find`, `meta`, `delete`, `usage`,
40
- `reconcile`, `purge-expired`, `setup`, `install`, `config`, `doctor`, `health`, `mcp`.
41
+ `reconcile`, `purge-expired`, `setup`, `install`, `config`, `doctor`, `health`, `mcp`,
42
+ `completion`.
43
+
44
+ **Help:** bare `uploads` / `uploads help` / `--help` shows essentials; use
45
+ `uploads help --all` (or `--help --all`) for the full command list. Per-command:
46
+ `uploads <cmd> --help`.
47
+
48
+ **Shell completion:** `uploads completion bash|zsh|fish` prints a script to
49
+ stdout. Example (zsh): `uploads completion zsh > ~/.zsh/completions/_uploads`.
41
50
 
42
51
  **Globals (before the command):** `--api-url`, `--token`, `--workspace` / `-w`,
43
- `--env-file`, `--json`, `--quiet`, `--version` / `-V`, `-h` / `--help`.
52
+ `--env-file`, `--json`, `--quiet`, `--version` / `-V`, `-h` / `--help`, `--all`
53
+ (with root help).
44
54
 
45
55
  **Update hints:** after a successful run the CLI may print one stderr line when a
46
56
  newer npm release is available (at most once/day, `~/.cache/uploads/`). Silence
@@ -65,7 +75,13 @@ use `gh/…`. Workspaces may restrict put/sign to those roots via
65
75
  **Image optimization:** by default, still images are re-encoded to WebP (long edge
66
76
  capped, high quality) before upload so GitHub embeds stay small, and **EXIF is
67
77
  stripped**. Pass `--keep-exif` / `UPLOADS_KEEP_EXIF=1` to preserve image metadata, or
68
- `--no-optimize` / `UPLOADS_NO_OPTIMIZE=1` to upload originals unchanged.
78
+ `--no-optimize` / `UPLOADS_NO_OPTIMIZE=1` to upload originals unchanged. Optimize
79
+ notes print human sizes (e.g. `411.5 KB → 94.2 KB`).
80
+
81
+ **Re-upload / hot-swap:** the same key overwrites in place with no prompt (stable
82
+ `--pr` / `attach` paths). Human mode notes `>> replaced existing object (same URL)`;
83
+ JSON includes `replaced`. Preview with `--dry-run` (reports _would replace_ when
84
+ the key already exists).
69
85
 
70
86
  **Frames (opt-in):** `--frame phone|browser|iphone-16-pro` composites chrome
71
87
  **before** optimize. `phone`/`browser` are procedural; `iphone-16-pro` fetches
@@ -98,7 +114,7 @@ Config layers (first match wins): CLI flags → env vars → `--env-file` → `~
98
114
 
99
115
  ## MCP server
100
116
 
101
- `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).
117
+ `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`, `set_metadata`, `find_files`, `usage`, `reconcile`, `purge_expired`, `comment`, `health`, and `doctor` — with the same config resolution and defaults, plus a per-call `workspace` argument. `put` and `attach` accept a `metadata` param (same `gh.*` auto-injection as the CLI's `attach`); `set_metadata` and `find_files` mirror `uploads meta set` and `uploads find`. 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).
102
118
 
103
119
  ```json
104
120
  { "command": "uploads", "args": ["--env-file", "/path/to/.env", "mcp"] }
@@ -106,7 +122,7 @@ Config layers (first match wins): CLI flags → env vars → `--env-file` → `~
106
122
 
107
123
  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`.
108
124
 
109
- 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 the skill + hosted MCP (short progress; `--verbose` for underlying output). 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.
125
+ 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. The hosted `put` also accepts a `metadata` param. `uploads install` registers the skill + hosted MCP (short progress; `--verbose` for underlying output). 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.
110
126
 
111
127
  ## Programmatic use
112
128
 
@@ -7,6 +7,8 @@ export interface GlobalFlags {
7
7
  quiet?: boolean;
8
8
  /** `--version` / `-V` — print package version and exit. */
9
9
  version?: boolean;
10
+ /** `--all` — with root help, show the full command list. */
11
+ all?: boolean;
10
12
  }
11
13
  export interface ParsedArgv {
12
14
  globals: GlobalFlags;
package/dist/cli-args.js CHANGED
@@ -33,6 +33,11 @@ export function parseArgv(argv) {
33
33
  i++;
34
34
  continue;
35
35
  }
36
+ if (arg === "--all") {
37
+ globals.all = true;
38
+ i++;
39
+ continue;
40
+ }
36
41
  if (VALUE_GLOBALS.has(arg)) {
37
42
  const value = args[i + 1];
38
43
  if (!value || value.startsWith("-")) {
@@ -0,0 +1,89 @@
1
+ /**
2
+ * uploads.sh brand tokens + terminal chevron mark for root help.
3
+ * Colors mirror packages/ui/src/tokens.css; geometry from Brand.tsx / favicon.
4
+ */
5
+ export interface Rgb {
6
+ r: number;
7
+ g: number;
8
+ b: number;
9
+ }
10
+ /** Design tokens from packages/ui (dark scheme). */
11
+ export declare const BRAND: {
12
+ readonly bg: {
13
+ readonly r: 10;
14
+ readonly g: 10;
15
+ readonly b: 11;
16
+ };
17
+ readonly panel: {
18
+ readonly r: 18;
19
+ readonly g: 18;
20
+ readonly b: 20;
21
+ };
22
+ readonly line: {
23
+ readonly r: 35;
24
+ readonly g: 35;
25
+ readonly b: 39;
26
+ };
27
+ readonly fg: {
28
+ readonly r: 236;
29
+ readonly g: 236;
30
+ readonly b: 234;
31
+ };
32
+ readonly body: {
33
+ readonly r: 179;
34
+ readonly g: 179;
35
+ readonly b: 173;
36
+ };
37
+ readonly muted: {
38
+ readonly r: 138;
39
+ readonly g: 138;
40
+ readonly b: 131;
41
+ };
42
+ readonly accent: {
43
+ readonly r: 194;
44
+ readonly g: 126;
45
+ readonly b: 255;
46
+ };
47
+ readonly green: {
48
+ readonly r: 143;
49
+ readonly g: 174;
50
+ readonly b: 98;
51
+ };
52
+ readonly red: {
53
+ readonly r: 217;
54
+ readonly g: 138;
55
+ readonly b: 156;
56
+ };
57
+ };
58
+ export declare function mixRgb(a: Rgb, b: Rgb, t: number): Rgb;
59
+ export declare function accentAt(opacity: number, over?: Rgb): Rgb;
60
+ /** Deep-copy the thin chevron opacity grid. */
61
+ export declare function rasterizeMark(): (number | null)[][];
62
+ /**
63
+ * Half-block render of the mark (▀/▄/█). Two pixel rows → one terminal row.
64
+ */
65
+ export declare function renderBrandMarkLines(options?: {
66
+ color?: boolean;
67
+ }): string[];
68
+ /** Default CLI tagline. */
69
+ export declare const DEFAULT_TAGLINE = "GitHub screenshot + recording uploads for agents";
70
+ /**
71
+ * Brand lockup:
72
+ * [mark] uploads.sh
73
+ * [mark] GitHub screenshot + recording uploads for agents
74
+ * [mark] v0.9.0
75
+ */
76
+ export declare function formatBrandHeader(options?: {
77
+ color?: boolean;
78
+ label?: string;
79
+ tagline?: string;
80
+ version?: string;
81
+ }): string;
82
+ export declare function formatUpdateBanner(options: {
83
+ current: string;
84
+ latest: string;
85
+ color?: boolean;
86
+ }): string;
87
+ export declare function formatAuthBanner(options?: {
88
+ color?: boolean;
89
+ }): string;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * uploads.sh brand tokens + terminal chevron mark for root help.
3
+ * Colors mirror packages/ui/src/tokens.css; geometry from Brand.tsx / favicon.
4
+ */
5
+ /** Design tokens from packages/ui (dark scheme). */
6
+ export const BRAND = {
7
+ bg: { r: 0x0a, g: 0x0a, b: 0x0b },
8
+ panel: { r: 0x12, g: 0x12, b: 0x14 },
9
+ line: { r: 0x23, g: 0x23, b: 0x27 },
10
+ fg: { r: 0xec, g: 0xec, b: 0xea },
11
+ body: { r: 0xb3, g: 0xb3, b: 0xad },
12
+ muted: { r: 0x8a, g: 0x8a, b: 0x83 },
13
+ accent: { r: 0xc2, g: 0x7e, b: 0xff },
14
+ green: { r: 0x8f, g: 0xae, b: 0x62 },
15
+ red: { r: 0xd9, g: 0x8a, b: 0x9c },
16
+ };
17
+ export function mixRgb(a, b, t) {
18
+ const u = Math.min(1, Math.max(0, t));
19
+ return {
20
+ r: Math.round(a.r * u + b.r * (1 - u)),
21
+ g: Math.round(a.g * u + b.g * (1 - u)),
22
+ b: Math.round(a.b * u + b.b * (1 - u)),
23
+ };
24
+ }
25
+ export function accentAt(opacity, over = BRAND.panel) {
26
+ return mixRgb(BRAND.accent, over, opacity);
27
+ }
28
+ /**
29
+ * Thin stacked chevrons (1-cell stroke, 5×9) with brand opacities 1 / 0.55 / 0.28.
30
+ * Mirrors the three fading Λ shapes from the favicon without fat 4×4 fills.
31
+ */
32
+ const THIN_CHEVRON = (() => {
33
+ const rows = [" # ", " # # ", "# #", " # ", " # # ", "# #", " # ", " # # ", "# #"];
34
+ const opacities = [1, 1, 1, 0.55, 0.55, 0.55, 0.28, 0.28, 0.28];
35
+ return rows.map((row, y) => [...row].map((ch) => (ch === "#" ? opacities[y] : null)));
36
+ })();
37
+ /** Deep-copy the thin chevron opacity grid. */
38
+ export function rasterizeMark() {
39
+ return THIN_CHEVRON.map((row) => row.slice());
40
+ }
41
+ function cellColor(v) {
42
+ if (v === null || v <= 0)
43
+ return null;
44
+ return accentAt(v);
45
+ }
46
+ function ansiFg(c) {
47
+ return `\u001b[38;2;${c.r};${c.g};${c.b}m`;
48
+ }
49
+ function ansiBg(c) {
50
+ return `\u001b[48;2;${c.r};${c.g};${c.b}m`;
51
+ }
52
+ const ANSI_RESET = "\u001b[0m";
53
+ /**
54
+ * Half-block render of the mark (▀/▄/█). Two pixel rows → one terminal row.
55
+ */
56
+ export function renderBrandMarkLines(options = {}) {
57
+ const grid = rasterizeMark();
58
+ const color = options.color ?? false;
59
+ const height = grid.length;
60
+ const width = grid[0]?.length ?? 0;
61
+ const lines = [];
62
+ for (let y = 0; y < height; y += 2) {
63
+ let line = "";
64
+ for (let x = 0; x < width; x++) {
65
+ const top = grid[y]?.[x] ?? null;
66
+ const bot = grid[y + 1]?.[x] ?? null;
67
+ const tc = cellColor(top);
68
+ const bc = cellColor(bot);
69
+ if (!color) {
70
+ const strength = Math.max(top ?? 0, bot ?? 0);
71
+ if (strength <= 0)
72
+ line += " ";
73
+ else if (top && bot)
74
+ line += strength >= 0.8 ? "█" : strength >= 0.4 ? "▓" : "░";
75
+ else if (top)
76
+ line += "▀";
77
+ else
78
+ line += "▄";
79
+ continue;
80
+ }
81
+ if (tc && bc) {
82
+ if (top === bot)
83
+ line += ansiFg(tc) + "█" + ANSI_RESET;
84
+ else
85
+ line += ansiFg(tc) + ansiBg(bc) + "▀" + ANSI_RESET;
86
+ }
87
+ else if (tc)
88
+ line += ansiFg(tc) + "▀" + ANSI_RESET;
89
+ else if (bc)
90
+ line += ansiFg(bc) + "▄" + ANSI_RESET;
91
+ else
92
+ line += " ";
93
+ }
94
+ lines.push(line.replace(/\s+$/, ""));
95
+ }
96
+ return lines;
97
+ }
98
+ /** Default CLI tagline. */
99
+ export const DEFAULT_TAGLINE = "GitHub screenshot + recording uploads for agents";
100
+ /**
101
+ * Brand lockup:
102
+ * [mark] uploads.sh
103
+ * [mark] GitHub screenshot + recording uploads for agents
104
+ * [mark] v0.9.0
105
+ */
106
+ export function formatBrandHeader(options = {}) {
107
+ const color = options.color ?? false;
108
+ const label = options.label ?? "uploads.sh";
109
+ const tagline = options.tagline ?? DEFAULT_TAGLINE;
110
+ const markLines = renderBrandMarkLines({ color });
111
+ const markWidth = Math.max(...markLines.map((l) => visibleWidth(l)), 1);
112
+ const gap = " ";
113
+ const paint = (text, rgb, bold = false) => color
114
+ ? `${bold ? "\u001b[1m" : ""}\u001b[38;2;${rgb.r};${rgb.g};${rgb.b}m${text}\u001b[0m`
115
+ : text;
116
+ const extras = [
117
+ paint(label, BRAND.fg, true),
118
+ paint(tagline, BRAND.muted),
119
+ options.version ? paint(`v${options.version}`, BRAND.muted) : "",
120
+ ];
121
+ const out = [];
122
+ const rows = Math.max(markLines.length, extras.filter(Boolean).length);
123
+ for (let i = 0; i < rows; i++) {
124
+ const mark = (markLines[i] ?? "").padEnd(markWidth, " ");
125
+ const extra = extras[i] ? gap + extras[i] : "";
126
+ out.push(mark + extra);
127
+ }
128
+ return out.join("\n") + "\n";
129
+ }
130
+ /** Strip CSI sequences for display-width (ESC is matched as \x1b for the linter). */
131
+ function visibleWidth(s) {
132
+ // eslint-disable-next-line no-control-regex -- intentional: strip ANSI CSI
133
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
134
+ }
135
+ function boxLines(lines, options = {}) {
136
+ const color = options.color ?? false;
137
+ const tone = options.tone ?? BRAND.accent;
138
+ const width = Math.max(...lines.map((l) => l.length), 1) + 4;
139
+ const bar = "─".repeat(width);
140
+ const pad = (s) => s.padEnd(width - 2);
141
+ if (!color) {
142
+ return [`┌${bar}┐`, ...lines.map((l) => `│ ${pad(l)}│`), `└${bar}┘`, ""].join("\n");
143
+ }
144
+ const edge = (s) => `\u001b[38;2;${tone.r};${tone.g};${tone.b}m${s}\u001b[0m`;
145
+ const title = (s) => `\u001b[1m\u001b[38;2;${tone.r};${tone.g};${tone.b}m${s}\u001b[0m`;
146
+ const cmd = (s) => `\u001b[1m\u001b[38;2;${BRAND.fg.r};${BRAND.fg.g};${BRAND.fg.b}m${s}\u001b[0m`;
147
+ const muted = (s) => `\u001b[38;2;${BRAND.muted.r};${BRAND.muted.g};${BRAND.muted.b}m${s}\u001b[0m`;
148
+ return [
149
+ edge(`┌${bar}┐`),
150
+ ...lines.map((l, i) => {
151
+ const body = i === 0 ? title(pad(l)) : i === 1 ? cmd(pad(l)) : muted(pad(l));
152
+ return `${edge("│ ")}${body}${edge("│")}`;
153
+ }),
154
+ edge(`└${bar}┘`),
155
+ "",
156
+ ].join("\n");
157
+ }
158
+ export function formatUpdateBanner(options) {
159
+ return boxLines([`Update available ${options.current} → ${options.latest}`, `npm i -g @buildinternet/uploads`], { color: options.color, tone: BRAND.accent });
160
+ }
161
+ export function formatAuthBanner(options = {}) {
162
+ return boxLines(["Sign in via browser", "uploads login"], {
163
+ color: options.color,
164
+ tone: BRAND.red,
165
+ });
166
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared CLI surface for help text and shell completions.
3
+ * Keep in sync when adding root commands or nested subcommands.
4
+ */
5
+ export interface CatalogCommand {
6
+ /** Root command name (no args). */
7
+ name: string;
8
+ summary: string;
9
+ /** Shown in help left column when set (e.g. `put <file>`). */
10
+ usage?: string;
11
+ /** Included in the short essentials help. */
12
+ essential?: boolean;
13
+ subcommands?: readonly {
14
+ name: string;
15
+ summary: string;
16
+ }[];
17
+ }
18
+ /** Global flags accepted before the subcommand. */
19
+ export declare const GLOBAL_FLAGS: readonly {
20
+ flag: string;
21
+ summary: string;
22
+ }[];
23
+ /** Common flags for put/attach (file-oriented commands). */
24
+ export declare const PUT_LIKE_FLAGS: readonly string[];
25
+ export declare const LIST_LIKE_FLAGS: readonly string[];
26
+ export declare const ROOT_COMMANDS: readonly CatalogCommand[];
27
+ export declare const COMPLETION_SHELLS: readonly ["bash", "zsh", "fish"];
28
+ export type CompletionShell = (typeof COMPLETION_SHELLS)[number];
29
+ export declare function isCompletionShell(value: string): value is CompletionShell;
30
+ export declare function rootCommandNames(): string[];
31
+ /** Also accept plural alias for the completion command. */
32
+ export declare const COMPLETION_ALIASES: readonly ["completion", "completions"];
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Shared CLI surface for help text and shell completions.
3
+ * Keep in sync when adding root commands or nested subcommands.
4
+ */
5
+ /** Global flags accepted before the subcommand. */
6
+ export const GLOBAL_FLAGS = [
7
+ { flag: "--api-url", summary: "API base URL" },
8
+ { flag: "--token", summary: "Bearer token" },
9
+ { flag: "--workspace", summary: "Workspace name" },
10
+ { flag: "-w", summary: "Workspace name (short)" },
11
+ { flag: "--env-file", summary: "Load env from file" },
12
+ { flag: "--json", summary: "JSON on stdout" },
13
+ { flag: "--quiet", summary: "Suppress stderr progress" },
14
+ { flag: "--version", summary: "Print package version" },
15
+ { flag: "-V", summary: "Print package version (short)" },
16
+ { flag: "--help", summary: "Show help" },
17
+ { flag: "-h", summary: "Show help (short)" },
18
+ { flag: "--all", summary: "Full root help listing" },
19
+ ];
20
+ /** Common flags for put/attach (file-oriented commands). */
21
+ export const PUT_LIKE_FLAGS = [
22
+ "--destination",
23
+ "--prefix",
24
+ "--repo",
25
+ "--ref",
26
+ "--pr",
27
+ "--issue",
28
+ "--comment",
29
+ "--no-comment",
30
+ "--format",
31
+ "--dry-run",
32
+ "--name",
33
+ "--no-optimize",
34
+ "--frame",
35
+ "--frame-url",
36
+ "--gallery",
37
+ "--meta",
38
+ "--workspace",
39
+ "-w",
40
+ "--help",
41
+ "-h",
42
+ ];
43
+ export const LIST_LIKE_FLAGS = [
44
+ "--prefix",
45
+ "--limit",
46
+ "--cursor",
47
+ "--meta",
48
+ "--workspace",
49
+ "-w",
50
+ "--help",
51
+ "-h",
52
+ ];
53
+ export const ROOT_COMMANDS = [
54
+ {
55
+ name: "attach",
56
+ usage: "attach <file...>",
57
+ summary: "Attach media to the current PR (stable URLs + managed comment)",
58
+ essential: true,
59
+ },
60
+ {
61
+ name: "put",
62
+ usage: "put <file>",
63
+ summary: "Upload (+ URL + markdown for GitHub)",
64
+ essential: true,
65
+ },
66
+ {
67
+ name: "gallery",
68
+ summary: "Create and organize public media galleries",
69
+ subcommands: [
70
+ { name: "create", summary: "Create a gallery" },
71
+ { name: "show", summary: "Show a gallery" },
72
+ { name: "list", summary: "List galleries" },
73
+ { name: "delete", summary: "Delete a gallery record" },
74
+ { name: "add", summary: "Add objects to a gallery" },
75
+ { name: "link", summary: "Link a gallery to a GitHub issue/PR" },
76
+ { name: "unlink", summary: "Unlink a gallery from GitHub" },
77
+ ],
78
+ },
79
+ {
80
+ name: "comment",
81
+ summary: "Create/update a PR/issue attachments comment (via gh)",
82
+ },
83
+ {
84
+ name: "list",
85
+ summary: "List objects (--meta k=v filters by queryable metadata)",
86
+ essential: true,
87
+ },
88
+ {
89
+ name: "find",
90
+ usage: "find k=v...",
91
+ summary: "List objects matching metadata (alias for list --meta)",
92
+ },
93
+ {
94
+ name: "meta",
95
+ summary: "Get/set an object's queryable metadata",
96
+ subcommands: [
97
+ { name: "get", summary: "Show metadata for an object" },
98
+ { name: "set", summary: "Merge-set and/or delete metadata pairs" },
99
+ ],
100
+ },
101
+ {
102
+ name: "delete",
103
+ usage: "delete <key>",
104
+ summary: "Delete object",
105
+ essential: true,
106
+ },
107
+ { name: "usage", summary: "Workspace storage / upload counters" },
108
+ { name: "reconcile", summary: "Rebuild usage ledger from storage" },
109
+ { name: "purge-expired", summary: "Delete objects past retentionDays" },
110
+ { name: "setup", summary: "Inspect/configure advanced CLI settings" },
111
+ {
112
+ name: "install",
113
+ summary: "Install the agent skill + register the remote MCP server",
114
+ essential: true,
115
+ subcommands: [
116
+ { name: "skill", summary: "Install the agent skill only" },
117
+ { name: "mcp", summary: "Register the remote MCP server only" },
118
+ { name: "all", summary: "Install skill and MCP (default)" },
119
+ ],
120
+ },
121
+ {
122
+ name: "login",
123
+ summary: "Sign in via browser (or an enrollment code) and save credentials",
124
+ essential: true,
125
+ },
126
+ {
127
+ name: "whoami",
128
+ summary: "Show active workspace and token (alias: status)",
129
+ essential: true,
130
+ },
131
+ {
132
+ name: "logout",
133
+ summary: "Remove the saved UPLOADS_TOKEN from the config file",
134
+ },
135
+ {
136
+ name: "invite",
137
+ summary: "Invite a teammate to a workspace (workspace admin; device login)",
138
+ },
139
+ {
140
+ name: "admin",
141
+ summary: "Site-operator invitation management (ADMIN_TOKEN)",
142
+ subcommands: [
143
+ { name: "invite", summary: "Create a workspace invitation" },
144
+ { name: "enrollment", summary: "Legacy alias for invite" },
145
+ ],
146
+ },
147
+ {
148
+ name: "config",
149
+ summary: "Show path, init, or set shared config",
150
+ subcommands: [
151
+ { name: "path", summary: "Print config file path" },
152
+ { name: "show", summary: "Show effective settings" },
153
+ { name: "init", summary: "Create or update UPLOADS_* keys" },
154
+ { name: "set", summary: "Set one UPLOADS_* key" },
155
+ ],
156
+ },
157
+ {
158
+ name: "doctor",
159
+ summary: "Health + auth + workspace checks",
160
+ essential: true,
161
+ },
162
+ { name: "health", summary: "API liveness (no auth)" },
163
+ { name: "mcp", summary: "Serve MCP over stdio (tools mirror the CLI)" },
164
+ {
165
+ name: "help",
166
+ summary: "Show this help (essentials; use --all for the full list)",
167
+ subcommands: [{ name: "--all", summary: "Full command list and config" }],
168
+ },
169
+ {
170
+ name: "completion",
171
+ summary: "Print shell completion script (bash, zsh, or fish)",
172
+ subcommands: [
173
+ { name: "bash", summary: "Bash completion script" },
174
+ { name: "zsh", summary: "Zsh completion script" },
175
+ { name: "fish", summary: "Fish completion script" },
176
+ ],
177
+ },
178
+ ];
179
+ export const COMPLETION_SHELLS = ["bash", "zsh", "fish"];
180
+ export function isCompletionShell(value) {
181
+ return COMPLETION_SHELLS.includes(value);
182
+ }
183
+ export function rootCommandNames() {
184
+ return ROOT_COMMANDS.map((c) => c.name);
185
+ }
186
+ /** Also accept plural alias for the completion command. */
187
+ export const COMPLETION_ALIASES = ["completion", "completions"];
@@ -0,0 +1,26 @@
1
+ import { type CliStyle } from "./cli-style.js";
2
+ export interface RootHelpOptions {
3
+ /** Full command list + config layers. Default: curated essentials. */
4
+ full?: boolean;
5
+ /** Force color on/off; default is TTY + env detection on stderr. */
6
+ color?: boolean;
7
+ style?: CliStyle;
8
+ /** Include pixel brand mark in the header (default true). */
9
+ brandMark?: boolean;
10
+ /** Override package version shown in the header. */
11
+ version?: string;
12
+ /** When set, show a noticeable update banner under the header. */
13
+ latestVersion?: string;
14
+ /**
15
+ * When true, show a loud "not signed in" banner at the very top with
16
+ * `uploads login`. Callers should set this when no token is configured.
17
+ */
18
+ needsAuth?: boolean;
19
+ }
20
+ /**
21
+ * Root help text. Default is a short essentials view; pass `full: true` for
22
+ * the complete command + config dump (`uploads help --all`).
23
+ */
24
+ export declare function formatRootHelp(options?: RootHelpOptions): string;
25
+ /** True when argv for the `help` command requests the full listing. */
26
+ export declare function wantsFullHelp(args: string[]): boolean;