@buildinternet/uploads 0.42.0 → 0.42.2

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
@@ -15,7 +15,7 @@ npx @buildinternet/uploads --help
15
15
  uploads setup
16
16
  uploads --version
17
17
  uploads attach ./before.png ./after.png
18
- uploads screenshot https://app.example --pr 123 --comment # capture + host in one step
18
+ uploads screenshot https://app.example --pr 123 # capture + host in one step
19
19
  uploads screenshot ./report.html --dark --selector "main"
20
20
  uploads screenshot http://localhost:3000 --via local --annotate ./callouts.json
21
21
  uploads annotate ./shot.png --spec ./callouts.json --out ./shot.marked.png
@@ -24,7 +24,7 @@ uploads put ./shot.png --destination screenshots
24
24
  uploads put ./shot.png --no-optimize
25
25
  uploads put ./mobile.png --frame phone
26
26
  uploads put ./ui.png --frame browser --frame-url "https://app.example"
27
- uploads put ./after.png --pr 123 --comment
27
+ uploads put ./after.png --pr 123
28
28
  uploads put ./capture-2026-…Z.png --pr 123 --name hero.png # clean leaf, stable path
29
29
  uploads put ./shot.png --pr 123 --name hero.png --dry-run --format url # preview URL, no upload
30
30
  uploads gallery create --title "Release screenshots"
@@ -190,7 +190,7 @@ When adding several keys, `uploads gallery add` processes them sequentially and
190
190
  individual failures in `--json` output. Gallery item updates use the API's current version to
191
191
  avoid overwriting concurrent changes.
192
192
 
193
- 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.
193
+ Link a gallery to a GitHub issue or pull request with `gallery link --github`. Run `uploads comment --pr <number>` 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.
194
194
 
195
195
  Config layers (first match wins): CLI flags → env vars → `--env-file` → `~/.config/buildinternet/config`. See `config.example` for keys.
196
196
 
package/dist/cli-help.js CHANGED
@@ -105,7 +105,7 @@ ${section(style, "Examples:")}
105
105
  ${style.command("uploads login")}
106
106
  ${style.command("uploads whoami")}
107
107
  ${style.command("uploads put")} ./shot.png --pr 123 --name hero.png
108
- ${style.command("uploads put")} ./after.png --pr 123 --comment
108
+ ${style.command("uploads put")} ./after.png --pr 123
109
109
  ${style.command("uploads put")} ./bug.png --issue 45
110
110
  ${style.command("uploads put")} ./shot.png --meta path=/settings --state after
111
111
  ${style.command("uploads attach")} ./before.png ./after.png
@@ -154,7 +154,7 @@ ${section(style, "Examples:")}
154
154
  ${style.command("uploads login")}
155
155
  ${style.command("uploads whoami")}
156
156
  ${style.command("uploads put")} ./shot.png --pr 123 --name hero.png
157
- ${style.command("uploads put")} ./after.png --pr 123 --comment
157
+ ${style.command("uploads put")} ./after.png --pr 123
158
158
  ${style.command("uploads put")} ./bug.png --issue 45 --repo myorg/myapp
159
159
  ${style.command("uploads put")} ./shot.png --dry-run --format url
160
160
  ${style.command("uploads put")} ./shot.png --meta path=/settings --state after
@@ -19,6 +19,7 @@ Usage:
19
19
  uploads hook pre-pr-screenshot
20
20
 
21
21
  Invoked by Claude Code / Codex / Grok / Cursor hooks. Never blocks.
22
+ Harness manifests no-op (exit 0, no output) when this binary is not on PATH.
22
23
 
23
24
  pre-pr-screenshot
