shippingszn 0.9.0 → 0.9.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.
Files changed (3) hide show
  1. package/README.md +40 -61
  2. package/dist/index.js +124 -76
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -6,65 +6,45 @@ leaked API keys, missing crawl assets, weak browser defenses, dangerous code
6
6
  patterns, and last-mile polish gaps.
7
7
 
8
8
  ```bash
9
- npx shippingszn
9
+ npx shippingszn@latest
10
10
  # or
11
- pnpm dlx shippingszn
11
+ pnpm dlx shippingszn@latest
12
12
  ```
13
13
 
14
14
  The CLI **never writes, modifies, or deletes** any files - it only reads.
15
- By default it posts one anonymous Wall of Launches entry with score, severity
16
- counts, files scanned, scanner version, and safe stack tags. It never uploads
17
- source code, paths, filenames, repo URLs, project names, secrets, handles, or
18
- emails. Use `--no-publish` for a private local run.
15
+ Normal runs send one anonymous aggregate summary with score, severity counts,
16
+ files scanned, scanner version, and safe stack tags. It never uploads source
17
+ code, paths, filenames, repo URLs, project names, secrets, handles, or emails.
18
+ This aggregate signal is automatic and not user-configurable.
19
19
 
20
- Human output starts with a Launch Readiness Score, scope covered, the top next
21
- step to fix, and severity counts. Run with `--json` to get machine-readable
22
- findings plus the Wall/report handoff fields:
20
+ Human output starts with a Launch Readiness Score, scope covered, and severity
21
+ counts. Run with `--json` to get the same score-and-counts summary in a
22
+ machine-readable shape:
23
23
 
24
24
  ```json
25
25
  {
26
- "launchReadiness": {
27
- "score": 43,
28
- "rawScore": 43,
29
- "label": "No-go: launch blocked",
30
- "decisionLabel": "No-go: launch blocked",
31
- "goNoGoLabel": "No-go",
32
- "confidence": "high",
33
- "coveragePenalty": 0,
34
- "coverageSummary": "CLI scan covered 4 readiness areas with 4 areas requiring owner verification.",
35
- "topNextStep": "Fix Lock up your API keys and passwords: Possible OpenAI API key hardcoded in source.",
36
- "wallUrl": "https://shippingszn.com/wall",
37
- "reportUrl": "https://shippingszn.com/report"
38
- },
39
- "findings": [
40
- {
41
- "severity": "critical",
42
- "message": "Possible OpenAI API key hardcoded in source.",
43
- "whatFailed": "A secret-like token appears in a scanned file.",
44
- "whyItBlocksLaunch": "A leaked key can spend money or expose customer data before you notice."
45
- }
46
- ]
26
+ "score": 43,
27
+ "band": "no_go",
28
+ "counts": { "critical": 1, "high": 3, "medium": 4, "lower": 2 },
29
+ "filesScanned": 128,
30
+ "detailsLocked": true,
31
+ "unlockUrl": "https://shippingszn.com/fix-kit"
47
32
  }
48
33
  ```
49
34
 
50
- When Critical or High findings make paid confidence relevant, the summary also
51
- includes a `/report` link. Clean scans and lower-risk findings do not push the
52
- paid report CTA.
35
+ Finding titles, files, evidence, fix prompts, and verification steps are paid.
36
+ They unlock inside the Launch Fix Kit.
53
37
 
54
- ## Wall publishing
38
+ ## Aggregate summary
55
39
 
56
- Plain `npx shippingszn` feeds the Wall automatically. The public row is
57
- anonymous and intentionally small: score, launch label, files scanned, finding
58
- counts, detected stack tags, scanner version, and timestamp.
40
+ Plain `npx shippingszn@latest` sends the aggregate summary automatically. The
41
+ stored row is anonymous and intentionally small: score, launch label, files
42
+ scanned, finding counts, detected stack tags, scanner version, and timestamp.
59
43
 
