@buildinternet/uploads 0.42.1 → 0.43.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 +3 -3
- package/dist/cli-help.js +3 -3
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/hook.js +1 -0
- package/dist/commands/install.js +2 -2
- package/dist/commands/screenshot.js +7 -1
- package/dist/commands.js +14 -2
- package/dist/comment-config.d.ts +7 -54
- package/dist/comment-config.generated.d.ts +48 -0
- package/dist/comment-config.generated.js +178 -0
- package/dist/comment-config.js +7 -174
- package/dist/comment-render.generated.d.ts +151 -0
- package/dist/comment-render.generated.js +534 -0
- package/dist/github.d.ts +2 -140
- package/dist/github.js +4 -492
- package/dist/hooks-install.d.ts +10 -2
- package/dist/hooks-install.js +31 -3
- package/dist/mcp/tools.js +22 -22
- package/dist/project-context-nudge.d.ts +9 -0
- package/dist/project-context-nudge.js +39 -0
- package/package.json +1 -1
package/dist/hooks-install.d.ts
CHANGED
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
* Install thin user-global hook manifests for Grok and Cursor.
|
|
3
3
|
*
|
|
4
4
|
* Claude and Codex ship the same PreToolUse hook via their plugins
|
|
5
|
-
* (`hooks/hooks.json` →
|
|
5
|
+
* (`hooks/hooks.json` → HOOK_COMMAND). Do not also write
|
|
6
6
|
* ~/.codex/hooks.json here, or the reminder would fire twice.
|
|
7
7
|
*
|
|
8
8
|
* Idempotent: skip when our command string is already present.
|
|
9
|
+
* An older bare `uploads hook …` entry is upgraded in place.
|
|
9
10
|
*/
|
|
10
|
-
|
|
11
|
+
/** What the harness actually runs once `uploads` is on PATH. */
|
|
12
|
+
export declare const HOOK_INVOCATION = "uploads hook pre-pr-screenshot";
|
|
13
|
+
/**
|
|
14
|
+
* Fail-open shell used in every harness manifest.
|
|
15
|
+
* A missing `uploads` binary is a silent no-op (exit 0, no stderr) so plugin
|
|
16
|
+
* users without the CLI do not get a hook-failure annotation on every shell tool.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HOOK_COMMAND = "if command -v uploads >/dev/null 2>&1; then uploads hook pre-pr-screenshot; fi";
|
|
11
19
|
export type HookWriteResult = {
|
|
12
20
|
path: string;
|
|
13
21
|
action: "wrote" | "merged" | "skipped" | "would-write" | "would-merge";
|
package/dist/hooks-install.js
CHANGED
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
* Install thin user-global hook manifests for Grok and Cursor.
|
|
3
3
|
*
|
|
4
4
|
* Claude and Codex ship the same PreToolUse hook via their plugins
|
|
5
|
-
* (`hooks/hooks.json` →
|
|
5
|
+
* (`hooks/hooks.json` → HOOK_COMMAND). Do not also write
|
|
6
6
|
* ~/.codex/hooks.json here, or the reminder would fire twice.
|
|
7
7
|
*
|
|
8
8
|
* Idempotent: skip when our command string is already present.
|
|
9
|
+
* An older bare `uploads hook …` entry is upgraded in place.
|
|
9
10
|
*/
|
|
10
11
|
import fs from "node:fs";
|
|
11
12
|
import os from "node:os";
|
|
12
13
|
import path from "node:path";
|
|
13
|
-
|
|
14
|
+
/** What the harness actually runs once `uploads` is on PATH. */
|
|
15
|
+
export const HOOK_INVOCATION = "uploads hook pre-pr-screenshot";
|
|
16
|
+
/**
|
|
17
|
+
* Fail-open shell used in every harness manifest.
|
|
18
|
+
* A missing `uploads` binary is a silent no-op (exit 0, no stderr) so plugin
|
|
19
|
+
* users without the CLI do not get a hook-failure annotation on every shell tool.
|
|
20
|
+
*/
|
|
21
|
+
export const HOOK_COMMAND = `if command -v uploads >/dev/null 2>&1; then ${HOOK_INVOCATION}; fi`;
|
|
14
22
|
const TIMEOUT_SEC = 15;
|
|
15
23
|
const GROK_PAYLOAD = {
|
|
16
24
|
description: "Advisory reminder to stage screenshots on uploads.sh before opening a PR that touches UI files.",
|
|
@@ -38,6 +46,16 @@ function containsCommand(filePath) {
|
|
|
38
46
|
return false;
|
|
39
47
|
}
|
|
40
48
|
}
|
|
49
|
+
function hookCommandOf(entry) {
|
|
50
|
+
if (!entry || typeof entry !== "object")
|
|
51
|
+
return undefined;
|
|
52
|
+
const command = entry.command;
|
|
53
|
+
return typeof command === "string" ? command : undefined;
|
|
54
|
+
}
|
|
55
|
+
function isOurHookEntry(entry) {
|
|
56
|
+
const command = hookCommandOf(entry);
|
|
57
|
+
return Boolean(command?.includes(HOOK_INVOCATION));
|
|
58
|
+
}
|
|
41
59
|
function writeJson(filePath, value) {
|
|
42
60
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
43
61
|
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
@@ -79,7 +97,17 @@ function mergeCursor(home, dryRun) {
|
|
|
79
97
|
? { ...existing.hooks }
|
|
80
98
|
: {};
|
|
81
99
|
const list = Array.isArray(hooks.beforeShellExecution) ? [...hooks.beforeShellExecution] : [];
|
|
82
|
-
list.
|
|
100
|
+
const ours = list.findIndex(isOurHookEntry);
|
|
101
|
+
const nextEntry = { command: HOOK_COMMAND, timeout: TIMEOUT_SEC };
|
|
102
|
+
if (ours >= 0) {
|
|
103
|
+
if (hookCommandOf(list[ours]) === HOOK_COMMAND) {
|
|
104
|
+
return { path: filePath, action: "skipped" };
|
|
105
|
+
}
|
|
106
|
+
list[ours] = { ...list[ours], ...nextEntry };
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
list.push(nextEntry);
|
|
110
|
+
}
|
|
83
111
|
hooks.beforeShellExecution = list;
|
|
84
112
|
existing.hooks = hooks;
|
|
85
113
|
if (existing.version === undefined)
|
package/dist/mcp/tools.js
CHANGED
|
@@ -108,7 +108,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
108
108
|
const { globals } = opts;
|
|
109
109
|
const run = opts.runner ?? execRunner;
|
|
110
110
|
const clientFactory = opts.clientFactory ?? createUploadsClient;
|
|
111
|
-
function clientFor(args, requireToken = true) {
|
|
111
|
+
async function clientFor(args, requireToken = true) {
|
|
112
112
|
const config = resolveConfig({
|
|
113
113
|
apiUrl: globals.apiUrl,
|
|
114
114
|
token: globals.token,
|
|
@@ -151,7 +151,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
151
151
|
const title = optString(args, "title");
|
|
152
152
|
if (!title)
|
|
153
153
|
usage("title is required");
|
|
154
|
-
const { client } = clientFor(args);
|
|
154
|
+
const { client } = await clientFor(args);
|
|
155
155
|
return client.createGallery({ title, description: optString(args, "description") });
|
|
156
156
|
},
|
|
157
157
|
},
|
|
@@ -171,7 +171,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
171
171
|
additionalProperties: false,
|
|
172
172
|
},
|
|
173
173
|
async handler(args) {
|
|
174
|
-
const { client } = clientFor(args);
|
|
174
|
+
const { client } = await clientFor(args);
|
|
175
175
|
return client.getGallery(galleryId(args));
|
|
176
176
|
},
|
|
177
177
|
},
|
|
@@ -197,7 +197,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
197
197
|
const objectKey = optString(args, "objectKey");
|
|
198
198
|
if (!objectKey)
|
|
199
199
|
usage("objectKey is required");
|
|
200
|
-
const { client } = clientFor(args);
|
|
200
|
+
const { client } = await clientFor(args);
|
|
201
201
|
const id = galleryId(args);
|
|
202
202
|
const current = await client.getGallery(id);
|
|
203
203
|
return client.addGalleryItem(id, objectKey, {
|
|
@@ -228,7 +228,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
228
228
|
additionalProperties: false,
|
|
229
229
|
},
|
|
230
230
|
async handler(args) {
|
|
231
|
-
const { client } = clientFor(args);
|
|
231
|
+
const { client } = await clientFor(args);
|
|
232
232
|
const id = galleryId(args);
|
|
233
233
|
const current = await client.getGallery(id);
|
|
234
234
|
return client.linkGalleryExternalReference(id, {
|
|
@@ -259,7 +259,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
259
259
|
additionalProperties: false,
|
|
260
260
|
},
|
|
261
261
|
async handler(args) {
|
|
262
|
-
const { client } = clientFor(args);
|
|
262
|
+
const { client } = await clientFor(args);
|
|
263
263
|
return client.findGalleriesByReference({
|
|
264
264
|
...galleryReference(args),
|
|
265
265
|
limit: optPosInt(args, "limit"),
|
|
@@ -426,7 +426,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
426
426
|
catch (err) {
|
|
427
427
|
usage(err instanceof Error ? err.message : String(err));
|
|
428
428
|
}
|
|
429
|
-
const { config, client } = clientFor(args);
|
|
429
|
+
const { config, client } = await clientFor(args);
|
|
430
430
|
const defaults = resolvePutDefaults({ envFile: globals.envFile });
|
|
431
431
|
const frameOpts = mcpFrameOptions(args);
|
|
432
432
|
const optimizeOpts = mcpOptimizeOptions(args, defaults);
|
|
@@ -736,7 +736,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
736
736
|
const metadata = metadataArgWithCanonical(args);
|
|
737
737
|
if (metadata)
|
|
738
738
|
validateMetaMap(metadata);
|
|
739
|
-
const { config, client } = clientFor(args);
|
|
739
|
+
const { config, client } = await clientFor(args);
|
|
740
740
|
const defaults = resolvePutDefaults({ envFile: globals.envFile });
|
|
741
741
|
const frameOpts = mcpFrameOptions(args);
|
|
742
742
|
const optimizeOpts = mcpOptimizeOptions(args, defaults);
|
|
@@ -969,7 +969,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
969
969
|
const explicitTarget = ghTargetFromArgs(args, run);
|
|
970
970
|
const target = explicitTarget ??
|
|
971
971
|
resolveCurrentPullRequest(resolveRepo(optString(args, "repo"), run), run);
|
|
972
|
-
const { config, client } = clientFor(args);
|
|
972
|
+
const { config, client } = await clientFor(args);
|
|
973
973
|
const contentType = optString(args, "contentType");
|
|
974
974
|
const defaults = resolvePutDefaults({ envFile: globals.envFile });
|
|
975
975
|
const frameOpts = mcpFrameOptions(args);
|
|
@@ -1037,7 +1037,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1037
1037
|
const prefixArg = optString(args, "prefix");
|
|
1038
1038
|
let prefix = prefixArg ?? (defaults.prefix ? `${defaults.prefix}/` : undefined);
|
|
1039
1039
|
const target = ghTargetFromArgs(args, run);
|
|
1040
|
-
const { client } = clientFor(args);
|
|
1040
|
+
const { client } = await clientFor(args);
|
|
1041
1041
|
// Also list every active private prefix, if any (issue #631) —
|
|
1042
1042
|
// mirrors syncAttachmentsComment's gh-fallback gather: a repo's
|
|
1043
1043
|
// attachment history can be split across the plain shape and
|
|
@@ -1093,7 +1093,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1093
1093
|
additionalProperties: false,
|
|
1094
1094
|
},
|
|
1095
1095
|
async handler(args) {
|
|
1096
|
-
const { client } = clientFor(args);
|
|
1096
|
+
const { client } = await clientFor(args);
|
|
1097
1097
|
const repo = resolveRepo(optString(args, "repo"), run);
|
|
1098
1098
|
const branch = optString(args, "branch") ?? resolveCurrentBranch(run);
|
|
1099
1099
|
return resolveStaged({ client, repo, branch });
|
|
@@ -1124,7 +1124,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1124
1124
|
usage("key is required");
|
|
1125
1125
|
if (optBool(args, "dryRun"))
|
|
1126
1126
|
return { key, deleted: false, dryRun: true };
|
|
1127
|
-
const { client } = clientFor(args);
|
|
1127
|
+
const { client } = await clientFor(args);
|
|
1128
1128
|
return client.delete(key);
|
|
1129
1129
|
},
|
|
1130
1130
|
},
|
|
@@ -1147,7 +1147,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1147
1147
|
const key = optString(args, "key");
|
|
1148
1148
|
if (!key)
|
|
1149
1149
|
usage("key is required");
|
|
1150
|
-
return clientFor(args).client.getMetadata(key);
|
|
1150
|
+
return (await clientFor(args)).client.getMetadata(key);
|
|
1151
1151
|
},
|
|
1152
1152
|
},
|
|
1153
1153
|
{
|
|
@@ -1184,7 +1184,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1184
1184
|
}
|
|
1185
1185
|
if (set)
|
|
1186
1186
|
validateMetaMap(set);
|
|
1187
|
-
const { client } = clientFor(args);
|
|
1187
|
+
const { client } = await clientFor(args);
|
|
1188
1188
|
return client.patchMetadata(key, { set, delete: del });
|
|
1189
1189
|
},
|
|
1190
1190
|
},
|
|
@@ -1223,7 +1223,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1223
1223
|
}
|
|
1224
1224
|
if (hasMeta)
|
|
1225
1225
|
validateMetaMap(filters);
|
|
1226
|
-
const { client } = clientFor(args);
|
|
1226
|
+
const { client } = await clientFor(args);
|
|
1227
1227
|
return client.findFiles(filters, {
|
|
1228
1228
|
name,
|
|
1229
1229
|
prefix: optString(args, "prefix"),
|
|
@@ -1249,7 +1249,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1249
1249
|
additionalProperties: false,
|
|
1250
1250
|
},
|
|
1251
1251
|
async handler(args) {
|
|
1252
|
-
const { client } = clientFor(args);
|
|
1252
|
+
const { client } = await clientFor(args);
|
|
1253
1253
|
const key = optString(args, "key");
|
|
1254
1254
|
return key ? client.listMetadataValues(key) : client.listMetadataKeys();
|
|
1255
1255
|
},
|
|
@@ -1266,7 +1266,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1266
1266
|
additionalProperties: false,
|
|
1267
1267
|
},
|
|
1268
1268
|
async handler(args) {
|
|
1269
|
-
const { client } = clientFor(args);
|
|
1269
|
+
const { client } = await clientFor(args);
|
|
1270
1270
|
return client.usage();
|
|
1271
1271
|
},
|
|
1272
1272
|
},
|
|
@@ -1282,7 +1282,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1282
1282
|
additionalProperties: false,
|
|
1283
1283
|
},
|
|
1284
1284
|
async handler(args) {
|
|
1285
|
-
const { client } = clientFor(args);
|
|
1285
|
+
const { client } = await clientFor(args);
|
|
1286
1286
|
return client.reconcile();
|
|
1287
1287
|
},
|
|
1288
1288
|
},
|
|
@@ -1298,7 +1298,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1298
1298
|
additionalProperties: false,
|
|
1299
1299
|
},
|
|
1300
1300
|
async handler(args) {
|
|
1301
|
-
const { client } = clientFor(args);
|
|
1301
|
+
const { client } = await clientFor(args);
|
|
1302
1302
|
return client.purgeExpired();
|
|
1303
1303
|
},
|
|
1304
1304
|
},
|
|
@@ -1320,7 +1320,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1320
1320
|
const target = ghTargetFromArgs(args, run);
|
|
1321
1321
|
if (!target)
|
|
1322
1322
|
usage("comment requires pr or issue");
|
|
1323
|
-
const { config, client } = clientFor(args);
|
|
1323
|
+
const { config, client } = await clientFor(args);
|
|
1324
1324
|
// Explicit resync, same as `uploads comment` (issue #480).
|
|
1325
1325
|
const result = await syncAttachmentsComment(client, target, run, config.workspace, {
|
|
1326
1326
|
resync: true,
|
|
@@ -1336,7 +1336,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1336
1336
|
description: "Check uploads.sh API liveness. No auth or arguments required.",
|
|
1337
1337
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1338
1338
|
async handler(args) {
|
|
1339
|
-
const { config, client } = clientFor(args, false);
|
|
1339
|
+
const { config, client } = await clientFor(args, false);
|
|
1340
1340
|
const result = await client.health();
|
|
1341
1341
|
return { ...result, apiUrl: config.apiUrl };
|
|
1342
1342
|
},
|
|
@@ -1353,7 +1353,7 @@ export function createUploadsMcpTools(opts) {
|
|
|
1353
1353
|
additionalProperties: false,
|
|
1354
1354
|
},
|
|
1355
1355
|
async handler(args) {
|
|
1356
|
-
const { config, client } = clientFor(args);
|
|
1356
|
+
const { config, client } = await clientFor(args);
|
|
1357
1357
|
return buildDoctorReport(config, client);
|
|
1358
1358
|
},
|
|
1359
1359
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-project-context nudge (issue #692 follow-up): a path-tagged upload with
|
|
3
|
+
* no repo/gh.repo/app metadata and no real (non-local) origin lands in the
|
|
4
|
+
* screenshots page's "local dev" / "Other" fallback buckets. One advisory
|
|
5
|
+
* stderr line teaches the fix at the moment the context went missing. The
|
|
6
|
+
* predicate mirrors apps/api's projectLabelFromMeta fallback rules exactly —
|
|
7
|
+
* a real URL host is a meaningful group, so it never fires there.
|
|
8
|
+
*/
|
|
9
|
+
export declare function noProjectContextNudge(meta: Record<string, string> | undefined): string | undefined;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-project-context nudge (issue #692 follow-up): a path-tagged upload with
|
|
3
|
+
* no repo/gh.repo/app metadata and no real (non-local) origin lands in the
|
|
4
|
+
* screenshots page's "local dev" / "Other" fallback buckets. One advisory
|
|
5
|
+
* stderr line teaches the fix at the moment the context went missing. The
|
|
6
|
+
* predicate mirrors apps/api's projectLabelFromMeta fallback rules exactly —
|
|
7
|
+
* a real URL host is a meaningful group, so it never fires there.
|
|
8
|
+
*/
|
|
9
|
+
/** Hosts that identify a dev machine, not an app — same set as the API's
|
|
10
|
+
* isLocalHostname (apps/api/src/file-metadata.ts). */
|
|
11
|
+
function isLocalHostname(hostname) {
|
|
12
|
+
const bare = hostname.toLowerCase();
|
|
13
|
+
return (bare === "localhost" ||
|
|
14
|
+
bare.endsWith(".localhost") ||
|
|
15
|
+
bare === "127.0.0.1" ||
|
|
16
|
+
bare === "0.0.0.0" ||
|
|
17
|
+
bare === "[::1]");
|
|
18
|
+
}
|
|
19
|
+
export function noProjectContextNudge(meta) {
|
|
20
|
+
// Only `path`-tagged uploads appear on the screenshots page at all.
|
|
21
|
+
if (!meta?.path)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (meta.repo || meta["gh.repo"] || meta.app)
|
|
24
|
+
return undefined;
|
|
25
|
+
let bucket = "Other";
|
|
26
|
+
if (meta.url) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(meta.url);
|
|
29
|
+
if (!isLocalHostname(parsed.hostname))
|
|
30
|
+
return undefined; // real host = real group
|
|
31
|
+
bucket = "local dev";
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// unparseable url is just "no url"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return (`note: no repo detected — the screenshots page will group this under "${bucket}". ` +
|
|
38
|
+
`Run from inside your project repo, or pass --app <name>.`);
|
|
39
|
+
}
|