@buildinternet/uploads 0.14.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 CHANGED
@@ -83,6 +83,17 @@ infers the pull request for the current branch via `gh`, uploads stable URLs, an
83
83
  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
84
84
  the target explicitly, or `--no-comment` to upload without changing GitHub comments.
85
85
 
86
+ **Branch staging (pre-PR):** `attach <files> --branch [name]` (also on
87
+ `screenshot`) stages files against a git branch before any PR exists — the
88
+ working mode for coding agents capturing as they go. Staged files live under
89
+ `gh/<owner>/<repo>/branch/<branch>/…` and are promoted into the PR's
90
+ attachments when one opens: automatically via the
91
+ [GitHub App](https://uploads.sh/docs/github-app) webhook, or on the first
92
+ `attach` after the PR exists (`--promote` forces it with no new files,
93
+ `--no-promote` opts out). `uploads github link` inspects or claims the
94
+ workspace↔repo binding the webhook path uses. Promoted staging is cleaned up
95
+ server-side after ~7 days (~30 for branches that never got a PR).
96
+
86
97
  **Screenshot capture:** `uploads screenshot <url|file.html>` renders a page to a
87
98
  hosted image in one step — no separate browser tooling needed. `--via auto`
88
99
  (default) drives a Chrome/Chromium already on the machine (`playwright-core`
@@ -25,6 +25,7 @@ export const PUT_LIKE_FLAGS = [
25
25
  "--ref",
26
26
  "--pr",
27
27
  "--issue",
28
+ "--branch",
28
29
  "--comment",
29
30
  "--no-comment",
30
31
  "--format",
@@ -75,6 +76,7 @@ export const SCREENSHOT_FLAGS = [
75
76
  "--no-git",
76
77
  "--pr",
77
78
  "--issue",
79
+ "--branch",
78
80
  "--comment",
79
81
  "--gallery",
80
82
  "--meta",
@@ -131,6 +133,14 @@ export const ROOT_COMMANDS = [
131
133
  name: "comment",
132
134
  summary: "Create/update a PR/issue attachments comment (via gh)",
133
135
  },
136
+ {
137
+ name: "github",
138
+ summary: "Claim/inspect this workspace's binding to a GitHub repo",
139
+ subcommands: [
140
+ { name: "link", summary: "Claim or inspect the repo binding" },
141
+ { name: "doctor", summary: "Check the GitHub App's webhook event subscriptions" },
142
+ ],
143
+ },
134
144
  {
135
145
  name: "list",
136
146
  summary: "List objects (--meta k=v filters by queryable metadata)",
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { UploadsError } from "./errors.js";
4
4
  import { commandWorkspace, flagString, isHelpFlag, parseArgv, parseCommandArgs, UsageError, } from "./cli-args.js";
5
5
  import { formatRootHelp, wantsFullHelp } from "./cli-help.js";
6
6
  import { colorEnabled, createStyle } from "./cli-style.js";
7
- import { runPut, runAttach, runList, runFind, runMeta, runDelete, runHealth, runDoctor, runComment, runUsage, runReconcile, runPurgeExpired, runGallery, } from "./commands.js";
7
+ import { runPut, runAttach, runList, runFind, runMeta, runDelete, runHealth, runDoctor, runComment, runGithub, runUsage, runReconcile, runPurgeExpired, runGallery, } from "./commands.js";
8
8
  import { runConfig } from "./commands/config.js";
9
9
  import { runSetup } from "./commands/setup.js";
10
10
  import { runLogin } from "./commands/login.js";
@@ -278,7 +278,8 @@ export async function runCli(argv) {
278
278
  case "reconcile":
279
279
  case "purge-expired":
280
280
  case "doctor":
281
- case "comment": {
281
+ case "comment":
282
+ case "github": {
282
283
  const ctx = createContext(parsed.globals, !showHelp, cmdArgs);
283
284
  switch (parsed.command) {
284
285
  case "attach":
@@ -296,6 +297,9 @@ export async function runCli(argv) {
296
297
  case "comment":
297
298
  code = await runComment(ctx, cmdArgs, showHelp);
298
299
  break;
300
+ case "github":
301
+ code = await runGithub(ctx, cmdArgs, showHelp);
302
+ break;
299
303
  case "list":
300
304
  code = await runList(ctx, cmdArgs, showHelp);
301
305
  break;
package/dist/client.d.ts CHANGED
@@ -73,6 +73,8 @@ export interface ListItem {
73
73
  key: string;
74
74
  url: string | null;
75
75
  embedUrl?: string | null;
76
+ /** Canonical `/f/` page URL when the API provides it. Absent on older API deployments. */
77
+ pageUrl?: string;
76
78
  size?: number;
77
79
  uploaded?: string;
78
80
  }
@@ -170,8 +172,16 @@ export interface FindGalleriesByReferenceOptions {
170
172
  limit?: number;
171
173
  cursor?: string;
172
174
  }
173
- /** Reasons the bot did not post; the CLI treats any of them as "fall back to gh". */
174
- export type GithubCommentDeclineReason = "app_unconfigured" | "not_installed" | "forbidden" | "unavailable";
175
+ /**
176
+ * Reasons the bot did not post. The CLI falls back to the local `gh` path
177
+ * for all of these except `not_authorized` (issue #297 baseline control):
178
+ * the target repo is bound to a different workspace (or is unbound and the
179
+ * caller is the communal `default` workspace, which can't claim new repos).
180
+ * Falling back to `gh` there would let the human's own credentials post
181
+ * anyway, defeating the point of the server-side gate, so the CLI surfaces
182
+ * the decline instead.
183
+ */
184
+ export type GithubCommentDeclineReason = "app_unconfigured" | "not_installed" | "forbidden" | "not_authorized" | "unavailable";
175
185
  export type GithubCommentResult = {
176
186
  posted: true;
177
187
  action: "created" | "updated" | "skipped";
@@ -180,10 +190,54 @@ export type GithubCommentResult = {
180
190
  } | {
181
191
  posted: false;
182
192
  reason: GithubCommentDeclineReason;
193
+ message?: string;
194
+ fixUrl?: string;
195
+ required?: string[];
183
196
  };
197
+ /** `POST /v1/:workspace/github/promote` request/response (server contract, PR #310). */
198
+ export interface PromoteBranchAttachmentsOptions {
199
+ repo: string;
200
+ num: number;
201
+ branch: string;
202
+ }
203
+ export interface PromoteSkip {
204
+ key: string;
205
+ reason: string;
206
+ }
207
+ export interface PromoteBranchAttachmentsResult {
208
+ promoted: string[];
209
+ skipped: PromoteSkip[];
210
+ }
211
+ /** `GET`/`POST /v1/:workspace/github/link` result (server contract, phase 4b). */
212
+ export interface GithubLinkResult {
213
+ repo: string;
214
+ linked: boolean;
215
+ workspace: string | null;
216
+ source: string | null;
217
+ createdAt: string | null;
218
+ }
219
+ /** POST-only: whether THIS call's workspace ended up owning the binding. */
220
+ export interface GithubLinkClaimResult extends GithubLinkResult {
221
+ claimed: boolean;
222
+ }
223
+ /** `DELETE /v1/:workspace/github/link` result (issue #318, self-serve unlink). */
224
+ export interface GithubLinkUnlinkResult {
225
+ repo: string;
226
+ unlinked: boolean;
227
+ reason?: "not_linked";
228
+ }
184
229
  export interface HealthResult {
185
230
  ok: boolean;
186
231
  }
232
+ /** `GET /v1/:workspace/github/health` result (issue #293 follow-up). */
233
+ export interface GithubHealthResult {
234
+ configured: boolean;
235
+ ok: boolean;
236
+ events: string[] | null;
237
+ missingEvents: string[];
238
+ requiredEvents: string[];
239
+ hint?: string;
240
+ }
187
241
  export interface UsageResult {
188
242
  workspace: string;
189
243
  bytes: number;
@@ -412,6 +466,39 @@ export declare function createUploadsClient(config: UploadsClientConfig): {
412
466
  num: number;
413
467
  kind: "pull" | "issues";
414
468
  }): Promise<GithubCommentResult>;
469
+ /**
470
+ * Promote a workspace's branch-staged attachments into a PR's stable
471
+ * attachment prefix (server contract, PR #310 — degrade-safe callers
472
+ * treat any failure, including a 404 from an older/self-hosted worker
473
+ * that doesn't have this route yet, as "nothing promoted").
474
+ */
475
+ promoteBranchAttachments(opts: PromoteBranchAttachmentsOptions): Promise<PromoteBranchAttachmentsResult>;
476
+ /** Current binding for `repo`, or `{ linked: false }` if unclaimed. Throws
477
+ * `UploadsError` (status 404) on an older/self-hosted server without this
478
+ * route — callers treat that as "bindings unsupported". */
479
+ githubLinkStatus(repo: string): Promise<GithubLinkResult>;
480
+ /**
481
+ * Explicitly claim `repo` for this workspace (first-claim-wins — see
482
+ * github-repo-links.ts server-side). `claimed: false` in the result means
483
+ * the repo is already bound to a DIFFERENT workspace; this call never
484
+ * steals it. Throws `UploadsError` (status 404) on an older/self-hosted
485
+ * server without this route.
486
+ */
487
+ githubLinkClaim(repo: string): Promise<GithubLinkClaimResult>;
488
+ /**
489
+ * GitHub App configuration + webhook event subscription check. Throws
490
+ * `UploadsError` (status 404) on an older/self-hosted server without
491
+ * this route — callers treat that as "unknown", not "broken".
492
+ */
493
+ githubHealth(): Promise<GithubHealthResult>;
494
+ /**
495
+ * Self-serve unlink (issue #318): removes `repo`'s binding, but only if
496
+ * this workspace currently owns it. Throws `UploadsError` (status 403)
497
+ * when a different workspace owns the binding — never steals or
498
+ * overwrites another workspace's claim. Throws (status 404) on an
499
+ * older/self-hosted server without this route.
500
+ */
501
+ githubLinkUnlink(repo: string): Promise<GithubLinkUnlinkResult>;
415
502
  health(): Promise<HealthResult>;
416
503
  /** Workspace storage / upload counters (+ limits when configured). */
417
504
  usage(): Promise<UsageResult>;
package/dist/client.js CHANGED
@@ -458,6 +458,55 @@ export function createUploadsClient(config) {
458
458
  headers: { "Content-Type": "application/json" },
459
459
  });
460
460
  },
461
+ /**
462
+ * Promote a workspace's branch-staged attachments into a PR's stable
463
+ * attachment prefix (server contract, PR #310 — degrade-safe callers
464
+ * treat any failure, including a 404 from an older/self-hosted worker
465
+ * that doesn't have this route yet, as "nothing promoted").
466
+ */
467
+ async promoteBranchAttachments(opts) {
468
+ return request("POST", `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/github/promote`, {
469
+ body: new TextEncoder().encode(JSON.stringify(opts)),
470
+ headers: { "Content-Type": "application/json" },
471
+ });
472
+ },
473
+ /** Current binding for `repo`, or `{ linked: false }` if unclaimed. Throws
474
+ * `UploadsError` (status 404) on an older/self-hosted server without this
475
+ * route — callers treat that as "bindings unsupported". */
476
+ async githubLinkStatus(repo) {
477
+ return request("GET", `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/github/link?repo=${encodeURIComponent(repo)}`);
478
+ },
479
+ /**
480
+ * Explicitly claim `repo` for this workspace (first-claim-wins — see
481
+ * github-repo-links.ts server-side). `claimed: false` in the result means
482
+ * the repo is already bound to a DIFFERENT workspace; this call never
483
+ * steals it. Throws `UploadsError` (status 404) on an older/self-hosted
484
+ * server without this route.
485
+ */
486
+ async githubLinkClaim(repo) {
487
+ return request("POST", `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/github/link`, {
488
+ body: new TextEncoder().encode(JSON.stringify({ repo })),
489
+ headers: { "Content-Type": "application/json" },
490
+ });
491
+ },
492
+ /**
493
+ * GitHub App configuration + webhook event subscription check. Throws
494
+ * `UploadsError` (status 404) on an older/self-hosted server without
495
+ * this route — callers treat that as "unknown", not "broken".
496
+ */
497
+ async githubHealth() {
498
+ return request("GET", `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/github/health`);
499
+ },
500
+ /**
501
+ * Self-serve unlink (issue #318): removes `repo`'s binding, but only if
502
+ * this workspace currently owns it. Throws `UploadsError` (status 403)
503
+ * when a different workspace owns the binding — never steals or
504
+ * overwrites another workspace's claim. Throws (status 404) on an
505
+ * older/self-hosted server without this route.
506
+ */
507
+ async githubLinkUnlink(repo) {
508
+ return request("DELETE", `${config.apiUrl}/v1/${encodeURIComponent(config.workspace)}/github/link?repo=${encodeURIComponent(repo)}`);
509
+ },
461
510
  async health() {
462
511
  return request("GET", `${config.apiUrl}/health`, { auth: false });
463
512
  },
@@ -2,11 +2,12 @@ import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
3
  import { flagBool, flagInt, flagString, flagValues, parseCommandArgs, UsageError, } from "../cli-args.js";
4
4
  import { writeCommandHelp } from "../cli-style.js";
5
- import { frameOptionsFromFlags, ghTargetFromFlags, optimizeOptionsFromFlags, syncAttachmentsComment, commentViaSuffix, uploadPreparedImage, } from "../commands.js";
5
+ import { branchFromFlags, frameOptionsFromFlags, ghTargetFromFlags, optimizeOptionsFromFlags, syncAttachmentsComment, commentViaSuffix, uploadPreparedImage, } from "../commands.js";
6
6
  import { resolvePutDefaults } from "../config.js";
7
7
  import { loadDefaultsRaw, resolveScreenshotDefaults } from "../config-file.js";
8
8
  import { resolvePutPrefix } from "../destinations.js";
9
- import { execRunner, ghMetadataFromTargetWithTitle } from "../github-gh.js";
9
+ import { execRunner, ghMetadataFromTargetWithTitle, resolveRepo, } from "../github-gh.js";
10
+ import { ghBranchAttachmentKey, ghMetadataForBranch } from "../github.js";
10
11
  import { parseMetaFlags, validateMetaMap } from "../metadata.js";
11
12
  import { writeJson, writeStdout } from "../io.js";
12
13
  import { assertHideSelector, captureScreenshot, parseViewport, parseWaitUntil, } from "../screenshot.js";
@@ -66,6 +67,11 @@ Options:
66
67
  --keep-exif Keep EXIF/XMP/ICC when optimizing
67
68
  --pr <num> Attach to a pull request (stable URL, no hash)
68
69
  --issue <num> Attach to an issue
70
+ --branch [name] Stage against a branch, pre-PR (default: current git branch):
71
+ key gh/<owner>/<repo>/branch/<branch>/<name>; not with
72
+ --pr/--issue/--comment/--key/--ref/--prefix. No managed
73
+ comment exists yet — promoting into the PR's comment once
74
+ one opens ships in a later phase.
69
75
  --comment With --pr/--issue: update the managed attachments comment.
70
76
  Posts as uploads-sh[bot] when the GitHub App is installed;
71
77
  otherwise via local gh.
@@ -84,6 +90,7 @@ Examples:
84
90
  uploads screenshot http://localhost:3000 --via local --full-page
85
91
  uploads screenshot https://uploads.sh --pr 128 --comment
86
92
  uploads screenshot ./card.html --no-upload --out ./card.png
93
+ uploads screenshot https://app.example/settings --branch
87
94
  `;
88
95
  function colorSchemeFromFlags(flags) {
89
96
  const dark = flagBool(flags, "--dark");
@@ -164,9 +171,13 @@ captureImpl = captureScreenshot) {
164
171
  const destFlag = flagString(parsed.flags, "--destination");
165
172
  const prefixFlag = flagString(parsed.flags, "--prefix");
166
173
  const ghTarget = ghTargetFromFlags(parsed.flags, run);
174
+ const branchArg = branchFromFlags(parsed.flags, run);
167
175
  const wantComment = parsed.flags.has("--comment");
168
176
  const galleryId = flagString(parsed.flags, "--gallery");
169
177
  const dryRun = flagBool(parsed.flags, "--dry-run");
178
+ if (branchArg !== undefined && ghTarget) {
179
+ throw new UsageError("--branch cannot be combined with --pr/--issue");
180
+ }
170
181
  if (wantComment && !ghTarget)
171
182
  throw new UsageError("--comment requires --pr or --issue");
172
183
  if (ghTarget) {
@@ -177,6 +188,16 @@ captureImpl = captureScreenshot) {
177
188
  if (prefixFlag)
178
189
  throw new UsageError("--prefix cannot be combined with --pr/--issue");
179
190
  }
191
+ if (branchArg !== undefined) {
192
+ if (wantComment)
193
+ throw new UsageError("--branch cannot be combined with --comment");
194
+ if (keyHint)
195
+ throw new UsageError("--key cannot be combined with --branch");
196
+ if (flagString(parsed.flags, "--ref"))
197
+ throw new UsageError("--ref cannot be combined with --branch");
198
+ if (prefixFlag)
199
+ throw new UsageError("--prefix cannot be combined with --branch");
200
+ }
180
201
  if (dryRun) {
181
202
  if (wantComment)
182
203
  throw new UsageError("--dry-run cannot be combined with --comment");
@@ -185,13 +206,14 @@ captureImpl = captureScreenshot) {
185
206
  if (noUpload)
186
207
  throw new UsageError("--dry-run cannot be combined with --no-upload");
187
208
  }
209
+ const branchRepo = branchArg !== undefined ? resolveRepo(flagString(parsed.flags, "--repo"), run) : undefined;
188
210
  let resolvedPrefix;
189
211
  try {
190
212
  resolvedPrefix = resolvePutPrefix({
191
213
  destination: destFlag,
192
214
  prefix: prefixFlag,
193
215
  key: keyHint,
194
- ghAttachment: Boolean(ghTarget),
216
+ ghAttachment: Boolean(ghTarget) || branchArg !== undefined,
195
217
  });
196
218
  }
197
219
  catch (err) {
@@ -218,6 +240,10 @@ captureImpl = captureScreenshot) {
218
240
  metadata = { ...metaExtras, ...ghMetadataFromTargetWithTitle(ghTarget, run) };
219
241
  validateMetaMap(metadata);
220
242
  }
243
+ else if (branchArg !== undefined) {
244
+ metadata = { ...metaExtras, ...ghMetadataForBranch(branchRepo, branchArg) };
245
+ validateMetaMap(metadata);
246
+ }
221
247
  else if (Object.keys(metaExtras).length > 0) {
222
248
  validateMetaMap(metaExtras);
223
249
  }
@@ -260,12 +286,15 @@ captureImpl = captureScreenshot) {
260
286
  }
261
287
  const repo = flagString(parsed.flags, "--repo") ?? putDefaults.repo;
262
288
  const ref = flagString(parsed.flags, "--ref") ?? putDefaults.ref;
289
+ const branchKey = branchArg !== undefined
290
+ ? ghBranchAttachmentKey(branchRepo, branchArg, captured.filename)
291
+ : undefined;
263
292
  const alt = altFlag ?? basename(captured.filename);
264
293
  const { result, prepared, markdown } = await uploadPreparedImage(ctx.client, captured.png, captured.filename, {
265
294
  frame: frameOpts,
266
295
  optimize: optimizeOpts,
267
296
  ghTarget,
268
- key: keyHint,
297
+ key: keyHint ?? branchKey,
269
298
  prefix: resolvedPrefix ?? putDefaults.prefix,
270
299
  repo,
271
300
  ref,
@@ -295,7 +324,7 @@ captureImpl = captureScreenshot) {
295
324
  let commentError;
296
325
  if (wantComment && ghTarget) {
297
326
  try {
298
- comment = await syncAttachmentsComment(ctx.client, ghTarget, run);
327
+ comment = await syncAttachmentsComment(ctx.client, ghTarget, run, ctx.config.workspace);
299
328
  if (logHuman)
300
329
  process.stderr.write(`>> attachments comment ${comment.action}${commentViaSuffix(comment.via)}\n`);
301
330
  }
@@ -28,6 +28,17 @@ export declare function readFileArg(fileArg: string): Uint8Array;
28
28
  export declare function makeGhTarget(pr: number | undefined, issue: number | undefined, repoArg: string | undefined, run: CommandRunner): GhTarget | undefined;
29
29
  /** Reads --pr/--issue (+ --repo) into a GhTarget; undefined when neither flag is present. */
30
30
  export declare function ghTargetFromFlags(flags: CommandFlags["flags"], run: CommandRunner): GhTarget | undefined;
31
+ /**
32
+ * Reads `--branch [name]` — an optional-value flag: `--branch` alone resolves
33
+ * the current git branch (`resolveCurrentBranch`); `--branch feature/x` uses
34
+ * the given name verbatim. Returns undefined when the flag is absent at all
35
+ * (distinct from an empty/whitespace value, which is rejected). Throws
36
+ * UsageError if `--branch` is given more than once, or if the value looks
37
+ * like a filename accidentally swallowed by the optional-value lookahead
38
+ * (e.g. `uploads attach --branch shot.png` with no other file args) — see
39
+ * `looksLikeFileNotBranch`.
40
+ */
41
+ export declare function branchFromFlags(flags: CommandFlags["flags"], run: CommandRunner): string | undefined;
31
42
  /** Shared put/attach optimize flags + UPLOADS_NO_OPTIMIZE default. */
32
43
  export declare function optimizeOptionsFromFlags(flags: CommandFlags["flags"], defaults: PutDefaults): OptimizeImageOptions;
33
44
  export type PreparedUpload = OptimizeImageResult & {
@@ -104,7 +115,18 @@ export interface AttachmentsCommentResult {
104
115
  }
105
116
  /** Human-mode suffix noting who posted the managed comment. */
106
117
  export declare function commentViaSuffix(via: AttachmentsCommentResult["via"]): string;
107
- export declare function syncAttachmentsComment(client: UploadsClient, target: GhTarget, run: CommandRunner): Promise<AttachmentsCommentResult>;
118
+ /**
119
+ * Thrown by `syncAttachmentsComment` when the server declines with
120
+ * `not_authorized` (issue #297 baseline control) — this repo is bound to a
121
+ * different workspace, or unbound and unclaimable by the communal `default`
122
+ * workspace. Deliberately not caught by the generic "bot endpoint
123
+ * unreachable" fallback below: falling back to gh here would let the
124
+ * human's own credentials post anyway, defeating the point of the
125
+ * server-side gate.
126
+ */
127
+ export declare class GithubCommentAuthorizationError extends Error {
128
+ }
129
+ export declare function syncAttachmentsComment(client: UploadsClient, target: GhTarget, run: CommandRunner, workspace?: string): Promise<AttachmentsCommentResult>;
108
130
  export type AttachUploadItem = PutResult & {
109
131
  file: string;
110
132
  markdown: string;
@@ -150,6 +172,37 @@ export declare function uploadAttachments(opts: {
150
172
  failures: AttachFailure[];
151
173
  firstError?: unknown;
152
174
  }>;
175
+ /** A branch to stage attachments against pre-PR (`uploads attach --branch`). */
176
+ export interface BranchTarget {
177
+ repo: string;
178
+ branch: string;
179
+ }
180
+ /**
181
+ * Prepare + put each path as a branch-staged attachment (pre-PR) with
182
+ * bounded concurrency. Same shape as `uploadAttachments`, keyed under
183
+ * `gh/<owner>/<repo>/branch/<branch>/<filename>` instead of a PR/issue
184
+ * number. Never syncs the managed comment — callers must not call
185
+ * `syncAttachmentsComment` for a branch target.
186
+ */
187
+ export declare function uploadBranchAttachments(opts: {
188
+ client: UploadsClient;
189
+ target: BranchTarget;
190
+ files: readonly string[];
191
+ contentType?: string;
192
+ optimize: OptimizeImageOptions;
193
+ frame: {
194
+ frameId?: string;
195
+ frameUrl?: string;
196
+ frameFit?: "cover" | "contain";
197
+ };
198
+ metadata?: Record<string, string>;
199
+ provenanceClient?: string;
200
+ concurrency?: number;
201
+ }): Promise<{
202
+ uploads: AttachUploadItem[];
203
+ failures: AttachFailure[];
204
+ firstError?: unknown;
205
+ }>;
153
206
  export type PutUploadItem = PutResult & {
154
207
  file: string;
155
208
  markdown: string;
@@ -199,6 +252,7 @@ export declare function runFind(ctx: CliContext, args: string[], help?: boolean)
199
252
  export declare function runMeta(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
200
253
  export declare function runDelete(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
201
254
  export declare function runComment(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
255
+ export declare function runGithub(ctx: CliContext, args: string[], help?: boolean, run?: CommandRunner): Promise<number>;
202
256
  export declare function runUsage(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
203
257
  export declare function runReconcile(ctx: CliContext, args: string[], help?: boolean): Promise<number>;
204
258
  export declare function runPurgeExpired(ctx: CliContext, args: string[], help?: boolean): Promise<number>;