24
25
  If the shell command is \`gh pr create\`, the branch touches UI files, and
@@ -2,7 +2,7 @@ import { flagBool, flagString, parseCommandArgs, UsageError, } from "../cli-args
2
2
  import { resolveConfig } from "../config.js";
3
3
  import { execRunner } from "../github-gh.js";
4
4
  import { writeCommandHelp } from "../cli-style.js";
5
- import { HOOK_COMMAND, installHookManifests } from "../hooks-install.js";
5
+ import { HOOK_COMMAND, HOOK_INVOCATION, installHookManifests, } from "../hooks-install.js";
6
6
  export const DEFAULT_MCP_URL = "https://agents.uploads.sh/mcp";
7
7
  const SKILL_SOURCE = "buildinternet/uploads";
8
8
  const SKILL_NAMES = ["uploads-cli", "github-screenshots", "annotate-screenshots"];
@@ -15,7 +15,7 @@ hook for Grok / Cursor when those tools are present. The remote MCP endpoint
15
15
  infers your workspace from the bearer token, so only the token is needed.
16
16
 
17
17
  Claude Code and Codex ship the same reminder via their plugins (same command:
18
- \`${HOOK_COMMAND}\`) — install those plugins instead of relying on this step.
18
+ \`${HOOK_INVOCATION}\`) — install those plugins instead of relying on this step.
19
19
 
20
20
  Safe to re-run. An MCP server already registered under this name is reported
21
21
  as \`already configured\` and left as-is — including the token it was created
@@ -1,59 +1,12 @@
1
1
  /**
2
- * Published-CLI COPY of the workspace parser/resolver (issue #307)
3
- * (packages/comment-config/src/index.ts). The published CLI imports no
4
- * @uploads/* package, so that private package cannot be shared here — its
5
- * parser/resolver is copied verbatim below. Kept in sync by
6
- * test/fixtures/comment-config-golden.json, asserted from both sides (this
7
- * package's comment-config.test.ts and the canonical package's
8
- * index.test.ts) — change both copies together. `readLocalRepoCommentConfig`
9
- * below is CLI-only: it reads the six candidate config paths off the working
10
- * tree (the server instead fetches them from GitHub's contents API —
11
- * apps/api/src/repo-comment-config.ts).
2
+ * CLI comment-config: shared parser from the generated copy of
3
+ * `@uploads/comment-config`, plus the local working-tree reader.
4
+ * `readLocalRepoCommentConfig` is CLI-only it reads the six candidate
5
+ * config paths off the working tree (the server instead fetches them from
6
+ * GitHub's contents API — apps/api/src/repo-comment-config.ts).
12
7
  */
13
- export interface RepoCommentConfig {
14
- imageWidth?: "auto" | "full" | number;
15
- maxInlineImages?: number;
16
- metaPath?: boolean;
17
- metaState?: boolean;
18
- linkToFilePage?: boolean;
19
- note?: string;
20
- ingestGithubAttachments?: boolean;
21
- }
22
- export interface WorkspaceCommentDefaults {
23
- imageWidth?: "full" | number;
24
- maxInlineImages?: number;
25
- showMetadata?: boolean;
26
- linkToFilePage?: boolean;
27
- note?: string;
28
- ingestGithubAttachments?: boolean;
29
- }
30
- export interface ResolvedCommentOptions {
31
- imageWidth: "auto" | "full" | number;
32
- maxInlineImages: number;
33
- metaPath: boolean;
34
- metaState: boolean;
35
- linkToFilePage: boolean;
36
- note: string | null;
37
- ingestGithubAttachments: boolean;
38
- }
39
- export type OptionSource = "repo" | "workspace" | "auto";
40
- export declare const AUTO_COMMENT_OPTIONS: ResolvedCommentOptions;
41
- export declare const NOTE_MAX_CHARS = 500;
42
- export declare function parseRepoCommentConfig(text: string, format: "yaml" | "json"): {
43
- config: RepoCommentConfig | null;
44
- warnings: string[];
45
- };
46
- export declare function resolveCommentOptions(repo: RepoCommentConfig | null, ws: WorkspaceCommentDefaults | null): {
47
- options: ResolvedCommentOptions;
48
- source: Record<keyof ResolvedCommentOptions, OptionSource>;
49
- };
50
- /**
51
- * Candidate paths, checked in this order — the first hit wins. Must match
52
- * the server's REPO_CONFIG_PATHS exactly (apps/api/src/repo-comment-config.ts)
53
- * so a committed config resolves identically whether the bot or the local
54
- * gh fallback renders the comment.
55
- */
56
- export declare const REPO_CONFIG_PATHS: readonly [".uploads.yml", ".uploads.yaml", ".uploads.json", ".github/uploads.yml", ".github/uploads.yaml", ".github/uploads.json"];
8
+ import { type RepoCommentConfig } from "./comment-config.generated.js";
9
+ export { AUTO_COMMENT_OPTIONS, NOTE_MAX_CHARS, REPO_CONFIG_PATHS, parseRepoCommentConfig, resolveCommentOptions, type OptionSource, type RepoCommentConfig, type ResolvedCommentOptions, type WorkspaceCommentDefaults, } from "./comment-config.generated.js";
57
10
  /**
58
11
  * Read the repo's comment config off the local working tree — the CLI has no
59
12
  * GitHub App installation token to fetch via the contents API, so this reads
@@ -0,0 +1,45 @@
1
+ /**
2
+ * GENERATED by packages/uploads/scripts/inline-shared.mjs — do not edit.
3
+ * Canonical source: packages/comment-config/src/index.ts
4
+ * The published CLI cannot import private @uploads/* packages, so this file
5
+ * is inlined into the tarball. Re-run the script after changing the source.
6
+ */
7
+ export interface RepoCommentConfig {
8
+ imageWidth?: "auto" | "full" | number;
9
+ maxInlineImages?: number;
10
+ metaPath?: boolean;
11
+ metaState?: boolean;
12
+ linkToFilePage?: boolean;
13
+ note?: string;
14
+ ingestGithubAttachments?: boolean;
15
+ }
16
+ export interface WorkspaceCommentDefaults {
17
+ imageWidth?: "full" | number;
18
+ maxInlineImages?: number;
19
+ showMetadata?: boolean;
20
+ linkToFilePage?: boolean;
21
+ note?: string;
22
+ ingestGithubAttachments?: boolean;
23
+ }
24
+ export interface ResolvedCommentOptions {
25
+ imageWidth: "auto" | "full" | number;
26
+ maxInlineImages: number;
27
+ metaPath: boolean;
28
+ metaState: boolean;
29
+ linkToFilePage: boolean;
30
+ note: string | null;
31
+ ingestGithubAttachments: boolean;
32
+ }
33
+ export type OptionSource = "repo" | "workspace" | "auto";
34
+ export declare const AUTO_COMMENT_OPTIONS: ResolvedCommentOptions;
35
+ export declare const NOTE_MAX_CHARS = 500;
36
+ export declare function parseRepoCommentConfig(text: string, format: "yaml" | "json"): {
37
+ config: RepoCommentConfig | null;
38
+ warnings: string[];
39
+ };
40
+ export declare function resolveCommentOptions(repo: RepoCommentConfig | null, ws: WorkspaceCommentDefaults | null): {
41
+ options: ResolvedCommentOptions;
42
+ source: Record<keyof ResolvedCommentOptions, OptionSource>;
43
+ };
44
+ /** Candidate paths, checked in this order — the first hit wins. */
45
+ export declare const REPO_CONFIG_PATHS: readonly [".uploads.yml", ".uploads.yaml", ".uploads.json", ".github/uploads.yml", ".github/uploads.yaml", ".github/uploads.json"];
@@ -0,0 +1,165 @@
1
+ /**
2
+ * GENERATED by packages/uploads/scripts/inline-shared.mjs — do not edit.
3
+ * Canonical source: packages/comment-config/src/index.ts
4
+ * The published CLI cannot import private @uploads/* packages, so this file
5
+ * is inlined into the tarball. Re-run the script after changing the source.
6
+ */
7
+ import { parse as parseYaml } from "yaml";
8
+ export const AUTO_COMMENT_OPTIONS = {
9
+ imageWidth: "auto",
10
+ maxInlineImages: 16, // MAX_INLINE_ATTACHMENT_IMAGES — renderer copies own the constant
11
+ metaPath: true,
12
+ metaState: true,
13
+ linkToFilePage: true,
14
+ note: null,
15
+ ingestGithubAttachments: false,
16
+ };
17
+ export const NOTE_MAX_CHARS = 500;
18
+ const WIDTH_MIN = 160;
19
+ const WIDTH_MAX = 1000;
20
+ const MAX_INLINE_MIN = 1;
21
+ const MAX_INLINE_MAX = 48;
22
+ const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, Math.round(n)));
23
+ export function parseRepoCommentConfig(text, format) {
24
+ const warnings = [];
25
+ let root;
26
+ try {
27
+ root = format === "json" ? JSON.parse(text) : parseYaml(text);
28
+ }
29
+ catch {
30
+ return { config: null, warnings: ["config file could not be parsed; ignoring it"] };
31
+ }
32
+ if (typeof root !== "object" || root === null || Array.isArray(root)) {
33
+ return { config: null, warnings };
34
+ }
35
+ const comment = root.comment;
36
+ if (typeof comment !== "object" || comment === null || Array.isArray(comment)) {
37
+ return { config: null, warnings };
38
+ }
39
+ const c = comment;
40
+ const config = {};
41
+ // imageWidth: "auto" | "full" | finite number (clamped)
42
+ if ("imageWidth" in c) {
43
+ const v = c.imageWidth;
44
+ if (v === "auto" || v === "full")
45
+ config.imageWidth = v;
46
+ else if (typeof v === "number" && Number.isFinite(v))
47
+ config.imageWidth = clamp(v, WIDTH_MIN, WIDTH_MAX);
48
+ else
49
+ warnings.push(`imageWidth: expected "auto", "full", or a number; dropped`);
50
+ }
51
+ // maxInlineImages: finite number (clamped)
52
+ if ("maxInlineImages" in c) {
53
+ const v = c.maxInlineImages;
54
+ if (typeof v === "number" && Number.isFinite(v))
55
+ config.maxInlineImages = clamp(v, MAX_INLINE_MIN, MAX_INLINE_MAX);
56
+ else
57
+ warnings.push(`maxInlineImages: expected a number; dropped`);
58
+ }
59
+ // linkToFilePage: boolean
60
+ if ("linkToFilePage" in c) {
61
+ const v = c.linkToFilePage;
62
+ if (typeof v === "boolean")
63
+ config.linkToFilePage = v;
64
+ else
65
+ warnings.push(`linkToFilePage: expected a boolean; dropped`);
66
+ }
67
+ // ingestGithubAttachments: boolean
68
+ if ("ingestGithubAttachments" in c) {
69
+ const v = c.ingestGithubAttachments;
70
+ if (typeof v === "boolean")
71
+ config.ingestGithubAttachments = v;
72
+ else
73
+ warnings.push(`ingestGithubAttachments: expected a boolean; dropped`);
74
+ }
75
+ // meta.path / meta.state: booleans nested under `meta`
76
+ if ("meta" in c) {
77
+ const v = c.meta;
78
+ if (typeof v === "object" && v !== null && !Array.isArray(v)) {
79
+ const meta = v;
80
+ if ("path" in meta) {
81
+ if (typeof meta.path === "boolean")
82
+ config.metaPath = meta.path;
83
+ else
84
+ warnings.push(`meta.path: expected a boolean; dropped`);
85
+ }
86
+ if ("state" in meta) {
87
+ if (typeof meta.state === "boolean")
88
+ config.metaState = meta.state;
89
+ else
90
+ warnings.push(`meta.state: expected a boolean; dropped`);
91
+ }
92
+ }
93
+ else {
94
+ warnings.push(`meta: expected an object; dropped`);
95
+ }
96
+ }
97
+ // note: non-empty trimmed string, max NOTE_MAX_CHARS (never truncated)
98
+ if ("note" in c) {
99
+ const v = c.note;
100
+ if (typeof v === "string") {
101
+ const trimmed = v.trim();
102
+ if (trimmed.length === 0) {
103
+ // empty/whitespace note is treated as absent
104
+ }
105
+ else if (trimmed.length > NOTE_MAX_CHARS) {
106
+ warnings.push(`note: longer than ${NOTE_MAX_CHARS} characters; dropped (not truncated)`);
107
+ }
108
+ else {
109
+ config.note = trimmed;
110
+ }
111
+ }
112
+ else {
113
+ warnings.push(`note: expected a string; dropped`);
114
+ }
115
+ }
116
+ return { config, warnings };
117
+ }
118
+ export function resolveCommentOptions(repo, ws) {
119
+ const wsAsRepo = {
120
+ ...(ws?.imageWidth !== undefined ? { imageWidth: ws.imageWidth } : {}),
121
+ ...(ws?.maxInlineImages !== undefined ? { maxInlineImages: ws.maxInlineImages } : {}),
122
+ ...(ws?.showMetadata !== undefined
123
+ ? { metaPath: ws.showMetadata, metaState: ws.showMetadata }
124
+ : {}),
125
+ ...(ws?.linkToFilePage !== undefined ? { linkToFilePage: ws.linkToFilePage } : {}),
126
+ ...(ws?.note ? { note: ws.note } : {}),
127
+ ...(ws?.ingestGithubAttachments !== undefined
128
+ ? { ingestGithubAttachments: ws.ingestGithubAttachments }
129
+ : {}),
130
+ };
131
+ const options = { ...AUTO_COMMENT_OPTIONS };
132
+ const source = Object.fromEntries(Object.keys(AUTO_COMMENT_OPTIONS).map((k) => [k, "auto"]));
133
+ const apply = (cfg, from) => {
134
+ for (const key of [
135
+ "imageWidth",
136
+ "maxInlineImages",
137
+ "metaPath",
138
+ "metaState",
139
+ "linkToFilePage",
140
+ "ingestGithubAttachments",
141
+ ]) {
142
+ if (cfg[key] !== undefined && source[key] === "auto") {
143
+ options[key] = cfg[key];
144
+ source[key] = from;
145
+ }
146
+ }
147
+ if (cfg.note !== undefined && source.note === "auto") {
148
+ options.note = cfg.note;
149
+ source.note = from;
150
+ }
151
+ };
152
+ if (repo)
153
+ apply(repo, "repo");
154
+ apply(wsAsRepo, "workspace");
155
+ return { options, source };
156
+ }
157
+ /** Candidate paths, checked in this order — the first hit wins. */
158
+ export const REPO_CONFIG_PATHS = [
159
+ ".uploads.yml",
160
+ ".uploads.yaml",
161
+ ".uploads.json",
162
+ ".github/uploads.yml",
163
+ ".github/uploads.yaml",
164
+ ".github/uploads.json",
165
+ ];
@@ -1,181 +1,14 @@
1
1
  /**
2
- * Published-CLI COPY of the workspace parser/resolver (issue #307)
3
- * (packages/comment-config/src/index.ts). The published CLI imports no
4
- * @uploads/* package, so that private package cannot be shared here — its
5
- * parser/resolver is copied verbatim below. Kept in sync by
6
- * test/fixtures/comment-config-golden.json, asserted from both sides (this
7
- * package's comment-config.test.ts and the canonical package's
8
- * index.test.ts) — change both copies together. `readLocalRepoCommentConfig`
9
- * below is CLI-only: it reads the six candidate config paths off the working
10
- * tree (the server instead fetches them from GitHub's contents API —
11
- * apps/api/src/repo-comment-config.ts).
2
+ * CLI comment-config: shared parser from the generated copy of
3
+ * `@uploads/comment-config`, plus the local working-tree reader.
4
+ * `readLocalRepoCommentConfig` is CLI-only it reads the six candidate
5
+ * config paths off the working tree (the server instead fetches them from
6
+ * GitHub's contents API — apps/api/src/repo-comment-config.ts).
12
7
  */
13
8
  import fs from "node:fs";
14
9
  import { join } from "node:path";
15
- import { parse as parseYaml } from "yaml";
16
- export const AUTO_COMMENT_OPTIONS = {
17
- imageWidth: "auto",
18
- maxInlineImages: 16, // MAX_INLINE_ATTACHMENT_IMAGES — renderer copies own the constant
19
- metaPath: true,
20
- metaState: true,
21
- linkToFilePage: true,
22
- note: null,
23
- ingestGithubAttachments: false,
24
- };
25
- export const NOTE_MAX_CHARS = 500;
26
- const WIDTH_MIN = 160;
27
- const WIDTH_MAX = 1000;
28
- const MAX_INLINE_MIN = 1;
29
- const MAX_INLINE_MAX = 48;
30
- const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, Math.round(n)));
31
- export function parseRepoCommentConfig(text, format) {
32
- const warnings = [];
33
- let root;
34
- try {
35
- root = format === "json" ? JSON.parse(text) : parseYaml(text);
36
- }
37
- catch {
38
- return { config: null, warnings: ["config file could not be parsed; ignoring it"] };
39
- }
40
- if (typeof root !== "object" || root === null || Array.isArray(root)) {
41
- return { config: null, warnings };
42
- }
43
- const comment = root.comment;
44
- if (typeof comment !== "object" || comment === null || Array.isArray(comment)) {
45
- return { config: null, warnings };
46
- }
47
- const c = comment;
48
- const config = {};
49
- // imageWidth: "auto" | "full" | finite number (clamped)
50
- if ("imageWidth" in c) {
51
- const v = c.imageWidth;
52
- if (v === "auto" || v === "full")
53
- config.imageWidth = v;
54
- else if (typeof v === "number" && Number.isFinite(v))
55
- config.imageWidth = clamp(v, WIDTH_MIN, WIDTH_MAX);
56
- else
57
- warnings.push(`imageWidth: expected "auto", "full", or a number; dropped`);
58
- }
59
- // maxInlineImages: finite number (clamped)
60
- if ("maxInlineImages" in c) {
61
- const v = c.maxInlineImages;
62
- if (typeof v === "number" && Number.isFinite(v))
63
- config.maxInlineImages = clamp(v, MAX_INLINE_MIN, MAX_INLINE_MAX);
64
- else
65
- warnings.push(`maxInlineImages: expected a number; dropped`);
66
- }
67
- // linkToFilePage: boolean
68
- if ("linkToFilePage" in c) {
69
- const v = c.linkToFilePage;
70
- if (typeof v === "boolean")
71
- config.linkToFilePage = v;
72
- else
73
- warnings.push(`linkToFilePage: expected a boolean; dropped`);
74
- }
75
- // ingestGithubAttachments: boolean
76
- if ("ingestGithubAttachments" in c) {
77
- const v = c.ingestGithubAttachments;
78
- if (typeof v === "boolean")
79
- config.ingestGithubAttachments = v;
80
- else
81
- warnings.push(`ingestGithubAttachments: expected a boolean; dropped`);
82
- }
83
- // meta.path / meta.state: booleans nested under `meta`
84
- if ("meta" in c) {
85
- const v = c.meta;
86
- if (typeof v === "object" && v !== null && !Array.isArray(v)) {
87
- const meta = v;
88
- if ("path" in meta) {
89
- if (typeof meta.path === "boolean")
90
- config.metaPath = meta.path;
91
- else
92
- warnings.push(`meta.path: expected a boolean; dropped`);
93
- }
94
- if ("state" in meta) {
95
- if (typeof meta.state === "boolean")
96
- config.metaState = meta.state;
97
- else
98
- warnings.push(`meta.state: expected a boolean; dropped`);
99
- }
100
- }
101
- else {
102
- warnings.push(`meta: expected an object; dropped`);
103
- }
104
- }
105
- // note: non-empty trimmed string, max NOTE_MAX_CHARS (never truncated)
106
- if ("note" in c) {
107
- const v = c.note;
108
- if (typeof v === "string") {
109
- const trimmed = v.trim();
110
- if (trimmed.length === 0) {
111
- // empty/whitespace note is treated as absent
112
- }
113
- else if (trimmed.length > NOTE_MAX_CHARS) {
114
- warnings.push(`note: longer than ${NOTE_MAX_CHARS} characters; dropped (not truncated)`);
115
- }
116
- else {
117
- config.note = trimmed;
118
- }
119
- }
120
- else {
121
- warnings.push(`note: expected a string; dropped`);
122
- }
123
- }
124
- return { config, warnings };
125
- }
126
- export function resolveCommentOptions(repo, ws) {
127
- const wsAsRepo = {
128
- ...(ws?.imageWidth !== undefined ? { imageWidth: ws.imageWidth } : {}),
129
- ...(ws?.maxInlineImages !== undefined ? { maxInlineImages: ws.maxInlineImages } : {}),
130
- ...(ws?.showMetadata !== undefined
131
- ? { metaPath: ws.showMetadata, metaState: ws.showMetadata }
132
- : {}),
133
- ...(ws?.linkToFilePage !== undefined ? { linkToFilePage: ws.linkToFilePage } : {}),
134
- ...(ws?.note ? { note: ws.note } : {}),
135
- ...(ws?.ingestGithubAttachments !== undefined
136
- ? { ingestGithubAttachments: ws.ingestGithubAttachments }
137
- : {}),
138
- };
139
- const options = { ...AUTO_COMMENT_OPTIONS };
140
- const source = Object.fromEntries(Object.keys(AUTO_COMMENT_OPTIONS).map((k) => [k, "auto"]));
141
- const apply = (cfg, from) => {
142
- for (const key of [
143
- "imageWidth",
144
- "maxInlineImages",
145
- "metaPath",
146
- "metaState",
147
- "linkToFilePage",
148
- "ingestGithubAttachments",
149
- ]) {
150
- if (cfg[key] !== undefined && source[key] === "auto") {
151
- options[key] = cfg[key];
152
- source[key] = from;
153
- }
154
- }
155
- if (cfg.note !== undefined && source.note === "auto") {
156
- options.note = cfg.note;
157
- source.note = from;
158
- }
159
- };
160
- if (repo)
161
- apply(repo, "repo");
162
- apply(wsAsRepo, "workspace");
163
- return { options, source };
164
- }
165
- /**
166
- * Candidate paths, checked in this order — the first hit wins. Must match
167
- * the server's REPO_CONFIG_PATHS exactly (apps/api/src/repo-comment-config.ts)
168
- * so a committed config resolves identically whether the bot or the local
169
- * gh fallback renders the comment.
170
- */
171
- export const REPO_CONFIG_PATHS = [
172
- ".uploads.yml",
173
- ".uploads.yaml",
174
- ".uploads.json",
175
- ".github/uploads.yml",
176
- ".github/uploads.yaml",
177
- ".github/uploads.json",
178
- ];
10
+ import { parseRepoCommentConfig, REPO_CONFIG_PATHS, } from "./comment-config.generated.js";
11
+ export { AUTO_COMMENT_OPTIONS, NOTE_MAX_CHARS, REPO_CONFIG_PATHS, parseRepoCommentConfig, resolveCommentOptions, } from "./comment-config.generated.js";
179
12
  /**
180
13
  * Read the repo's comment config off the local working tree — the CLI has no
181
14
  * GitHub App installation token to fetch via the contents API, so this reads