60
- Use this when you want a completely private local run:
61
-
62
- ```bash
63
- npx shippingszn --no-publish
64
- ```
65
-
66
- For a paid-report handoff, run `npx shippingszn --json > shippingszn-scan.json`
67
- and paste or upload the file at `https://shippingszn.com/scan`.
44
+ For a scan-specific paid-report handoff, run `npx shippingszn@latest --proof`.
45
+ That uploads the canonical scan result, returns a proof URL plus a
46
+ `/fix-kit?scanResultId=...` URL, and gives the paid Launch Fix Kit the evidence
47
+ it needs after checkout.
68
48
 
69
49
  ## What gets checked
70
50
 
@@ -90,8 +70,9 @@ maps back to one of the items on the checklist.
90
70
  - Placeholder content (`lorem ipsum`, `John Doe`, `test@example.com`) and
91
71
  `TODO` / `FIXME` / `XXX` / `HACK` comments.
92
72
 
93
- Each finding is tagged Critical, High, Medium, or Lower and links back to the
94
- relevant checklist item on shippingszn.com.
73
+ Internally each finding is tagged Critical, High, Medium, or Lower and maps to
74
+ the relevant checklist item. The free CLI output keeps that finding-level detail
75
+ locked.
95
76
 
96
77
  ## Suppressing false positives
97
78
 
@@ -141,12 +122,12 @@ ship.
141
122
  shippingszn [path] [options]
142
123
 
143
124
  Options:
144
- --json Output a machine-readable JSON report.
145
- --publish Legacy alias. Anonymous Wall stats publish by default.
146
- --no-publish Disable the anonymous Wall post for this run.
147
- --base-url <url> Base URL used to build links back to checklist items.
125
+ --json Output a machine-readable JSON summary.
126
+ --proof Upload full scan evidence and return a scan-specific
127
+ Launch Fix Kit URL.
128
+ --base-url <url> Base URL used to build checkout and Fix Kit links.
148
129
  --cwd <path> Directory to scan. Default: current working directory.
149
- --no-color Disable ANSI colors in the human-readable report.
130
+ --no-color Disable ANSI colors in the human-readable summary.
150
131
  -h, --help Show help.
151
132
  -v, --version Print version.
152
133
  ```
@@ -161,22 +142,20 @@ This makes the CLI suitable for CI:
161
142
 
162
143
  ```yaml
163
144
  # .github/workflows/launch-check.yml
