@drantoniou/uploadcheck 0.1.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/index.mjs ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import { createReadStream } from "node:fs";
3
+ import { buildCostBasisRequest, buildEstimateRequest, buildJobRequest, buildLaunchDoctorRequest, buildLaunchEvidenceRequest, buildLaunchHandoffRequest, buildLaunchStatusRequest, buildNpoPipelineHandoffRequest, buildPipelineHandoffRequest, buildPipelineRecipesRequest, buildRemoteLaunchEvidence, buildUsageRequest, formatCostBasisSummary, formatJobSummary, formatLaunchDoctorSummary, formatLaunchEvidenceSummary, formatLaunchHandoffSummary, formatLaunchStatusSummary, formatNpoPipelineHandoffSummary, formatPipelineHandoffSummary, formatPipelineRecipesSummary, formatUsageSummary, parseArgs } from "./request-builder.mjs";
4
+
5
+ try {
6
+ const { command, target, options } = parseArgs(process.argv.slice(2));
7
+ const apiKey = options.apiKey || process.env.UPLOADCHECK_API_KEY || process.env.QCGENIE_API_KEY;
8
+
9
+ const request = command === "estimate"
10
+ ? buildEstimateRequest(options)
11
+ : (command === "usage" ? buildUsageRequest(options) : (command === "launch-status" ? buildLaunchStatusRequest(options) : (command === "launch-handoff" ? buildLaunchHandoffRequest(options) : (command === "launch-doctor" ? buildLaunchDoctorRequest(options) : (command === "launch-evidence" ? buildLaunchEvidenceRequest(options) : (command === "pipeline-handoff" ? buildPipelineHandoffRequest(options) : (command === "npo-pipeline-handoff" ? buildNpoPipelineHandoffRequest(options) : (command === "recipes" ? buildPipelineRecipesRequest(options) : (command === "cost-basis" ? buildCostBasisRequest(options) : buildJobRequest(target, options))))))))));
12
+ if (!request.public && !apiKey) throw new Error("Set UPLOADCHECK_API_KEY or pass --api-key.");
13
+ const rawPayload = request.kind === "signed_upload"
14
+ ? await runSignedUploadJob(request, apiKey)
15
+ : (request.method === "GET" ? await getJson(request.apiBaseUrl, request.path, apiKey) : await postJson(request.apiBaseUrl, request.path, request.payload, apiKey));
16
+ const payload = request.kind === "launch_evidence" && rawPayload.name !== "UploadCheck.app Remote Launch Evidence"
17
+ ? buildRemoteLaunchEvidence(rawPayload, { source: `${request.apiBaseUrl}${request.path}` })
18
+ : rawPayload;
19
+
20
+ console.log(options.json ? JSON.stringify(payload, null, 2) : formatSummary(request.kind, payload));
21
+ } catch (error) {
22
+ console.error(error.message);
23
+ process.exitCode = 1;
24
+ }
25
+
26
+ function formatSummary(kind, payload) {
27
+ if (kind === "usage") return formatUsageSummary(payload);
28
+ if (kind === "launch_status") return formatLaunchStatusSummary(payload);
29
+ if (kind === "launch_handoff") return formatLaunchHandoffSummary(payload);
30
+ if (kind === "launch_doctor") return formatLaunchDoctorSummary(payload);
31
+ if (kind === "launch_evidence") return formatLaunchEvidenceSummary(payload);
32
+ if (kind === "pipeline_handoff") return formatPipelineHandoffSummary(payload);
33
+ if (kind === "npo_pipeline_handoff") return formatNpoPipelineHandoffSummary(payload);
34
+ if (kind === "pipeline_recipes") return formatPipelineRecipesSummary(payload);
35
+ if (kind === "cost_basis") return formatCostBasisSummary(payload);
36
+ return formatJobSummary(payload);
37
+ }
38
+
39
+ async function runSignedUploadJob(request, apiKey) {
40
+ const upload = await postJson(request.apiBaseUrl, request.createUpload.path, request.createUpload.payload, apiKey);
41
+ const putResponse = await fetch(upload.signedPutUrl, {
42
+ method: "PUT",
43
+ headers: {
44
+ "content-type": request.contentType,
45
+ "content-length": String(request.sizeBytes)
46
+ },
47
+ body: createReadStream(request.filePath),
48
+ duplex: "half"
49
+ });
50
+ if (!putResponse.ok) {
51
+ const text = await putResponse.text();
52
+ throw new Error(`UploadCheck upload ${putResponse.status}: ${text}`);
53
+ }
54
+ const jobPayload = {
55
+ ...request.createJob.payload,
56
+ upload_id: upload.uploadId
57
+ };
58
+ return postJson(request.apiBaseUrl, request.createJob.path, jobPayload, apiKey);
59
+ }
60
+
61
+ async function postJson(apiBaseUrl, path, payload, apiKey) {
62
+ const response = await fetch(`${apiBaseUrl}${path}`, {
63
+ method: "POST",
64
+ headers: {
65
+ "content-type": "application/json",
66
+ authorization: `Bearer ${apiKey}`
67
+ },
68
+ body: JSON.stringify(payload)
69
+ });
70
+ const body = await response.json();
71
+ if (!response.ok) {
72
+ throw new Error(`UploadCheck API ${response.status}: ${JSON.stringify(body)}`);
73
+ }
74
+ return body;
75
+ }
76
+
77
+ async function getJson(apiBaseUrl, path, apiKey) {
78
+ const response = await fetch(`${apiBaseUrl}${path}`, {
79
+ method: "GET",
80
+ headers: apiKey ? {
81
+ authorization: `Bearer ${apiKey}`
82
+ } : undefined
83
+ });
84
+ const body = await response.json();
85
+ if (!response.ok) {
86
+ throw new Error(`UploadCheck API ${response.status}: ${JSON.stringify(body)}`);
87
+ }
88
+ return body;
89
+ }
@@ -0,0 +1,42 @@
1
+ export const LAUNCH_PROOF_CONTRACT_VERSION = "2026-06-06.render-web-proof";
2
+
3
+ export function buildRemoteLaunchEvidence(payload = {}, options = {}) {
4
+ const blockers = (payload.remainingBlockers || []).map((blocker) => blocker.id).filter(Boolean);
5
+ const commands = Array.isArray(payload.launchDoctorCommands) ? payload.launchDoctorCommands : [];
6
+ const phases = Array.isArray(payload.blockerFixPlan?.phases) ? payload.blockerFixPlan.phases : [];
7
+ return {
8
+ name: "UploadCheck.app Remote Launch Evidence",
9
+ contractVersion: LAUNCH_PROOF_CONTRACT_VERSION,
10
+ generatedAt: options.generatedAt || new Date().toISOString(),
11
+ source: options.source || "https://api.uploadcheck.app/v1/launch-doctor",
12
+ productHuntReady: Boolean(payload.productHuntReady),
13
+ status: payload.productHuntReady && blockers.length === 0 ? "ready" : "blocked",
14
+ blockers,
15
+ redaction: {
16
+ rawStdoutIncluded: false,
17
+ rawStderrIncluded: false,
18
+ protectedValues: ["API bearer tokens", "checkout URL paths", "Lemon Squeezy variant ids", "local temp paths"]
19
+ },
20
+ commandCoverage: commands.map((command) => redactLaunchText(command)),
21
+ fixPhases: phases.map((phase) => ({
22
+ id: phase.id,
23
+ title: phase.title,
24
+ blockers: phase.blockers || [],
25
+ proofCommands: (phase.proof_commands || []).map((command) => redactLaunchText(command))
26
+ })),
27
+ completionRule: payload.blockerFixPlan?.completionRule || payload.rule || "Only launch when Product Hunt readiness is true and no blockers remain."
28
+ };
29
+ }
30
+
31
+ export function redactLaunchText(value = "") {
32
+ return String(value)
33
+ .replace(/UPLOADCHECK_API_KEY=([^\s"']+)/g, "UPLOADCHECK_API_KEY=<private_bearer>")
34
+ .replace(/(authorization:\s*bearer\s+)[^\s"']+/gi, "$1<private_bearer>")
35
+ .replace(/(https:\/\/[^/\s"']+\/checkout\/buy\/)[A-Za-z0-9_-]+/g, "$1<variant_id>")
36
+ .replace(/(https:\/\/[^/\s"']+)<checkout_path>/g, "$1<checkout_path>")
37
+ .replace(/(https:\/\/[^/\s"']+)\/[^\s"']*(checkout|creator|studio|network)[^\s"']*/gi, (match, origin) => {
38
+ if (match.includes("/checkout/buy/<variant_id>")) return match;
39
+ return `${origin}<checkout_path>`;
40
+ })
41
+ .replace(/\/tmp\/uploadcheck[-/][^\s"']+/g, "/tmp/uploadcheck/<redacted>");
42
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@drantoniou/uploadcheck",
3
+ "version": "0.1.0",
4
+ "description": "Quality check videos, podcasts, and clips before you upload.",
5
+ "homepage": "https://uploadcheck.app",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ajantoniou/uploadcheck.git",
9
+ "directory": "cli"
10
+ },
11
+ "type": "module",
12
+ "exports": {
13
+ ".": "./index.mjs",
14
+ "./request-builder": "./request-builder.mjs"
15
+ },
16
+ "bin": {
17
+ "uploadcheck": "./index.mjs"
18
+ },
19
+ "files": [
20
+ "index.mjs",
21
+ "launch-evidence.mjs",
22
+ "request-builder.mjs"
23
+ ],
24
+ "license": "UNLICENSED",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ }
28
+ }
@@ -0,0 +1,553 @@
1
+ import { extname, basename, join, relative } from "node:path";
2
+ import { statSync, readFileSync, existsSync, readdirSync } from "node:fs";
3
+
4
+ const DEFAULT_API_BASE_URL = "https://api.uploadcheck.app";
5
+ const DEFAULT_MAX_INLINE_MB = 128;
6
+ const LAUNCH_PROOF_CONTRACT_VERSION = "2026-06-06.render-web-proof";
7
+
8
+ const CONTENT_TYPES = new Map([
9
+ [".mp4", "video/mp4"],
10
+ [".mov", "video/quicktime"],
11
+ [".m4v", "video/x-m4v"],
12
+ [".webm", "video/webm"],
13
+ [".mp3", "audio/mpeg"],
14
+ [".m4a", "audio/mp4"],
15
+ [".wav", "audio/wav"],
16
+ [".aac", "audio/aac"],
17
+ [".ogg", "audio/ogg"],
18
+ [".jpg", "image/jpeg"],
19
+ [".jpeg", "image/jpeg"],
20
+ [".png", "image/png"],
21
+ [".webp", "image/webp"]
22
+ ]);
23
+
24
+ export function buildJobRequest(target, options = {}) {
25
+ if (!target) throw new Error("Usage: uploadcheck check <file-or-url>");
26
+
27
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
28
+ const maxInlineMb = Number(options.maxInlineMb || process.env.UPLOADCHECK_INLINE_MEDIA_MAX_MB || DEFAULT_MAX_INLINE_MB);
29
+ const payload = {};
30
+
31
+ if (isHttpUrl(target)) {
32
+ if (isYouTubeUrl(target)) {
33
+ payload.youtube_url = target;
34
+ } else {
35
+ payload.signed_url = target;
36
+ }
37
+ } else {
38
+ if (!existsSync(target)) throw new Error(`File not found: ${target}`);
39
+ const fileStat = statSync(target);
40
+ const maxBytes = maxInlineMb * 1024 * 1024;
41
+ const uploadMode = options.uploadMode || "auto";
42
+ if (uploadMode === "signed" || (uploadMode === "auto" && fileStat.size > maxBytes)) {
43
+ return buildSignedUploadPlan(target, options, fileStat);
44
+ }
45
+ if (uploadMode === "inline" && fileStat.size > maxBytes) {
46
+ throw new Error(`File is ${formatMb(fileStat.size)} MB; inline CLI payload limit is ${maxInlineMb} MB. Use a signed URL or raise --max-inline-mb.`);
47
+ }
48
+ const contentType = inferContentType(target);
49
+ const mediaKind = inferMediaKind(contentType);
50
+ payload.media_base64 = readFileSync(target).toString("base64");
51
+ payload.media_content_type = contentType;
52
+ payload.media_kind = mediaKind;
53
+ payload.filename = basename(target);
54
+ }
55
+
56
+ if (options.checks) payload.checks = options.checks;
57
+ attachManifest(payload, options);
58
+ attachTranscript(payload, options);
59
+ attachWatchlist(payload, options);
60
+ attachExpectedScript(payload, options);
61
+ attachChunkSidecars(payload, options);
62
+ if (options.callbackUrl) payload.callback_url = options.callbackUrl;
63
+ if (options.idempotencyKey) payload.idempotency_key = options.idempotencyKey;
64
+ attachCostOptions(payload, options);
65
+
66
+ return {
67
+ apiBaseUrl,
68
+ path: "/v1/qc/jobs",
69
+ method: "POST",
70
+ kind: "job",
71
+ payload
72
+ };
73
+ }
74
+
75
+ export function buildSignedUploadPlan(target, options = {}, fileStat = null) {
76
+ if (!existsSync(target)) throw new Error(`File not found: ${target}`);
77
+ const stat = fileStat || statSync(target);
78
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
79
+ const contentType = inferContentType(target);
80
+ const jobPayload = {};
81
+ if (options.checks) jobPayload.checks = options.checks;
82
+ attachManifest(jobPayload, options);
83
+ attachTranscript(jobPayload, options);
84
+ attachWatchlist(jobPayload, options);
85
+ attachExpectedScript(jobPayload, options);
86
+ attachChunkSidecars(jobPayload, options);
87
+ if (options.callbackUrl) jobPayload.callback_url = options.callbackUrl;
88
+ if (options.idempotencyKey) jobPayload.idempotency_key = options.idempotencyKey;
89
+ attachCostOptions(jobPayload, options);
90
+
91
+ return {
92
+ apiBaseUrl,
93
+ kind: "signed_upload",
94
+ filePath: target,
95
+ contentType,
96
+ sizeBytes: stat.size,
97
+ createUpload: {
98
+ path: "/v1/uploads",
99
+ method: "POST",
100
+ payload: {
101
+ filename: basename(target),
102
+ content_type: contentType,
103
+ size_bytes: stat.size
104
+ }
105
+ },
106
+ createJob: {
107
+ path: "/v1/qc/jobs",
108
+ method: "POST",
109
+ payload: jobPayload
110
+ }
111
+ };
112
+ }
113
+
114
+ export function parseArgs(argv) {
115
+ const args = [...argv];
116
+ const command = args.shift();
117
+ if (!["check", "estimate", "usage", "launch-status", "launch-handoff", "launch-doctor", "launch-evidence", "pipeline-handoff", "npo-pipeline-handoff", "recipes", "cost-basis"].includes(command)) throw new Error("Usage: uploadcheck check <file-or-url> | uploadcheck estimate --minutes N | uploadcheck usage | uploadcheck launch-status | uploadcheck launch-handoff | uploadcheck launch-doctor | uploadcheck launch-evidence | uploadcheck pipeline-handoff | uploadcheck npo-pipeline-handoff | uploadcheck recipes | uploadcheck cost-basis");
118
+
119
+ const target = command === "check" ? args.shift() : null;
120
+ const options = { json: false };
121
+
122
+ while (args.length) {
123
+ const arg = args.shift();
124
+ if (arg === "--json") {
125
+ options.json = true;
126
+ } else if (arg === "--api-base") {
127
+ options.apiBaseUrl = requireValue(arg, args.shift());
128
+ } else if (arg === "--api-key") {
129
+ options.apiKey = requireValue(arg, args.shift());
130
+ } else if (arg === "--checks") {
131
+ options.checks = requireValue(arg, args.shift());
132
+ } else if (arg === "--manifest") {
133
+ options.manifestPath = requireValue(arg, args.shift());
134
+ } else if (arg === "--transcript") {
135
+ options.transcriptPath = requireValue(arg, args.shift());
136
+ } else if (arg === "--output") {
137
+ options.outputPath = requireValue(arg, args.shift());
138
+ } else if (arg === "--model") {
139
+ options.model = requireValue(arg, args.shift());
140
+ } else if (arg === "--keep-file") {
141
+ options.keepFile = true;
142
+ } else if (arg === "--watchlist") {
143
+ options.watchlistPath = requireValue(arg, args.shift());
144
+ } else if (arg === "--expected-script") {
145
+ options.expectedScriptPath = requireValue(arg, args.shift());
146
+ } else if (arg === "--sidecar-dir") {
147
+ options.sidecarDir = requireValue(arg, args.shift());
148
+ } else if (arg === "--callback-url") {
149
+ options.callbackUrl = requireValue(arg, args.shift());
150
+ } else if (arg === "--idempotency-key") {
151
+ options.idempotencyKey = requireValue(arg, args.shift());
152
+ } else if (arg === "--plan") {
153
+ options.planId = requireValue(arg, args.shift());
154
+ } else if (arg === "--plan-price-cents") {
155
+ options.planPriceCents = requireValue(arg, args.shift());
156
+ } else if (arg === "--included-minutes") {
157
+ options.includedMinutes = requireValue(arg, args.shift());
158
+ } else if (arg === "--ai-review-seconds") {
159
+ options.aiReviewSeconds = requireValue(arg, args.shift());
160
+ } else if (arg === "--cost-guardrail") {
161
+ const mode = requireValue(arg, args.shift());
162
+ if (!["downgrade", "block", "off"].includes(mode)) throw new Error("--cost-guardrail must be downgrade, block, or off");
163
+ options.costGuardrail = mode;
164
+ } else if (arg === "--minutes") {
165
+ options.minutes = requireValue(arg, args.shift());
166
+ } else if (arg === "--duration-seconds") {
167
+ options.durationSeconds = requireValue(arg, args.shift());
168
+ } else if (arg === "--billing-period") {
169
+ options.billingPeriod = requireValue(arg, args.shift());
170
+ } else if (arg === "--limit") {
171
+ options.limit = requireValue(arg, args.shift());
172
+ } else if (arg === "--max-inline-mb") {
173
+ options.maxInlineMb = requireValue(arg, args.shift());
174
+ } else if (arg === "--upload-mode") {
175
+ const mode = requireValue(arg, args.shift());
176
+ if (!["auto", "inline", "signed"].includes(mode)) throw new Error("--upload-mode must be auto, inline, or signed");
177
+ options.uploadMode = mode;
178
+ } else {
179
+ throw new Error(`Unknown option: ${arg}`);
180
+ }
181
+ }
182
+
183
+ return { command, target, options };
184
+ }
185
+
186
+ export function buildLaunchStatusRequest(options = {}) {
187
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
188
+ return {
189
+ apiBaseUrl,
190
+ path: "/v1/launch-status",
191
+ method: "GET",
192
+ kind: "launch_status",
193
+ public: true
194
+ };
195
+ }
196
+
197
+ export function buildLaunchHandoffRequest(options = {}) {
198
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
199
+ return {
200
+ apiBaseUrl,
201
+ path: "/v1/launch-handoff",
202
+ method: "GET",
203
+ kind: "launch_handoff",
204
+ public: true
205
+ };
206
+ }
207
+
208
+ export function buildLaunchDoctorRequest(options = {}) {
209
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
210
+ return {
211
+ apiBaseUrl,
212
+ path: "/v1/launch-doctor",
213
+ method: "GET",
214
+ kind: "launch_doctor",
215
+ public: true
216
+ };
217
+ }
218
+
219
+ export function buildLaunchEvidenceRequest(options = {}) {
220
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
221
+ return {
222
+ apiBaseUrl,
223
+ path: "/v1/launch-evidence",
224
+ method: "GET",
225
+ kind: "launch_evidence",
226
+ public: true
227
+ };
228
+ }
229
+
230
+ export function buildPipelineHandoffRequest(options = {}) {
231
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
232
+ return {
233
+ apiBaseUrl,
234
+ path: "/pipeline-handoff.json",
235
+ method: "GET",
236
+ kind: "pipeline_handoff",
237
+ public: true
238
+ };
239
+ }
240
+
241
+ export function buildNpoPipelineHandoffRequest(options = {}) {
242
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
243
+ return {
244
+ apiBaseUrl,
245
+ path: "/npo-pipeline-handoff.json",
246
+ method: "GET",
247
+ kind: "npo_pipeline_handoff",
248
+ public: true
249
+ };
250
+ }
251
+
252
+ export function buildPipelineRecipesRequest(options = {}) {
253
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
254
+ return {
255
+ apiBaseUrl,
256
+ path: "/pipeline-recipes.json",
257
+ method: "GET",
258
+ kind: "pipeline_recipes",
259
+ public: true
260
+ };
261
+ }
262
+
263
+ export function buildCostBasisRequest(options = {}) {
264
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
265
+ return {
266
+ apiBaseUrl,
267
+ path: "/cost-basis.json",
268
+ method: "GET",
269
+ kind: "cost_basis",
270
+ public: true
271
+ };
272
+ }
273
+
274
+ export function buildUsageRequest(options = {}) {
275
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
276
+ const params = new URLSearchParams();
277
+ if (options.billingPeriod) params.set("billing_period", options.billingPeriod);
278
+ if (options.limit) params.set("limit", Number(options.limit));
279
+ const query = params.toString();
280
+ return {
281
+ apiBaseUrl,
282
+ path: `/v1/usage/margins${query ? `?${query}` : ""}`,
283
+ method: "GET",
284
+ kind: "usage"
285
+ };
286
+ }
287
+
288
+ export function buildEstimateRequest(options = {}) {
289
+ const apiBaseUrl = trimTrailingSlash(options.apiBaseUrl || process.env.UPLOADCHECK_API_BASE_URL || DEFAULT_API_BASE_URL);
290
+ const payload = {};
291
+ if (options.checks) payload.checks = options.checks;
292
+ if (options.minutes) payload.minutes = Number(options.minutes);
293
+ if (options.durationSeconds) payload.duration_seconds = Number(options.durationSeconds);
294
+ attachCostOptions(payload, options);
295
+ return {
296
+ apiBaseUrl,
297
+ path: "/v1/qc/estimate",
298
+ method: "POST",
299
+ kind: "estimate",
300
+ payload
301
+ };
302
+ }
303
+
304
+ export function formatJobSummary(payload) {
305
+ const status = payload.status || "unknown";
306
+ const verdict = payload.verdict || "pending";
307
+ const minutes = payload.minutesMetered ?? 0;
308
+ const ingressSuffix = formatMediaIngress(payload.mediaIngress);
309
+ const cost = payload.costEstimate?.estimatedCogsUsd ?? (
310
+ payload.costEstimate?.estimatedCogsCents == null ? null : payload.costEstimate.estimatedCogsCents / 100
311
+ );
312
+ const observed = payload.costEstimate?.observedTotalCogsCents;
313
+ const observedSuffix = observed == null ? "" : ` | observed COGS $${(Number(observed) / 100).toFixed(4)}`;
314
+ const suffix = cost == null ? observedSuffix : ` | est. COGS $${Number(cost).toFixed(4)}${observedSuffix}`;
315
+ return `UploadCheck job ${payload.jobId || payload.id || "(unknown)"}: ${status} / ${verdict} | ${minutes} min${ingressSuffix}${suffix}`;
316
+ }
317
+
318
+ export function formatUsageSummary(payload) {
319
+ const summary = payload.summary || {};
320
+ const minutes = Number(summary.minutes || 0);
321
+ const cogs = Number(summary.estimatedCogsCents || 0) / 100;
322
+ const costPerMinuteCents = Number(summary.estimatedCostPerMinuteCents || 0);
323
+ const grossMarginPct = Number(summary.estimatedGrossMarginPct || 0);
324
+ const status = summary.marginSafe === false ? "MARGIN RISK" : "MARGIN SAFE";
325
+ const observed = summary.observedProviderUsageEntries > 0
326
+ ? ` | observed cost/min ${Number(summary.observedCostPerMinuteCents || 0).toFixed(4)}c | observed margin ${Number(summary.observedGrossMarginPct || 0).toFixed(2)}%`
327
+ : "";
328
+ return `UploadCheck usage: ${status} | ${minutes} min | est. COGS $${cogs.toFixed(4)} | cost/min ${costPerMinuteCents.toFixed(4)}c | margin ${grossMarginPct.toFixed(2)}%${observed}`;
329
+ }
330
+
331
+ export function formatLaunchStatusSummary(payload) {
332
+ const status = payload.product_hunt_ready ? "READY" : "NOT READY";
333
+ const blockers = (payload.remaining_blockers || []).map((blocker) => blocker.id).filter(Boolean);
334
+ return `UploadCheck launch status: ${status}${blockers.length ? ` | blockers ${blockers.join(", ")}` : ""}`;
335
+ }
336
+
337
+ export function formatLaunchHandoffSummary(payload) {
338
+ const status = payload.productHuntReady ? "READY" : "NOT READY";
339
+ const blockers = (payload.remainingBlockers || []).map((blocker) => blocker.id).filter(Boolean);
340
+ const actions = (payload.requiredActions || []).map((action) => action.id).filter(Boolean);
341
+ return `UploadCheck launch handoff: ${status}${blockers.length ? ` | blockers ${blockers.join(", ")}` : ""}${actions.length ? ` | required actions ${actions.join(", ")}` : ""}`;
342
+ }
343
+
344
+ export function formatLaunchDoctorSummary(payload) {
345
+ const status = payload.productHuntReady ? "READY" : "NOT READY";
346
+ const blockers = (payload.remainingBlockers || []).map((blocker) => blocker.id).filter(Boolean);
347
+ const phases = payload.blockerFixPlan?.phases?.length || 0;
348
+ const commands = payload.launchDoctorCommands?.length || 0;
349
+ return `UploadCheck launch doctor: ${status}${blockers.length ? ` | blockers ${blockers.join(", ")}` : ""}${phases ? ` | fix phases ${phases}` : ""}${commands ? ` | doctor commands ${commands}` : ""}`;
350
+ }
351
+
352
+ export function formatLaunchEvidenceSummary(payload) {
353
+ const evidence = payload.name === "UploadCheck.app Remote Launch Evidence" ? payload : buildRemoteLaunchEvidence(payload);
354
+ const status = evidence.status === "ready" ? "READY" : "NOT READY";
355
+ const blockers = evidence.blockers || [];
356
+ const phases = evidence.fixPhases?.length || 0;
357
+ const commands = evidence.commandCoverage?.length || 0;
358
+ return `UploadCheck launch evidence: ${status}${blockers.length ? ` | blockers ${blockers.join(", ")}` : ""}${phases ? ` | fix phases ${phases}` : ""}${commands ? ` | commands ${commands}` : ""}`;
359
+ }
360
+
361
+ export function formatPipelineRecipesSummary(payload) {
362
+ const profiles = Object.keys(payload.profiles || {});
363
+ const checks = payload.nto_replacement_qc?.implemented_gates?.length || 0;
364
+ return `UploadCheck pipeline recipes: ${profiles.length} profiles${profiles.length ? ` (${profiles.join(", ")})` : ""} | ${checks} implemented NTO/NPO replacement gates`;
365
+ }
366
+
367
+ export function formatPipelineHandoffSummary(payload) {
368
+ const steps = payload.call_sequence?.length || 0;
369
+ const profiles = payload.profiles || [];
370
+ const ingress = Object.keys(payload.media_ingress || {});
371
+ return `UploadCheck pipeline handoff: ${steps} steps${profiles.length ? ` | profiles ${profiles.join(", ")}` : ""}${ingress.length ? ` | media ingress ${ingress.join(", ")}` : ""}`;
372
+ }
373
+
374
+ export function formatNpoPipelineHandoffSummary(payload) {
375
+ const checks = payload.cost_preflight?.checks || "";
376
+ const steps = payload.mcp_sequence?.length || 0;
377
+ const sidecars = Object.keys(payload.required_sidecars || {});
378
+ return `UploadCheck NPO pipeline handoff: ${steps} MCP steps | checks ${checks}${sidecars.length ? ` | sidecars ${sidecars.join(", ")}` : ""}`;
379
+ }
380
+
381
+ export function formatCostBasisSummary(payload) {
382
+ const target = Number(payload.target_gross_margin_pct || 0);
383
+ const stress = (payload.plans || []).find((plan) => plan.plan_id === "stress_99_5000");
384
+ const remaining = stress?.remaining_cost_per_minute_after_deterministic_full_allowance_cents;
385
+ const verdict = payload.verdict?.stress_99_5000 || "";
386
+ const remainingText = remaining == null ? "unknown" : `${Number(remaining).toFixed(4)}c/min`;
387
+ return `UploadCheck cost basis: target margin ${target}% | $99/5,000 remaining post-deterministic COGS ${remainingText}${verdict ? ` | ${verdict}` : ""}`;
388
+ }
389
+
390
+ function requireValue(flag, value) {
391
+ if (!value) throw new Error(`${flag} requires a value`);
392
+ return value;
393
+ }
394
+
395
+ function trimTrailingSlash(value) {
396
+ return value.replace(/\/+$/, "");
397
+ }
398
+
399
+ function isHttpUrl(value) {
400
+ return /^https?:\/\//i.test(value);
401
+ }
402
+
403
+ function isYouTubeUrl(value) {
404
+ return /(^https?:\/\/)?([^/]+\.)?(youtube\.com|youtu\.be)\//i.test(value);
405
+ }
406
+
407
+ export function inferContentType(filePath) {
408
+ const ext = extname(filePath).toLowerCase();
409
+ return CONTENT_TYPES.get(ext) || "application/octet-stream";
410
+ }
411
+
412
+ function inferMediaKind(contentType) {
413
+ if (contentType.startsWith("audio/")) return "audio";
414
+ if (contentType.startsWith("image/")) return "image";
415
+ return "video";
416
+ }
417
+
418
+ function attachManifest(payload, options) {
419
+ if (!options.manifestPath) return;
420
+ if (!existsSync(options.manifestPath)) throw new Error(`Manifest not found: ${options.manifestPath}`);
421
+ const text = readFileSync(options.manifestPath, "utf8");
422
+ payload.manifest_json = JSON.parse(text);
423
+ payload.manifest_filename = basename(options.manifestPath);
424
+ }
425
+
426
+ function attachTranscript(payload, options) {
427
+ if (!options.transcriptPath) return;
428
+ if (!existsSync(options.transcriptPath)) throw new Error(`Transcript not found: ${options.transcriptPath}`);
429
+ const text = readFileSync(options.transcriptPath, "utf8");
430
+ if (options.transcriptPath.toLowerCase().endsWith(".json")) {
431
+ payload.transcript_json = JSON.parse(text);
432
+ } else {
433
+ payload.transcript_text = text;
434
+ }
435
+ payload.transcript_filename = basename(options.transcriptPath);
436
+ }
437
+
438
+ function attachWatchlist(payload, options) {
439
+ if (!options.watchlistPath) return;
440
+ if (!existsSync(options.watchlistPath)) throw new Error(`Watchlist not found: ${options.watchlistPath}`);
441
+ const text = readFileSync(options.watchlistPath, "utf8");
442
+ payload.watchlist_json = JSON.parse(text);
443
+ payload.watchlist_filename = basename(options.watchlistPath);
444
+ }
445
+
446
+ function attachExpectedScript(payload, options) {
447
+ if (!options.expectedScriptPath) return;
448
+ if (!existsSync(options.expectedScriptPath)) throw new Error(`Expected script not found: ${options.expectedScriptPath}`);
449
+ const text = readFileSync(options.expectedScriptPath, "utf8");
450
+ if (options.expectedScriptPath.toLowerCase().endsWith(".json")) {
451
+ payload.expected_script_json = JSON.parse(text);
452
+ } else {
453
+ payload.expected_script_text = text;
454
+ }
455
+ payload.expected_script_filename = basename(options.expectedScriptPath);
456
+ }
457
+
458
+ function attachChunkSidecars(payload, options) {
459
+ if (!options.sidecarDir) return;
460
+ if (!existsSync(options.sidecarDir)) throw new Error(`Sidecar dir not found: ${options.sidecarDir}`);
461
+ if (!statSync(options.sidecarDir).isDirectory()) throw new Error(`Sidecar dir is not a directory: ${options.sidecarDir}`);
462
+ const files = collectJsonFiles(options.sidecarDir).slice(0, 200);
463
+ payload.chunk_sidecars_json = files.map((filePath) => ({
464
+ relative_path: relative(options.sidecarDir, filePath),
465
+ filename: basename(filePath),
466
+ json: JSON.parse(readFileSync(filePath, "utf8"))
467
+ }));
468
+ payload.chunk_sidecar_dirname = basename(options.sidecarDir);
469
+ }
470
+
471
+ function collectJsonFiles(dir) {
472
+ const out = [];
473
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
474
+ const filePath = join(dir, entry.name);
475
+ if (entry.isDirectory()) out.push(...collectJsonFiles(filePath));
476
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".json")) out.push(filePath);
477
+ }
478
+ return out.sort();
479
+ }
480
+
481
+ function attachCostOptions(payload, options) {
482
+ if (options.planId) payload.plan_id = options.planId;
483
+ if (options.planPriceCents) payload.plan_price_cents = Number(options.planPriceCents);
484
+ if (options.includedMinutes) payload.included_minutes = Number(options.includedMinutes);
485
+ if (options.aiReviewSeconds) payload.ai_review_seconds = Number(options.aiReviewSeconds);
486
+ if (options.costGuardrail) payload.cost_guardrail = options.costGuardrail;
487
+ }
488
+
489
+ function formatMediaIngress(mediaIngress) {
490
+ if (!mediaIngress?.mode) return "";
491
+ const detail = [mediaIngress.mode, mediaIngress.contentType, formatBytes(mediaIngress.bytes), formatSha256(mediaIngress.sha256)].filter(Boolean).join(" ");
492
+ return detail ? ` | media ${detail}` : "";
493
+ }
494
+
495
+ function formatSha256(value) {
496
+ const text = String(value || "");
497
+ return /^[a-f0-9]{64}$/i.test(text) ? `sha256 ${text.slice(0, 12).toLowerCase()}...` : "";
498
+ }
499
+
500
+ function formatBytes(bytes) {
501
+ const value = Number(bytes);
502
+ if (!Number.isFinite(value) || value <= 0) return "";
503
+ if (value < 1024) return `${value} B`;
504
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(value < 10 * 1024 ? 1 : 0)} KB`;
505
+ return `${(value / 1024 / 1024).toFixed(value < 10 * 1024 * 1024 ? 2 : 1)} MB`;
506
+ }
507
+
508
+ function formatMb(bytes) {
509
+ return (bytes / 1024 / 1024).toFixed(1);
510
+ }
511
+
512
+ function buildRemoteLaunchEvidence(payload = {}, options = {}) {
513
+ const blockers = (payload.remainingBlockers || []).map((blocker) => blocker.id).filter(Boolean);
514
+ const commands = Array.isArray(payload.launchDoctorCommands) ? payload.launchDoctorCommands : [];
515
+ const phases = Array.isArray(payload.blockerFixPlan?.phases) ? payload.blockerFixPlan.phases : [];
516
+ return {
517
+ name: "UploadCheck.app Remote Launch Evidence",
518
+ contractVersion: LAUNCH_PROOF_CONTRACT_VERSION,
519
+ generatedAt: options.generatedAt || new Date().toISOString(),
520
+ source: options.source || "https://api.uploadcheck.app/v1/launch-doctor",
521
+ productHuntReady: Boolean(payload.productHuntReady),
522
+ status: payload.productHuntReady && blockers.length === 0 ? "ready" : "blocked",
523
+ blockers,
524
+ redaction: {
525
+ rawStdoutIncluded: false,
526
+ rawStderrIncluded: false,
527
+ protectedValues: ["API bearer tokens", "checkout URL paths", "Lemon Squeezy variant ids", "local temp paths"]
528
+ },
529
+ commandCoverage: commands.map((command) => redactLaunchText(command)),
530
+ fixPhases: phases.map((phase) => ({
531
+ id: phase.id,
532
+ title: phase.title,
533
+ blockers: phase.blockers || [],
534
+ proofCommands: (phase.proof_commands || []).map((command) => redactLaunchText(command))
535
+ })),
536
+ completionRule: payload.blockerFixPlan?.completionRule || payload.rule || "Only launch when Product Hunt readiness is true and no blockers remain."
537
+ };
538
+ }
539
+
540
+ function redactLaunchText(value = "") {
541
+ return String(value)
542
+ .replace(/UPLOADCHECK_API_KEY=([^\s"']+)/g, "UPLOADCHECK_API_KEY=<private_bearer>")
543
+ .replace(/(authorization:\s*bearer\s+)[^\s"']+/gi, "$1<private_bearer>")
544
+ .replace(/(https:\/\/[^/\s"']+\/checkout\/buy\/)[A-Za-z0-9_-]+/g, "$1<variant_id>")
545
+ .replace(/(https:\/\/[^/\s"']+)<checkout_path>/g, "$1<checkout_path>")
546
+ .replace(/(https:\/\/[^/\s"']+)\/[^\s"']*(checkout|creator|studio|network)[^\s"']*/gi, (match, origin) => {
547
+ if (match.includes("/checkout/buy/<variant_id>")) return match;
548
+ return `${origin}<checkout_path>`;
549
+ })
550
+ .replace(/\/tmp\/uploadcheck[-/][^\s"']+/g, "/tmp/uploadcheck/<redacted>");
551
+ }
552
+
553
+ export { buildRemoteLaunchEvidence };