@krawlup/cli 1.2.1 → 1.2.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/CHANGELOG.md +17 -0
  2. package/dist/entry.js +221 -47
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to `@krawlup/cli` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/) and the package
5
5
  uses [semantic versioning](https://semver.org/).
6
6
 
7
+ ## 1.2.2 - 2026-11-03
8
+
9
+ ### Added
10
+
11
+ - **Colored output across the CLI.** Battery marks read at a glance —
12
+ ✓ green, ! yellow, ✗ red, – gray — with section titles bold cyan,
13
+ `fix:` values cyan, and summary counts in their own colors. Success
14
+ messages print green, errors red, and the login URL cyan.
15
+ - **Pace-colored loading screens** ("lower is better"): the free-report
16
+ progress bar and elapsed clock run green while a stage is within its
17
+ expected time, yellow up to twice over, red beyond — completed, green.
18
+ - `NO_COLOR` is honored everywhere; `FORCE_COLOR=1`/`FORCE_COLOR=0`
19
+ forces color on or off past the TTY auto-detection. `--json` and
20
+ `--md` output never carry escape codes, and pipes stay byte-clean.
21
+ - The interactive TUI renders battery results as a structured colored
22
+ view (same palette) instead of pasting the plain text render.
23
+
7
24
  ## 1.2.1 - 2026-11-03
8
25
 
9
26
  ### Added
package/dist/entry.js CHANGED
@@ -41209,7 +41209,7 @@ import { join as join2 } from "node:path";
41209
41209
  // package.json
41210
41210
  var package_default = {
41211
41211
  name: "@krawlup/cli",
41212
- version: "1.2.1",
41212
+ version: "1.2.2",
41213
41213
  description: "Official CLI for krawlup — manage products, insights and analytics from the terminal, with a free no-account site battery.",
41214
41214
  license: "MIT",
41215
41215
  author: "Nowshad Hossain Rahat",
@@ -42631,6 +42631,31 @@ function severityTone(severity) {
42631
42631
  }
42632
42632
  }
42633
42633
 
42634
+ // src/checks/verdict.ts
42635
+ var MARKS = {
42636
+ pass: "✓",
42637
+ warn: "!",
42638
+ fail: "✗",
42639
+ unknown: "–"
42640
+ };
42641
+ var STATUS_COLOR = {
42642
+ pass: "green",
42643
+ warn: "yellow",
42644
+ fail: "red",
42645
+ unknown: "gray"
42646
+ };
42647
+ function summarize(sections) {
42648
+ const s = { pass: 0, warn: 0, fail: 0, unknown: 0 };
42649
+ for (const sec of sections) {
42650
+ for (const c of sec.checks)
42651
+ s[c.status]++;
42652
+ }
42653
+ return s;
42654
+ }
42655
+ function exitFor(summary) {
42656
+ return summary.fail > 0 ? 1 : 0;
42657
+ }
42658
+
42634
42659
  // src/ui/components.tsx
42635
42660
  var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
42636
42661
  var SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -42864,6 +42889,110 @@ function IssueLine({ issue: issue2 }) {
42864
42889
  ]
42865
42890
  }, undefined, true, undefined, this);
42866
42891
  }