164
- - run: npx shippingszn --json > launch-check.json
145
+ - run: npx shippingszn@latest --json > launch-check.json
165
146
  ```
166
147
 
167
148
  For PR scan signal, have GitHub Actions run the scanner and post the JSON
168
- summary as a comment: score, severity counts, top next step, Wall URL, report
169
- URL, and worst Critical/High findings.
149
+ summary as a comment: score, severity counts, and unlock URL.
170
150
 
171
151
  ## Privacy
172
152
 
173
153
  `shippingszn` reads files on your machine. It never uploads source code. By
174
- default it makes one best-effort outbound request to post anonymous Wall stats:
175
- score, launch label, files scanned count, finding counts by severity, detected
176
- stack tags, scanner version, and timestamp. It never uploads source code, file
177
- paths, filenames, project names, repo URLs, secrets, emails, handles, or report
178
- contents. Use `--no-publish` or `SHIPPINGSZN_DISABLE_PUBLISH=1` to disable that
179
- request.
154
+ default it makes one best-effort outbound request to post an anonymous aggregate
155
+ summary: score, launch label, files scanned count, finding counts by severity,
156
+ detected stack tags, scanner version, and timestamp. It never uploads source
157
+ code, file paths, filenames, project names, repo URLs, secrets, emails, handles,
158
+ or report contents. The aggregate summary is automatic and not user-configurable.
180
159
 
181
160
  ## License
182
161
 
package/dist/index.js CHANGED
@@ -3598,7 +3598,10 @@ async function detectStack(cwd2) {
3598
3598
  try {
3599
3599
  const raw = await fs2.readFile(path17.join(cwd2, "package.json"), "utf8");
3600
3600
  const pkg = JSON.parse(raw);
3601
- const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
3601
+ const deps = {
3602
+ ...pkg.dependencies ?? {},
3603
+ ...pkg.devDependencies ?? {}
3604
+ };
3602
3605
  const has = (n) => n in deps;
3603
3606
  if (has("react")) tags.add("react");
3604
3607
  if (has("next")) tags.add("next");
@@ -3726,6 +3729,7 @@ function buildProofPayload(report, scannerVersion) {
3726
3729
  checkedAt: report.generatedAt,
3727
3730
  counts,
3728
3731
  findings: report.findings.slice(0, MAX_FINDINGS).map((finding2) => ({
3732
+ itemId: finding2.itemId,
3729
3733
  severity: finding2.severity,
3730
3734
  title: clampText(finding2.itemTitle || finding2.checkId, 160),
3731
3735
  body: clampText(finding2.message, 3e3),
@@ -3825,7 +3829,7 @@ async function uploadProof(report, opts) {
3825
3829
  status: "uploaded",
3826
3830
  id,
3827
3831
  proofUrl: `${baseUrl}/proof/${encodeURIComponent(id)}`,
3828
- reportUrl: `${baseUrl}/report?${new URLSearchParams({ scanResultId: id }).toString()}`,
3832
+ reportUrl: `${baseUrl}/fix-kit?${new URLSearchParams({ scanResultId: id }).toString()}`,
3829
3833
  badgeMarkdown: buildBadgeMarkdown(
3830
3834
  baseUrl,
3831
3835
  id,
@@ -4137,21 +4141,7 @@ function locationLine(finding2) {
4137
4141
  if (!finding2.file?.trim()) return "";
4138
4142
  return finding2.line ? `${finding2.file.trim()}:${finding2.line}` : finding2.file.trim();
4139
4143
  }
4140
- function defaultPrompt(input) {
4141
- const where = input.location ? `Likely location: ${input.location}.` : "Inspect the relevant app, deploy, and configuration files before editing.";
4142
- return [
4143
- "You are fixing a shippingszn launch-readiness blocker in an AI-built app.",
4144
- `Severity: ${input.severity}.`,
4145
- `Finding: ${input.title}.`,
4146
- `What failed: ${input.whatFailed}`,
4147
- `Why it blocks launch: ${input.whyItBlocksLaunch}`,
4148
- where,
4149
- `Fix exactly this: ${input.fixInstructions}`,
4150
- `Verification required: ${input.verificationStep}`,
4151
- "Make the smallest production-safe change, preserve existing behavior, list files changed, and do not suppress the scanner unless you prove a false positive."
4152
- ].join(" ");
4153
- }
4154
- function normalizeLaunchFinding(finding2, source = "manual") {
4144
+ function normalizeLaunchFinding(finding2, source = "manual", promptBuilder) {
4155
4145
  const severity = canonicalSeverity(finding2.severity);
4156
4146
  const title = clampText2(
4157
4147
  finding2.title ?? finding2.itemTitle,
@@ -4182,7 +4172,7 @@ function normalizeLaunchFinding(finding2, source = "manual") {
4182
4172
  const location = locationLine(finding2);
4183
4173
  const aiBuilderPrompt = clampText2(
4184
4174
  finding2.aiBuilderPrompt,
4185
- defaultPrompt({
4175
+ promptBuilder ? promptBuilder({
4186
4176
  severity,
4187
4177
  title,
4188
4178
  whatFailed,
@@ -4190,7 +4180,7 @@ function normalizeLaunchFinding(finding2, source = "manual") {
4190
4180
  fixInstructions,
4191
4181
  verificationStep,
4192
4182
  location
4193
- }),
4183
+ }) : "",
4194
4184
  4e3
4195
4185
  );
4196
4186
  const evidence2 = clampText2(
@@ -4224,8 +4214,8 @@ function normalizeLaunchFinding(finding2, source = "manual") {
4224
4214
  ...finding2.itemTitle ? { itemTitle: finding2.itemTitle } : {}
4225
4215
  };
4226
4216
  }
4227
- function prioritizeLaunchFindings(findings, source = "manual") {
4228
- return findings.map((finding2) => normalizeLaunchFinding(finding2, source)).sort((a, b) => {
4217
+ function prioritizeLaunchFindings(findings, source = "manual", promptBuilder) {
4218
+ return findings.map((finding2) => normalizeLaunchFinding(finding2, source, promptBuilder)).sort((a, b) => {
4229
4219
  const bySeverity = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
4230
4220
  if (bySeverity !== 0) return bySeverity;
4231
4221
  return a.title.localeCompare(b.title);
@@ -4286,9 +4276,13 @@ function aggregateConfidence(source, findings) {
4286
4276
  if (source === "url") return "medium";
4287
4277
  return "high";
4288
4278
  }
4289
- function assessLaunchReadiness(input) {
4279
+ function assessLaunchReadiness(input, promptBuilder) {
4290
4280
  const source = input.source ?? "manual";
4291
- const findings = prioritizeLaunchFindings(input.findings ?? [], source);
4281
+ const findings = prioritizeLaunchFindings(
4282
+ input.findings ?? [],
4283
+ source,
4284
+ promptBuilder
4285
+ );
4292
4286
  const counts = input.counts ? normalizeLaunchCounts(input.counts) : countLaunchFindings(findings);
4293
4287
  const { score, rawScore, weightedPenalty, coveragePenalty } = scoreFromCounts(
4294
4288
  counts,
@@ -4320,7 +4314,11 @@ function assessLaunchReadiness(input) {
4320
4314
  coverageSummary: coverageSummary(source, coverage),
4321
4315
  checkedAreas,
4322
4316
  uncheckedAreas,
4323
- topNextStep: topNextStep(blockers.length ? blockers : findings, counts, source),
4317
+ topNextStep: topNextStep(
4318
+ blockers.length ? blockers : findings,
4319
+ counts,
4320
+ source
4321
+ ),
4324
4322
  reportRecommended,
4325
4323
  confidence: aggregateConfidence(source, findings),
4326
4324
  blockers,
@@ -4328,6 +4326,26 @@ function assessLaunchReadiness(input) {
4328
4326
  };
4329
4327
  }
4330
4328
 
4329
+ // ../../lib/launch-readiness/src/server-prompts.ts
4330
+ var defaultPrompt = (input) => {
4331
+ const where = input.location ? `Likely location: ${input.location}.` : "Inspect the relevant app, deploy, and configuration files before editing.";
4332
+ return [
4333
+ "You are fixing a shippingszn launch-readiness blocker in an AI-built app.",
4334
+ `Severity: ${input.severity}.`,
4335
+ `Finding: ${input.title}.`,
4336
+ `What failed: ${input.whatFailed}`,
4337
+ `Why it blocks launch: ${input.whyItBlocksLaunch}`,
4338
+ where,
4339
+ `Fix exactly this: ${input.fixInstructions}`,
4340
+ `Verification required: ${input.verificationStep}`,
4341
+ "Make the smallest production-safe change, preserve existing behavior, list files changed, and do not suppress the scanner unless you prove a false positive."
4342
+ ].join(" ");
4343
+ };
4344
+
4345
+ // ../../lib/launch-readiness/src/server.ts
4346
+ var normalizeLaunchFinding2 = (finding2, source, promptBuilder) => normalizeLaunchFinding(finding2, source, promptBuilder ?? defaultPrompt);
4347
+ var assessLaunchReadiness2 = (input, promptBuilder) => assessLaunchReadiness(input, promptBuilder ?? defaultPrompt);
4348
+
4331
4349
  // src/index.ts
4332
4350
  var UNTRACKED_DOWNGRADE = {
4333
4351
  critical: "lower",
@@ -4364,7 +4382,6 @@ function parseArgs(argv2) {
4364
4382
  help: false,
4365
4383
  version: false,
4366
4384
  noColor: !!process2.env.NO_COLOR,
4367
- publish: !publishDisabledByEnv(),
4368
4385
  proof: shouldUploadProof()
4369
4386
  };
4370
4387
  for (let i = 0; i < argv2.length; i++) {
@@ -4372,31 +4389,16 @@ function parseArgs(argv2) {
4372
4389
  if (a === "--help" || a === "-h") opts.help = true;
4373
4390
  else if (a === "--version" || a === "-v") opts.version = true;
4374
4391
  else if (a === "--json") opts.json = true;
4375
- else if (a === "--publish") opts.publish = true;
4376
- else if (a === "--no-publish") opts.publish = false;
4377
4392
  else if (a === "--proof") opts.proof = true;
4378
4393
  else if (a === "--no-color") opts.noColor = true;
4394
+ else if (a === "--no-publish" || a === "--publish")
4395
+ opts.unsupportedWallFlag = a;
4379
4396
  else if (a === "--base-url") opts.baseUrl = argv2[++i] ?? opts.baseUrl;
4380
4397
  else if (a === "--cwd") opts.cwd = path19.resolve(argv2[++i] ?? opts.cwd);
4381
4398
  else if (!a.startsWith("-")) opts.cwd = path19.resolve(a);
4382
4399
  }
4383
4400
  return opts;
4384
4401
  }
4385
- function envTruthy(value) {
4386
- if (!value) return false;
4387
- const normalized = value.trim().toLowerCase();
4388
- return normalized === "1" || normalized === "true" || normalized === "yes";
4389
- }
4390
- function envFalsey(value) {
4391
- if (!value) return false;
4392
- const normalized = value.trim().toLowerCase();
4393
- return normalized === "0" || normalized === "false" || normalized === "no";
4394
- }
4395
- function publishDisabledByEnv() {
4396
- if (envTruthy(process2.env.SHIPPINGSZN_DISABLE_PUBLISH)) return true;
4397
- if (envFalsey(process2.env.SHIPPINGSZN_PUBLISH)) return true;
4398
- return false;
4399
- }
4400
4402
  function color(enabled) {
4401
4403
  const wrap = (codes) => (s) => enabled ? `\x1B[${codes}m${s}\x1B[0m` : s;
4402
4404
  return {
@@ -4420,13 +4422,13 @@ Read-only scanner that checks the current project against a small set of
4420
4422
  high-signal launch-readiness items from shippingszn.
4421
4423
 
4422
4424
  Usage:
4423
- npx shippingszn [path] [options]
4425
+ npx shippingszn@latest [path] [options]
4424
4426
 
4425
4427
  Options:
4426
4428
  --json Output a machine-readable JSON summary.
4427
- --publish Legacy alias. Anonymous Wall stats publish by default.
4428
- --no-publish Disable the anonymous Wall post for this run.
4429
- --base-url <url> Base URL used to build links back to the Launch Fix Kit.
4429
+ --proof Upload full scan evidence and return a scan-specific
4430
+ Launch Fix Kit URL.
4431
+ --base-url <url> Base URL used to build checkout and Fix Kit links.
4430
4432
  (default: ${DEFAULT_BASE_URL2})
4431
4433
  --cwd <path> Directory to scan. Default: current working directory.
4432
4434
  --no-color Disable ANSI colors in the human-readable summary.
@@ -4437,7 +4439,9 @@ This is the FREE preflight. It tells you launch debt exists. It does NOT tell
4437
4439
  you what's broken or how to fix it \u2014 that's gated behind the $49 Launch Fix Kit
4438
4440
  (unlocks full findings + 46-item workbook + paste-ready AI-builder prompts +
4439
4441
  verification steps + red-flag checks). Read-only on disk. Anonymous aggregate
4440
- counts post to the Wall by default. Exit code non-zero if any critical findings.
4442
+ counts (score, severity counts, file count, scanner version \u2014 no project name,
4443
+ no paths, no findings) are sent on every normal run; there is no publish opt-in
4444
+ or opt-out flag. Exit code non-zero if any critical findings.
4441
4445
  `
4442
4446
  );
4443
4447
  }
@@ -4455,6 +4459,13 @@ async function run() {
4455
4459
  `);
4456
4460
  return 0;
4457
4461
  }
4462
+ if (opts.unsupportedWallFlag) {
4463
+ process2.stderr.write(
4464
+ `${opts.unsupportedWallFlag} is not supported. shippingszn sends anonymous score and severity counts on normal runs; it never uploads code, file paths, findings, project names, repo URLs, secrets, emails, or handles.
4465
+ `
4466
+ );
4467
+ return 2;
4468
+ }
4458
4469
  const c = color(!opts.noColor && process2.stdout.isTTY === true && !opts.json);
4459
4470
  const files = await listFiles(opts.cwd);
4460
4471
  const tracked = getTrackedFiles(opts.cwd);
@@ -4478,14 +4489,14 @@ async function run() {
4478
4489
  const fixKitUrl = `${trimmedBaseUrl}/fix-kit`;
4479
4490
  const reportUrl = fixKitUrl;
4480
4491
  const proofCreatePath = `${trimmedBaseUrl}/scan`;
4481
- const proofUploadHint = "Run `npx shippingszn --proof` if you want to publish a proof URL with this scan.";
4492
+ const proofUploadHint = "Run `npx shippingszn@latest --proof` to generate a scan-specific Launch Fix Kit URL.";
4482
4493
  const tracked_aware = applyTrackingAwareSeverity(all, tracked);
4483
4494
  const source = scanSource();
4484
4495
  const enriched = tracked_aware.map((f) => {
4485
4496
  const item = CHECKLIST_ITEMS[f.itemId];
4486
4497
  const itemTitle = item?.title ?? f.itemId;
4487
4498
  const permalink = permalinkFor(f.itemId, opts.baseUrl);
4488
- const normalized = normalizeLaunchFinding(
4499
+ const normalized = normalizeLaunchFinding2(
4489
4500
  {
4490
4501
  severity: f.severity,
4491
4502
  title: itemTitle,
@@ -4550,7 +4561,7 @@ async function run() {
4550
4561
  lower: 0
4551
4562
  };
4552
4563
  for (const f of enriched) totals[f.severity]++;
4553
- const assessment = assessLaunchReadiness({
4564
+ const assessment = assessLaunchReadiness2({
4554
4565
  source,
4555
4566
  findings: enriched,
4556
4567
  counts: totals
@@ -4607,7 +4618,7 @@ async function run() {
4607
4618
  let publishResult = "skipped";
4608
4619
  try {
4609
4620
  const proofAlreadyPublishedWall = proofResult.status === "uploaded" && !!proofResult.wallUrl;
4610
- if (opts.publish && !proofAlreadyPublishedWall) {
4621
+ if (!proofAlreadyPublishedWall) {
4611
4622
  publishResult = await publishScan(totals, files.length, {
4612
4623
  cwd: opts.cwd,
4613
4624
  baseUrl: opts.baseUrl,
@@ -4627,19 +4638,31 @@ async function run() {
4627
4638
  const ownerVerifyAreas = CHECKLIST.filter(
4628
4639
  (item) => item.cliCoverage === "manual_only"
4629
4640
  ).length;
4630
- const band = totals.critical > 0 ? "no_go" : totals.high > 0 ? "fix_first" : launchReadiness.score >= 90 ? "launchable" : launchReadiness.score >= 70 ? "verify_before_launch" : "fix_first";
4631
- const bandLabel = {
4632
- no_go: "NO-GO",
4641
+ const SCORE_BAND_LABEL = {
4642
+ blocked: "NO-GO",
4633
4643
  fix_first: "FIX FIRST",
4634
- verify_before_launch: "VERIFY BEFORE LAUNCH",
4644
+ verify_first: "VERIFY BEFORE LAUNCH",
4635
4645
  launchable: "LAUNCHABLE"
4636
- }[band];
4646
+ };
4647
+ const CLI_BAND_ID = {
4648
+ blocked: "no_go",
4649
+ fix_first: "fix_first",
4650
+ verify_first: "verify_before_launch",
4651
+ launchable: "launchable"
4652
+ };
4653
+ const canonicalBand = SCORE_BANDS.find(
4654
+ (b) => launchReadiness.score >= b.minScore && launchReadiness.score <= b.maxScore
4655
+ ) ?? SCORE_BANDS[0];
4656
+ const band = CLI_BAND_ID[canonicalBand.id];
4657
+ const bandLabel = SCORE_BAND_LABEL[canonicalBand.id];
4637
4658
  const wallPublishedViaProof = proofResult.status === "uploaded" && !!proofResult.wallUrl;
4638
4659
  const wallStatus = wallPublishedViaProof ? "published" : proofResult.status === "uploaded" && proofResult.wallPublishError ? "failed" : publishResult;
4639
4660
  const wallUrl = wallPublishedViaProof ? proofResult.wallUrl : publishResult === "published" ? `${trimmedBaseUrl}/wall` : void 0;
4640
4661
  const wallError = proofResult.wallPublishError;
4662
+ const scanSpecificUnlockUrl = proofResult.status === "uploaded" && proofResult.reportUrl ? proofResult.reportUrl : void 0;
4663
+ const unlockUrl = scanSpecificUnlockUrl ?? fixKitUrl;
4641
4664
  if (opts.json) {
4642
- const panicSummary = {
4665
+ const scoreSummary = {
4643
4666
  score: launchReadiness.score,
4644
4667
  band,
4645
4668
  counts: { ...totals },
@@ -4651,7 +4674,7 @@ async function run() {
4651
4674
  },
4652
4675
  scannerVersion: PKG_VERSION,
4653
4676
  detailsLocked: true,
4654
- unlockUrl: fixKitUrl,
4677
+ unlockUrl,
4655
4678
  wall: {
4656
4679
  status: wallStatus,
4657
4680
  ...wallUrl ? { url: wallUrl } : {},
@@ -4661,12 +4684,13 @@ async function run() {
4661
4684
  status: proofResult.status,
4662
4685
  ...proofResult.status === "uploaded" ? {
4663
4686
  url: proofResult.proofUrl,
4664
- resultId: proofResult.id
4687
+ resultId: proofResult.id,
4688
+ reportUrl: proofResult.reportUrl
4665
4689
  } : {},
4666
4690
  ...proofResult.status === "failed" && proofResult.error ? { error: proofResult.error } : {}
4667
4691
  }
4668
4692
  };
4669
- process2.stdout.write(JSON.stringify(panicSummary, null, 2) + "\n");
4693
+ process2.stdout.write(JSON.stringify(scoreSummary, null, 2) + "\n");
4670
4694
  return totals.critical > 0 ? 1 : 0;
4671
4695
  }
4672
4696
  const bandColor = band === "no_go" ? c.red : band === "fix_first" ? c.yellow : band === "verify_before_launch" ? c.blue : c.green;
@@ -4708,26 +4732,51 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
4708
4732
  `)