42892
+ var LOCAL_DETAIL_CAP = 240;
42893
+ function LocalResultView({ result: result2 }) {
42894
+ const s = result2.summary;
42895
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
42896
+ flexDirection: "column",
42897
+ children: [
42898
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42899
+ bold: true,
42900
+ children: result2.title
42901
+ }, undefined, false, undefined, this),
42902
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42903
+ children: [
42904
+ s.fail ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42905
+ color: "red",
42906
+ children: [
42907
+ s.fail,
42908
+ " fail"
42909
+ ]
42910
+ }, "f", true, undefined, this) : null,
42911
+ s.warn ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42912
+ color: "yellow",
42913
+ children: [
42914
+ s.warn,
42915
+ " warn"
42916
+ ]
42917
+ }, "w", true, undefined, this) : null,
42918
+ s.unknown ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42919
+ color: "gray",
42920
+ children: [
42921
+ s.unknown,
42922
+ " unknown"
42923
+ ]
42924
+ }, "u", true, undefined, this) : null,
42925
+ s.pass ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42926
+ color: "green",
42927
+ children: [
42928
+ s.pass,
42929
+ " pass"
42930
+ ]
42931
+ }, "p", true, undefined, this) : null
42932
+ ].filter(Boolean).map((node2, i) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42933
+ children: [
42934
+ i > 0 ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42935
+ dimColor: true,
42936
+ children: " · "
42937
+ }, undefined, false, undefined, this) : null,
42938
+ node2
42939
+ ]
42940
+ }, i, true, undefined, this))
42941
+ }, undefined, false, undefined, this),
42942
+ result2.sections.map((sec) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
42943
+ flexDirection: "column",
42944
+ marginTop: 1,
42945
+ children: [
42946
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42947
+ bold: true,
42948
+ color: "cyan",
42949
+ children: sec.title
42950
+ }, undefined, false, undefined, this),
42951
+ sec.checks.map((ch) => /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
42952
+ flexDirection: "column",
42953
+ children: [
42954
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42955
+ children: [
42956
+ " ",
42957
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42958
+ color: STATUS_COLOR[ch.status],
42959
+ bold: ch.status !== "unknown",
42960
+ children: MARKS[ch.status]
42961
+ }, undefined, false, undefined, this),
42962
+ " ",
42963
+ ch.label.padEnd(18),
42964
+ ch.detail.slice(0, LOCAL_DETAIL_CAP)
42965
+ ]
42966
+ }, undefined, true, undefined, this),
42967
+ ch.fix ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42968
+ children: [
42969
+ " ",
42970
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42971
+ dimColor: true,
42972
+ children: "fix: "
42973
+ }, undefined, false, undefined, this),
42974
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42975
+ color: "cyan",
42976
+ children: ch.fix
42977
+ }, undefined, false, undefined, this)
42978
+ ]
42979
+ }, undefined, true, undefined, this) : null
42980
+ ]
42981
+ }, ch.id, true, undefined, this)),
42982
+ sec.note ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
42983
+ dimColor: true,
42984
+ children: [
42985
+ " ",
42986
+ sec.note.split(`
42987
+ `).map((l) => l.slice(0, LOCAL_DETAIL_CAP)).join(`
42988
+ `)
42989
+ ]
42990
+ }, undefined, true, undefined, this) : null
42991
+ ]
42992
+ }, sec.id, true, undefined, this))
42993
+ ]
42994
+ }, undefined, true, undefined, this);
42995
+ }
42867
42996
 
42868
42997
  // src/ui/live-pane.tsx
42869
42998
  var import_react32 = __toESM(require_react(), 1);