4709
4733
  );
4710
4734
  if (totals.critical + totals.high + totals.medium + totals.lower > 0) {
4711
- process2.stdout.write(
4712
- c.bold(
4713
- "Unlock the Launch Fix Kit to see exactly what's broken and how to fix it:\n"
4714
- )
4715
- );
4716
- process2.stdout.write(` ${c.cyan(fixKitUrl)}
4717
-
4735
+ if (scanSpecificUnlockUrl) {
4736
+ process2.stdout.write(c.bold("Unlock this exact scan:\n"));
4737
+ process2.stdout.write(` ${c.cyan(scanSpecificUnlockUrl)}
4738
+ `);
4739
+ process2.stdout.write(
4740
+ c.dim(
4741
+ " Full findings, file evidence, and AI-builder prompts stay locked until purchase.\n\n"
4742
+ )
4743
+ );
4744
+ } else {
4745
+ process2.stdout.write(c.bold("Full findings are locked:\n"));
4746
+ process2.stdout.write(` ${c.cyan(fixKitUrl)}
4718
4747
  `);
4748
+ process2.stdout.write(
4749
+ c.dim(
4750
+ " For a scan-specific Fix Kit link, rerun: npx shippingszn@latest --proof\n\n"
4751
+ )
4752
+ );
4753
+ }
4719
4754
  } else {
4720
- process2.stdout.write(
4721
- c.green(
4722
- "No findings detected. The Launch Fix Kit covers the 27 owner-verify areas the scanner can't reach:\n"
4723
- )
4724
- );
4725
- process2.stdout.write(` ${c.cyan(fixKitUrl)}
4755
+ if (scanSpecificUnlockUrl) {
4756
+ process2.stdout.write(
4757
+ c.green("No automated findings detected for this scan.\n")
4758
+ );
4759
+ process2.stdout.write(c.bold("Unlock the owner-verification Fix Kit:\n"));
4760
+ process2.stdout.write(` ${c.cyan(scanSpecificUnlockUrl)}
4726
4761
 
4727
4762
  `);
4763
+ } else {
4764
+ process2.stdout.write(
4765
+ c.green(
4766
+ "No automated findings detected. The Launch Fix Kit covers the 27 owner-verify areas the scanner can't reach:\n"
4767
+ )
4768
+ );
4769
+ process2.stdout.write(` ${c.cyan(fixKitUrl)}
4770
+ `);
4771
+ process2.stdout.write(
4772
+ c.dim(
4773
+ " For a scan-specific Fix Kit link, rerun: npx shippingszn@latest --proof\n\n"
4774
+ )
4775
+ );
4776
+ }
4728
4777
  }
4729
4778
  if (proofResult.status === "uploaded" && proofResult.proofUrl) {
4730
- process2.stdout.write(c.dim(`Proof URL: ${proofResult.proofUrl}
4779
+ process2.stdout.write(c.dim(`Share score proof: ${proofResult.proofUrl}
4731
4780
  `));
4732
4781
  } else if (proofResult.status === "failed") {
4733
4782
  process2.stdout.write(
@@ -4737,8 +4786,7 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
4737
4786
  }
4738
4787
  if (publishResult === "published") {
4739
4788
  process2.stdout.write(
4740
- c.dim(`(Anonymous score posted to ${trimmedBaseUrl}/wall)
4741
- `)
4789
+ c.dim("(Anonymous aggregate score sent to shippingszn.)\n")
4742
4790
  );
4743
4791
  }
4744
4792
  if (totals.critical > 0) {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "shippingszn",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "Read-only CLI scanner that checks a project for common pre-launch issues from the shippingszn.com launch checklist.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
- "shippingszn": "./dist/index.js"
8
+ "shippingszn": "dist/index.js"
9
9
  },
10
10
  "files": [
11
11
  "dist",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "repository": {
32
32
  "type": "git",
33
- "url": "https://github.com/keeptahoeblueish/shippingszn-cli.git"
33
+ "url": "git+https://github.com/keeptahoeblueish/shippingszn-cli.git"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=18"