@@ -42963,6 +43092,48 @@ function pickFirstEditAuditId(issues) {
42963
43092
  // src/ui/public-report.tsx
42964
43093
  var import_react31 = __toESM(require_react(), 1);
42965
43094
 
43095
+ // src/lib/ansi.ts
43096
+ var CODES = {
43097
+ green: "\x1B[32m",
43098
+ yellow: "\x1B[33m",
43099
+ red: "\x1B[31m",
43100
+ cyan: "\x1B[36m",
43101
+ gray: "\x1B[90m",
43102
+ dim: "\x1B[2m",
43103
+ bold: "\x1B[1m"
43104
+ };
43105
+ var RESET = "\x1B[0m";
43106
+ function colorEnabled(stream = process.stdout) {
43107
+ if (process.env.FORCE_COLOR === "1" || process.env.FORCE_COLOR === "true")
43108
+ return true;
43109
+ if (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false")
43110
+ return false;
43111
+ if (process.env.NO_COLOR)
43112
+ return false;
43113
+ return !!stream?.isTTY;
43114
+ }
43115
+ function paint(preset, s, enabled) {
43116
+ if (!enabled || s.length === 0)
43117
+ return s;
43118
+ return `${CODES[preset]}${s}${RESET}`;
43119
+ }
43120
+ var green = (s, enabled) => paint("green", s, enabled);
43121
+ var yellow = (s, enabled) => paint("yellow", s, enabled);
43122
+ var red = (s, enabled) => paint("red", s, enabled);
43123
+ var cyan = (s, enabled) => paint("cyan", s, enabled);
43124
+ var gray = (s, enabled) => paint("gray", s, enabled);
43125
+ var dim = (s, enabled) => paint("dim", s, enabled);
43126
+ var bold = (s, enabled) => paint("bold", s, enabled);
43127
+ function paceColor(elapsedSec, expectedSec) {
43128
+ if (expectedSec <= 0)
43129
+ return "green";
43130
+ if (elapsedSec <= expectedSec)
43131
+ return "green";
43132
+ if (elapsedSec <= expectedSec * 2)
43133
+ return "yellow";
43134
+ return "red";
43135
+ }
43136
+
42966
43137
  // src/ui/report-sections.tsx
42967
43138
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
42968
43139
  function safeText(v) {
@@ -43654,15 +43825,16 @@ function fmtClock(sec) {
43654
43825
  const s = Math.max(0, Math.floor(sec));
43655
43826
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
43656
43827
  }
43657
- function Bar({ pct, right, width }) {
43828
+ function Bar({ pct, right, rightTone, tone, width }) {
43658
43829
  const w = Math.max(6, width);
43659
43830
  const filled = Math.round(Math.min(100, Math.max(0, pct)) / 100 * w);
43660
43831
  const bar = "█".repeat(Math.min(filled, w)) + "░".repeat(Math.max(0, w - Math.min(filled, w)));
43832
+ const color = pct >= 100 ? "green" : tone === "yellow" || tone === "red" ? tone : "cyan";
43661
43833
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
43662
43834
  children: [
43663
43835
  " ",
43664
43836
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
43665
- color: pct >= 100 ? "green" : "cyan",
43837
+ color,
43666
43838
  children: bar
43667
43839
  }, undefined, false, undefined, this),
43668
43840
  " ",
@@ -43673,7 +43845,13 @@ function Bar({ pct, right, width }) {
43673
43845
  "%"
43674
43846
  ]
43675
43847
  }, undefined, true, undefined, this),
43676
- right ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
43848
+ right ? rightTone && rightTone !== "green" ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
43849
+ color: rightTone,
43850
+ children: [
43851
+ " ",
43852
+ right
43853
+ ]
43854
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
43677
43855
  dimColor: true,
43678
43856
  children: [
43679
43857
  " ",
@@ -43701,6 +43879,10 @@ function PublicScanProgress({
43701
43879
  const elapsedInStage = (Date.now() - state.stageStartMs) / 1000;
43702
43880
  const barPct = Math.max(state.barPct, stageBarPct(state.stageIdx, elapsedInStage, pacing));
43703
43881
  const cols = useTerminalColumns();
43882
+ const stageExpected = PUBLIC_SCAN_STAGES[state.stageIdx]?.seconds ?? 0;
43883
+ const barTone = state.phase === "done" ? "green" : paceColor(elapsedInStage, stageExpected);
43884
+ const plannedSec = PUBLIC_SCAN_STAGES.slice(0, state.stageIdx + 1).reduce((a, s) => a + s.seconds, 0);
43885
+ const clockTone = state.phase === "done" ? "green" : paceColor(state.elapsedSec, plannedSec);
43704
43886
  const labelW = Math.max(20, Math.min(42, cols - 40));
43705
43887
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
43706
43888
  flexDirection: "column",
@@ -43774,6 +43956,8 @@ function PublicScanProgress({
43774
43956
  children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Bar, {
43775
43957
  pct: barPct,
43776
43958
  right: fmtClock(state.elapsedSec),
43959
+ rightTone: clockTone,
43960
+ tone: barTone,
43777
43961
  width: Math.max(12, Math.min(32, cols - 44))
43778
43962
  }, undefined, false, undefined, this)
43779
43963
  }, undefined, false, undefined, this),
@@ -46303,25 +46487,6 @@ function defaultSnapshotDir() {
46303
46487
  return path.join(homedir3(), ".krawlup", "snapshots");
46304
46488
  }
46305
46489
 
46306
- // src/checks/verdict.ts
46307
- var MARKS = {
46308
- pass: "✓",
46309
- warn: "!",
46310
- fail: "✗",
46311
- unknown: "–"
46312
- };
46313
- function summarize(sections) {
46314
- const s = { pass: 0, warn: 0, fail: 0, unknown: 0 };
46315
- for (const sec of sections) {
46316
- for (const c of sec.checks)
46317
- s[c.status]++;
46318
- }
46319
- return s;
46320
- }
46321
- function exitFor(summary) {
46322
- return summary.fail > 0 ? 1 : 0;
46323
- }
46324
-
46325
46490
  // src/commands/local.ts
46326
46491
  class UsageError extends Error {
46327
46492
  }
@@ -46628,31 +46793,37 @@ async function runLocalCommand(cmd, args, io = {}) {
46628
46793
  }
46629
46794
  }
46630
46795
  var DETAIL_CAP = 240;
46631
- function renderLocalText(r) {
46796
+ function renderLocalText(r, opts) {
46797
+ const on = opts?.color ?? colorEnabled();
46632
46798
  const out = [];
46633
- out.push(r.title);
46799
+ out.push(bold(r.title, on));
46634
46800
  out.push([
46635
- r.summary.fail ? `${r.summary.fail} fail` : null,
46636
- r.summary.warn ? `${r.summary.warn} warn` : null,
46637
- r.summary.unknown ? `${r.summary.unknown} unknown` : null,
46638
- r.summary.pass ? `${r.summary.pass} pass` : null
46639
- ].filter(Boolean).join(" · "));
46801
+ r.summary.fail ? red(`${r.summary.fail} fail`, on) : null,
46802
+ r.summary.warn ? yellow(`${r.summary.warn} warn`, on) : null,
46803
+ r.summary.unknown ? gray(`${r.summary.unknown} unknown`, on) : null,
46804
+ r.summary.pass ? green(`${r.summary.pass} pass`, on) : null
46805
+ ].filter(Boolean).join(dim(" · ", on)));
46640
46806
  for (const sec of r.sections) {
46641
46807
  out.push("");
46642
- out.push(sec.title);
46643
- for (const c of sec.checks) {
46644
- out.push(` ${MARKS[c.status]} ${c.label.padEnd(18)}${c.detail.slice(0, DETAIL_CAP)}`);
46645
- if (c.fix)
46646
- out.push(` fix: ${c.fix}`);
46808
+ out.push(bold(cyan(sec.title, on), on));
46809
+ for (const ch of sec.checks) {
46810
+ out.push(` ${mark(ch.status, on)} ${ch.label.padEnd(18)}${ch.detail.slice(0, DETAIL_CAP)}`);
46811
+ if (ch.fix)
46812
+ out.push(` ${dim("fix:", on)} ${cyan(ch.fix, on)}`);
46647
46813
  }
46648
46814
  if (sec.note)
46649
46815
  for (const line of sec.note.split(`
46650
46816
  `))
46651
- out.push(` ${line.slice(0, DETAIL_CAP)}`);
46817
+ out.push(` ${dim(line.slice(0, DETAIL_CAP), on)}`);
46652
46818
  }
46653
46819
  return out.join(`
46654
46820
  `);
46655
46821
  }
46822
+ function mark(status, on) {
46823
+ const preset = STATUS_COLOR[status];
46824
+ const painter = preset === "green" ? green : preset === "yellow" ? yellow : preset === "red" ? red : gray;
46825
+ return painter(MARKS[status], on);
46826
+ }
46656
46827
  function renderLocalJson(r) {
46657
46828
  return JSON.stringify(r, null, 2);
46658
46829
  }
@@ -46670,10 +46841,10 @@ function renderLocalMarkdown(r) {
46670
46841
  for (const sec of r.sections) {
46671
46842
  out.push("", `## ${sec.title}`, "");
46672
46843
  for (const c of sec.checks) {
46673
- const mark = MARKS[c.status];
46844
+ const mark2 = MARKS[c.status];
46674
46845
  const fix = c.fix ? ` Fix: \`${c.fix}\`.` : "";
46675
46846
  const detail = c.detail.replace(/\n/g, " ").slice(0, DETAIL_CAP);
46676
- out.push(`- ${mark} **${c.label}** — ${detail}${/[.!?…]$/.test(detail) ? "" : "."}${fix}`);
46847
+ out.push(`- ${mark2} **${c.label}** — ${detail}${/[.!?…]$/.test(detail) ? "" : "."}${fix}`);
46677
46848
  }
46678
46849
  if (sec.note)
46679
46850
  out.push("", ...sec.note.split(`
@@ -47227,8 +47398,8 @@ function App2() {
47227
47398
  }
47228
47399
  try {
47229
47400
  const result2 = await runLocalCommand(cmd, localArgs);
47230
- push(/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
47231
- children: renderLocalText(result2)
47401
+ push(/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(LocalResultView, {
47402
+ result: result2
47232
47403
  }, undefined, false, undefined, this));
47233
47404
  } catch (e) {
47234
47405
  if (e instanceof UsageError) {
@@ -48151,19 +48322,20 @@ async function argvMode() {
48151
48322
  console.log(helpText(scanConfig.get()));
48152
48323
  return;
48153
48324
  case "login": {
48325
+ const outOn = colorEnabled();
48154
48326
  const out = await runLogin({
48155
48327
  apiBase: args[0],
48156
- onUrl: (url2, opened) => console.log(opened ? `opening your browser — if it did not, sign in at: ${url2}` : `sign in: ${url2}`)
48328
+ onUrl: (url2, opened) => console.log(opened ? `opening your browser — if it did not, sign in at: ${cyan(url2, outOn)}` : `sign in: ${cyan(url2, outOn)}`)
48157
48329
  });
48158
- console.log(`✓ signed in → ${out.api} (session stored at ~/.krawlup/session.json)`);
48330
+ console.log(green("✓", outOn) + ` signed in → ${out.api} (session stored at ~/.krawlup/session.json)`);
48159
48331
  await refreshScanConfig(out.api);
48160
48332
  if (scanConfig.get().betaUnlimited)
48161
- console.log("✓ beta tester — free scans are unlimited on this account");
48333
+ console.log(green("✓", outOn) + " beta tester — free scans are unlimited on this account");
48162
48334
  return;
48163
48335
  }
48164
48336
  case "logout":
48165
48337
  await runLogout();
48166
- console.log("✓ signed out");
48338
+ console.log(green("✓", colorEnabled()) + " signed out");
48167
48339
  return;
48168
48340
  case "report": {
48169
48341
  let initialScanState = function() {
@@ -48346,8 +48518,10 @@ async function argvMode() {
48346
48518
  break;
48347
48519
  case "scan":
48348
48520
  result2 = await scanAction(product, (update2) => {
48349
- if (process.stderr.isTTY)
48350
- console.error(update2.pending ? "⏳ scan running…" : "⏳ scan finished");
48521
+ if (process.stderr.isTTY) {
48522
+ const errOn = colorEnabled(process.stderr);
48523
+ console.error(update2.pending ? "⏳ scan running…" : green("⏳ scan finished", errOn));
48524
+ }
48351
48525
  });
48352
48526
  break;
48353
48527
  case "publish":
@@ -48425,7 +48599,7 @@ ${helpText(scanConfig.get())}`);
48425
48599
  if (argv.includes("--json")) {
48426
48600
  console.log(JSON.stringify({ error: true, message: msg }, null, 2));
48427
48601
  } else {
48428
- console.error(`✗ ${msg}`);
48602
+ console.error(red(`✗ ${msg}`, colorEnabled(process.stderr)));
48429
48603
  }
48430
48604
  process.exitCode = e instanceof ReLoginRequired ? 2 : 1;
48431
48605
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krawlup/cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Official CLI for krawlup — manage products, insights and analytics from the terminal, with a free no-account site battery.",
5
5
  "license": "MIT",
6
6
  "author": "Nowshad Hossain Rahat",