@sonnechasser/ntrp 1.3.8 → 1.3.9
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/dist/index.js +2009 -1826
- package/dist/mcp/server.js +37 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9861,1157 +9861,1474 @@ var init_layout = __esm({
|
|
|
9861
9861
|
}
|
|
9862
9862
|
});
|
|
9863
9863
|
|
|
9864
|
-
// src/ui/
|
|
9864
|
+
// src/ui/slides.ts
|
|
9865
9865
|
import chalk6 from "chalk";
|
|
9866
|
-
function
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
const offset = " ".repeat(Math.max(0, Math.floor((width - maxLogoW) / 2)));
|
|
9874
|
-
console.log();
|
|
9875
|
-
for (const line of logo) console.log(offset + line);
|
|
9876
|
-
console.log();
|
|
9866
|
+
function liveFromVital(vs) {
|
|
9867
|
+
const dollarLine = vs.dollar_value != null && vs.dollar_value > 0 ? `$${vs.dollar_value >= 1e6 ? `${(vs.dollar_value / 1e6).toFixed(1)}M` : vs.dollar_value >= 1e3 ? `${(vs.dollar_value / 1e3).toFixed(0)}K` : vs.dollar_value.toFixed(0)} ${vs.dollar_label ?? ""}`.trim() : void 0;
|
|
9868
|
+
return {
|
|
9869
|
+
formatted: String(Math.round(vs.score)),
|
|
9870
|
+
status: vs.status,
|
|
9871
|
+
dollarLine
|
|
9872
|
+
};
|
|
9877
9873
|
}
|
|
9878
|
-
function
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
const maxLogoW = Math.max(...logo.map((l) => visibleWidth(l)));
|
|
9885
|
-
const logoOffset = " ".repeat(Math.max(0, Math.floor((cardW - maxLogoW) / 2)));
|
|
9886
|
-
console.log();
|
|
9887
|
-
for (const line of logo) console.log(outerPad + logoOffset + line);
|
|
9888
|
-
console.log();
|
|
9889
|
-
const versionTag = ` v${version} `;
|
|
9890
|
-
const gap = Math.max(0, innerW - versionTag.length);
|
|
9891
|
-
const gapL = Math.floor(gap / 2);
|
|
9892
|
-
console.log(
|
|
9893
|
-
outerPad + paint("border", `\u256D${"\u2500".repeat(gapL)}`) + chalk6.dim(versionTag) + paint("border", `${"\u2500".repeat(gap - gapL)}\u256E`)
|
|
9894
|
-
);
|
|
9895
|
-
console.log();
|
|
9874
|
+
function liveFromMetric(m) {
|
|
9875
|
+
return {
|
|
9876
|
+
formatted: m.formatted,
|
|
9877
|
+
status: m.status === "neutral" ? "neutral" : m.status,
|
|
9878
|
+
benchmarkNote: m.benchmark_note
|
|
9879
|
+
};
|
|
9896
9880
|
}
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
"use strict";
|
|
9901
|
-
init_theme();
|
|
9902
|
-
init_layout();
|
|
9903
|
-
LOGO_LINES = [
|
|
9904
|
-
" \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
9905
|
-
" \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
9906
|
-
" \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
9907
|
-
" \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u255D ",
|
|
9908
|
-
" \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
9909
|
-
" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
|
|
9910
|
-
];
|
|
9911
|
-
TAGLINE = "It listens to the pipeline, then names the dollars at risk.";
|
|
9881
|
+
function clearSlideScreen() {
|
|
9882
|
+
if (process.stdout.isTTY) {
|
|
9883
|
+
process.stdout.write("\x1B[2J\x1B[H");
|
|
9912
9884
|
}
|
|
9913
|
-
});
|
|
9914
|
-
|
|
9915
|
-
// src/license/trial-policy.ts
|
|
9916
|
-
function getCheckoutUrl() {
|
|
9917
|
-
return process.env.NTRP_CHECKOUT_URL ?? process.env.NTRP_PURCHASE_URL ?? SIGNUP_CHECKOUT_URL;
|
|
9918
9885
|
}
|
|
9919
|
-
function
|
|
9920
|
-
return
|
|
9886
|
+
function slideCardWidth() {
|
|
9887
|
+
return resolveCardWidth({ min: 64, max: 100, margin: 4 });
|
|
9921
9888
|
}
|
|
9922
|
-
function
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
return {
|
|
9935
|
-
phase: "grace",
|
|
9936
|
-
daysSinceActivation: daysSince,
|
|
9937
|
-
daysUntilLockout: TRIAL_GRACE_END_DAYS - daysSince,
|
|
9938
|
-
trialDaysRemaining: 0,
|
|
9939
|
-
shouldNudge: true
|
|
9940
|
-
};
|
|
9889
|
+
function tonePaint(tone = "accent") {
|
|
9890
|
+
switch (tone) {
|
|
9891
|
+
case "green":
|
|
9892
|
+
return chalk6.hex("#22c55e");
|
|
9893
|
+
case "yellow":
|
|
9894
|
+
return chalk6.hex("#eab308");
|
|
9895
|
+
case "red":
|
|
9896
|
+
return chalk6.hex("#ef4444");
|
|
9897
|
+
case "neutral":
|
|
9898
|
+
return chalk6.dim;
|
|
9899
|
+
default:
|
|
9900
|
+
return (t) => paint("accent", t);
|
|
9941
9901
|
}
|
|
9942
|
-
const trialDaysRemaining = TRIAL_FULL_DAYS - daysSince;
|
|
9943
|
-
return {
|
|
9944
|
-
phase: "active",
|
|
9945
|
-
daysSinceActivation: daysSince,
|
|
9946
|
-
daysUntilLockout: TRIAL_GRACE_END_DAYS - daysSince,
|
|
9947
|
-
trialDaysRemaining,
|
|
9948
|
-
shouldNudge: daysSince >= TRIAL_ACTIVE_NUDGE_FROM_DAY
|
|
9949
|
-
};
|
|
9950
9902
|
}
|
|
9951
|
-
function
|
|
9952
|
-
const
|
|
9953
|
-
|
|
9954
|
-
const
|
|
9955
|
-
|
|
9903
|
+
function renderBarRow(bar, barWidth, labelW) {
|
|
9904
|
+
const fill = Math.max(0, Math.min(barWidth, Math.round(bar.value / 100 * barWidth)));
|
|
9905
|
+
const body = "\u2588".repeat(fill) + "\u2591".repeat(barWidth - fill);
|
|
9906
|
+
const colored = tonePaint(bar.tone)(body);
|
|
9907
|
+
const label = padRight(truncateVisible(bar.label, labelW), labelW);
|
|
9908
|
+
const pct = String(Math.round(bar.value)).padStart(3);
|
|
9909
|
+
return `${label} ${colored} ${chalk6.dim(pct)}`;
|
|
9956
9910
|
}
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9911
|
+
function renderBars(bars, inner) {
|
|
9912
|
+
const labelW = Math.min(18, Math.max(...bars.map((b) => visibleWidth(b.label)), 8));
|
|
9913
|
+
const barWidth = Math.max(8, Math.min(28, inner - labelW - 6));
|
|
9914
|
+
return bars.map((b) => renderBarRow(b, barWidth, labelW));
|
|
9915
|
+
}
|
|
9916
|
+
function renderFunnel(steps, inner) {
|
|
9917
|
+
const maxBar = Math.max(12, Math.min(40, inner - 22));
|
|
9918
|
+
const lines = [];
|
|
9919
|
+
for (const step of steps) {
|
|
9920
|
+
const w = Math.max(2, Math.round(step.widthPct / 100 * maxBar));
|
|
9921
|
+
const bar = paint("accent", "\u2588".repeat(w));
|
|
9922
|
+
const label = truncateVisible(step.label, Math.max(8, inner - maxBar - 8));
|
|
9923
|
+
lines.push(`${padRight(label, Math.min(18, inner - maxBar - 6))} ${bar} ${chalk6.dim(`${step.widthPct}%`)}`);
|
|
9966
9924
|
}
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
// src/license/upgrade-whimsy.ts
|
|
9970
|
-
function daysLabel(d) {
|
|
9971
|
-
return `${d} day${d === 1 ? "" : "s"}`;
|
|
9925
|
+
return lines;
|
|
9972
9926
|
}
|
|
9973
|
-
function
|
|
9974
|
-
|
|
9927
|
+
function renderWaterfall(steps, inner) {
|
|
9928
|
+
const maxAbs = Math.max(...steps.map((s) => Math.abs(s.cumulative)), 1);
|
|
9929
|
+
const barW = Math.max(10, Math.min(28, inner - 28));
|
|
9930
|
+
const lines = [];
|
|
9931
|
+
for (const step of steps) {
|
|
9932
|
+
const fill = Math.max(1, Math.round(Math.abs(step.cumulative) / maxAbs * barW));
|
|
9933
|
+
const bar = step.delta >= 0 ? chalk6.hex("#22c55e")("\u2588".repeat(fill)) : chalk6.hex("#ef4444")("\u2588".repeat(fill));
|
|
9934
|
+
const deltaStr = step.delta > 0 ? `+${step.delta}` : step.delta < 0 ? `${step.delta}` : `${step.delta}`;
|
|
9935
|
+
const deltaPainted = step.delta > 0 ? chalk6.hex("#22c55e")(deltaStr.padStart(5)) : step.delta < 0 ? chalk6.hex("#ef4444")(deltaStr.padStart(5)) : chalk6.dim(deltaStr.padStart(5));
|
|
9936
|
+
const label = padRight(truncateVisible(step.label, 14), 14);
|
|
9937
|
+
lines.push(`${label} ${deltaPainted} ${bar} ${chalk6.dim(`\u2192 ${step.cumulative}`)}`);
|
|
9938
|
+
}
|
|
9939
|
+
return lines;
|
|
9975
9940
|
}
|
|
9976
|
-
function
|
|
9977
|
-
|
|
9941
|
+
function renderLayerStack(layers, inner) {
|
|
9942
|
+
const lines = [];
|
|
9943
|
+
for (let i = 0; i < layers.length; i++) {
|
|
9944
|
+
const layer = layers[i];
|
|
9945
|
+
const marker2 = layer.highlight ? paint("accent", "\u25C6") : chalk6.dim("\u25C7");
|
|
9946
|
+
const text = layer.highlight ? bold(layer.label) : chalk6.dim(layer.label);
|
|
9947
|
+
lines.push(`${marker2} ${truncateVisible(text, inner - 4)}`);
|
|
9948
|
+
if (i < layers.length - 1) {
|
|
9949
|
+
lines.push(chalk6.dim(" \u2502"));
|
|
9950
|
+
}
|
|
9951
|
+
}
|
|
9952
|
+
return lines;
|
|
9978
9953
|
}
|
|
9979
|
-
function
|
|
9980
|
-
|
|
9954
|
+
function renderLevers(levers, inner) {
|
|
9955
|
+
const cell = Math.floor((inner - 9) / 2);
|
|
9956
|
+
const lines = [];
|
|
9957
|
+
lines.push(chalk6.dim("\u250C" + "\u2500".repeat(Math.max(8, cell)) + "\u2510 \u250C" + "\u2500".repeat(Math.max(8, cell)) + "\u2510"));
|
|
9958
|
+
for (let i = 0; i < levers.length; i += 2) {
|
|
9959
|
+
const a = padRight(truncateVisible(levers[i] ?? "", cell - 2), cell - 2);
|
|
9960
|
+
const b = padRight(truncateVisible(levers[i + 1] ?? "", cell - 2), cell - 2);
|
|
9961
|
+
lines.push(
|
|
9962
|
+
`${paint("accent", "\u2502")} ${a} ${paint("accent", "\u2502")} ${paint("accent", "\u2502")} ${b} ${paint("accent", "\u2502")}`
|
|
9963
|
+
);
|
|
9964
|
+
}
|
|
9965
|
+
lines.push(chalk6.dim("\u2514" + "\u2500".repeat(Math.max(8, cell)) + "\u2518 \u2514" + "\u2500".repeat(Math.max(8, cell)) + "\u2518"));
|
|
9966
|
+
return lines;
|
|
9981
9967
|
}
|
|
9982
|
-
function
|
|
9983
|
-
|
|
9968
|
+
function renderGauge(score, inner, status) {
|
|
9969
|
+
const st = status ?? (score >= 80 ? "green" : score >= 60 ? "yellow" : "red");
|
|
9970
|
+
const bar = scoreBar(score, st === "neutral" ? "yellow" : st, Math.min(28, inner - 12));
|
|
9971
|
+
return [`${statusDot(st)} ${bar} ${bold(String(Math.round(score)))}`];
|
|
9984
9972
|
}
|
|
9985
|
-
function
|
|
9986
|
-
return
|
|
9973
|
+
function renderSplit(bars, inner) {
|
|
9974
|
+
if (bars.length < 2) return renderBars(bars, inner);
|
|
9975
|
+
const total = bars.reduce((s, b) => s + b.value, 0) || 100;
|
|
9976
|
+
const width = Math.max(16, Math.min(40, inner - 4));
|
|
9977
|
+
let used = 0;
|
|
9978
|
+
const parts = [];
|
|
9979
|
+
for (let i = 0; i < bars.length; i++) {
|
|
9980
|
+
const b = bars[i];
|
|
9981
|
+
const w = i === bars.length - 1 ? width - used : Math.max(1, Math.round(b.value / total * width));
|
|
9982
|
+
used += w;
|
|
9983
|
+
parts.push(tonePaint(b.tone)("\u2588".repeat(w)));
|
|
9984
|
+
}
|
|
9985
|
+
const legend = bars.map((b) => `${tonePaint(b.tone)("\u25CF")} ${b.label} ${chalk6.dim(`${Math.round(b.value)}%`)}`).join(" ");
|
|
9986
|
+
return [parts.join(""), truncateVisible(legend, inner)];
|
|
9987
9987
|
}
|
|
9988
|
-
function
|
|
9989
|
-
|
|
9988
|
+
function renderVisual(visual, inner) {
|
|
9989
|
+
const lines = [];
|
|
9990
|
+
switch (visual.kind) {
|
|
9991
|
+
case "bars":
|
|
9992
|
+
if (visual.bars?.length) lines.push(...renderBars(visual.bars, inner));
|
|
9993
|
+
break;
|
|
9994
|
+
case "funnel":
|
|
9995
|
+
if (visual.funnel?.length) lines.push(...renderFunnel(visual.funnel, inner));
|
|
9996
|
+
break;
|
|
9997
|
+
case "waterfall":
|
|
9998
|
+
if (visual.waterfall?.length) lines.push(...renderWaterfall(visual.waterfall, inner));
|
|
9999
|
+
break;
|
|
10000
|
+
case "layer_stack":
|
|
10001
|
+
if (visual.layers?.length) lines.push(...renderLayerStack(visual.layers, inner));
|
|
10002
|
+
break;
|
|
10003
|
+
case "levers":
|
|
10004
|
+
if (visual.levers?.length) lines.push(...renderLevers(visual.levers, inner));
|
|
10005
|
+
break;
|
|
10006
|
+
case "gauge":
|
|
10007
|
+
lines.push(...renderGauge(visual.gauge ?? 50, inner));
|
|
10008
|
+
if (visual.bars?.length) lines.push(...renderBars(visual.bars, inner));
|
|
10009
|
+
break;
|
|
10010
|
+
case "split":
|
|
10011
|
+
if (visual.bars?.length) lines.push(...renderSplit(visual.bars, inner));
|
|
10012
|
+
break;
|
|
10013
|
+
case "none":
|
|
10014
|
+
default:
|
|
10015
|
+
break;
|
|
10016
|
+
}
|
|
10017
|
+
if (visual.caption) {
|
|
10018
|
+
lines.push(chalk6.dim(truncateVisible(visual.caption, inner)));
|
|
10019
|
+
}
|
|
10020
|
+
return lines;
|
|
9990
10021
|
}
|
|
9991
|
-
function
|
|
9992
|
-
|
|
10022
|
+
function kindBadge(explainer) {
|
|
10023
|
+
if (explainer.kind === "vital") return badge("VITAL", "accent");
|
|
10024
|
+
return badge("SAAS", "info");
|
|
9993
10025
|
}
|
|
9994
|
-
function
|
|
9995
|
-
|
|
10026
|
+
function statusToTone(status) {
|
|
10027
|
+
if (status === "green") return "success";
|
|
10028
|
+
if (status === "yellow") return "warning";
|
|
10029
|
+
if (status === "red") return "error";
|
|
10030
|
+
return "muted";
|
|
9996
10031
|
}
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
(
|
|
10010
|
-
(
|
|
10011
|
-
(d) => `You are in extra time. ${daysLabel(d)} left. Type /upgrade.`,
|
|
10012
|
-
(d) => `${daysLabel(d)} left before trial end. Type /upgrade.`,
|
|
10013
|
-
(d) => `Access continues for ${daysLabel(d)}. Type /upgrade to stay.`,
|
|
10014
|
-
(d) => `Trial grace: ${daysLabel(d)} left. Type /upgrade.`,
|
|
10015
|
-
(d) => `Decide in ${daysLabel(d)}. Type /upgrade to continue.`,
|
|
10016
|
-
(d) => `Last notice: ${daysLabel(d)} of grace left. Type /upgrade.`
|
|
10017
|
-
];
|
|
10018
|
-
ACTIVE_TRIAL_NUDGES = [
|
|
10019
|
-
(d) => `Your trial has ${daysLabel(d)} left. Type /upgrade to continue.`,
|
|
10020
|
-
(d) => `Trial status: ${daysLabel(d)} left. Type /upgrade when you decide.`,
|
|
10021
|
-
(d) => `${daysLabel(d)} left on the trial. Type /upgrade to keep access.`,
|
|
10022
|
-
(d) => `The trial ends in ${daysLabel(d)}. Type /upgrade to continue.`,
|
|
10023
|
-
(d) => `You have ${daysLabel(d)} of trial time left. Type /upgrade to continue.`
|
|
10024
|
-
];
|
|
10025
|
-
CUTOFF_NUDGES = [
|
|
10026
|
-
"The trial has ended. Type /upgrade to continue.",
|
|
10027
|
-
"Access is paused. Type /upgrade to resume.",
|
|
10028
|
-
"The trial is over. Type /upgrade to restore access.",
|
|
10029
|
-
"Trial end. Type /upgrade to continue.",
|
|
10030
|
-
"NTRP is paused. Type /upgrade first.",
|
|
10031
|
-
"The free trial has ended. Type /upgrade.",
|
|
10032
|
-
"Access is closed. Type /upgrade to open it."
|
|
10033
|
-
];
|
|
10034
|
-
BLOCKED_WHILE_CUTOFF = [
|
|
10035
|
-
"This command needs Pro. Type /upgrade first.",
|
|
10036
|
-
"Access is paused. Type /upgrade. Then try again.",
|
|
10037
|
-
"This action is not available after trial end. Type /upgrade.",
|
|
10038
|
-
"Pro is required for this command. Type /upgrade."
|
|
10039
|
-
];
|
|
10040
|
-
UPGRADE_HEADLINES = [
|
|
10041
|
-
() => "Upgrade to Pro",
|
|
10042
|
-
() => "Trial status",
|
|
10043
|
-
(d) => d !== void 0 ? `${daysLabel(d)} left` : "Upgrade required",
|
|
10044
|
-
() => "Continue with Pro",
|
|
10045
|
-
() => "Pro upgrade",
|
|
10046
|
-
() => "Stay on Pro"
|
|
10047
|
-
];
|
|
10048
|
-
UPGRADE_SUBTITLES = [
|
|
10049
|
-
(r) => r === "expired" ? "The trial has ended. Complete checkout. Then paste your key." : r === "grace" ? `You had ${TRIAL_FULL_DAYS} trial days plus extra time. Type /upgrade to continue.` : "Complete checkout. Then paste your key.",
|
|
10050
|
-
(r) => r === "expired" ? "Your data is unchanged. You need a Pro key." : r === "grace" ? "The grace period is limited. Type /upgrade to stay." : "No call is required. Paste the key from your email.",
|
|
10051
|
-
(r) => r === "expired" ? "Your work is still here. Paste a Pro key to continue." : r === "grace" ? "Stay if you want Pro. Paste a key after checkout." : "Checkout takes about one minute."
|
|
10052
|
-
];
|
|
10053
|
-
PRO_ACTIVATED_LINES = [
|
|
10054
|
-
"Pro is active. Continue your work.",
|
|
10055
|
-
"License activated. Continue.",
|
|
10056
|
-
"Pro is ready. Continue from the last step.",
|
|
10057
|
-
"Activation complete. Continue.",
|
|
10058
|
-
"License stored. Continue.",
|
|
10059
|
-
"You are on Pro. Continue."
|
|
10032
|
+
function pushWrapped(out, text, inner, indent = "") {
|
|
10033
|
+
for (const w of wrapWords(text, inner - indent.length)) {
|
|
10034
|
+
out.push(indent + w);
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
function buildSlideContent(explainer, opts = {}) {
|
|
10038
|
+
const width = slideCardWidth();
|
|
10039
|
+
const inner = width - 4;
|
|
10040
|
+
const lines = [];
|
|
10041
|
+
const title = opts.titleOverride ?? (explainer ? explainer.label : "Metrics");
|
|
10042
|
+
if (explainer) {
|
|
10043
|
+
const headerBits = [
|
|
10044
|
+
kindBadge(explainer),
|
|
10045
|
+
chalk6.dim(explainer.group)
|
|
10060
10046
|
];
|
|
10047
|
+
if (opts.index != null && opts.total != null) {
|
|
10048
|
+
headerBits.push(chalk6.dim(`slide ${opts.index}/${opts.total}`));
|
|
10049
|
+
}
|
|
10050
|
+
lines.push(headerBits.join(chalk6.dim(" \xB7 ")));
|
|
10051
|
+
lines.push(chalk6.dim(explainer.tagline));
|
|
10052
|
+
lines.push("");
|
|
10053
|
+
if (opts.live) {
|
|
10054
|
+
const live = opts.live;
|
|
10055
|
+
const tone = statusToTone(live.status);
|
|
10056
|
+
const liveLine = `${statusDot(live.status)} ${bold("Your reading:")} ${bold(live.formatted)} ` + badge(String(live.status), tone);
|
|
10057
|
+
lines.push(truncateVisible(liveLine, inner));
|
|
10058
|
+
if (live.dollarLine) {
|
|
10059
|
+
lines.push(chalk6.dim(` $ ${live.dollarLine}`));
|
|
10060
|
+
}
|
|
10061
|
+
if (live.benchmarkNote) {
|
|
10062
|
+
lines.push(chalk6.dim(` ${live.benchmarkNote}`));
|
|
10063
|
+
}
|
|
10064
|
+
lines.push("");
|
|
10065
|
+
} else {
|
|
10066
|
+
const hint = explainer.benchmarkHint?.(opts.motion);
|
|
10067
|
+
if (hint) {
|
|
10068
|
+
lines.push(chalk6.dim(`Benchmark \xB7 ${hint}`));
|
|
10069
|
+
lines.push("");
|
|
10070
|
+
}
|
|
10071
|
+
}
|
|
10072
|
+
const visual = opts.visualOverride ?? explainer.visual;
|
|
10073
|
+
const visLines = renderVisual(visual, inner);
|
|
10074
|
+
if (visLines.length) {
|
|
10075
|
+
lines.push(...visLines);
|
|
10076
|
+
lines.push("");
|
|
10077
|
+
}
|
|
10078
|
+
lines.push(sectionHeading("What it means"));
|
|
10079
|
+
pushWrapped(lines, explainer.meaning, inner, " ");
|
|
10080
|
+
lines.push("");
|
|
10081
|
+
if (!opts.skipFormula) {
|
|
10082
|
+
lines.push(sectionHeading("How it's calculated"));
|
|
10083
|
+
pushWrapped(lines, explainer.how_computed, inner, " ");
|
|
10084
|
+
for (const f of explainer.formula_lines) {
|
|
10085
|
+
lines.push(paint("accent", ` ${f}`));
|
|
10086
|
+
}
|
|
10087
|
+
lines.push("");
|
|
10088
|
+
}
|
|
10089
|
+
if (opts.deepdive) {
|
|
10090
|
+
lines.push(sectionHeading("Deep dive"));
|
|
10091
|
+
pushWrapped(lines, explainer.expert_read, inner, " ");
|
|
10092
|
+
lines.push("");
|
|
10093
|
+
for (const bullet of explainer.deepdive) {
|
|
10094
|
+
pushWrapped(lines, `\xB7 ${bullet}`, inner, " ");
|
|
10095
|
+
}
|
|
10096
|
+
if (explainer.play_id) {
|
|
10097
|
+
lines.push("");
|
|
10098
|
+
lines.push(
|
|
10099
|
+
chalk6.dim(" Play: ") + paint("accent", explainer.play_id)
|
|
10100
|
+
);
|
|
10101
|
+
}
|
|
10102
|
+
lines.push("");
|
|
10103
|
+
}
|
|
10104
|
+
} else if (opts.visualOverride) {
|
|
10105
|
+
const visLines = renderVisual(opts.visualOverride, inner);
|
|
10106
|
+
if (visLines.length) {
|
|
10107
|
+
lines.push(...visLines);
|
|
10108
|
+
lines.push("");
|
|
10109
|
+
}
|
|
10061
10110
|
}
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
key = key.replace(/^\[>\s*/, "");
|
|
10068
|
-
key = key.replace(/^\[\s*▶\s*/, "");
|
|
10069
|
-
key = key.replace(/^▶\s*/, "");
|
|
10070
|
-
key = key.replace(/\s+/g, "");
|
|
10071
|
-
if (NTRP_PREFIX.test(key)) {
|
|
10072
|
-
return `NTRP-${key.replace(/^NTRP-/i, "").toLowerCase()}`;
|
|
10111
|
+
if (opts.extraLines?.length) {
|
|
10112
|
+
for (const line of opts.extraLines) {
|
|
10113
|
+
if (line === "") lines.push("");
|
|
10114
|
+
else pushWrapped(lines, line, inner);
|
|
10115
|
+
}
|
|
10073
10116
|
}
|
|
10074
|
-
|
|
10075
|
-
return key;
|
|
10076
|
-
}
|
|
10077
|
-
function detectLicenseFormat(key) {
|
|
10078
|
-
if (NTRP_PREFIX.test(key)) return "ntrp";
|
|
10079
|
-
if (UUID_KEY.test(key)) return "lemonsqueezy";
|
|
10080
|
-
return "unknown";
|
|
10117
|
+
return { title, lines, width, inner };
|
|
10081
10118
|
}
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
"
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10119
|
+
function renderMetricSlide(explainer, opts = {}) {
|
|
10120
|
+
const { title, lines, width, inner } = buildSlideContent(explainer, opts);
|
|
10121
|
+
const border = (s) => paint("border", s);
|
|
10122
|
+
const termW = termWidth();
|
|
10123
|
+
const outerPad = " ".repeat(Math.max(0, Math.floor((termW - width) / 2)));
|
|
10124
|
+
const out = [];
|
|
10125
|
+
out.push("");
|
|
10126
|
+
out.push(`${outerPad}${border(`\u256D${"\u2500".repeat(width - 2)}\u256E`)}`);
|
|
10127
|
+
out.push(
|
|
10128
|
+
`${outerPad}${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`
|
|
10129
|
+
);
|
|
10130
|
+
out.push(`${outerPad}${border(`\u251C${"\u2500".repeat(width - 2)}\u2524`)}`);
|
|
10131
|
+
for (const row of lines) {
|
|
10132
|
+
out.push(
|
|
10133
|
+
`${outerPad}${border("\u2502 ")}${padRight(truncateVisible(row, inner), inner)}${border(" \u2502")}`
|
|
10134
|
+
);
|
|
10088
10135
|
}
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
message
|
|
10099
|
-
};
|
|
10136
|
+
const footer = opts.footer ?? (opts.deepdive ? `\u23CE next \xB7 b back \xB7 q quit` : `\u23CE next \xB7 /deepdive more \xB7 b back \xB7 q quit`);
|
|
10137
|
+
out.push(`${outerPad}${border(`\u251C${"\u2500".repeat(width - 2)}\u2524`)}`);
|
|
10138
|
+
out.push(
|
|
10139
|
+
`${outerPad}${border("\u2502 ")}${padRight(chalk6.dim(truncateVisible(footer, inner)), inner)}${border(" \u2502")}`
|
|
10140
|
+
);
|
|
10141
|
+
out.push(`${outerPad}${border(`\u2570${"\u2500".repeat(width - 2)}\u256F`)}`);
|
|
10142
|
+
out.push("");
|
|
10143
|
+
if (opts.asLines) return out;
|
|
10144
|
+
for (const line of out) console.log(line);
|
|
10100
10145
|
}
|
|
10101
|
-
function
|
|
10102
|
-
const
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10146
|
+
function progressDots(index, total) {
|
|
10147
|
+
const parts = [];
|
|
10148
|
+
for (let i = 1; i <= total; i++) {
|
|
10149
|
+
parts.push(i === index ? paint("accent", "\u25CF") : chalk6.dim("\u25CB"));
|
|
10150
|
+
}
|
|
10151
|
+
return parts.join("");
|
|
10106
10152
|
}
|
|
10107
|
-
function
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10153
|
+
function printExplainerCatalogLine(explainer) {
|
|
10154
|
+
const kind = explainer.kind === "vital" ? paint("accent", "vital") : chalk6.dim("saas ");
|
|
10155
|
+
console.log(
|
|
10156
|
+
` ${kind} ${bold(explainer.id.padEnd(20))} ${chalk6.dim(explainer.label)} \u2014 ${chalk6.dim(explainer.tagline)}`
|
|
10157
|
+
);
|
|
10111
10158
|
}
|
|
10112
|
-
function
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
|
|
10159
|
+
function printGuideCatalogLine(slide) {
|
|
10160
|
+
console.log(
|
|
10161
|
+
` ${paint("accent", "how ")} ${bold(slide.id.padEnd(20))} ${chalk6.dim(slide.label)} \u2014 ${chalk6.dim(slide.tagline)}`
|
|
10162
|
+
);
|
|
10116
10163
|
}
|
|
10117
|
-
function
|
|
10118
|
-
const
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
return invalid("License disabled. Contact support or purchase a new license.");
|
|
10124
|
-
}
|
|
10125
|
-
if (error?.toLowerCase().includes("activation limit")) {
|
|
10126
|
-
return invalid(
|
|
10127
|
-
"License activation limit reached. Deactivate an old machine in your Lemon Squeezy account, then try again."
|
|
10128
|
-
);
|
|
10129
|
-
}
|
|
10130
|
-
return invalid(error?.trim() || "Could not activate license key");
|
|
10164
|
+
function printDeepdiveHint(metricId, label) {
|
|
10165
|
+
const name = label ?? metricId;
|
|
10166
|
+
console.log(
|
|
10167
|
+
" " + chalk6.dim("How this number works: ") + paint("accent", `/deepdive ${metricId}`) + chalk6.dim(` \u2014 ${name}`)
|
|
10168
|
+
);
|
|
10169
|
+
console.log();
|
|
10131
10170
|
}
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
body,
|
|
10138
|
-
signal: AbortSignal.timeout(15e3)
|
|
10139
|
-
});
|
|
10140
|
-
const data = await res.json();
|
|
10141
|
-
if (!res.ok && !data.error) {
|
|
10142
|
-
throw new Error(`License server error (${res.status})`);
|
|
10171
|
+
var init_slides = __esm({
|
|
10172
|
+
"src/ui/slides.ts"() {
|
|
10173
|
+
"use strict";
|
|
10174
|
+
init_theme();
|
|
10175
|
+
init_layout();
|
|
10143
10176
|
}
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
return
|
|
10177
|
+
});
|
|
10178
|
+
|
|
10179
|
+
// src/ui/banner.ts
|
|
10180
|
+
import chalk7 from "chalk";
|
|
10181
|
+
function renderLogo() {
|
|
10182
|
+
return LOGO_LINES.map((line, i) => chalk7.hex(GRADIENT[i % GRADIENT.length])(line));
|
|
10150
10183
|
}
|
|
10151
|
-
|
|
10152
|
-
const
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
const edition = editionFromMeta(data.meta);
|
|
10160
|
-
const expiresAt = expiresAtFromKey(data.license_key);
|
|
10161
|
-
const instanceId = data.instance?.id;
|
|
10162
|
-
if (!instanceId) {
|
|
10163
|
-
return invalid("Activation succeeded but no instance id was returned. Try again.");
|
|
10164
|
-
}
|
|
10165
|
-
return {
|
|
10166
|
-
valid: true,
|
|
10167
|
-
edition,
|
|
10168
|
-
expiresAt,
|
|
10169
|
-
message: statusMessage(edition, expiresAt, data.meta),
|
|
10170
|
-
instanceId
|
|
10171
|
-
};
|
|
10184
|
+
function printCenteredLogo() {
|
|
10185
|
+
const width = termWidth();
|
|
10186
|
+
const logo = renderLogo();
|
|
10187
|
+
const maxLogoW = Math.max(...logo.map((l) => visibleWidth(l)));
|
|
10188
|
+
const offset = " ".repeat(Math.max(0, Math.floor((width - maxLogoW) / 2)));
|
|
10189
|
+
console.log();
|
|
10190
|
+
for (const line of logo) console.log(offset + line);
|
|
10191
|
+
console.log();
|
|
10172
10192
|
}
|
|
10173
|
-
|
|
10174
|
-
const
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
const
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10193
|
+
function printReplHeader(version) {
|
|
10194
|
+
const width = termWidth();
|
|
10195
|
+
const cardW = Math.min(width - 2, 120);
|
|
10196
|
+
const innerW = cardW - 2;
|
|
10197
|
+
const outerPad = " ".repeat(Math.max(0, Math.floor((width - cardW) / 2)));
|
|
10198
|
+
const logo = renderLogo();
|
|
10199
|
+
const maxLogoW = Math.max(...logo.map((l) => visibleWidth(l)));
|
|
10200
|
+
const logoOffset = " ".repeat(Math.max(0, Math.floor((cardW - maxLogoW) / 2)));
|
|
10201
|
+
console.log();
|
|
10202
|
+
for (const line of logo) console.log(outerPad + logoOffset + line);
|
|
10203
|
+
console.log();
|
|
10204
|
+
const versionTag = ` v${version} `;
|
|
10205
|
+
const gap = Math.max(0, innerW - versionTag.length);
|
|
10206
|
+
const gapL = Math.floor(gap / 2);
|
|
10207
|
+
console.log(
|
|
10208
|
+
outerPad + paint("border", `\u256D${"\u2500".repeat(gapL)}`) + chalk7.dim(versionTag) + paint("border", `${"\u2500".repeat(gap - gapL)}\u256E`)
|
|
10209
|
+
);
|
|
10210
|
+
console.log();
|
|
10189
10211
|
}
|
|
10190
|
-
var
|
|
10191
|
-
var
|
|
10192
|
-
"src/
|
|
10212
|
+
var LOGO_LINES, TAGLINE;
|
|
10213
|
+
var init_banner = __esm({
|
|
10214
|
+
"src/ui/banner.ts"() {
|
|
10193
10215
|
"use strict";
|
|
10194
|
-
|
|
10216
|
+
init_theme();
|
|
10217
|
+
init_layout();
|
|
10218
|
+
LOGO_LINES = [
|
|
10219
|
+
" \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
10220
|
+
" \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
|
|
10221
|
+
" \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
10222
|
+
" \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u255D ",
|
|
10223
|
+
" \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
10224
|
+
" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
|
|
10225
|
+
];
|
|
10226
|
+
TAGLINE = "It listens to the pipeline, then names the dollars at risk.";
|
|
10195
10227
|
}
|
|
10196
10228
|
});
|
|
10197
10229
|
|
|
10198
|
-
// src/license/
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
const secret2 = process.env.NTRP_SIGNING_SECRET;
|
|
10202
|
-
if (!secret2) return null;
|
|
10203
|
-
return secret2;
|
|
10204
|
-
}
|
|
10205
|
-
function validateLicenseKey(key) {
|
|
10206
|
-
const invalid2 = (msg) => ({
|
|
10207
|
-
valid: false,
|
|
10208
|
-
edition: "trial",
|
|
10209
|
-
expiresAt: null,
|
|
10210
|
-
message: msg
|
|
10211
|
-
});
|
|
10212
|
-
if (!key || !key.startsWith("NTRP-")) {
|
|
10213
|
-
return invalid2("Invalid key format");
|
|
10214
|
-
}
|
|
10215
|
-
const secret2 = signingSecret();
|
|
10216
|
-
if (!secret2) {
|
|
10217
|
-
return invalid2("Invalid key format");
|
|
10218
|
-
}
|
|
10219
|
-
const parts = key.replace("NTRP-", "").split("-");
|
|
10220
|
-
if (parts.length !== 3) {
|
|
10221
|
-
return invalid2("Invalid key format");
|
|
10222
|
-
}
|
|
10223
|
-
const [payload, meta, signature] = parts;
|
|
10224
|
-
const dataToSign = `${payload}-${meta}`;
|
|
10225
|
-
const expectedSig = createHmac2("sha256", secret2).update(dataToSign).digest("hex").slice(0, 8);
|
|
10226
|
-
if (signature !== expectedSig) {
|
|
10227
|
-
return invalid2("Invalid license key");
|
|
10228
|
-
}
|
|
10229
|
-
const editionCode = meta.slice(0, 2);
|
|
10230
|
-
const expiryHex = meta.slice(2);
|
|
10231
|
-
const edition = editionCode === "01" ? "pro" : editionCode === "02" ? "team" : "trial";
|
|
10232
|
-
const expiryTs = parseInt(expiryHex, 16);
|
|
10233
|
-
const expiresAt = expiryTs > 0 ? new Date(expiryTs * 1e3) : null;
|
|
10234
|
-
if (expiresAt && expiresAt < /* @__PURE__ */ new Date() && edition !== "trial") {
|
|
10235
|
-
return invalid2(`License expired on ${expiresAt.toISOString().slice(0, 10)}`);
|
|
10236
|
-
}
|
|
10237
|
-
return {
|
|
10238
|
-
valid: true,
|
|
10239
|
-
edition,
|
|
10240
|
-
expiresAt,
|
|
10241
|
-
message: edition === "trial" ? "trial license" : `${edition} license${expiresAt ? ` (expires ${expiresAt.toISOString().slice(0, 10)})` : " (perpetual)"}`
|
|
10242
|
-
};
|
|
10243
|
-
}
|
|
10244
|
-
function trialActivatedAt() {
|
|
10245
|
-
const stored = getConfigValue("license-activated-at");
|
|
10246
|
-
if (stored) {
|
|
10247
|
-
const parsed = new Date(stored);
|
|
10248
|
-
if (!Number.isNaN(parsed.getTime())) return parsed;
|
|
10249
|
-
}
|
|
10250
|
-
const now2 = /* @__PURE__ */ new Date();
|
|
10251
|
-
setConfigValue("license-activated-at", now2.toISOString());
|
|
10252
|
-
return now2;
|
|
10230
|
+
// src/license/trial-policy.ts
|
|
10231
|
+
function getCheckoutUrl() {
|
|
10232
|
+
return process.env.NTRP_CHECKOUT_URL ?? process.env.NTRP_PURCHASE_URL ?? SIGNUP_CHECKOUT_URL;
|
|
10253
10233
|
}
|
|
10254
|
-
function
|
|
10255
|
-
|
|
10256
|
-
setConfigValue("license-activated-at", (/* @__PURE__ */ new Date()).toISOString());
|
|
10257
|
-
} else {
|
|
10258
|
-
deleteConfigValue("license-activated-at");
|
|
10259
|
-
}
|
|
10234
|
+
function getUpgradeUrl() {
|
|
10235
|
+
return process.env.NTRP_UPGRADE_URL ?? PRO_UPGRADE_CHECKOUT_URL ?? getCheckoutUrl();
|
|
10260
10236
|
}
|
|
10261
|
-
function
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
if (trial.phase === "expired") {
|
|
10237
|
+
function evaluateTrial(activatedAt, now2 = /* @__PURE__ */ new Date()) {
|
|
10238
|
+
const daysSince = Math.floor((now2.getTime() - activatedAt.getTime()) / 864e5);
|
|
10239
|
+
if (daysSince >= TRIAL_GRACE_END_DAYS) {
|
|
10265
10240
|
return {
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
expiresAt: result.expiresAt,
|
|
10269
|
-
message: randomCutoffNudge(),
|
|
10270
|
-
trialPhase: "expired",
|
|
10271
|
-
shouldNudgeUpgrade: false,
|
|
10241
|
+
phase: "expired",
|
|
10242
|
+
daysSinceActivation: daysSince,
|
|
10272
10243
|
daysUntilLockout: 0,
|
|
10273
|
-
trialDaysRemaining: 0
|
|
10244
|
+
trialDaysRemaining: 0,
|
|
10245
|
+
shouldNudge: false
|
|
10274
10246
|
};
|
|
10275
10247
|
}
|
|
10276
|
-
|
|
10277
|
-
return {
|
|
10278
|
-
...result,
|
|
10279
|
-
message,
|
|
10280
|
-
trialPhase: trial.phase,
|
|
10281
|
-
shouldNudgeUpgrade: trial.shouldNudge,
|
|
10282
|
-
daysUntilLockout: trial.daysUntilLockout,
|
|
10283
|
-
trialDaysRemaining: trial.trialDaysRemaining
|
|
10284
|
-
};
|
|
10285
|
-
}
|
|
10286
|
-
function storedLicenseProvider(key) {
|
|
10287
|
-
const configured = getConfigValue("license-provider");
|
|
10288
|
-
if (configured === "ntrp" || configured === "lemonsqueezy") return configured;
|
|
10289
|
-
return detectLicenseFormat(key) === "lemonsqueezy" ? "lemonsqueezy" : "ntrp";
|
|
10290
|
-
}
|
|
10291
|
-
function checkLemonSqueezyLicense(key) {
|
|
10292
|
-
const instanceId = getConfigValue("license-instance-id");
|
|
10293
|
-
if (!instanceId) {
|
|
10248
|
+
if (daysSince >= TRIAL_FULL_DAYS) {
|
|
10294
10249
|
return {
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10250
|
+
phase: "grace",
|
|
10251
|
+
daysSinceActivation: daysSince,
|
|
10252
|
+
daysUntilLockout: TRIAL_GRACE_END_DAYS - daysSince,
|
|
10253
|
+
trialDaysRemaining: 0,
|
|
10254
|
+
shouldNudge: true
|
|
10299
10255
|
};
|
|
10300
10256
|
}
|
|
10301
|
-
const
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10257
|
+
const trialDaysRemaining = TRIAL_FULL_DAYS - daysSince;
|
|
10258
|
+
return {
|
|
10259
|
+
phase: "active",
|
|
10260
|
+
daysSinceActivation: daysSince,
|
|
10261
|
+
daysUntilLockout: TRIAL_GRACE_END_DAYS - daysSince,
|
|
10262
|
+
trialDaysRemaining,
|
|
10263
|
+
shouldNudge: daysSince >= TRIAL_ACTIVE_NUDGE_FROM_DAY
|
|
10307
10264
|
};
|
|
10308
|
-
return applyTrialPolicy(base);
|
|
10309
10265
|
}
|
|
10310
|
-
|
|
10311
|
-
const
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
valid: false,
|
|
10316
|
-
edition: "trial",
|
|
10317
|
-
expiresAt: null,
|
|
10318
|
-
message: "Invalid key format"
|
|
10319
|
-
};
|
|
10320
|
-
}
|
|
10321
|
-
if (format === "ntrp") {
|
|
10322
|
-
const result = validateLicenseKey(key);
|
|
10323
|
-
if (!result.valid) return result;
|
|
10324
|
-
setConfigValue("license-key", key);
|
|
10325
|
-
setConfigValue("license-provider", "ntrp");
|
|
10326
|
-
deleteConfigValue("license-instance-id");
|
|
10327
|
-
deleteConfigValue("license-edition");
|
|
10328
|
-
recordLicenseActivation(result.edition);
|
|
10329
|
-
return checkLicense();
|
|
10330
|
-
}
|
|
10331
|
-
const activated = await activateLemonSqueezyLicense(key);
|
|
10332
|
-
if (!activated.valid || !activated.instanceId) return activated;
|
|
10333
|
-
setConfigValue("license-key", key);
|
|
10334
|
-
setConfigValue("license-provider", "lemonsqueezy");
|
|
10335
|
-
setConfigValue("license-instance-id", activated.instanceId);
|
|
10336
|
-
setConfigValue("license-edition", activated.edition);
|
|
10337
|
-
recordLicenseActivation(activated.edition);
|
|
10338
|
-
return checkLicense();
|
|
10266
|
+
function formatTrialActiveMessage(daysSince) {
|
|
10267
|
+
const daysLeft = TRIAL_FULL_DAYS - daysSince;
|
|
10268
|
+
if (daysLeft <= 0) return "trial license";
|
|
10269
|
+
const dayWord = daysLeft === 1 ? "day" : "days";
|
|
10270
|
+
return `trial license (${daysLeft} ${dayWord} remaining)`;
|
|
10339
10271
|
}
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
if (!result.valid) return result;
|
|
10350
|
-
setConfigValue("license-edition", result.edition);
|
|
10351
|
-
return checkLicense();
|
|
10352
|
-
} catch {
|
|
10353
|
-
return checkLicense();
|
|
10272
|
+
var TRIAL_FULL_DAYS, TRIAL_GRACE_END_DAYS, TRIAL_ACTIVE_NUDGE_FROM_DAY, SIGNUP_CHECKOUT_URL, PRO_UPGRADE_CHECKOUT_URL;
|
|
10273
|
+
var init_trial_policy = __esm({
|
|
10274
|
+
"src/license/trial-policy.ts"() {
|
|
10275
|
+
"use strict";
|
|
10276
|
+
TRIAL_FULL_DAYS = 11;
|
|
10277
|
+
TRIAL_GRACE_END_DAYS = 30;
|
|
10278
|
+
TRIAL_ACTIVE_NUDGE_FROM_DAY = 8;
|
|
10279
|
+
SIGNUP_CHECKOUT_URL = "https://sonnechasser.lemonsqueezy.com/checkout/buy/d62d35a2-a369-4cf5-a88b-328223866b5f";
|
|
10280
|
+
PRO_UPGRADE_CHECKOUT_URL = "https://sonnechasser.lemonsqueezy.com/checkout/buy/3bd1da42-936f-49a6-a6c3-d11eee213884";
|
|
10354
10281
|
}
|
|
10282
|
+
});
|
|
10283
|
+
|
|
10284
|
+
// src/license/upgrade-whimsy.ts
|
|
10285
|
+
function daysLabel(d) {
|
|
10286
|
+
return `${d} day${d === 1 ? "" : "s"}`;
|
|
10355
10287
|
}
|
|
10356
|
-
function
|
|
10357
|
-
|
|
10358
|
-
if (!key) {
|
|
10359
|
-
return {
|
|
10360
|
-
valid: false,
|
|
10361
|
-
edition: "trial",
|
|
10362
|
-
expiresAt: null,
|
|
10363
|
-
message: "No license key found. Run: ntrp activate <key>"
|
|
10364
|
-
};
|
|
10365
|
-
}
|
|
10366
|
-
if (storedLicenseProvider(key) === "lemonsqueezy") {
|
|
10367
|
-
return checkLemonSqueezyLicense(key);
|
|
10368
|
-
}
|
|
10369
|
-
const result = validateLicenseKey(key);
|
|
10370
|
-
if (!result.valid || result.edition !== "trial") {
|
|
10371
|
-
return result;
|
|
10372
|
-
}
|
|
10373
|
-
return applyTrialPolicy(result);
|
|
10288
|
+
function pick(items) {
|
|
10289
|
+
return items[Math.floor(Math.random() * items.length)] ?? items[0];
|
|
10374
10290
|
}
|
|
10375
|
-
|
|
10376
|
-
|
|
10291
|
+
function randomGraceNudge(daysLeft) {
|
|
10292
|
+
return pick(GRACE_NUDGES)(daysLeft);
|
|
10293
|
+
}
|
|
10294
|
+
function randomActiveTrialNudge(daysLeft) {
|
|
10295
|
+
return pick(ACTIVE_TRIAL_NUDGES)(daysLeft);
|
|
10296
|
+
}
|
|
10297
|
+
function randomCutoffNudge() {
|
|
10298
|
+
return pick(CUTOFF_NUDGES);
|
|
10299
|
+
}
|
|
10300
|
+
function randomBlockedNudge() {
|
|
10301
|
+
return pick(BLOCKED_WHILE_CUTOFF);
|
|
10302
|
+
}
|
|
10303
|
+
function randomUpgradeHeadline(daysLeft) {
|
|
10304
|
+
return pick(UPGRADE_HEADLINES)(daysLeft);
|
|
10305
|
+
}
|
|
10306
|
+
function randomUpgradeSubtitle(reason) {
|
|
10307
|
+
return pick(UPGRADE_SUBTITLES)(reason);
|
|
10308
|
+
}
|
|
10309
|
+
function randomProActivatedLine() {
|
|
10310
|
+
return pick(PRO_ACTIVATED_LINES);
|
|
10311
|
+
}
|
|
10312
|
+
var GRACE_NUDGES, ACTIVE_TRIAL_NUDGES, CUTOFF_NUDGES, BLOCKED_WHILE_CUTOFF, UPGRADE_HEADLINES, UPGRADE_SUBTITLES, PRO_ACTIVATED_LINES;
|
|
10313
|
+
var init_upgrade_whimsy = __esm({
|
|
10314
|
+
"src/license/upgrade-whimsy.ts"() {
|
|
10377
10315
|
"use strict";
|
|
10378
|
-
init_store();
|
|
10379
10316
|
init_trial_policy();
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10317
|
+
GRACE_NUDGES = [
|
|
10318
|
+
(d) => `Your trial has ${daysLabel(d)} left. Type /upgrade to continue.`,
|
|
10319
|
+
(d) => `Grace period: ${daysLabel(d)} left. Type /upgrade to stay on Pro.`,
|
|
10320
|
+
(d) => `The trial ended. You have ${daysLabel(d)} of extra time. Type /upgrade.`,
|
|
10321
|
+
(d) => `Status: ${daysLabel(d)} of grace left. Type /upgrade to continue.`,
|
|
10322
|
+
(d) => `You have ${daysLabel(d)} left in the grace period. Type /upgrade.`,
|
|
10323
|
+
(d) => `NTRP will stop after ${daysLabel(d)}. Type /upgrade to continue.`,
|
|
10324
|
+
(d) => `The ${TRIAL_FULL_DAYS}-day trial has ended. Extra time: ${daysLabel(d)}. Type /upgrade.`,
|
|
10325
|
+
(d) => `Grace ends in ${daysLabel(d)}. Type /upgrade to continue.`,
|
|
10326
|
+
(d) => `You are in extra time. ${daysLabel(d)} left. Type /upgrade.`,
|
|
10327
|
+
(d) => `${daysLabel(d)} left before trial end. Type /upgrade.`,
|
|
10328
|
+
(d) => `Access continues for ${daysLabel(d)}. Type /upgrade to stay.`,
|
|
10329
|
+
(d) => `Trial grace: ${daysLabel(d)} left. Type /upgrade.`,
|
|
10330
|
+
(d) => `Decide in ${daysLabel(d)}. Type /upgrade to continue.`,
|
|
10331
|
+
(d) => `Last notice: ${daysLabel(d)} of grace left. Type /upgrade.`
|
|
10332
|
+
];
|
|
10333
|
+
ACTIVE_TRIAL_NUDGES = [
|
|
10334
|
+
(d) => `Your trial has ${daysLabel(d)} left. Type /upgrade to continue.`,
|
|
10335
|
+
(d) => `Trial status: ${daysLabel(d)} left. Type /upgrade when you decide.`,
|
|
10336
|
+
(d) => `${daysLabel(d)} left on the trial. Type /upgrade to keep access.`,
|
|
10337
|
+
(d) => `The trial ends in ${daysLabel(d)}. Type /upgrade to continue.`,
|
|
10338
|
+
(d) => `You have ${daysLabel(d)} of trial time left. Type /upgrade to continue.`
|
|
10339
|
+
];
|
|
10340
|
+
CUTOFF_NUDGES = [
|
|
10341
|
+
"The trial has ended. Type /upgrade to continue.",
|
|
10342
|
+
"Access is paused. Type /upgrade to resume.",
|
|
10343
|
+
"The trial is over. Type /upgrade to restore access.",
|
|
10344
|
+
"Trial end. Type /upgrade to continue.",
|
|
10345
|
+
"NTRP is paused. Type /upgrade first.",
|
|
10346
|
+
"The free trial has ended. Type /upgrade.",
|
|
10347
|
+
"Access is closed. Type /upgrade to open it."
|
|
10348
|
+
];
|
|
10349
|
+
BLOCKED_WHILE_CUTOFF = [
|
|
10350
|
+
"This command needs Pro. Type /upgrade first.",
|
|
10351
|
+
"Access is paused. Type /upgrade. Then try again.",
|
|
10352
|
+
"This action is not available after trial end. Type /upgrade.",
|
|
10353
|
+
"Pro is required for this command. Type /upgrade."
|
|
10354
|
+
];
|
|
10355
|
+
UPGRADE_HEADLINES = [
|
|
10356
|
+
() => "Upgrade to Pro",
|
|
10357
|
+
() => "Trial status",
|
|
10358
|
+
(d) => d !== void 0 ? `${daysLabel(d)} left` : "Upgrade required",
|
|
10359
|
+
() => "Continue with Pro",
|
|
10360
|
+
() => "Pro upgrade",
|
|
10361
|
+
() => "Stay on Pro"
|
|
10362
|
+
];
|
|
10363
|
+
UPGRADE_SUBTITLES = [
|
|
10364
|
+
(r) => r === "expired" ? "The trial has ended. Complete checkout. Then paste your key." : r === "grace" ? `You had ${TRIAL_FULL_DAYS} trial days plus extra time. Type /upgrade to continue.` : "Complete checkout. Then paste your key.",
|
|
10365
|
+
(r) => r === "expired" ? "Your data is unchanged. You need a Pro key." : r === "grace" ? "The grace period is limited. Type /upgrade to stay." : "No call is required. Paste the key from your email.",
|
|
10366
|
+
(r) => r === "expired" ? "Your work is still here. Paste a Pro key to continue." : r === "grace" ? "Stay if you want Pro. Paste a key after checkout." : "Checkout takes about one minute."
|
|
10367
|
+
];
|
|
10368
|
+
PRO_ACTIVATED_LINES = [
|
|
10369
|
+
"Pro is active. Continue your work.",
|
|
10370
|
+
"License activated. Continue.",
|
|
10371
|
+
"Pro is ready. Continue from the last step.",
|
|
10372
|
+
"Activation complete. Continue.",
|
|
10373
|
+
"License stored. Continue.",
|
|
10374
|
+
"You are on Pro. Continue."
|
|
10375
|
+
];
|
|
10383
10376
|
}
|
|
10384
10377
|
});
|
|
10385
10378
|
|
|
10386
|
-
// src/
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
case "win32":
|
|
10399
|
-
cmd = "cmd";
|
|
10400
|
-
args = ["/c", "start", "", url];
|
|
10401
|
-
break;
|
|
10402
|
-
default:
|
|
10403
|
-
cmd = "xdg-open";
|
|
10404
|
-
args = [url];
|
|
10405
|
-
break;
|
|
10406
|
-
}
|
|
10407
|
-
const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
10408
|
-
child.on("error", reject);
|
|
10409
|
-
child.unref();
|
|
10410
|
-
resolve10();
|
|
10411
|
-
});
|
|
10379
|
+
// src/license/normalize.ts
|
|
10380
|
+
function normalizeLicenseKeyInput(raw) {
|
|
10381
|
+
let key = raw.trim();
|
|
10382
|
+
key = key.replace(/^\[>\s*/, "");
|
|
10383
|
+
key = key.replace(/^\[\s*▶\s*/, "");
|
|
10384
|
+
key = key.replace(/^▶\s*/, "");
|
|
10385
|
+
key = key.replace(/\s+/g, "");
|
|
10386
|
+
if (NTRP_PREFIX.test(key)) {
|
|
10387
|
+
return `NTRP-${key.replace(/^NTRP-/i, "").toLowerCase()}`;
|
|
10388
|
+
}
|
|
10389
|
+
if (UUID_KEY.test(key)) return key.toLowerCase();
|
|
10390
|
+
return key;
|
|
10412
10391
|
}
|
|
10413
|
-
|
|
10414
|
-
|
|
10392
|
+
function detectLicenseFormat(key) {
|
|
10393
|
+
if (NTRP_PREFIX.test(key)) return "ntrp";
|
|
10394
|
+
if (UUID_KEY.test(key)) return "lemonsqueezy";
|
|
10395
|
+
return "unknown";
|
|
10396
|
+
}
|
|
10397
|
+
var NTRP_PREFIX, UUID_KEY;
|
|
10398
|
+
var init_normalize = __esm({
|
|
10399
|
+
"src/license/normalize.ts"() {
|
|
10415
10400
|
"use strict";
|
|
10401
|
+
NTRP_PREFIX = /^NTRP-/i;
|
|
10402
|
+
UUID_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10416
10403
|
}
|
|
10417
10404
|
});
|
|
10418
10405
|
|
|
10419
|
-
// src/license/
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
printActiveTrialNudge: () => printActiveTrialNudge,
|
|
10429
|
-
printGraceNudge: () => printGraceNudge,
|
|
10430
|
-
printLicenseBlocked: () => printLicenseBlocked,
|
|
10431
|
-
printTrialNudge: () => printTrialNudge,
|
|
10432
|
-
promptForLicenseKey: () => promptForLicenseKey,
|
|
10433
|
-
promptOpenCheckout: () => promptOpenCheckout,
|
|
10434
|
-
resolveUpgradeReason: () => resolveUpgradeReason,
|
|
10435
|
-
runUpgradeFlow: () => runUpgradeFlow
|
|
10436
|
-
});
|
|
10437
|
-
import chalk7 from "chalk";
|
|
10438
|
-
function checkoutUrlFor(purpose) {
|
|
10439
|
-
return purpose === "upgrade" ? getUpgradeUrl() : getCheckoutUrl();
|
|
10440
|
-
}
|
|
10441
|
-
function isTrialCutoff(lic) {
|
|
10442
|
-
return lic.trialPhase === "expired";
|
|
10443
|
-
}
|
|
10444
|
-
function isTrialGrace(lic) {
|
|
10445
|
-
return lic.trialPhase === "grace";
|
|
10406
|
+
// src/license/lemonsqueezy.ts
|
|
10407
|
+
import { hostname } from "os";
|
|
10408
|
+
function invalid(message) {
|
|
10409
|
+
return {
|
|
10410
|
+
valid: false,
|
|
10411
|
+
edition: "trial",
|
|
10412
|
+
expiresAt: null,
|
|
10413
|
+
message
|
|
10414
|
+
};
|
|
10446
10415
|
}
|
|
10447
|
-
function
|
|
10448
|
-
const
|
|
10449
|
-
|
|
10450
|
-
if (
|
|
10451
|
-
|
|
10452
|
-
} else {
|
|
10453
|
-
console.log(chalk7.red(` A license is required for ${context}.`));
|
|
10454
|
-
console.log(
|
|
10455
|
-
" " + chalk7.dim("Type ") + paint("accent", "/upgrade") + chalk7.dim(" or ") + paint("accent", "/checkout") + chalk7.dim(" to get a license.")
|
|
10456
|
-
);
|
|
10457
|
-
}
|
|
10458
|
-
console.log();
|
|
10416
|
+
function editionFromMeta(meta) {
|
|
10417
|
+
const label = `${meta?.variant_name ?? ""} ${meta?.product_name ?? ""}`.toLowerCase();
|
|
10418
|
+
if (label.includes("trial")) return "trial";
|
|
10419
|
+
if (label.includes("team")) return "team";
|
|
10420
|
+
return "pro";
|
|
10459
10421
|
}
|
|
10460
|
-
function
|
|
10461
|
-
if (!
|
|
10462
|
-
|
|
10463
|
-
|
|
10422
|
+
function expiresAtFromKey(licenseKey) {
|
|
10423
|
+
if (!licenseKey?.expires_at) return null;
|
|
10424
|
+
const parsed = new Date(licenseKey.expires_at);
|
|
10425
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
10464
10426
|
}
|
|
10465
|
-
function
|
|
10466
|
-
|
|
10467
|
-
const
|
|
10468
|
-
|
|
10469
|
-
console.log(" " + chalk7.yellow(randomActiveTrialNudge(daysLeft)));
|
|
10470
|
-
console.log();
|
|
10427
|
+
function statusMessage(edition, expiresAt, meta) {
|
|
10428
|
+
const product = meta?.variant_name || meta?.product_name;
|
|
10429
|
+
const base = product ? `${edition} license (${product})` : `${edition} license`;
|
|
10430
|
+
return expiresAt ? `${base} (expires ${expiresAt.toISOString().slice(0, 10)})` : base;
|
|
10471
10431
|
}
|
|
10472
|
-
function
|
|
10473
|
-
|
|
10474
|
-
if (
|
|
10475
|
-
|
|
10476
|
-
return;
|
|
10432
|
+
function mapLsFailure(error, licenseKey) {
|
|
10433
|
+
const status = licenseKey?.status?.toLowerCase();
|
|
10434
|
+
if (status === "expired") {
|
|
10435
|
+
return invalid("Time's up \u2014 /upgrade and paste your key.");
|
|
10477
10436
|
}
|
|
10478
|
-
if (
|
|
10479
|
-
|
|
10437
|
+
if (status === "disabled") {
|
|
10438
|
+
return invalid("License disabled. Contact support or purchase a new license.");
|
|
10439
|
+
}
|
|
10440
|
+
if (error?.toLowerCase().includes("activation limit")) {
|
|
10441
|
+
return invalid(
|
|
10442
|
+
"License activation limit reached. Deactivate an old machine in your Lemon Squeezy account, then try again."
|
|
10443
|
+
);
|
|
10480
10444
|
}
|
|
10445
|
+
return invalid(error?.trim() || "Could not activate license key");
|
|
10481
10446
|
}
|
|
10482
|
-
function
|
|
10483
|
-
|
|
10484
|
-
}
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
if (!
|
|
10492
|
-
|
|
10493
|
-
return;
|
|
10494
|
-
}
|
|
10495
|
-
const session = createPromptSession(ctx.rl, ctx);
|
|
10496
|
-
try {
|
|
10497
|
-
console.log();
|
|
10498
|
-
await session.askPressEnter("Open checkout in your browser");
|
|
10499
|
-
try {
|
|
10500
|
-
await openInBrowser(url);
|
|
10501
|
-
console.log(" " + chalk7.green("\u2713 Browser opened"));
|
|
10502
|
-
console.log(
|
|
10503
|
-
" " + chalk7.dim(
|
|
10504
|
-
purpose === "upgrade" ? "Complete checkout in your browser. Then paste your Pro key below." : "Complete signup in your browser. Then paste your key below."
|
|
10505
|
-
)
|
|
10506
|
-
);
|
|
10507
|
-
} catch {
|
|
10508
|
-
console.log(" " + chalk7.yellow("Could not open the browser. Copy the URL above."));
|
|
10509
|
-
}
|
|
10510
|
-
console.log();
|
|
10511
|
-
} finally {
|
|
10512
|
-
session.close();
|
|
10447
|
+
async function postLicense(path, fields) {
|
|
10448
|
+
const body = new URLSearchParams(fields);
|
|
10449
|
+
const res = await fetch(`${LICENSE_API}/${path}`, {
|
|
10450
|
+
method: "POST",
|
|
10451
|
+
headers: { Accept: "application/json" },
|
|
10452
|
+
body,
|
|
10453
|
+
signal: AbortSignal.timeout(15e3)
|
|
10454
|
+
});
|
|
10455
|
+
const data = await res.json();
|
|
10456
|
+
if (!res.ok && !data.error) {
|
|
10457
|
+
throw new Error(`License server error (${res.status})`);
|
|
10513
10458
|
}
|
|
10459
|
+
return data;
|
|
10514
10460
|
}
|
|
10515
|
-
|
|
10516
|
-
const
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10461
|
+
function defaultInstanceName() {
|
|
10462
|
+
const host = hostname().replace(/[^\w.-]/g, "-").slice(0, 48) || "machine";
|
|
10463
|
+
const user = (process.env.USER || process.env.USERNAME || "user").replace(/[^\w.-]/g, "-").slice(0, 15);
|
|
10464
|
+
return `ntrp-${host}-${user}`;
|
|
10465
|
+
}
|
|
10466
|
+
async function activateLemonSqueezyLicense(licenseKey, instanceName = defaultInstanceName()) {
|
|
10467
|
+
const data = await postLicense("activate", {
|
|
10468
|
+
license_key: licenseKey,
|
|
10469
|
+
instance_name: instanceName
|
|
10470
|
+
});
|
|
10471
|
+
if (!data.activated) {
|
|
10472
|
+
return mapLsFailure(data.error, data.license_key);
|
|
10522
10473
|
}
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10474
|
+
const edition = editionFromMeta(data.meta);
|
|
10475
|
+
const expiresAt = expiresAtFromKey(data.license_key);
|
|
10476
|
+
const instanceId = data.instance?.id;
|
|
10477
|
+
if (!instanceId) {
|
|
10478
|
+
return invalid("Activation succeeded but no instance id was returned. Try again.");
|
|
10528
10479
|
}
|
|
10529
|
-
|
|
10530
|
-
|
|
10480
|
+
return {
|
|
10481
|
+
valid: true,
|
|
10482
|
+
edition,
|
|
10483
|
+
expiresAt,
|
|
10484
|
+
message: statusMessage(edition, expiresAt, data.meta),
|
|
10485
|
+
instanceId
|
|
10486
|
+
};
|
|
10531
10487
|
}
|
|
10532
|
-
async function
|
|
10533
|
-
const
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
} catch (err) {
|
|
10540
|
-
if (err instanceof Error && err.message === "Cancelled") {
|
|
10541
|
-
console.log(" " + chalk7.dim("Activation cancelled."));
|
|
10542
|
-
return false;
|
|
10543
|
-
}
|
|
10544
|
-
throw err;
|
|
10545
|
-
}
|
|
10546
|
-
if (!key.trim()) {
|
|
10547
|
-
console.log(" " + chalk7.red("A license key is required."));
|
|
10548
|
-
continue;
|
|
10549
|
-
}
|
|
10550
|
-
let result;
|
|
10551
|
-
try {
|
|
10552
|
-
result = await activateLicenseKey(key.trim());
|
|
10553
|
-
} catch (err) {
|
|
10554
|
-
const message = err instanceof Error ? err.message : "License activation failed";
|
|
10555
|
-
console.log(" " + chalk7.red(message));
|
|
10556
|
-
console.log(" " + chalk7.dim("Check your network connection and try again."));
|
|
10557
|
-
console.log();
|
|
10558
|
-
continue;
|
|
10559
|
-
}
|
|
10560
|
-
if (!result.valid) {
|
|
10561
|
-
console.log(" " + chalk7.red(result.message));
|
|
10562
|
-
console.log(
|
|
10563
|
-
" " + chalk7.dim(`Use the key from your purchase email, or try again: ${checkoutUrlFor(purpose)}`)
|
|
10564
|
-
);
|
|
10565
|
-
console.log();
|
|
10566
|
-
continue;
|
|
10567
|
-
}
|
|
10568
|
-
console.log();
|
|
10569
|
-
console.log(chalk7.green(` \u2713 ${randomProActivatedLine()}`));
|
|
10570
|
-
console.log();
|
|
10571
|
-
return true;
|
|
10572
|
-
}
|
|
10573
|
-
} finally {
|
|
10574
|
-
session.close();
|
|
10488
|
+
async function validateLemonSqueezyLicense(licenseKey, instanceId) {
|
|
10489
|
+
const data = await postLicense("validate", {
|
|
10490
|
+
license_key: licenseKey,
|
|
10491
|
+
instance_id: instanceId
|
|
10492
|
+
});
|
|
10493
|
+
if (!data.valid) {
|
|
10494
|
+
return mapLsFailure(data.error, data.license_key);
|
|
10575
10495
|
}
|
|
10496
|
+
const edition = editionFromMeta(data.meta);
|
|
10497
|
+
const expiresAt = expiresAtFromKey(data.license_key);
|
|
10498
|
+
return {
|
|
10499
|
+
valid: true,
|
|
10500
|
+
edition,
|
|
10501
|
+
expiresAt,
|
|
10502
|
+
message: statusMessage(edition, expiresAt, data.meta)
|
|
10503
|
+
};
|
|
10576
10504
|
}
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
console.log(" " + bold(headlineFor(reason, lic)));
|
|
10581
|
-
console.log(" " + chalk7.dim(subtitleFor(reason)));
|
|
10582
|
-
console.log();
|
|
10583
|
-
await promptOpenCheckout(ctx, "upgrade");
|
|
10584
|
-
console.log(" " + chalk7.dim("Paste your license key when it arrives by email."));
|
|
10585
|
-
console.log();
|
|
10586
|
-
return promptForLicenseKey(ctx, "upgrade");
|
|
10587
|
-
}
|
|
10588
|
-
function resolveUpgradeReason() {
|
|
10589
|
-
const lic = checkLicense();
|
|
10590
|
-
if (isTrialCutoff(lic)) return "expired";
|
|
10591
|
-
if (isTrialGrace(lic)) return "grace";
|
|
10592
|
-
return "convert";
|
|
10593
|
-
}
|
|
10594
|
-
function hasStoredLicenseKey() {
|
|
10595
|
-
return Boolean(getConfigValue("license-key"));
|
|
10596
|
-
}
|
|
10597
|
-
var init_upgrade = __esm({
|
|
10598
|
-
"src/license/upgrade.ts"() {
|
|
10505
|
+
var LICENSE_API;
|
|
10506
|
+
var init_lemonsqueezy = __esm({
|
|
10507
|
+
"src/license/lemonsqueezy.ts"() {
|
|
10599
10508
|
"use strict";
|
|
10600
|
-
|
|
10601
|
-
init_store();
|
|
10602
|
-
init_banner();
|
|
10603
|
-
init_theme();
|
|
10604
|
-
init_verify();
|
|
10605
|
-
init_trial_policy();
|
|
10606
|
-
init_upgrade_whimsy();
|
|
10607
|
-
init_open_browser();
|
|
10509
|
+
LICENSE_API = "https://api.lemonsqueezy.com/v1/licenses";
|
|
10608
10510
|
}
|
|
10609
10511
|
});
|
|
10610
10512
|
|
|
10611
|
-
// src/license/
|
|
10612
|
-
import
|
|
10613
|
-
function
|
|
10614
|
-
|
|
10513
|
+
// src/license/verify.ts
|
|
10514
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
10515
|
+
function signingSecret() {
|
|
10516
|
+
const secret2 = process.env.NTRP_SIGNING_SECRET;
|
|
10517
|
+
if (!secret2) return null;
|
|
10518
|
+
return secret2;
|
|
10615
10519
|
}
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
process.exit(1);
|
|
10520
|
+
function validateLicenseKey(key) {
|
|
10521
|
+
const invalid2 = (msg) => ({
|
|
10522
|
+
valid: false,
|
|
10523
|
+
edition: "trial",
|
|
10524
|
+
expiresAt: null,
|
|
10525
|
+
message: msg
|
|
10526
|
+
});
|
|
10527
|
+
if (!key || !key.startsWith("NTRP-")) {
|
|
10528
|
+
return invalid2("Invalid key format");
|
|
10626
10529
|
}
|
|
10627
|
-
const
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
const upgraded = await runUpgradeFlow(ctx, "expired");
|
|
10631
|
-
if (!upgraded) {
|
|
10632
|
-
if (exitOnCancel) exitActivationCancelled();
|
|
10633
|
-
printActivationCancelledStay();
|
|
10634
|
-
return true;
|
|
10635
|
-
}
|
|
10636
|
-
return true;
|
|
10530
|
+
const secret2 = signingSecret();
|
|
10531
|
+
if (!secret2) {
|
|
10532
|
+
return invalid2("Invalid key format");
|
|
10637
10533
|
}
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
|
|
10641
|
-
console.log(" " + chalk8.dim("Paste a trial key or a Pro key. If you do not have a key, NTRP opens signup."));
|
|
10642
|
-
console.log(" " + chalk8.dim("Activating accepts the NTRP license (LICENSE in the install, or ntrp.sonnechasser.com)."));
|
|
10643
|
-
console.log();
|
|
10644
|
-
await promptOpenCheckout(ctx);
|
|
10645
|
-
const activated = await promptForLicenseKey(ctx);
|
|
10646
|
-
if (!activated) {
|
|
10647
|
-
if (exitOnCancel) exitActivationCancelled();
|
|
10648
|
-
printActivationCancelledStay();
|
|
10649
|
-
return true;
|
|
10534
|
+
const parts = key.replace("NTRP-", "").split("-");
|
|
10535
|
+
if (parts.length !== 3) {
|
|
10536
|
+
return invalid2("Invalid key format");
|
|
10650
10537
|
}
|
|
10651
|
-
|
|
10652
|
-
}
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10538
|
+
const [payload, meta, signature] = parts;
|
|
10539
|
+
const dataToSign = `${payload}-${meta}`;
|
|
10540
|
+
const expectedSig = createHmac2("sha256", secret2).update(dataToSign).digest("hex").slice(0, 8);
|
|
10541
|
+
if (signature !== expectedSig) {
|
|
10542
|
+
return invalid2("Invalid license key");
|
|
10543
|
+
}
|
|
10544
|
+
const editionCode = meta.slice(0, 2);
|
|
10545
|
+
const expiryHex = meta.slice(2);
|
|
10546
|
+
const edition = editionCode === "01" ? "pro" : editionCode === "02" ? "team" : "trial";
|
|
10547
|
+
const expiryTs = parseInt(expiryHex, 16);
|
|
10548
|
+
const expiresAt = expiryTs > 0 ? new Date(expiryTs * 1e3) : null;
|
|
10549
|
+
if (expiresAt && expiresAt < /* @__PURE__ */ new Date() && edition !== "trial") {
|
|
10550
|
+
return invalid2(`License expired on ${expiresAt.toISOString().slice(0, 10)}`);
|
|
10551
|
+
}
|
|
10552
|
+
return {
|
|
10553
|
+
valid: true,
|
|
10554
|
+
edition,
|
|
10555
|
+
expiresAt,
|
|
10556
|
+
message: edition === "trial" ? "trial license" : `${edition} license${expiresAt ? ` (expires ${expiresAt.toISOString().slice(0, 10)})` : " (perpetual)"}`
|
|
10557
|
+
};
|
|
10657
10558
|
}
|
|
10658
|
-
function
|
|
10659
|
-
|
|
10660
|
-
|
|
10559
|
+
function trialActivatedAt() {
|
|
10560
|
+
const stored = getConfigValue("license-activated-at");
|
|
10561
|
+
if (stored) {
|
|
10562
|
+
const parsed = new Date(stored);
|
|
10563
|
+
if (!Number.isNaN(parsed.getTime())) return parsed;
|
|
10564
|
+
}
|
|
10565
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
10566
|
+
setConfigValue("license-activated-at", now2.toISOString());
|
|
10567
|
+
return now2;
|
|
10661
10568
|
}
|
|
10662
|
-
|
|
10663
|
-
"
|
|
10664
|
-
"
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
init_verify();
|
|
10668
|
-
init_trial_policy();
|
|
10669
|
-
init_upgrade();
|
|
10569
|
+
function recordLicenseActivation(edition) {
|
|
10570
|
+
if (edition === "trial") {
|
|
10571
|
+
setConfigValue("license-activated-at", (/* @__PURE__ */ new Date()).toISOString());
|
|
10572
|
+
} else {
|
|
10573
|
+
deleteConfigValue("license-activated-at");
|
|
10670
10574
|
}
|
|
10671
|
-
}
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
const scenario = SCENARIOS[key];
|
|
10687
|
-
if (!scenario) {
|
|
10688
|
-
throw new Error(`Unknown scenario: ${key}. Valid: ${Object.keys(SCENARIOS).join(", ")}`);
|
|
10575
|
+
}
|
|
10576
|
+
function applyTrialPolicy(result) {
|
|
10577
|
+
if (result.edition !== "trial") return result;
|
|
10578
|
+
const trial = evaluateTrial(trialActivatedAt());
|
|
10579
|
+
if (trial.phase === "expired") {
|
|
10580
|
+
return {
|
|
10581
|
+
valid: false,
|
|
10582
|
+
edition: "trial",
|
|
10583
|
+
expiresAt: result.expiresAt,
|
|
10584
|
+
message: randomCutoffNudge(),
|
|
10585
|
+
trialPhase: "expired",
|
|
10586
|
+
shouldNudgeUpgrade: false,
|
|
10587
|
+
daysUntilLockout: 0,
|
|
10588
|
+
trialDaysRemaining: 0
|
|
10589
|
+
};
|
|
10689
10590
|
}
|
|
10690
|
-
|
|
10591
|
+
const message = trial.phase === "grace" ? `trial license (grace: ${trial.daysUntilLockout} day${trial.daysUntilLockout === 1 ? "" : "s"} left)` : formatTrialActiveMessage(trial.daysSinceActivation);
|
|
10592
|
+
return {
|
|
10593
|
+
...result,
|
|
10594
|
+
message,
|
|
10595
|
+
trialPhase: trial.phase,
|
|
10596
|
+
shouldNudgeUpgrade: trial.shouldNudge,
|
|
10597
|
+
daysUntilLockout: trial.daysUntilLockout,
|
|
10598
|
+
trialDaysRemaining: trial.trialDaysRemaining
|
|
10599
|
+
};
|
|
10691
10600
|
}
|
|
10692
|
-
function
|
|
10693
|
-
|
|
10601
|
+
function storedLicenseProvider(key) {
|
|
10602
|
+
const configured = getConfigValue("license-provider");
|
|
10603
|
+
if (configured === "ntrp" || configured === "lemonsqueezy") return configured;
|
|
10604
|
+
return detectLicenseFormat(key) === "lemonsqueezy" ? "lemonsqueezy" : "ntrp";
|
|
10694
10605
|
}
|
|
10695
|
-
function
|
|
10696
|
-
const
|
|
10697
|
-
if (!
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10606
|
+
function checkLemonSqueezyLicense(key) {
|
|
10607
|
+
const instanceId = getConfigValue("license-instance-id");
|
|
10608
|
+
if (!instanceId) {
|
|
10609
|
+
return {
|
|
10610
|
+
valid: false,
|
|
10611
|
+
edition: "trial",
|
|
10612
|
+
expiresAt: null,
|
|
10613
|
+
message: "License not activated on this machine. Run: ntrp activate <key>"
|
|
10614
|
+
};
|
|
10701
10615
|
}
|
|
10702
|
-
const
|
|
10703
|
-
|
|
10704
|
-
|
|
10616
|
+
const edition = getConfigValue("license-edition") ?? "pro";
|
|
10617
|
+
const base = {
|
|
10618
|
+
valid: true,
|
|
10619
|
+
edition,
|
|
10620
|
+
expiresAt: null,
|
|
10621
|
+
message: `${edition} license`
|
|
10622
|
+
};
|
|
10623
|
+
return applyTrialPolicy(base);
|
|
10624
|
+
}
|
|
10625
|
+
async function activateLicenseKey(rawKey) {
|
|
10626
|
+
const key = normalizeLicenseKeyInput(rawKey);
|
|
10627
|
+
const format = detectLicenseFormat(key);
|
|
10628
|
+
if (format === "unknown") {
|
|
10629
|
+
return {
|
|
10630
|
+
valid: false,
|
|
10631
|
+
edition: "trial",
|
|
10632
|
+
expiresAt: null,
|
|
10633
|
+
message: "Invalid key format"
|
|
10634
|
+
};
|
|
10705
10635
|
}
|
|
10706
|
-
|
|
10636
|
+
if (format === "ntrp") {
|
|
10637
|
+
const result = validateLicenseKey(key);
|
|
10638
|
+
if (!result.valid) return result;
|
|
10639
|
+
setConfigValue("license-key", key);
|
|
10640
|
+
setConfigValue("license-provider", "ntrp");
|
|
10641
|
+
deleteConfigValue("license-instance-id");
|
|
10642
|
+
deleteConfigValue("license-edition");
|
|
10643
|
+
recordLicenseActivation(result.edition);
|
|
10644
|
+
return checkLicense();
|
|
10645
|
+
}
|
|
10646
|
+
const activated = await activateLemonSqueezyLicense(key);
|
|
10647
|
+
if (!activated.valid || !activated.instanceId) return activated;
|
|
10648
|
+
setConfigValue("license-key", key);
|
|
10649
|
+
setConfigValue("license-provider", "lemonsqueezy");
|
|
10650
|
+
setConfigValue("license-instance-id", activated.instanceId);
|
|
10651
|
+
setConfigValue("license-edition", activated.edition);
|
|
10652
|
+
recordLicenseActivation(activated.edition);
|
|
10653
|
+
return checkLicense();
|
|
10707
10654
|
}
|
|
10708
|
-
function
|
|
10709
|
-
|
|
10655
|
+
async function refreshLicenseOnline() {
|
|
10656
|
+
const key = getConfigValue("license-key");
|
|
10657
|
+
if (!key || storedLicenseProvider(key) !== "lemonsqueezy") {
|
|
10658
|
+
return checkLicense();
|
|
10659
|
+
}
|
|
10660
|
+
const instanceId = getConfigValue("license-instance-id");
|
|
10661
|
+
if (!instanceId) return checkLicense();
|
|
10662
|
+
try {
|
|
10663
|
+
const result = await validateLemonSqueezyLicense(key, instanceId);
|
|
10664
|
+
if (!result.valid) return result;
|
|
10665
|
+
setConfigValue("license-edition", result.edition);
|
|
10666
|
+
return checkLicense();
|
|
10667
|
+
} catch {
|
|
10668
|
+
return checkLicense();
|
|
10669
|
+
}
|
|
10710
10670
|
}
|
|
10711
|
-
function
|
|
10712
|
-
const
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
|
|
10724
|
-
|
|
10725
|
-
|
|
10726
|
-
|
|
10727
|
-
}
|
|
10728
|
-
return
|
|
10671
|
+
function checkLicense() {
|
|
10672
|
+
const key = getConfigValue("license-key");
|
|
10673
|
+
if (!key) {
|
|
10674
|
+
return {
|
|
10675
|
+
valid: false,
|
|
10676
|
+
edition: "trial",
|
|
10677
|
+
expiresAt: null,
|
|
10678
|
+
message: "No license key found. Run: ntrp activate <key>"
|
|
10679
|
+
};
|
|
10680
|
+
}
|
|
10681
|
+
if (storedLicenseProvider(key) === "lemonsqueezy") {
|
|
10682
|
+
return checkLemonSqueezyLicense(key);
|
|
10683
|
+
}
|
|
10684
|
+
const result = validateLicenseKey(key);
|
|
10685
|
+
if (!result.valid || result.edition !== "trial") {
|
|
10686
|
+
return result;
|
|
10687
|
+
}
|
|
10688
|
+
return applyTrialPolicy(result);
|
|
10729
10689
|
}
|
|
10730
|
-
var
|
|
10731
|
-
|
|
10732
|
-
"src/demo/scenarios.ts"() {
|
|
10690
|
+
var init_verify = __esm({
|
|
10691
|
+
"src/license/verify.ts"() {
|
|
10733
10692
|
"use strict";
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
staleContactRatioEnterprise: 0.2,
|
|
10740
|
-
staleContactRatioSmb: 0.1,
|
|
10741
|
-
pastCloseDateRatio: 0.1,
|
|
10742
|
-
staleDays: 120,
|
|
10743
|
-
mqlDropRatio: 0.1,
|
|
10744
|
-
qualifiedNoOutreachRatio: 0.1,
|
|
10745
|
-
stuckDealRatio: 0.1,
|
|
10746
|
-
stuckInNegotiationDays: 45,
|
|
10747
|
-
avgDaysPerStageEnterprise: 25,
|
|
10748
|
-
avgDaysPerStageSmb: 8,
|
|
10749
|
-
activityVolumeMultiplier: 1,
|
|
10750
|
-
noiseActivityRatio: 0.15,
|
|
10751
|
-
singleThreadRatio: 0.2,
|
|
10752
|
-
loneWolfRepIndex: null,
|
|
10753
|
-
loneWolfSingleThreadRatio: 0,
|
|
10754
|
-
freshnessGapDays: 90
|
|
10755
|
-
};
|
|
10756
|
-
SCENARIOS = {
|
|
10757
|
-
hidden_crisis: {
|
|
10758
|
-
...BASELINE,
|
|
10759
|
-
key: "hidden_crisis",
|
|
10760
|
-
label: "The Hidden Crisis",
|
|
10761
|
-
description: "Overall health looks yellow but Enterprise is deep red, masked by strong SMB numbers.",
|
|
10762
|
-
story: "Your aggregate numbers look okay \u2014 but when you break it by segment, Enterprise is dying. 60% of enterprise contacts have gone dark, deals are single-threaded, and SMB is carrying the average.",
|
|
10763
|
-
hook: "SMB is carrying the average while Enterprise dies quietly.",
|
|
10764
|
-
staleContactRatio: 0.3,
|
|
10765
|
-
staleContactRatioEnterprise: 0.6,
|
|
10766
|
-
staleContactRatioSmb: 0.1,
|
|
10767
|
-
singleThreadRatio: 0.5,
|
|
10768
|
-
enterpriseRatio: 0.3,
|
|
10769
|
-
midMarketRatio: 0.3,
|
|
10770
|
-
smbRatio: 0.4
|
|
10771
|
-
},
|
|
10772
|
-
leaky_bucket: {
|
|
10773
|
-
...BASELINE,
|
|
10774
|
-
key: "leaky_bucket",
|
|
10775
|
-
label: "The Leaky Bucket",
|
|
10776
|
-
description: "Marketing generates plenty of leads but 40% vanish at handoff to sales.",
|
|
10777
|
-
story: "Marketing is doing its job \u2014 MQLs are flowing. But 40% of qualified leads never show up in sales workflows. They're falling through the cracks at handoff, and nobody's noticing because marketing reports MQL count and sales reports pipeline value.",
|
|
10778
|
-
hook: "MQLs flow in, then 40% vanish at the sales handoff.",
|
|
10779
|
-
mqlDropRatio: 0.4,
|
|
10780
|
-
qualifiedNoOutreachRatio: 0.35,
|
|
10781
|
-
staleContactRatio: 0.2
|
|
10782
|
-
},
|
|
10783
|
-
stale_pipeline: {
|
|
10784
|
-
...BASELINE,
|
|
10785
|
-
key: "stale_pipeline",
|
|
10786
|
-
label: "The Stale Pipeline",
|
|
10787
|
-
description: "Big pipeline number but half the deals are zombies stuck in late stages.",
|
|
10788
|
-
story: "The pipeline report says $5M. But look closer: half those deals have close dates in the past, 40% are stuck in Negotiation for 120+ days, and nobody's touching them. You're forecasting on fiction.",
|
|
10789
|
-
hook: "Half the pipeline is zombies \u2014 you're forecasting on fiction.",
|
|
10790
|
-
pastCloseDateRatio: 0.5,
|
|
10791
|
-
stuckDealRatio: 0.4,
|
|
10792
|
-
stuckInNegotiationDays: 120,
|
|
10793
|
-
staleContactRatio: 0.25,
|
|
10794
|
-
staleDays: 90
|
|
10795
|
-
},
|
|
10796
|
-
lone_wolf: {
|
|
10797
|
-
...BASELINE,
|
|
10798
|
-
key: "lone_wolf",
|
|
10799
|
-
label: "The Lone Wolf",
|
|
10800
|
-
description: "One rep has great numbers but every single deal is single-threaded.",
|
|
10801
|
-
story: "Your top rep is crushing it on paper \u2014 biggest pipeline, highest close rate. But every deal has exactly one contact. One champion goes on vacation, gets promoted, or leaves, and the entire pipeline collapses.",
|
|
10802
|
-
hook: "Top rep, huge pipeline, one contact per deal \u2014 one exit from collapse.",
|
|
10803
|
-
loneWolfRepIndex: 0,
|
|
10804
|
-
loneWolfSingleThreadRatio: 1,
|
|
10805
|
-
singleThreadRatio: 0.15
|
|
10806
|
-
},
|
|
10807
|
-
busy_bees: {
|
|
10808
|
-
...BASELINE,
|
|
10809
|
-
key: "busy_bees",
|
|
10810
|
-
label: "The Busy Bees",
|
|
10811
|
-
description: "High activity volume across the team, but most of it hits dead ends.",
|
|
10812
|
-
story: "Your team is busy. Activity metrics look great \u2014 calls are up, emails are up, meetings are up. But 60% of that activity is aimed at contacts with no associated pipeline. Reps are spraying, not aiming.",
|
|
10813
|
-
hook: "Reps are spraying, not aiming.",
|
|
10814
|
-
activityVolumeMultiplier: 3,
|
|
10815
|
-
noiseActivityRatio: 0.6,
|
|
10816
|
-
staleContactRatio: 0.2
|
|
10817
|
-
},
|
|
10818
|
-
even_keel: {
|
|
10819
|
-
...BASELINE,
|
|
10820
|
-
key: "even_keel",
|
|
10821
|
-
label: "The Even Keel",
|
|
10822
|
-
description: "A reasonably healthy book \u2014 enough yellow to listen, not a five-alarm fire.",
|
|
10823
|
-
story: "Most numbers sit in a normal band. A few contacts have gone quiet, a handful of deals are slow, activity is mostly on-pipeline. This is what 'fine' looks like on the stethoscope \u2014 useful when you want to evaluate NTRP without a manufactured crisis.",
|
|
10824
|
-
hook: "Reasonably healthy \u2014 enough signal to listen, not a crisis."
|
|
10825
|
-
},
|
|
10826
|
-
compound_pain: {
|
|
10827
|
-
...BASELINE,
|
|
10828
|
-
key: "compound_pain",
|
|
10829
|
-
label: "The Compound Fracture",
|
|
10830
|
-
description: "Several vitals are red at once \u2014 stale pipeline, leaky handoff, noisy activity, thin threads.",
|
|
10831
|
-
story: "This isn't one problem. Enterprise contacts have gone dark, MQLs vanish at handoff, late-stage deals are zombies, and a lot of activity never touches pipeline. The gating logic has to pick a first red \u2014 that's the point of this book.",
|
|
10832
|
-
hook: "Several vitals red at once \u2014 the stethoscope has to pick a first listen.",
|
|
10833
|
-
enterpriseRatio: 0.3,
|
|
10834
|
-
midMarketRatio: 0.3,
|
|
10835
|
-
smbRatio: 0.4,
|
|
10836
|
-
staleContactRatio: 0.35,
|
|
10837
|
-
staleContactRatioEnterprise: 0.55,
|
|
10838
|
-
staleContactRatioSmb: 0.15,
|
|
10839
|
-
pastCloseDateRatio: 0.35,
|
|
10840
|
-
staleDays: 100,
|
|
10841
|
-
mqlDropRatio: 0.3,
|
|
10842
|
-
qualifiedNoOutreachRatio: 0.25,
|
|
10843
|
-
stuckDealRatio: 0.3,
|
|
10844
|
-
stuckInNegotiationDays: 90,
|
|
10845
|
-
activityVolumeMultiplier: 2,
|
|
10846
|
-
noiseActivityRatio: 0.4,
|
|
10847
|
-
singleThreadRatio: 0.4
|
|
10848
|
-
}
|
|
10849
|
-
};
|
|
10850
|
-
SCENARIO_LIST = Object.values(SCENARIOS);
|
|
10851
|
-
NAMED_DEMO_SCENARIOS = [
|
|
10852
|
-
"hidden_crisis",
|
|
10853
|
-
"leaky_bucket",
|
|
10854
|
-
"stale_pipeline",
|
|
10855
|
-
"lone_wolf",
|
|
10856
|
-
"busy_bees",
|
|
10857
|
-
"even_keel",
|
|
10858
|
-
"compound_pain"
|
|
10859
|
-
];
|
|
10860
|
-
RANDOM_POOL = [...NAMED_DEMO_SCENARIOS];
|
|
10693
|
+
init_store();
|
|
10694
|
+
init_trial_policy();
|
|
10695
|
+
init_upgrade_whimsy();
|
|
10696
|
+
init_normalize();
|
|
10697
|
+
init_lemonsqueezy();
|
|
10861
10698
|
}
|
|
10862
10699
|
});
|
|
10863
10700
|
|
|
10864
|
-
// src/
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10701
|
+
// src/ui/open-browser.ts
|
|
10702
|
+
import { spawn } from "child_process";
|
|
10703
|
+
import { platform } from "os";
|
|
10704
|
+
function openInBrowser(url) {
|
|
10705
|
+
return new Promise((resolve10, reject) => {
|
|
10706
|
+
let cmd;
|
|
10707
|
+
let args;
|
|
10708
|
+
switch (platform()) {
|
|
10709
|
+
case "darwin":
|
|
10710
|
+
cmd = "open";
|
|
10711
|
+
args = [url];
|
|
10712
|
+
break;
|
|
10713
|
+
case "win32":
|
|
10714
|
+
cmd = "cmd";
|
|
10715
|
+
args = ["/c", "start", "", url];
|
|
10716
|
+
break;
|
|
10717
|
+
default:
|
|
10718
|
+
cmd = "xdg-open";
|
|
10719
|
+
args = [url];
|
|
10720
|
+
break;
|
|
10721
|
+
}
|
|
10722
|
+
const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
10723
|
+
child.on("error", reject);
|
|
10724
|
+
child.unref();
|
|
10725
|
+
resolve10();
|
|
10726
|
+
});
|
|
10875
10727
|
}
|
|
10876
|
-
var
|
|
10877
|
-
|
|
10878
|
-
"src/baselines/metrics-benchmarks.ts"() {
|
|
10728
|
+
var init_open_browser = __esm({
|
|
10729
|
+
"src/ui/open-browser.ts"() {
|
|
10879
10730
|
"use strict";
|
|
10880
|
-
METRICS_BENCHMARKS = {
|
|
10881
|
-
plg: {
|
|
10882
|
-
nrr: { green: 110, yellow: 100 },
|
|
10883
|
-
grr: { green: 85, yellow: 75 },
|
|
10884
|
-
win_rate: { green: 25, yellow: 15 },
|
|
10885
|
-
pipeline_coverage: { green: 4, yellow: 2.5 },
|
|
10886
|
-
magic_number: { green: 1, yellow: 0.75 },
|
|
10887
|
-
payback_months: { green: 12, yellow: 18 }
|
|
10888
|
-
},
|
|
10889
|
-
smb_velocity: {
|
|
10890
|
-
nrr: { green: 105, yellow: 95 },
|
|
10891
|
-
grr: { green: 88, yellow: 78 },
|
|
10892
|
-
win_rate: { green: 22, yellow: 12 },
|
|
10893
|
-
pipeline_coverage: { green: 3.5, yellow: 2 },
|
|
10894
|
-
magic_number: { green: 0.9, yellow: 0.6 },
|
|
10895
|
-
payback_months: { green: 14, yellow: 20 }
|
|
10896
|
-
},
|
|
10897
|
-
mid_market: {
|
|
10898
|
-
nrr: { green: 100, yellow: 90 },
|
|
10899
|
-
grr: { green: 90, yellow: 80 },
|
|
10900
|
-
win_rate: { green: 20, yellow: 12 },
|
|
10901
|
-
pipeline_coverage: { green: 3, yellow: 2 },
|
|
10902
|
-
magic_number: { green: 0.75, yellow: 0.5 },
|
|
10903
|
-
payback_months: { green: 16, yellow: 22 }
|
|
10904
|
-
},
|
|
10905
|
-
enterprise: {
|
|
10906
|
-
nrr: { green: 95, yellow: 85 },
|
|
10907
|
-
grr: { green: 92, yellow: 82 },
|
|
10908
|
-
win_rate: { green: 15, yellow: 8 },
|
|
10909
|
-
pipeline_coverage: { green: 2.5, yellow: 1.5 },
|
|
10910
|
-
magic_number: { green: 0.6, yellow: 0.4 },
|
|
10911
|
-
payback_months: { green: 18, yellow: 24 }
|
|
10912
|
-
}
|
|
10913
|
-
};
|
|
10914
|
-
MOTION_LABELS = {
|
|
10915
|
-
plg: "PLG",
|
|
10916
|
-
smb_velocity: "SMB Velocity",
|
|
10917
|
-
mid_market: "Mid-Market",
|
|
10918
|
-
enterprise: "Enterprise"
|
|
10919
|
-
};
|
|
10920
10731
|
}
|
|
10921
10732
|
});
|
|
10922
10733
|
|
|
10923
|
-
// src/
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10734
|
+
// src/license/upgrade.ts
|
|
10735
|
+
var upgrade_exports = {};
|
|
10736
|
+
__export(upgrade_exports, {
|
|
10737
|
+
getCheckoutUrl: () => getCheckoutUrl,
|
|
10738
|
+
getUpgradeUrl: () => getUpgradeUrl,
|
|
10739
|
+
hasStoredLicenseKey: () => hasStoredLicenseKey,
|
|
10740
|
+
isTrialCutoff: () => isTrialCutoff,
|
|
10741
|
+
isTrialGrace: () => isTrialGrace,
|
|
10742
|
+
openCheckoutInBrowser: () => openCheckoutInBrowser,
|
|
10743
|
+
printActiveTrialNudge: () => printActiveTrialNudge,
|
|
10744
|
+
printGraceNudge: () => printGraceNudge,
|
|
10745
|
+
printLicenseBlocked: () => printLicenseBlocked,
|
|
10746
|
+
printTrialNudge: () => printTrialNudge,
|
|
10747
|
+
promptForLicenseKey: () => promptForLicenseKey,
|
|
10748
|
+
promptOpenCheckout: () => promptOpenCheckout,
|
|
10749
|
+
resolveUpgradeReason: () => resolveUpgradeReason,
|
|
10750
|
+
runUpgradeFlow: () => runUpgradeFlow
|
|
10751
|
+
});
|
|
10752
|
+
import chalk8 from "chalk";
|
|
10753
|
+
function checkoutUrlFor(purpose) {
|
|
10754
|
+
return purpose === "upgrade" ? getUpgradeUrl() : getCheckoutUrl();
|
|
10928
10755
|
}
|
|
10929
|
-
function
|
|
10930
|
-
|
|
10931
|
-
const t = METRICS_BENCHMARKS[m].payback_months;
|
|
10932
|
-
return `${motionBenchmarkLabel(m)} green \u2264${t.green}mo, yellow \u2264${t.yellow}mo`;
|
|
10756
|
+
function isTrialCutoff(lic) {
|
|
10757
|
+
return lic.trialPhase === "expired";
|
|
10933
10758
|
}
|
|
10934
|
-
function
|
|
10935
|
-
|
|
10936
|
-
const t = METRICS_BENCHMARKS[m].magic_number;
|
|
10937
|
-
return `${motionBenchmarkLabel(m)} green \u2265${t.green}, yellow \u2265${t.yellow}`;
|
|
10759
|
+
function isTrialGrace(lic) {
|
|
10760
|
+
return lic.trialPhase === "grace";
|
|
10938
10761
|
}
|
|
10939
|
-
function
|
|
10940
|
-
|
|
10762
|
+
function printLicenseBlocked(context) {
|
|
10763
|
+
const lic = checkLicense();
|
|
10764
|
+
console.log();
|
|
10765
|
+
if (isTrialCutoff(lic)) {
|
|
10766
|
+
console.log(" " + chalk8.yellow(randomBlockedNudge()));
|
|
10767
|
+
} else {
|
|
10768
|
+
console.log(chalk8.red(` A license is required for ${context}.`));
|
|
10769
|
+
console.log(
|
|
10770
|
+
" " + chalk8.dim("Type ") + paint("accent", "/activate") + chalk8.dim(" to paste a key, or ") + paint("accent", "/checkout") + chalk8.dim(" to sign up.")
|
|
10771
|
+
);
|
|
10772
|
+
}
|
|
10773
|
+
console.log();
|
|
10941
10774
|
}
|
|
10942
|
-
function
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
const direct = ALIAS_INDEX.get(q);
|
|
10947
|
-
if (direct) return direct;
|
|
10948
|
-
const norm = q.replace(/[-\s]+/g, "_");
|
|
10949
|
-
if (BY_ID.has(norm)) return norm;
|
|
10950
|
-
return ALIAS_INDEX.get(norm);
|
|
10775
|
+
function printGraceNudge(lic) {
|
|
10776
|
+
if (!lic.shouldNudgeUpgrade || lic.daysUntilLockout === void 0) return;
|
|
10777
|
+
console.log(" " + chalk8.yellow(randomGraceNudge(lic.daysUntilLockout)));
|
|
10778
|
+
console.log();
|
|
10951
10779
|
}
|
|
10952
|
-
function
|
|
10953
|
-
if (!
|
|
10954
|
-
|
|
10780
|
+
function printActiveTrialNudge(lic) {
|
|
10781
|
+
if (!lic.shouldNudgeUpgrade || lic.trialPhase !== "active") return;
|
|
10782
|
+
const daysLeft = lic.trialDaysRemaining;
|
|
10783
|
+
if (daysLeft === void 0 || daysLeft <= 0) return;
|
|
10784
|
+
console.log(" " + chalk8.yellow(randomActiveTrialNudge(daysLeft)));
|
|
10785
|
+
console.log();
|
|
10955
10786
|
}
|
|
10956
|
-
function
|
|
10957
|
-
|
|
10787
|
+
function printTrialNudge(lic) {
|
|
10788
|
+
if (!lic.shouldNudgeUpgrade) return;
|
|
10789
|
+
if (lic.trialPhase === "grace") {
|
|
10790
|
+
printGraceNudge(lic);
|
|
10791
|
+
return;
|
|
10792
|
+
}
|
|
10793
|
+
if (lic.trialPhase === "active") {
|
|
10794
|
+
printActiveTrialNudge(lic);
|
|
10795
|
+
}
|
|
10958
10796
|
}
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
|
|
10995
|
-
|
|
10996
|
-
|
|
10997
|
-
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
|
|
10797
|
+
function headlineFor(reason, lic) {
|
|
10798
|
+
return randomUpgradeHeadline(lic.daysUntilLockout);
|
|
10799
|
+
}
|
|
10800
|
+
function subtitleFor(reason) {
|
|
10801
|
+
return randomUpgradeSubtitle(reason);
|
|
10802
|
+
}
|
|
10803
|
+
async function promptOpenCheckout(ctx, purpose = "signup") {
|
|
10804
|
+
const url = checkoutUrlFor(purpose);
|
|
10805
|
+
console.log(" " + chalk8.dim(url));
|
|
10806
|
+
if (!process.stdin.isTTY || process.env.NTRP_NO_BROWSER_OPEN === "1") {
|
|
10807
|
+
console.log();
|
|
10808
|
+
return;
|
|
10809
|
+
}
|
|
10810
|
+
const session = createPromptSession(ctx.rl, ctx);
|
|
10811
|
+
try {
|
|
10812
|
+
console.log();
|
|
10813
|
+
await session.askPressEnter("Open checkout in your browser");
|
|
10814
|
+
try {
|
|
10815
|
+
await openInBrowser(url);
|
|
10816
|
+
console.log(" " + chalk8.green("\u2713 Browser opened"));
|
|
10817
|
+
console.log(
|
|
10818
|
+
" " + chalk8.dim(
|
|
10819
|
+
purpose === "upgrade" ? "Complete checkout in your browser. Then paste your Pro key below." : "Complete signup in your browser. Then paste your key below."
|
|
10820
|
+
)
|
|
10821
|
+
);
|
|
10822
|
+
} catch {
|
|
10823
|
+
console.log(" " + chalk8.yellow("Could not open the browser. Copy the URL above."));
|
|
10824
|
+
}
|
|
10825
|
+
console.log();
|
|
10826
|
+
} finally {
|
|
10827
|
+
session.close();
|
|
10828
|
+
}
|
|
10829
|
+
}
|
|
10830
|
+
async function openCheckoutInBrowser() {
|
|
10831
|
+
const url = getCheckoutUrl();
|
|
10832
|
+
console.log();
|
|
10833
|
+
console.log(" " + chalk8.dim(url));
|
|
10834
|
+
if (!process.stdin.isTTY) {
|
|
10835
|
+
console.log();
|
|
10836
|
+
return;
|
|
10837
|
+
}
|
|
10838
|
+
try {
|
|
10839
|
+
await openInBrowser(url);
|
|
10840
|
+
console.log(" " + chalk8.green("\u2713 Browser opened"));
|
|
10841
|
+
} catch {
|
|
10842
|
+
console.log(" " + chalk8.yellow("Could not open the browser. Copy the URL above."));
|
|
10843
|
+
}
|
|
10844
|
+
console.log(" " + chalk8.dim("After signup, paste your key with /activate or /upgrade."));
|
|
10845
|
+
console.log();
|
|
10846
|
+
}
|
|
10847
|
+
async function promptForLicenseKey(ctx, purpose = "signup") {
|
|
10848
|
+
const session = createPromptSession(ctx.rl, ctx);
|
|
10849
|
+
try {
|
|
10850
|
+
for (; ; ) {
|
|
10851
|
+
let key;
|
|
10852
|
+
try {
|
|
10853
|
+
key = await session.askSecret("Paste your license key", { confirm: false });
|
|
10854
|
+
} catch (err) {
|
|
10855
|
+
if (err instanceof Error && err.message === "Cancelled") {
|
|
10856
|
+
console.log(" " + chalk8.dim("Activation cancelled."));
|
|
10857
|
+
return false;
|
|
10858
|
+
}
|
|
10859
|
+
throw err;
|
|
10860
|
+
}
|
|
10861
|
+
if (!key.trim()) {
|
|
10862
|
+
console.log(" " + chalk8.red("A license key is required."));
|
|
10863
|
+
continue;
|
|
10864
|
+
}
|
|
10865
|
+
let result;
|
|
10866
|
+
try {
|
|
10867
|
+
result = await activateLicenseKey(key.trim());
|
|
10868
|
+
} catch (err) {
|
|
10869
|
+
const message = err instanceof Error ? err.message : "License activation failed";
|
|
10870
|
+
console.log(" " + chalk8.red(message));
|
|
10871
|
+
console.log(" " + chalk8.dim("Check your network connection and try again."));
|
|
10872
|
+
console.log();
|
|
10873
|
+
continue;
|
|
10874
|
+
}
|
|
10875
|
+
if (!result.valid) {
|
|
10876
|
+
console.log(" " + chalk8.red(result.message));
|
|
10877
|
+
console.log(
|
|
10878
|
+
" " + chalk8.dim(`Use the key from your purchase email, or try again: ${checkoutUrlFor(purpose)}`)
|
|
10879
|
+
);
|
|
10880
|
+
console.log();
|
|
10881
|
+
continue;
|
|
10882
|
+
}
|
|
10883
|
+
console.log();
|
|
10884
|
+
console.log(chalk8.green(` \u2713 ${randomProActivatedLine()}`));
|
|
10885
|
+
console.log();
|
|
10886
|
+
return true;
|
|
10887
|
+
}
|
|
10888
|
+
} finally {
|
|
10889
|
+
session.close();
|
|
10890
|
+
}
|
|
10891
|
+
}
|
|
10892
|
+
async function runUpgradeFlow(ctx, reason) {
|
|
10893
|
+
const lic = checkLicense();
|
|
10894
|
+
printCenteredLogo();
|
|
10895
|
+
console.log(" " + bold(headlineFor(reason, lic)));
|
|
10896
|
+
console.log(" " + chalk8.dim(subtitleFor(reason)));
|
|
10897
|
+
console.log();
|
|
10898
|
+
await promptOpenCheckout(ctx, "upgrade");
|
|
10899
|
+
console.log(" " + chalk8.dim("Paste your license key when it arrives by email."));
|
|
10900
|
+
console.log();
|
|
10901
|
+
return promptForLicenseKey(ctx, "upgrade");
|
|
10902
|
+
}
|
|
10903
|
+
function resolveUpgradeReason() {
|
|
10904
|
+
const lic = checkLicense();
|
|
10905
|
+
if (isTrialCutoff(lic)) return "expired";
|
|
10906
|
+
if (isTrialGrace(lic)) return "grace";
|
|
10907
|
+
return "convert";
|
|
10908
|
+
}
|
|
10909
|
+
function hasStoredLicenseKey() {
|
|
10910
|
+
return Boolean(getConfigValue("license-key"));
|
|
10911
|
+
}
|
|
10912
|
+
var init_upgrade = __esm({
|
|
10913
|
+
"src/license/upgrade.ts"() {
|
|
10914
|
+
"use strict";
|
|
10915
|
+
init_prompts();
|
|
10916
|
+
init_store();
|
|
10917
|
+
init_banner();
|
|
10918
|
+
init_theme();
|
|
10919
|
+
init_verify();
|
|
10920
|
+
init_trial_policy();
|
|
10921
|
+
init_upgrade_whimsy();
|
|
10922
|
+
init_open_browser();
|
|
10923
|
+
}
|
|
10924
|
+
});
|
|
10925
|
+
|
|
10926
|
+
// src/license/activation.ts
|
|
10927
|
+
import chalk9 from "chalk";
|
|
10928
|
+
function hasValidLicense() {
|
|
10929
|
+
return checkLicense().valid;
|
|
10930
|
+
}
|
|
10931
|
+
async function ensureLicenseActivated(ctx, options = {}) {
|
|
10932
|
+
if (hasValidLicense()) return false;
|
|
10933
|
+
if (!process.stdin.isTTY) {
|
|
10934
|
+
console.error();
|
|
10935
|
+
console.error(chalk9.red(" A license key is required."));
|
|
10936
|
+
console.error(chalk9.dim(` Sign up: ${getCheckoutUrl()}`));
|
|
10937
|
+
console.error(chalk9.dim(" Then type: ntrp activate <key>"));
|
|
10938
|
+
console.error(chalk9.dim(" Or set NTRP_LICENSE_KEY for headless use."));
|
|
10939
|
+
console.error();
|
|
10940
|
+
process.exit(1);
|
|
10941
|
+
}
|
|
10942
|
+
const exitOnCancel = options.exitOnCancel !== false;
|
|
10943
|
+
const lic = checkLicense();
|
|
10944
|
+
if (hasStoredLicenseKey() && isTrialCutoff(lic)) {
|
|
10945
|
+
const upgraded = await runUpgradeFlow(ctx, "expired");
|
|
10946
|
+
if (!upgraded) {
|
|
10947
|
+
if (exitOnCancel) exitActivationCancelled();
|
|
10948
|
+
printActivationCancelledStay();
|
|
10949
|
+
return true;
|
|
10950
|
+
}
|
|
10951
|
+
return true;
|
|
10952
|
+
}
|
|
10953
|
+
printCenteredLogo();
|
|
10954
|
+
console.log(" " + bold("Welcome to NTRP"));
|
|
10955
|
+
console.log(" " + chalk9.dim(TAGLINE));
|
|
10956
|
+
console.log(" " + chalk9.dim("Paste a trial key or a Pro key. If you do not have a key, NTRP opens signup."));
|
|
10957
|
+
console.log(" " + chalk9.dim("Activating accepts the NTRP license (LICENSE in the install, or ntrp.sonnechasser.com)."));
|
|
10958
|
+
console.log();
|
|
10959
|
+
await promptOpenCheckout(ctx);
|
|
10960
|
+
const activated = await promptForLicenseKey(ctx);
|
|
10961
|
+
if (!activated) {
|
|
10962
|
+
if (exitOnCancel) exitActivationCancelled();
|
|
10963
|
+
printActivationCancelledStay();
|
|
10964
|
+
return true;
|
|
10965
|
+
}
|
|
10966
|
+
return true;
|
|
10967
|
+
}
|
|
10968
|
+
function exitActivationCancelled() {
|
|
10969
|
+
console.log(" " + chalk9.dim("No license activated. Type ") + chalk9.cyan("ntrp") + chalk9.dim(" to try again."));
|
|
10970
|
+
console.log();
|
|
10971
|
+
process.exit(130);
|
|
10972
|
+
}
|
|
10973
|
+
function printActivationCancelledStay() {
|
|
10974
|
+
console.log(
|
|
10975
|
+
" " + chalk9.dim("No license activated. Type ") + chalk9.cyan("/activate") + chalk9.dim(" to paste a key. Type ") + chalk9.cyan("/checkout") + chalk9.dim(" if you need to sign up.")
|
|
10976
|
+
);
|
|
10977
|
+
console.log();
|
|
10978
|
+
}
|
|
10979
|
+
var init_activation = __esm({
|
|
10980
|
+
"src/license/activation.ts"() {
|
|
10981
|
+
"use strict";
|
|
10982
|
+
init_banner();
|
|
10983
|
+
init_theme();
|
|
10984
|
+
init_verify();
|
|
10985
|
+
init_trial_policy();
|
|
10986
|
+
init_upgrade();
|
|
10987
|
+
}
|
|
10988
|
+
});
|
|
10989
|
+
|
|
10990
|
+
// src/demo/scenarios.ts
|
|
10991
|
+
var scenarios_exports = {};
|
|
10992
|
+
__export(scenarios_exports, {
|
|
10993
|
+
NAMED_DEMO_SCENARIOS: () => NAMED_DEMO_SCENARIOS,
|
|
10994
|
+
SCENARIOS: () => SCENARIOS,
|
|
10995
|
+
SCENARIO_LIST: () => SCENARIO_LIST,
|
|
10996
|
+
blendScenarios: () => blendScenarios,
|
|
10997
|
+
getScenario: () => getScenario,
|
|
10998
|
+
isNamedDemoScenario: () => isNamedDemoScenario,
|
|
10999
|
+
pickRandomScenario: () => pickRandomScenario,
|
|
11000
|
+
resolveScenarioInput: () => resolveScenarioInput
|
|
11001
|
+
});
|
|
11002
|
+
function getScenario(key) {
|
|
11003
|
+
const scenario = SCENARIOS[key];
|
|
11004
|
+
if (!scenario) {
|
|
11005
|
+
throw new Error(`Unknown scenario: ${key}. Valid: ${Object.keys(SCENARIOS).join(", ")}`);
|
|
11006
|
+
}
|
|
11007
|
+
return scenario;
|
|
11008
|
+
}
|
|
11009
|
+
function isNamedDemoScenario(raw) {
|
|
11010
|
+
return NAMED_DEMO_SCENARIOS.includes(raw);
|
|
11011
|
+
}
|
|
11012
|
+
function resolveScenarioInput(raw) {
|
|
11013
|
+
const input = raw?.trim();
|
|
11014
|
+
if (!input) return void 0;
|
|
11015
|
+
if (input === "research_blend") return "research_blend";
|
|
11016
|
+
if (NAMED_DEMO_SCENARIOS.includes(input)) {
|
|
11017
|
+
return input;
|
|
11018
|
+
}
|
|
11019
|
+
const n = Number(input);
|
|
11020
|
+
if (Number.isInteger(n) && n >= 1 && n <= NAMED_DEMO_SCENARIOS.length) {
|
|
11021
|
+
return NAMED_DEMO_SCENARIOS[n - 1];
|
|
11022
|
+
}
|
|
11023
|
+
return null;
|
|
11024
|
+
}
|
|
11025
|
+
function pickRandomScenario() {
|
|
11026
|
+
return RANDOM_POOL[Math.floor(Math.random() * RANDOM_POOL.length)];
|
|
11027
|
+
}
|
|
11028
|
+
function blendScenarios(_research) {
|
|
11029
|
+
const blended = {
|
|
11030
|
+
...BASELINE,
|
|
11031
|
+
key: "research_blend",
|
|
11032
|
+
label: "Research-Derived Blend",
|
|
11033
|
+
description: "Realistic data with mild-to-moderate problems across all vital signs.",
|
|
11034
|
+
story: "Generated with default research blend. Problems are seeded across all vital signs at realistic levels.",
|
|
11035
|
+
hook: "Mild-to-moderate problems seeded across all five vitals.",
|
|
11036
|
+
// Bump all problems slightly above baseline for discoverability
|
|
11037
|
+
staleContactRatio: 0.2,
|
|
11038
|
+
pastCloseDateRatio: 0.15,
|
|
11039
|
+
mqlDropRatio: 0.15,
|
|
11040
|
+
qualifiedNoOutreachRatio: 0.15,
|
|
11041
|
+
stuckDealRatio: 0.15,
|
|
11042
|
+
noiseActivityRatio: 0.2,
|
|
11043
|
+
singleThreadRatio: 0.25
|
|
11044
|
+
};
|
|
11045
|
+
return blended;
|
|
11046
|
+
}
|
|
11047
|
+
var BASELINE, SCENARIOS, SCENARIO_LIST, NAMED_DEMO_SCENARIOS, RANDOM_POOL;
|
|
11048
|
+
var init_scenarios = __esm({
|
|
11049
|
+
"src/demo/scenarios.ts"() {
|
|
11050
|
+
"use strict";
|
|
11051
|
+
BASELINE = {
|
|
11052
|
+
enterpriseRatio: 0.2,
|
|
11053
|
+
midMarketRatio: 0.3,
|
|
11054
|
+
smbRatio: 0.5,
|
|
11055
|
+
staleContactRatio: 0.15,
|
|
11056
|
+
staleContactRatioEnterprise: 0.2,
|
|
11057
|
+
staleContactRatioSmb: 0.1,
|
|
11058
|
+
pastCloseDateRatio: 0.1,
|
|
11059
|
+
staleDays: 120,
|
|
11060
|
+
mqlDropRatio: 0.1,
|
|
11061
|
+
qualifiedNoOutreachRatio: 0.1,
|
|
11062
|
+
stuckDealRatio: 0.1,
|
|
11063
|
+
stuckInNegotiationDays: 45,
|
|
11064
|
+
avgDaysPerStageEnterprise: 25,
|
|
11065
|
+
avgDaysPerStageSmb: 8,
|
|
11066
|
+
activityVolumeMultiplier: 1,
|
|
11067
|
+
noiseActivityRatio: 0.15,
|
|
11068
|
+
singleThreadRatio: 0.2,
|
|
11069
|
+
loneWolfRepIndex: null,
|
|
11070
|
+
loneWolfSingleThreadRatio: 0,
|
|
11071
|
+
freshnessGapDays: 90
|
|
11072
|
+
};
|
|
11073
|
+
SCENARIOS = {
|
|
11074
|
+
hidden_crisis: {
|
|
11075
|
+
...BASELINE,
|
|
11076
|
+
key: "hidden_crisis",
|
|
11077
|
+
label: "The Hidden Crisis",
|
|
11078
|
+
description: "Overall health looks yellow but Enterprise is deep red, masked by strong SMB numbers.",
|
|
11079
|
+
story: "Your aggregate numbers look okay \u2014 but when you break it by segment, Enterprise is dying. 60% of enterprise contacts have gone dark, deals are single-threaded, and SMB is carrying the average.",
|
|
11080
|
+
hook: "SMB is carrying the average while Enterprise dies quietly.",
|
|
11081
|
+
staleContactRatio: 0.3,
|
|
11082
|
+
staleContactRatioEnterprise: 0.6,
|
|
11083
|
+
staleContactRatioSmb: 0.1,
|
|
11084
|
+
singleThreadRatio: 0.5,
|
|
11085
|
+
enterpriseRatio: 0.3,
|
|
11086
|
+
midMarketRatio: 0.3,
|
|
11087
|
+
smbRatio: 0.4
|
|
11088
|
+
},
|
|
11089
|
+
leaky_bucket: {
|
|
11090
|
+
...BASELINE,
|
|
11091
|
+
key: "leaky_bucket",
|
|
11092
|
+
label: "The Leaky Bucket",
|
|
11093
|
+
description: "Marketing generates plenty of leads but 40% vanish at handoff to sales.",
|
|
11094
|
+
story: "Marketing is doing its job \u2014 MQLs are flowing. But 40% of qualified leads never show up in sales workflows. They're falling through the cracks at handoff, and nobody's noticing because marketing reports MQL count and sales reports pipeline value.",
|
|
11095
|
+
hook: "MQLs flow in, then 40% vanish at the sales handoff.",
|
|
11096
|
+
mqlDropRatio: 0.4,
|
|
11097
|
+
qualifiedNoOutreachRatio: 0.35,
|
|
11098
|
+
staleContactRatio: 0.2
|
|
11099
|
+
},
|
|
11100
|
+
stale_pipeline: {
|
|
11101
|
+
...BASELINE,
|
|
11102
|
+
key: "stale_pipeline",
|
|
11103
|
+
label: "The Stale Pipeline",
|
|
11104
|
+
description: "Big pipeline number but half the deals are zombies stuck in late stages.",
|
|
11105
|
+
story: "The pipeline report says $5M. But look closer: half those deals have close dates in the past, 40% are stuck in Negotiation for 120+ days, and nobody's touching them. You're forecasting on fiction.",
|
|
11106
|
+
hook: "Half the pipeline is zombies \u2014 you're forecasting on fiction.",
|
|
11107
|
+
pastCloseDateRatio: 0.5,
|
|
11108
|
+
stuckDealRatio: 0.4,
|
|
11109
|
+
stuckInNegotiationDays: 120,
|
|
11110
|
+
staleContactRatio: 0.25,
|
|
11111
|
+
staleDays: 90
|
|
11112
|
+
},
|
|
11113
|
+
lone_wolf: {
|
|
11114
|
+
...BASELINE,
|
|
11115
|
+
key: "lone_wolf",
|
|
11116
|
+
label: "The Lone Wolf",
|
|
11117
|
+
description: "One rep has great numbers but every single deal is single-threaded.",
|
|
11118
|
+
story: "Your top rep is crushing it on paper \u2014 biggest pipeline, highest close rate. But every deal has exactly one contact. One champion goes on vacation, gets promoted, or leaves, and the entire pipeline collapses.",
|
|
11119
|
+
hook: "Top rep, huge pipeline, one contact per deal \u2014 one exit from collapse.",
|
|
11120
|
+
loneWolfRepIndex: 0,
|
|
11121
|
+
loneWolfSingleThreadRatio: 1,
|
|
11122
|
+
singleThreadRatio: 0.15
|
|
11123
|
+
},
|
|
11124
|
+
busy_bees: {
|
|
11125
|
+
...BASELINE,
|
|
11126
|
+
key: "busy_bees",
|
|
11127
|
+
label: "The Busy Bees",
|
|
11128
|
+
description: "High activity volume across the team, but most of it hits dead ends.",
|
|
11129
|
+
story: "Your team is busy. Activity metrics look great \u2014 calls are up, emails are up, meetings are up. But 60% of that activity is aimed at contacts with no associated pipeline. Reps are spraying, not aiming.",
|
|
11130
|
+
hook: "Reps are spraying, not aiming.",
|
|
11131
|
+
activityVolumeMultiplier: 3,
|
|
11132
|
+
noiseActivityRatio: 0.6,
|
|
11133
|
+
staleContactRatio: 0.2
|
|
11134
|
+
},
|
|
11135
|
+
even_keel: {
|
|
11136
|
+
...BASELINE,
|
|
11137
|
+
key: "even_keel",
|
|
11138
|
+
label: "The Even Keel",
|
|
11139
|
+
description: "A reasonably healthy book \u2014 enough yellow to listen, not a five-alarm fire.",
|
|
11140
|
+
story: "Most numbers sit in a normal band. A few contacts have gone quiet, a handful of deals are slow, activity is mostly on-pipeline. This is what 'fine' looks like on the stethoscope \u2014 useful when you want to evaluate NTRP without a manufactured crisis.",
|
|
11141
|
+
hook: "Reasonably healthy \u2014 enough signal to listen, not a crisis."
|
|
11142
|
+
},
|
|
11143
|
+
compound_pain: {
|
|
11144
|
+
...BASELINE,
|
|
11145
|
+
key: "compound_pain",
|
|
11146
|
+
label: "The Compound Fracture",
|
|
11147
|
+
description: "Several vitals are red at once \u2014 stale pipeline, leaky handoff, noisy activity, thin threads.",
|
|
11148
|
+
story: "This isn't one problem. Enterprise contacts have gone dark, MQLs vanish at handoff, late-stage deals are zombies, and a lot of activity never touches pipeline. The gating logic has to pick a first red \u2014 that's the point of this book.",
|
|
11149
|
+
hook: "Several vitals red at once \u2014 the stethoscope has to pick a first listen.",
|
|
11150
|
+
enterpriseRatio: 0.3,
|
|
11151
|
+
midMarketRatio: 0.3,
|
|
11152
|
+
smbRatio: 0.4,
|
|
11153
|
+
staleContactRatio: 0.35,
|
|
11154
|
+
staleContactRatioEnterprise: 0.55,
|
|
11155
|
+
staleContactRatioSmb: 0.15,
|
|
11156
|
+
pastCloseDateRatio: 0.35,
|
|
11157
|
+
staleDays: 100,
|
|
11158
|
+
mqlDropRatio: 0.3,
|
|
11159
|
+
qualifiedNoOutreachRatio: 0.25,
|
|
11160
|
+
stuckDealRatio: 0.3,
|
|
11161
|
+
stuckInNegotiationDays: 90,
|
|
11162
|
+
activityVolumeMultiplier: 2,
|
|
11163
|
+
noiseActivityRatio: 0.4,
|
|
11164
|
+
singleThreadRatio: 0.4
|
|
11165
|
+
}
|
|
11166
|
+
};
|
|
11167
|
+
SCENARIO_LIST = Object.values(SCENARIOS);
|
|
11168
|
+
NAMED_DEMO_SCENARIOS = [
|
|
11169
|
+
"hidden_crisis",
|
|
11170
|
+
"leaky_bucket",
|
|
11171
|
+
"stale_pipeline",
|
|
11172
|
+
"lone_wolf",
|
|
11173
|
+
"busy_bees",
|
|
11174
|
+
"even_keel",
|
|
11175
|
+
"compound_pain"
|
|
11176
|
+
];
|
|
11177
|
+
RANDOM_POOL = [...NAMED_DEMO_SCENARIOS];
|
|
11178
|
+
}
|
|
11179
|
+
});
|
|
11180
|
+
|
|
11181
|
+
// src/baselines/metrics-benchmarks.ts
|
|
11182
|
+
function resolveMetricBenchmarks(motion) {
|
|
11183
|
+
return METRICS_BENCHMARKS[motion ?? "mid_market"];
|
|
11184
|
+
}
|
|
11185
|
+
function motionBenchmarkLabel(motion) {
|
|
11186
|
+
return MOTION_LABELS[motion ?? "mid_market"];
|
|
11187
|
+
}
|
|
11188
|
+
function metricStatusHigherIsBetter(value, threshold) {
|
|
11189
|
+
if (value >= threshold.green) return "green";
|
|
11190
|
+
if (value >= threshold.yellow) return "yellow";
|
|
11191
|
+
return "red";
|
|
11192
|
+
}
|
|
11193
|
+
var METRICS_BENCHMARKS, MOTION_LABELS;
|
|
11194
|
+
var init_metrics_benchmarks = __esm({
|
|
11195
|
+
"src/baselines/metrics-benchmarks.ts"() {
|
|
11196
|
+
"use strict";
|
|
11197
|
+
METRICS_BENCHMARKS = {
|
|
11198
|
+
plg: {
|
|
11199
|
+
nrr: { green: 110, yellow: 100 },
|
|
11200
|
+
grr: { green: 85, yellow: 75 },
|
|
11201
|
+
win_rate: { green: 25, yellow: 15 },
|
|
11202
|
+
pipeline_coverage: { green: 4, yellow: 2.5 },
|
|
11203
|
+
magic_number: { green: 1, yellow: 0.75 },
|
|
11204
|
+
payback_months: { green: 12, yellow: 18 }
|
|
11205
|
+
},
|
|
11206
|
+
smb_velocity: {
|
|
11207
|
+
nrr: { green: 105, yellow: 95 },
|
|
11208
|
+
grr: { green: 88, yellow: 78 },
|
|
11209
|
+
win_rate: { green: 22, yellow: 12 },
|
|
11210
|
+
pipeline_coverage: { green: 3.5, yellow: 2 },
|
|
11211
|
+
magic_number: { green: 0.9, yellow: 0.6 },
|
|
11212
|
+
payback_months: { green: 14, yellow: 20 }
|
|
11213
|
+
},
|
|
11214
|
+
mid_market: {
|
|
11215
|
+
nrr: { green: 100, yellow: 90 },
|
|
11216
|
+
grr: { green: 90, yellow: 80 },
|
|
11217
|
+
win_rate: { green: 20, yellow: 12 },
|
|
11218
|
+
pipeline_coverage: { green: 3, yellow: 2 },
|
|
11219
|
+
magic_number: { green: 0.75, yellow: 0.5 },
|
|
11220
|
+
payback_months: { green: 16, yellow: 22 }
|
|
11221
|
+
},
|
|
11222
|
+
enterprise: {
|
|
11223
|
+
nrr: { green: 95, yellow: 85 },
|
|
11224
|
+
grr: { green: 92, yellow: 82 },
|
|
11225
|
+
win_rate: { green: 15, yellow: 8 },
|
|
11226
|
+
pipeline_coverage: { green: 2.5, yellow: 1.5 },
|
|
11227
|
+
magic_number: { green: 0.6, yellow: 0.4 },
|
|
11228
|
+
payback_months: { green: 18, yellow: 24 }
|
|
11229
|
+
}
|
|
11230
|
+
};
|
|
11231
|
+
MOTION_LABELS = {
|
|
11232
|
+
plg: "PLG",
|
|
11233
|
+
smb_velocity: "SMB Velocity",
|
|
11234
|
+
mid_market: "Mid-Market",
|
|
11235
|
+
enterprise: "Enterprise"
|
|
11236
|
+
};
|
|
11237
|
+
}
|
|
11238
|
+
});
|
|
11239
|
+
|
|
11240
|
+
// src/data/metric-definitions.ts
|
|
11241
|
+
function pctBand(metric, motion) {
|
|
11242
|
+
const m = motion ?? "mid_market";
|
|
11243
|
+
const t = METRICS_BENCHMARKS[m][metric];
|
|
11244
|
+
return `${motionBenchmarkLabel(m)} green \u2265${t.green}${metric === "pipeline_coverage" ? "x" : "%"}, yellow \u2265${t.yellow}${metric === "pipeline_coverage" ? "x" : "%"}`;
|
|
11245
|
+
}
|
|
11246
|
+
function monthsBand(motion) {
|
|
11247
|
+
const m = motion ?? "mid_market";
|
|
11248
|
+
const t = METRICS_BENCHMARKS[m].payback_months;
|
|
11249
|
+
return `${motionBenchmarkLabel(m)} green \u2264${t.green}mo, yellow \u2264${t.yellow}mo`;
|
|
11250
|
+
}
|
|
11251
|
+
function magicBand(motion) {
|
|
11252
|
+
const m = motion ?? "mid_market";
|
|
11253
|
+
const t = METRICS_BENCHMARKS[m].magic_number;
|
|
11254
|
+
return `${motionBenchmarkLabel(m)} green \u2265${t.green}, yellow \u2265${t.yellow}`;
|
|
11255
|
+
}
|
|
11256
|
+
function getMetricExplainer(id) {
|
|
11257
|
+
return BY_ID.get(id);
|
|
11258
|
+
}
|
|
11259
|
+
function resolveMetricId(query) {
|
|
11260
|
+
const q = query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
11261
|
+
if (!q) return void 0;
|
|
11262
|
+
if (BY_ID.has(q)) return q;
|
|
11263
|
+
const direct = ALIAS_INDEX.get(q);
|
|
11264
|
+
if (direct) return direct;
|
|
11265
|
+
const norm = q.replace(/[-\s]+/g, "_");
|
|
11266
|
+
if (BY_ID.has(norm)) return norm;
|
|
11267
|
+
return ALIAS_INDEX.get(norm);
|
|
11268
|
+
}
|
|
11269
|
+
function listMetricExplainers(kind) {
|
|
11270
|
+
if (!kind) return METRIC_DEFINITIONS.slice();
|
|
11271
|
+
return METRIC_DEFINITIONS.filter((m) => m.kind === kind);
|
|
11272
|
+
}
|
|
11273
|
+
function getCoreDeckExplainers() {
|
|
11274
|
+
return CORE_DECK_IDS.map((id) => BY_ID.get(id)).filter(Boolean);
|
|
11275
|
+
}
|
|
11276
|
+
var VITALS, SAAS, METRIC_DEFINITIONS, CORE_DECK_IDS, BY_ID, ALIAS_INDEX, SAAS_METRIC_IDS, GATING_LAYER_VISUAL;
|
|
11277
|
+
var init_metric_definitions = __esm({
|
|
11278
|
+
"src/data/metric-definitions.ts"() {
|
|
11279
|
+
"use strict";
|
|
11280
|
+
init_metrics_benchmarks();
|
|
11281
|
+
VITALS = [
|
|
11282
|
+
{
|
|
11283
|
+
id: "freshness",
|
|
11284
|
+
kind: "vital",
|
|
11285
|
+
label: "Freshness",
|
|
11286
|
+
group: "Vital Signs",
|
|
11287
|
+
tagline: "Does the CRM report which records are still active?",
|
|
11288
|
+
how_computed: "NTRP computes a weighted average of people, organizations, and opportunities with recent activity. Open opportunities must also not be past-due. Default windows: people and organizations 90 days, opportunities 30 days. Weights are 35 / 30 / 35.",
|
|
11289
|
+
formula_lines: [
|
|
11290
|
+
"freshness = people%\xD70.35 + orgs%\xD70.30 + opps%\xD70.35",
|
|
11291
|
+
"people/orgs fresh if activity within 90d",
|
|
11292
|
+
"opps fresh if activity within 30d AND not past-due"
|
|
11293
|
+
],
|
|
11294
|
+
meaning: "Board question: how much of this pipeline is real versus fiction? Dollar value equals the sum of amount on stale opportunities: pipeline at risk.",
|
|
11295
|
+
expert_read: "Cut by owner and by stage first. Freshness reds concentrate on people or process. They rarely spread evenly. In a long-cycle enterprise motion, 30 quiet days can be normal cadence. In a velocity motion, 30 quiet days is a dead deal. A sudden cliff usually means a broken integration or a departed rep. It is not gradual decay. Check this false positive: bulk-imported records that nobody has touched yet.",
|
|
11296
|
+
deepdive: [
|
|
11297
|
+
"Status bands: green 80 or more, yellow 60 or more, red below 60. Motion presets can change the windows.",
|
|
11298
|
+
'Dollar translation: sum of amount on stale open opportunities \u2192 "pipeline at risk".',
|
|
11299
|
+
"Layer 1 of the gating stack. A red score here limits trust in later layers.",
|
|
11300
|
+
"Trigger play: Clean Dead Pipeline (clean-dead-pipeline) when the score is below 60.",
|
|
11301
|
+
"Levers: stale-deal alert at N quiet days. Weekly hygiene scrub. Enrichment refresh on quiet records. Signal-triggered reactivation for paid dormant accounts."
|
|
11302
|
+
],
|
|
11303
|
+
visual: {
|
|
11304
|
+
kind: "bars",
|
|
11305
|
+
caption: "Example component mix. Higher bars are fresher.",
|
|
11306
|
+
bars: [
|
|
11307
|
+
{ label: "People", value: 72, tone: "yellow" },
|
|
11308
|
+
{ label: "Organizations", value: 81, tone: "green" },
|
|
11309
|
+
{ label: "Opportunities", value: 44, tone: "red" }
|
|
11310
|
+
]
|
|
11311
|
+
},
|
|
11312
|
+
play_id: "clean-dead-pipeline",
|
|
11313
|
+
dollar_label: "pipeline at risk",
|
|
11314
|
+
audience: {
|
|
11315
|
+
board: "Freshness answers whether the pipeline number is real. Low freshness means forecast risk. Stale deals inflate coverage and hide the true gap.",
|
|
11316
|
+
ops: "Score equals weighted recency across people, organizations, and opportunities. Cut by owner and stage. Set a stale-deal alert and a weekly scrub. Play: Clean Dead Pipeline."
|
|
11317
|
+
},
|
|
11318
|
+
aliases: ["data freshness", "stale", "zombie deals", "crm freshness"]
|
|
11319
|
+
},
|
|
11320
|
+
{
|
|
11321
|
+
id: "flow_rate",
|
|
11322
|
+
kind: "vital",
|
|
11323
|
+
label: "Flow Rate",
|
|
11324
|
+
group: "Vital Signs",
|
|
11325
|
+
tagline: "How fast do deals move, and where do they stop?",
|
|
11326
|
+
how_computed: "NTRP sets a base score from average open-deal age versus max_days. It then applies a penalty of up to 20 for the share of stuck deals. A deal is stuck when it has no update beyond stuck_days, or a past-due close. Status uses average open age, not the score alone.",
|
|
11327
|
+
formula_lines: [
|
|
11328
|
+
"base = 100 \xD7 (1 \u2212 avgOpenAge / max_days)",
|
|
11329
|
+
"score = base \u2212 stuckSharePenalty (\u226420)",
|
|
11330
|
+
"stuck = no update > stuck_days OR past-due close"
|
|
11331
|
+
],
|
|
11015
11332
|
meaning: "Board question: is next quarter slipping because deals are stuck? Dollar value equals the amount stuck in pipeline.",
|
|
11016
11333
|
expert_read: "Cut by stage age, not only deal age. Find the stage where deals stop. That is usually one stage. Compare stuck-deal age to this company's own median cycle. Do not use a generic norm. Stuck deals plus past-due close dates signal optimistic forecasting. That is a credibility problem before it is a revenue problem.",
|
|
11017
11334
|
deepdive: [
|
|
@@ -11580,714 +11897,399 @@ var init_metric_definitions = __esm({
|
|
|
11580
11897
|
caption: "Example cycle versus motion norm",
|
|
11581
11898
|
bars: [
|
|
11582
11899
|
{ label: "Your cycle", value: 78, tone: "yellow" },
|
|
11583
|
-
{ label: "Motion norm", value: 55, tone: "green" }
|
|
11584
|
-
]
|
|
11585
|
-
},
|
|
11586
|
-
audience: {
|
|
11587
|
-
board: "Cycle stretch is an early soft signal that quality or process is slipping. This appears before the miss shows in bookings.",
|
|
11588
|
-
ops: "Mean create to close on dated wins. Investigate the stage that aged."
|
|
11589
|
-
},
|
|
11590
|
-
aliases: ["sales cycle", "cycle length", "time to close"]
|
|
11591
|
-
},
|
|
11592
|
-
{
|
|
11593
|
-
id: "stage_conversion",
|
|
11594
|
-
kind: "saas",
|
|
11595
|
-
label: "Stage Conversion",
|
|
11596
|
-
group: "Sales Efficiency",
|
|
11597
|
-
tagline: "Where in the stage model does advancement collapse?",
|
|
11598
|
-
how_computed: "From metadata.stage_history stage advances when present. Otherwise a win-rate proxy.",
|
|
11599
|
-
formula_lines: [
|
|
11600
|
-
"Preferred: advancement rates from stage_history",
|
|
11601
|
-
"Fallback: win-rate proxy when history is missing"
|
|
11602
|
-
],
|
|
11603
|
-
meaning: "Board question: which single stage is starving everything downstream?",
|
|
11604
|
-
expert_read: "Find the one stage where conversion collapses. That is the process problem. Everything downstream is starvation.",
|
|
11605
|
-
deepdive: [
|
|
11606
|
-
"Best with stage_history metadata. Otherwise treat this as a proxy.",
|
|
11607
|
-
"Pairs with Flow Rate stage-age cuts."
|
|
11608
|
-
],
|
|
11609
|
-
visual: {
|
|
11610
|
-
kind: "funnel",
|
|
11611
|
-
caption: "Example: find the collapse",
|
|
11612
|
-
funnel: [
|
|
11613
|
-
{ label: "Stage 1\u21922", widthPct: 100 },
|
|
11614
|
-
{ label: "Stage 2\u21923", widthPct: 72 },
|
|
11615
|
-
{ label: "Stage 3\u21924", widthPct: 28 },
|
|
11616
|
-
{ label: "Stage 4\u2192Close", widthPct: 18 }
|
|
11617
|
-
]
|
|
11618
|
-
},
|
|
11619
|
-
audience: {
|
|
11620
|
-
board: "Stage Conversion names the bottleneck stage. One collapse starves every stage after it.",
|
|
11621
|
-
ops: "Prefer stage_history advances. Fix the collapse stage before you coach downstream reps."
|
|
11622
|
-
},
|
|
11623
|
-
aliases: ["stage conversion", "stage advance", "conversion by stage"]
|
|
11624
|
-
},
|
|
11625
|
-
// —— Unit economics ——
|
|
11626
|
-
{
|
|
11627
|
-
id: "ltv_proxy",
|
|
11628
|
-
kind: "saas",
|
|
11629
|
-
label: "LTV (Proxy)",
|
|
11630
|
-
group: "Unit Economics",
|
|
11631
|
-
tagline: "Rough lifetime value from deal size and GRR.",
|
|
11632
|
-
how_computed: "avgDeal / ((100 \u2212 GRR) / 100) when GRR is below 100. This metric is unavailable when GRR is 100% or more, or missing.",
|
|
11633
|
-
formula_lines: [
|
|
11634
|
-
"LTV \u2248 avgDeal / churnRate",
|
|
11635
|
-
"churnRate = (100 \u2212 GRR) / 100 (requires GRR < 100)"
|
|
11636
|
-
],
|
|
11637
|
-
meaning: "Board question: what is a customer roughly worth over their life?",
|
|
11638
|
-
expert_read: "This is a proxy, not a cohort LTV. Use it for direction, not capital allocation.",
|
|
11639
|
-
deepdive: [
|
|
11640
|
-
"Unavailable when GRR is 100 or more, or missing.",
|
|
11641
|
-
"Pairs with CAC for LTV:CAC when spend data exists."
|
|
11642
|
-
],
|
|
11643
|
-
visual: {
|
|
11644
|
-
kind: "gauge",
|
|
11645
|
-
caption: "Example LTV proxy. Directional only.",
|
|
11646
|
-
gauge: 68
|
|
11647
|
-
},
|
|
11648
|
-
audience: {
|
|
11649
|
-
board: "LTV Proxy is directional from deal size and GRR. It is not a cohort LTV. Use it for orientation, not capital decisions.",
|
|
11650
|
-
ops: "avgDeal / ((100\u2212GRR)/100). Needs GRR below 100. Prefer cohort math when billing data arrives."
|
|
11651
|
-
},
|
|
11652
|
-
aliases: ["ltv", "lifetime value"]
|
|
11653
|
-
},
|
|
11654
|
-
{
|
|
11655
|
-
id: "cac",
|
|
11656
|
-
kind: "saas",
|
|
11657
|
-
label: "CAC",
|
|
11658
|
-
group: "Unit Economics",
|
|
11659
|
-
tagline: "Customer acquisition cost. This metric needs spend data.",
|
|
11660
|
-
how_computed: "This metric requires campaign or sales spend data. It is currently unavailable on CRM-only datasets.",
|
|
11661
|
-
formula_lines: ["CAC = sales & marketing spend / new customers", "(requires spend data \u2014 not in CRM-only exports)"],
|
|
11662
|
-
meaning: "Board question: what does a new logo cost to win?",
|
|
11663
|
-
expert_read: "Without spend, NTRP cannot invent CAC. Connect campaign spend or finance exports to unlock unit economics.",
|
|
11664
|
-
deepdive: [
|
|
11665
|
-
"Always unavailable on CRM-only demos. That is expected.",
|
|
11666
|
-
"Unlocks LTV:CAC, Payback, and Magic Number when spend lands."
|
|
11667
|
-
],
|
|
11668
|
-
visual: { kind: "none", caption: "Needs campaign spend or a finance export" },
|
|
11669
|
-
audience: {
|
|
11670
|
-
board: "CAC is locked until spend data is connected. CRM alone cannot price acquisition.",
|
|
11671
|
-
ops: "Bring campaign or S&M spend. Until then, unit-econ metrics stay unavailable by design."
|
|
11672
|
-
},
|
|
11673
|
-
aliases: ["customer acquisition cost", "acquisition cost"]
|
|
11674
|
-
},
|
|
11675
|
-
{
|
|
11676
|
-
id: "ltv_cac_ratio",
|
|
11677
|
-
kind: "saas",
|
|
11678
|
-
label: "LTV:CAC Ratio",
|
|
11679
|
-
group: "Unit Economics",
|
|
11680
|
-
tagline: "Does acquisition spend return enough value?",
|
|
11681
|
-
how_computed: "LTV proxy divided by CAC. Unavailable without spend (CAC).",
|
|
11682
|
-
formula_lines: ["LTV:CAC = LTV_proxy / CAC", "(requires CAC)"],
|
|
11683
|
-
meaning: "Board question: do we earn enough lifetime value per dollar spent to acquire?",
|
|
11684
|
-
expert_read: "In the efficiency era, boards weigh LTV:CAC and payback as heavily as growth. Classic rule of thumb is 3x or more. Motion and gross margin matter.",
|
|
11685
|
-
deepdive: ["Blocked on CAC. See LTV Proxy and CAC."],
|
|
11686
|
-
visual: { kind: "none", caption: "This metric needs CAC (spend data)" },
|
|
11687
|
-
audience: {
|
|
11688
|
-
board: "LTV:CAC is the acquisition ROI story. It is available once spend is connected.",
|
|
11689
|
-
ops: "LTV_proxy / CAC. Unlocks with spend import."
|
|
11690
|
-
},
|
|
11691
|
-
aliases: ["ltv cac", "ltv/cac", "ltv to cac"]
|
|
11692
|
-
},
|
|
11693
|
-
{
|
|
11694
|
-
id: "payback_months",
|
|
11695
|
-
kind: "saas",
|
|
11696
|
-
label: "Payback Months",
|
|
11697
|
-
group: "Unit Economics",
|
|
11698
|
-
tagline: "How many months to recover CAC?",
|
|
11699
|
-
how_computed: "This metric requires CAC and spend. Lower is better.",
|
|
11700
|
-
formula_lines: ["Payback \u2248 CAC / (monthly gross profit per customer)", "(requires spend data)"],
|
|
11701
|
-
meaning: "Board question: how fast does acquisition spend return? Efficiency-era prior: under 18 months is often healthy.",
|
|
11702
|
-
expert_read: "Boards now weigh payback (under 18 months) as heavily as growth in many motions.",
|
|
11703
|
-
deepdive: ["Blocked on CAC. Benchmarks exist per motion once data lands."],
|
|
11704
|
-
visual: { kind: "none", caption: "This metric needs CAC (spend data)" },
|
|
11900
|
+
{ label: "Motion norm", value: 55, tone: "green" }
|
|
11901
|
+
]
|
|
11902
|
+
},
|
|
11705
11903
|
audience: {
|
|
11706
|
-
board: "
|
|
11707
|
-
ops: "
|
|
11904
|
+
board: "Cycle stretch is an early soft signal that quality or process is slipping. This appears before the miss shows in bookings.",
|
|
11905
|
+
ops: "Mean create to close on dated wins. Investigate the stage that aged."
|
|
11708
11906
|
},
|
|
11709
|
-
aliases: ["
|
|
11710
|
-
benchmarkHint: (motion) => monthsBand(motion)
|
|
11907
|
+
aliases: ["sales cycle", "cycle length", "time to close"]
|
|
11711
11908
|
},
|
|
11712
11909
|
{
|
|
11713
|
-
id: "
|
|
11910
|
+
id: "stage_conversion",
|
|
11714
11911
|
kind: "saas",
|
|
11715
|
-
label: "
|
|
11716
|
-
group: "
|
|
11717
|
-
tagline: "
|
|
11718
|
-
how_computed: "
|
|
11912
|
+
label: "Stage Conversion",
|
|
11913
|
+
group: "Sales Efficiency",
|
|
11914
|
+
tagline: "Where in the stage model does advancement collapse?",
|
|
11915
|
+
how_computed: "From metadata.stage_history stage advances when present. Otherwise a win-rate proxy.",
|
|
11719
11916
|
formula_lines: [
|
|
11720
|
-
"
|
|
11721
|
-
"
|
|
11917
|
+
"Preferred: advancement rates from stage_history",
|
|
11918
|
+
"Fallback: win-rate proxy when history is missing"
|
|
11722
11919
|
],
|
|
11723
|
-
meaning: "Board question:
|
|
11724
|
-
expert_read: "
|
|
11725
|
-
deepdive: [
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
"flow_rate",
|
|
11745
|
-
"drop_rate",
|
|
11746
|
-
"signal_to_noise",
|
|
11747
|
-
"thread_depth"
|
|
11748
|
-
];
|
|
11749
|
-
BY_ID = new Map(METRIC_DEFINITIONS.map((m) => [m.id, m]));
|
|
11750
|
-
ALIAS_INDEX = (() => {
|
|
11751
|
-
const idx = /* @__PURE__ */ new Map();
|
|
11752
|
-
for (const m of METRIC_DEFINITIONS) {
|
|
11753
|
-
idx.set(m.id.toLowerCase(), m.id);
|
|
11754
|
-
idx.set(m.label.toLowerCase(), m.id);
|
|
11755
|
-
for (const a of m.aliases ?? []) {
|
|
11756
|
-
idx.set(a.toLowerCase(), m.id);
|
|
11757
|
-
}
|
|
11758
|
-
}
|
|
11759
|
-
idx.set("signal-to-noise", "signal_to_noise");
|
|
11760
|
-
idx.set("signal:noise", "signal_to_noise");
|
|
11761
|
-
idx.set("flow-rate", "flow_rate");
|
|
11762
|
-
idx.set("drop-rate", "drop_rate");
|
|
11763
|
-
idx.set("thread-depth", "thread_depth");
|
|
11764
|
-
return idx;
|
|
11765
|
-
})();
|
|
11766
|
-
SAAS_METRIC_IDS = SAAS.map((m) => m.id);
|
|
11767
|
-
GATING_LAYER_VISUAL = {
|
|
11768
|
-
kind: "layer_stack",
|
|
11769
|
-
caption: "Gating order. First red in layer order wins. If none, first yellow. If none, lowest score.",
|
|
11770
|
-
layers: [
|
|
11771
|
-
{ label: "L1 Freshness", highlight: true },
|
|
11772
|
-
{ label: "L2 Flow Rate \xB7 Drop Rate" },
|
|
11773
|
-
{ label: "L3 Signal:Noise" },
|
|
11774
|
-
{ label: "L4 Thread Depth" }
|
|
11775
|
-
]
|
|
11776
|
-
};
|
|
11777
|
-
}
|
|
11778
|
-
});
|
|
11779
|
-
|
|
11780
|
-
// src/data/guide-slides.ts
|
|
11781
|
-
function normalize(raw) {
|
|
11782
|
-
return raw.trim().toLowerCase().replace(/\s+/g, "-").replace(/_/g, "-");
|
|
11783
|
-
}
|
|
11784
|
-
function isGuideSectionQuery(raw) {
|
|
11785
|
-
const q = normalize(raw);
|
|
11786
|
-
return GUIDE_SECTION_ALIASES.includes(q);
|
|
11787
|
-
}
|
|
11788
|
-
function resolveGuideId(raw) {
|
|
11789
|
-
const q = normalize(raw);
|
|
11790
|
-
if (!q) return null;
|
|
11791
|
-
if (isGuideSectionQuery(q)) return GUIDE_DECK_IDS[0];
|
|
11792
|
-
const underscored = q.replace(/-/g, "_");
|
|
11793
|
-
if (GUIDE_DECK_IDS.includes(underscored)) {
|
|
11794
|
-
return underscored;
|
|
11795
|
-
}
|
|
11796
|
-
return SLIDE_ALIASES[q] ?? SLIDE_ALIASES[underscored] ?? null;
|
|
11797
|
-
}
|
|
11798
|
-
function getGuideSlide(id) {
|
|
11799
|
-
return BY_ID2.get(id);
|
|
11800
|
-
}
|
|
11801
|
-
function listGuideSlides() {
|
|
11802
|
-
return GUIDE_DECK_IDS.map((id) => BY_ID2.get(id));
|
|
11803
|
-
}
|
|
11804
|
-
var GUIDE_DECK_IDS, GUIDE_SECTION_ALIASES, SLIDE_ALIASES, TALK, ASK, HANDOFF, LOOP, BY_ID2;
|
|
11805
|
-
var init_guide_slides = __esm({
|
|
11806
|
-
"src/data/guide-slides.ts"() {
|
|
11807
|
-
"use strict";
|
|
11808
|
-
GUIDE_DECK_IDS = ["talk", "ask", "handoff", "loop"];
|
|
11809
|
-
GUIDE_SECTION_ALIASES = [
|
|
11810
|
-
"guide",
|
|
11811
|
-
"how",
|
|
11812
|
-
"howto",
|
|
11813
|
-
"how-to",
|
|
11814
|
-
"how_to",
|
|
11815
|
-
"use",
|
|
11816
|
-
"using",
|
|
11817
|
-
"nav",
|
|
11818
|
-
"navigate",
|
|
11819
|
-
"userflow",
|
|
11820
|
-
"user-guide",
|
|
11821
|
-
"userguide"
|
|
11822
|
-
];
|
|
11823
|
-
SLIDE_ALIASES = {
|
|
11824
|
-
talk: "talk",
|
|
11825
|
-
chat: "talk",
|
|
11826
|
-
type: "talk",
|
|
11827
|
-
speak: "talk",
|
|
11828
|
-
conversation: "talk",
|
|
11829
|
-
ask: "ask",
|
|
11830
|
-
connect: "ask",
|
|
11831
|
-
glossary: "ask",
|
|
11832
|
-
keyless: "ask",
|
|
11833
|
-
enter: "ask",
|
|
11834
|
-
definitions: "ask",
|
|
11835
|
-
handoff: "handoff",
|
|
11836
|
-
inbox: "handoff",
|
|
11837
|
-
skill: "handoff",
|
|
11838
|
-
claude: "handoff",
|
|
11839
|
-
ship: "handoff",
|
|
11840
|
-
pickup: "handoff",
|
|
11841
|
-
chatgpt: "handoff",
|
|
11842
|
-
cursor: "handoff",
|
|
11843
|
-
export: "handoff",
|
|
11844
|
-
loop: "loop",
|
|
11845
|
-
strategy: "loop",
|
|
11846
|
-
strategist: "loop",
|
|
11847
|
-
playbook: "loop",
|
|
11848
|
-
remember: "loop",
|
|
11849
|
-
sessions: "loop",
|
|
11850
|
-
progress: "loop"
|
|
11851
|
-
};
|
|
11852
|
-
TALK = {
|
|
11853
|
-
id: "talk",
|
|
11854
|
-
label: "Talk to NTRP",
|
|
11855
|
-
tagline: "Type English. Confirm the scope. Load data. Then listen.",
|
|
11856
|
-
visual: {
|
|
11857
|
-
kind: "funnel",
|
|
11858
|
-
caption: "Bare Enter submits the dim \u23CE hint at these gates. If nothing is armed, Enter does nothing.",
|
|
11859
|
-
funnel: [
|
|
11860
|
-
{ label: "Type a question", widthPct: 100 },
|
|
11861
|
-
{ label: "\u23CE yes (scope)", widthPct: 78 },
|
|
11862
|
-
{ label: "Load data", widthPct: 56 },
|
|
11863
|
-
{ label: "\u23CE go ahead", widthPct: 34 }
|
|
11864
|
-
]
|
|
11865
|
-
},
|
|
11866
|
-
lines: [
|
|
11867
|
-
'You do not need a slash. Type the question you need. Examples: "is our retention real for the board?" or "pipeline health".',
|
|
11868
|
-
"",
|
|
11869
|
-
"NTRP restates the question as a scope card. Confirm with \u23CE yes, or type yes. NTRP listens. NTRP does not invent a different question.",
|
|
11870
|
-
"",
|
|
11871
|
-
"When the dataset is empty, type \u23CE use demo data. Or paste a CSV path. Or type /ingest. When the gap card shows that the formulas can compute, type \u23CE go ahead.",
|
|
11872
|
-
"",
|
|
11873
|
-
"Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it."
|
|
11874
|
-
],
|
|
11875
|
-
deepdive: [
|
|
11876
|
-
"Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
|
|
11877
|
-
'Type "use demo data" to load the hidden_crisis scenario. Company profile is optional. In scripts, type /demo --no-profile.',
|
|
11878
|
-
'After compute, the prompt becomes ask \u203A. Brief is the default depth. Type "go deep" when you want the long read.',
|
|
11879
|
-
"Type /home to show phase status. Type /help to list conversation shortcuts. /help does not list every command."
|
|
11880
|
-
]
|
|
11881
|
-
};
|
|
11882
|
-
ASK = {
|
|
11883
|
-
id: "ask",
|
|
11884
|
-
label: "After the numbers",
|
|
11885
|
-
tagline: 'Glossary is free. "Our ARR" is compute. Narrative needs /connect.',
|
|
11886
|
-
visual: {
|
|
11887
|
-
kind: "split",
|
|
11888
|
-
caption: "Possessives such as our, my, and the team's skip the glossary. They go to compute or scope.",
|
|
11889
|
-
bars: [
|
|
11890
|
-
{ label: "what is ARR?", value: 55, tone: "accent" },
|
|
11891
|
-
{ label: "our ARR", value: 45, tone: "neutral" }
|
|
11892
|
-
]
|
|
11893
|
-
},
|
|
11894
|
-
lines: [
|
|
11895
|
-
'"what is ARR?" and "how is freshness calculated?" answer from the built-in glossary. No key. No data.',
|
|
11896
|
-
"",
|
|
11897
|
-
'"what is our ARR?" and "why is this red?" need a loaded dataset. AI findings and /ask need a stored key. Type /connect and paste any provider key. NTRP detects it.',
|
|
11898
|
-
"",
|
|
11899
|
-
"Type /deepdive to replay this tour. Type /deepdive freshness for one slide. You can also type nrr, arr, or another id. Type /deepdive list for the catalog. Type /deepdive guide for this how-to section.",
|
|
11900
|
-
"",
|
|
11901
|
-
"When the prompt shows a dim \u23CE hint, bare Enter submits that action. Examples: \u23CE yes, \u23CE use demo data, \u23CE go ahead, \u23CE /connect. If there is no hint, Enter does nothing."
|
|
11902
|
-
],
|
|
11903
|
-
deepdive: [
|
|
11904
|
-
"Type /connect ollama for a keyless local model. Type /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
|
|
11905
|
-
"Type /model refresh to re-discover models. A retired model self-heals on the first 404.",
|
|
11906
|
-
"First-run skip of this tour does not complete it. A home \u2691 chip can bring you back after the first analysis.",
|
|
11907
|
-
"Tab completes /deepdive <metric>. Finding cards and playbook triggers also link here."
|
|
11908
|
-
]
|
|
11909
|
-
};
|
|
11910
|
-
HANDOFF = {
|
|
11911
|
-
id: "handoff",
|
|
11912
|
-
label: "Ship work to Claude",
|
|
11913
|
-
tagline: "Teach the inbox once. Later, tell Claude to pick it up.",
|
|
11914
|
-
visual: {
|
|
11915
|
-
kind: "layer_stack",
|
|
11916
|
-
caption: "You and Claude find and open the file. NTRP only writes.",
|
|
11917
|
-
layers: [
|
|
11918
|
-
{ label: "Set a pickup folder (demo, onboard, or /inbox set)" },
|
|
11919
|
-
{ label: "Paste the finder skill once (/inbox skill)", highlight: true },
|
|
11920
|
-
{ label: "/handoff writes files. There is no paste block." },
|
|
11921
|
-
{ label: '"Pick up the latest NTRP handoff."' }
|
|
11922
|
-
]
|
|
11923
|
-
},
|
|
11924
|
-
lines: [
|
|
11925
|
-
'Type "ship a board deck" or type /handoff. Files land in ~/Documents/Claude/ntrp-inbox by default. Type /inbox set to change the folder.',
|
|
11926
|
-
"",
|
|
11927
|
-
"During demo or company setup, or any time, type /inbox skill. Paste a standing finder into Claude, ChatGPT, or Cursor once. That skill tells the tool to follow latest-handoff.md and INDEX.md.",
|
|
11928
|
-
"",
|
|
11929
|
-
'After that, each /handoff prints "Handoff ready". You will not get a paste block every write. Tell Claude: pick up the latest NTRP handoff.',
|
|
11930
|
-
"",
|
|
11931
|
-
"If you skipped this step, NTRP asks once when you load your own data. Skip then, and type /inbox set ~/Documents/Claude/ntrp-inbox. Then type /inbox skill. Type /handoff skill to reprint the same finder."
|
|
11932
|
-
],
|
|
11933
|
-
deepdive: [
|
|
11934
|
-
"Inbox copies: latest-handoff.md, latest-pickup.md, SKILL.md, INDEX.md. Dated files also live under ~/.ntrp/exports/.",
|
|
11935
|
-
"Type /exports to list writes. Type /inbox show to print the folder and the latest pointer. Type /handoff --print to show the prompt body in the terminal.",
|
|
11936
|
-
"Audience-framed Metric definitions append to decks and reports. Claude then has the same glossary you walked.",
|
|
11937
|
-
"Type /end to close a session. NTRP writes a transcript plus a 1-page context brief under ~/.ntrp/sessions/."
|
|
11938
|
-
]
|
|
11939
|
-
};
|
|
11940
|
-
LOOP = {
|
|
11941
|
-
id: "loop",
|
|
11942
|
-
label: "Stay in the loop",
|
|
11943
|
-
tagline: "Diagnose \u2192 plan \u2192 review \u2192 remember. NTRP learns your business.",
|
|
11944
|
-
visual: {
|
|
11945
|
-
kind: "layer_stack",
|
|
11946
|
-
caption: "Stethoscope, not hospital. Observe and recommend. Never prescribe surgery.",
|
|
11947
|
-
layers: [
|
|
11948
|
-
{ label: "Listen (/diagnose, /metrics)" },
|
|
11949
|
-
{ label: 'Plan ("how should we fix this?" / /strategy)', highlight: true },
|
|
11950
|
-
{ label: "Review (/strategy review, /playbook)" },
|
|
11951
|
-
{ label: "Remember (/remember, /rate, ANALYST.md)" }
|
|
11952
|
-
]
|
|
11920
|
+
meaning: "Board question: which single stage is starving everything downstream?",
|
|
11921
|
+
expert_read: "Find the one stage where conversion collapses. That is the process problem. Everything downstream is starvation.",
|
|
11922
|
+
deepdive: [
|
|
11923
|
+
"Best with stage_history metadata. Otherwise treat this as a proxy.",
|
|
11924
|
+
"Pairs with Flow Rate stage-age cuts."
|
|
11925
|
+
],
|
|
11926
|
+
visual: {
|
|
11927
|
+
kind: "funnel",
|
|
11928
|
+
caption: "Example: find the collapse",
|
|
11929
|
+
funnel: [
|
|
11930
|
+
{ label: "Stage 1\u21922", widthPct: 100 },
|
|
11931
|
+
{ label: "Stage 2\u21923", widthPct: 72 },
|
|
11932
|
+
{ label: "Stage 3\u21924", widthPct: 28 },
|
|
11933
|
+
{ label: "Stage 4\u2192Close", widthPct: 18 }
|
|
11934
|
+
]
|
|
11935
|
+
},
|
|
11936
|
+
audience: {
|
|
11937
|
+
board: "Stage Conversion names the bottleneck stage. One collapse starves every stage after it.",
|
|
11938
|
+
ops: "Prefer stage_history advances. Fix the collapse stage before you coach downstream reps."
|
|
11939
|
+
},
|
|
11940
|
+
aliases: ["stage conversion", "stage advance", "conversion by stage"]
|
|
11953
11941
|
},
|
|
11954
|
-
|
|
11955
|
-
|
|
11956
|
-
"",
|
|
11957
|
-
|
|
11958
|
-
"",
|
|
11959
|
-
"
|
|
11960
|
-
"",
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
"
|
|
11967
|
-
"
|
|
11968
|
-
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11978
|
-
|
|
11979
|
-
|
|
11980
|
-
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
}
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
}
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
lines.push(`${marker2} ${truncateVisible(text, inner - 4)}`);
|
|
12063
|
-
if (i < layers.length - 1) {
|
|
12064
|
-
lines.push(chalk9.dim(" \u2502"));
|
|
12065
|
-
}
|
|
12066
|
-
}
|
|
12067
|
-
return lines;
|
|
12068
|
-
}
|
|
12069
|
-
function renderLevers(levers, inner) {
|
|
12070
|
-
const cell = Math.floor((inner - 9) / 2);
|
|
12071
|
-
const lines = [];
|
|
12072
|
-
lines.push(chalk9.dim("\u250C" + "\u2500".repeat(Math.max(8, cell)) + "\u2510 \u250C" + "\u2500".repeat(Math.max(8, cell)) + "\u2510"));
|
|
12073
|
-
for (let i = 0; i < levers.length; i += 2) {
|
|
12074
|
-
const a = padRight(truncateVisible(levers[i] ?? "", cell - 2), cell - 2);
|
|
12075
|
-
const b = padRight(truncateVisible(levers[i + 1] ?? "", cell - 2), cell - 2);
|
|
12076
|
-
lines.push(
|
|
12077
|
-
`${paint("accent", "\u2502")} ${a} ${paint("accent", "\u2502")} ${paint("accent", "\u2502")} ${b} ${paint("accent", "\u2502")}`
|
|
12078
|
-
);
|
|
12079
|
-
}
|
|
12080
|
-
lines.push(chalk9.dim("\u2514" + "\u2500".repeat(Math.max(8, cell)) + "\u2518 \u2514" + "\u2500".repeat(Math.max(8, cell)) + "\u2518"));
|
|
12081
|
-
return lines;
|
|
12082
|
-
}
|
|
12083
|
-
function renderGauge(score, inner, status) {
|
|
12084
|
-
const st = status ?? (score >= 80 ? "green" : score >= 60 ? "yellow" : "red");
|
|
12085
|
-
const bar = scoreBar(score, st === "neutral" ? "yellow" : st, Math.min(28, inner - 12));
|
|
12086
|
-
return [`${statusDot(st)} ${bar} ${bold(String(Math.round(score)))}`];
|
|
12087
|
-
}
|
|
12088
|
-
function renderSplit(bars, inner) {
|
|
12089
|
-
if (bars.length < 2) return renderBars(bars, inner);
|
|
12090
|
-
const total = bars.reduce((s, b) => s + b.value, 0) || 100;
|
|
12091
|
-
const width = Math.max(16, Math.min(40, inner - 4));
|
|
12092
|
-
let used = 0;
|
|
12093
|
-
const parts = [];
|
|
12094
|
-
for (let i = 0; i < bars.length; i++) {
|
|
12095
|
-
const b = bars[i];
|
|
12096
|
-
const w = i === bars.length - 1 ? width - used : Math.max(1, Math.round(b.value / total * width));
|
|
12097
|
-
used += w;
|
|
12098
|
-
parts.push(tonePaint(b.tone)("\u2588".repeat(w)));
|
|
12099
|
-
}
|
|
12100
|
-
const legend = bars.map((b) => `${tonePaint(b.tone)("\u25CF")} ${b.label} ${chalk9.dim(`${Math.round(b.value)}%`)}`).join(" ");
|
|
12101
|
-
return [parts.join(""), truncateVisible(legend, inner)];
|
|
12102
|
-
}
|
|
12103
|
-
function renderVisual(visual, inner) {
|
|
12104
|
-
const lines = [];
|
|
12105
|
-
switch (visual.kind) {
|
|
12106
|
-
case "bars":
|
|
12107
|
-
if (visual.bars?.length) lines.push(...renderBars(visual.bars, inner));
|
|
12108
|
-
break;
|
|
12109
|
-
case "funnel":
|
|
12110
|
-
if (visual.funnel?.length) lines.push(...renderFunnel(visual.funnel, inner));
|
|
12111
|
-
break;
|
|
12112
|
-
case "waterfall":
|
|
12113
|
-
if (visual.waterfall?.length) lines.push(...renderWaterfall(visual.waterfall, inner));
|
|
12114
|
-
break;
|
|
12115
|
-
case "layer_stack":
|
|
12116
|
-
if (visual.layers?.length) lines.push(...renderLayerStack(visual.layers, inner));
|
|
12117
|
-
break;
|
|
12118
|
-
case "levers":
|
|
12119
|
-
if (visual.levers?.length) lines.push(...renderLevers(visual.levers, inner));
|
|
12120
|
-
break;
|
|
12121
|
-
case "gauge":
|
|
12122
|
-
lines.push(...renderGauge(visual.gauge ?? 50, inner));
|
|
12123
|
-
if (visual.bars?.length) lines.push(...renderBars(visual.bars, inner));
|
|
12124
|
-
break;
|
|
12125
|
-
case "split":
|
|
12126
|
-
if (visual.bars?.length) lines.push(...renderSplit(visual.bars, inner));
|
|
12127
|
-
break;
|
|
12128
|
-
case "none":
|
|
12129
|
-
default:
|
|
12130
|
-
break;
|
|
12131
|
-
}
|
|
12132
|
-
if (visual.caption) {
|
|
12133
|
-
lines.push(chalk9.dim(truncateVisible(visual.caption, inner)));
|
|
12134
|
-
}
|
|
12135
|
-
return lines;
|
|
12136
|
-
}
|
|
12137
|
-
function kindBadge(explainer) {
|
|
12138
|
-
if (explainer.kind === "vital") return badge("VITAL", "accent");
|
|
12139
|
-
return badge("SAAS", "info");
|
|
12140
|
-
}
|
|
12141
|
-
function statusToTone(status) {
|
|
12142
|
-
if (status === "green") return "success";
|
|
12143
|
-
if (status === "yellow") return "warning";
|
|
12144
|
-
if (status === "red") return "error";
|
|
12145
|
-
return "muted";
|
|
12146
|
-
}
|
|
12147
|
-
function pushWrapped(out, text, inner, indent = "") {
|
|
12148
|
-
for (const w of wrapWords(text, inner - indent.length)) {
|
|
12149
|
-
out.push(indent + w);
|
|
12150
|
-
}
|
|
12151
|
-
}
|
|
12152
|
-
function buildSlideContent(explainer, opts = {}) {
|
|
12153
|
-
const width = slideCardWidth();
|
|
12154
|
-
const inner = width - 4;
|
|
12155
|
-
const lines = [];
|
|
12156
|
-
const title = opts.titleOverride ?? (explainer ? explainer.label : "Metrics");
|
|
12157
|
-
if (explainer) {
|
|
12158
|
-
const headerBits = [
|
|
12159
|
-
kindBadge(explainer),
|
|
12160
|
-
chalk9.dim(explainer.group)
|
|
12161
|
-
];
|
|
12162
|
-
if (opts.index != null && opts.total != null) {
|
|
12163
|
-
headerBits.push(chalk9.dim(`slide ${opts.index}/${opts.total}`));
|
|
12164
|
-
}
|
|
12165
|
-
lines.push(headerBits.join(chalk9.dim(" \xB7 ")));
|
|
12166
|
-
lines.push(chalk9.dim(explainer.tagline));
|
|
12167
|
-
lines.push("");
|
|
12168
|
-
if (opts.live) {
|
|
12169
|
-
const live = opts.live;
|
|
12170
|
-
const tone = statusToTone(live.status);
|
|
12171
|
-
const liveLine = `${statusDot(live.status)} ${bold("Your reading:")} ${bold(live.formatted)} ` + badge(String(live.status), tone);
|
|
12172
|
-
lines.push(truncateVisible(liveLine, inner));
|
|
12173
|
-
if (live.dollarLine) {
|
|
12174
|
-
lines.push(chalk9.dim(` $ ${live.dollarLine}`));
|
|
12175
|
-
}
|
|
12176
|
-
if (live.benchmarkNote) {
|
|
12177
|
-
lines.push(chalk9.dim(` ${live.benchmarkNote}`));
|
|
12178
|
-
}
|
|
12179
|
-
lines.push("");
|
|
12180
|
-
} else {
|
|
12181
|
-
const hint = explainer.benchmarkHint?.(opts.motion);
|
|
12182
|
-
if (hint) {
|
|
12183
|
-
lines.push(chalk9.dim(`Benchmark \xB7 ${hint}`));
|
|
12184
|
-
lines.push("");
|
|
12185
|
-
}
|
|
12186
|
-
}
|
|
12187
|
-
const visual = opts.visualOverride ?? explainer.visual;
|
|
12188
|
-
const visLines = renderVisual(visual, inner);
|
|
12189
|
-
if (visLines.length) {
|
|
12190
|
-
lines.push(...visLines);
|
|
12191
|
-
lines.push("");
|
|
12192
|
-
}
|
|
12193
|
-
lines.push(sectionHeading("What it means"));
|
|
12194
|
-
pushWrapped(lines, explainer.meaning, inner, " ");
|
|
12195
|
-
lines.push("");
|
|
12196
|
-
if (!opts.skipFormula) {
|
|
12197
|
-
lines.push(sectionHeading("How it's calculated"));
|
|
12198
|
-
pushWrapped(lines, explainer.how_computed, inner, " ");
|
|
12199
|
-
for (const f of explainer.formula_lines) {
|
|
12200
|
-
lines.push(paint("accent", ` ${f}`));
|
|
12201
|
-
}
|
|
12202
|
-
lines.push("");
|
|
12203
|
-
}
|
|
12204
|
-
if (opts.deepdive) {
|
|
12205
|
-
lines.push(sectionHeading("Deep dive"));
|
|
12206
|
-
pushWrapped(lines, explainer.expert_read, inner, " ");
|
|
12207
|
-
lines.push("");
|
|
12208
|
-
for (const bullet of explainer.deepdive) {
|
|
12209
|
-
pushWrapped(lines, `\xB7 ${bullet}`, inner, " ");
|
|
11942
|
+
// —— Unit economics ——
|
|
11943
|
+
{
|
|
11944
|
+
id: "ltv_proxy",
|
|
11945
|
+
kind: "saas",
|
|
11946
|
+
label: "LTV (Proxy)",
|
|
11947
|
+
group: "Unit Economics",
|
|
11948
|
+
tagline: "Rough lifetime value from deal size and GRR.",
|
|
11949
|
+
how_computed: "avgDeal / ((100 \u2212 GRR) / 100) when GRR is below 100. This metric is unavailable when GRR is 100% or more, or missing.",
|
|
11950
|
+
formula_lines: [
|
|
11951
|
+
"LTV \u2248 avgDeal / churnRate",
|
|
11952
|
+
"churnRate = (100 \u2212 GRR) / 100 (requires GRR < 100)"
|
|
11953
|
+
],
|
|
11954
|
+
meaning: "Board question: what is a customer roughly worth over their life?",
|
|
11955
|
+
expert_read: "This is a proxy, not a cohort LTV. Use it for direction, not capital allocation.",
|
|
11956
|
+
deepdive: [
|
|
11957
|
+
"Unavailable when GRR is 100 or more, or missing.",
|
|
11958
|
+
"Pairs with CAC for LTV:CAC when spend data exists."
|
|
11959
|
+
],
|
|
11960
|
+
visual: {
|
|
11961
|
+
kind: "gauge",
|
|
11962
|
+
caption: "Example LTV proxy. Directional only.",
|
|
11963
|
+
gauge: 68
|
|
11964
|
+
},
|
|
11965
|
+
audience: {
|
|
11966
|
+
board: "LTV Proxy is directional from deal size and GRR. It is not a cohort LTV. Use it for orientation, not capital decisions.",
|
|
11967
|
+
ops: "avgDeal / ((100\u2212GRR)/100). Needs GRR below 100. Prefer cohort math when billing data arrives."
|
|
11968
|
+
},
|
|
11969
|
+
aliases: ["ltv", "lifetime value"]
|
|
11970
|
+
},
|
|
11971
|
+
{
|
|
11972
|
+
id: "cac",
|
|
11973
|
+
kind: "saas",
|
|
11974
|
+
label: "CAC",
|
|
11975
|
+
group: "Unit Economics",
|
|
11976
|
+
tagline: "Customer acquisition cost. This metric needs spend data.",
|
|
11977
|
+
how_computed: "This metric requires campaign or sales spend data. It is currently unavailable on CRM-only datasets.",
|
|
11978
|
+
formula_lines: ["CAC = sales & marketing spend / new customers", "(requires spend data \u2014 not in CRM-only exports)"],
|
|
11979
|
+
meaning: "Board question: what does a new logo cost to win?",
|
|
11980
|
+
expert_read: "Without spend, NTRP cannot invent CAC. Connect campaign spend or finance exports to unlock unit economics.",
|
|
11981
|
+
deepdive: [
|
|
11982
|
+
"Always unavailable on CRM-only demos. That is expected.",
|
|
11983
|
+
"Unlocks LTV:CAC, Payback, and Magic Number when spend lands."
|
|
11984
|
+
],
|
|
11985
|
+
visual: { kind: "none", caption: "Needs campaign spend or a finance export" },
|
|
11986
|
+
audience: {
|
|
11987
|
+
board: "CAC is locked until spend data is connected. CRM alone cannot price acquisition.",
|
|
11988
|
+
ops: "Bring campaign or S&M spend. Until then, unit-econ metrics stay unavailable by design."
|
|
11989
|
+
},
|
|
11990
|
+
aliases: ["customer acquisition cost", "acquisition cost"]
|
|
11991
|
+
},
|
|
11992
|
+
{
|
|
11993
|
+
id: "ltv_cac_ratio",
|
|
11994
|
+
kind: "saas",
|
|
11995
|
+
label: "LTV:CAC Ratio",
|
|
11996
|
+
group: "Unit Economics",
|
|
11997
|
+
tagline: "Does acquisition spend return enough value?",
|
|
11998
|
+
how_computed: "LTV proxy divided by CAC. Unavailable without spend (CAC).",
|
|
11999
|
+
formula_lines: ["LTV:CAC = LTV_proxy / CAC", "(requires CAC)"],
|
|
12000
|
+
meaning: "Board question: do we earn enough lifetime value per dollar spent to acquire?",
|
|
12001
|
+
expert_read: "In the efficiency era, boards weigh LTV:CAC and payback as heavily as growth. Classic rule of thumb is 3x or more. Motion and gross margin matter.",
|
|
12002
|
+
deepdive: ["Blocked on CAC. See LTV Proxy and CAC."],
|
|
12003
|
+
visual: { kind: "none", caption: "This metric needs CAC (spend data)" },
|
|
12004
|
+
audience: {
|
|
12005
|
+
board: "LTV:CAC is the acquisition ROI story. It is available once spend is connected.",
|
|
12006
|
+
ops: "LTV_proxy / CAC. Unlocks with spend import."
|
|
12007
|
+
},
|
|
12008
|
+
aliases: ["ltv cac", "ltv/cac", "ltv to cac"]
|
|
12009
|
+
},
|
|
12010
|
+
{
|
|
12011
|
+
id: "payback_months",
|
|
12012
|
+
kind: "saas",
|
|
12013
|
+
label: "Payback Months",
|
|
12014
|
+
group: "Unit Economics",
|
|
12015
|
+
tagline: "How many months to recover CAC?",
|
|
12016
|
+
how_computed: "This metric requires CAC and spend. Lower is better.",
|
|
12017
|
+
formula_lines: ["Payback \u2248 CAC / (monthly gross profit per customer)", "(requires spend data)"],
|
|
12018
|
+
meaning: "Board question: how fast does acquisition spend return? Efficiency-era prior: under 18 months is often healthy.",
|
|
12019
|
+
expert_read: "Boards now weigh payback (under 18 months) as heavily as growth in many motions.",
|
|
12020
|
+
deepdive: ["Blocked on CAC. Benchmarks exist per motion once data lands."],
|
|
12021
|
+
visual: { kind: "none", caption: "This metric needs CAC (spend data)" },
|
|
12022
|
+
audience: {
|
|
12023
|
+
board: "Payback is how fast CAC returns. Efficiency-era boards often want under 18 months.",
|
|
12024
|
+
ops: "Requires CAC. Motion green and yellow bands apply when available."
|
|
12025
|
+
},
|
|
12026
|
+
aliases: ["payback", "cac payback"],
|
|
12027
|
+
benchmarkHint: (motion) => monthsBand(motion)
|
|
12028
|
+
},
|
|
12029
|
+
{
|
|
12030
|
+
id: "magic_number",
|
|
12031
|
+
kind: "saas",
|
|
12032
|
+
label: "Magic Number",
|
|
12033
|
+
group: "Unit Economics",
|
|
12034
|
+
tagline: "Sales efficiency: net new ARR per sales dollar.",
|
|
12035
|
+
how_computed: "This metric requires sales spend. Classic form: net new ARR (quarter) / prior-quarter S&M spend.",
|
|
12036
|
+
formula_lines: [
|
|
12037
|
+
"Magic Number \u2248 Net New ARR(q) / S&M spend(q\u22121)",
|
|
12038
|
+
"(requires spend data)"
|
|
12039
|
+
],
|
|
12040
|
+
meaning: "Board question: how efficiently does sales spend produce net new ARR? Prior: above 0.75 is often healthy. Above 1 is strong.",
|
|
12041
|
+
expert_read: "In the efficiency era, a magic number above 0.75 is weighed alongside growth. Without spend, NTRP keeps this unavailable. NTRP does not invent it.",
|
|
12042
|
+
deepdive: ["Blocked on spend. Benchmarks per motion are ready when data lands."],
|
|
12043
|
+
visual: { kind: "none", caption: "This metric needs S&M spend data" },
|
|
12044
|
+
audience: {
|
|
12045
|
+
board: "Magic Number prices sales efficiency. It is available once S&M spend is connected.",
|
|
12046
|
+
ops: "Net new ARR / prior S&M. Motion benchmarks apply when spend lands."
|
|
12047
|
+
},
|
|
12048
|
+
aliases: ["sales magic number", "sales efficiency magic number"],
|
|
12049
|
+
benchmarkHint: (motion) => magicBand(motion)
|
|
12210
12050
|
}
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
|
|
12051
|
+
];
|
|
12052
|
+
METRIC_DEFINITIONS = [...VITALS, ...SAAS];
|
|
12053
|
+
CORE_DECK_IDS = [
|
|
12054
|
+
"arr",
|
|
12055
|
+
"nrr",
|
|
12056
|
+
"grr",
|
|
12057
|
+
"pipeline_coverage",
|
|
12058
|
+
"win_rate",
|
|
12059
|
+
"pipeline_velocity",
|
|
12060
|
+
"freshness",
|
|
12061
|
+
"flow_rate",
|
|
12062
|
+
"drop_rate",
|
|
12063
|
+
"signal_to_noise",
|
|
12064
|
+
"thread_depth"
|
|
12065
|
+
];
|
|
12066
|
+
BY_ID = new Map(METRIC_DEFINITIONS.map((m) => [m.id, m]));
|
|
12067
|
+
ALIAS_INDEX = (() => {
|
|
12068
|
+
const idx = /* @__PURE__ */ new Map();
|
|
12069
|
+
for (const m of METRIC_DEFINITIONS) {
|
|
12070
|
+
idx.set(m.id.toLowerCase(), m.id);
|
|
12071
|
+
idx.set(m.label.toLowerCase(), m.id);
|
|
12072
|
+
for (const a of m.aliases ?? []) {
|
|
12073
|
+
idx.set(a.toLowerCase(), m.id);
|
|
12074
|
+
}
|
|
12216
12075
|
}
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12230
|
-
|
|
12076
|
+
idx.set("signal-to-noise", "signal_to_noise");
|
|
12077
|
+
idx.set("signal:noise", "signal_to_noise");
|
|
12078
|
+
idx.set("flow-rate", "flow_rate");
|
|
12079
|
+
idx.set("drop-rate", "drop_rate");
|
|
12080
|
+
idx.set("thread-depth", "thread_depth");
|
|
12081
|
+
return idx;
|
|
12082
|
+
})();
|
|
12083
|
+
SAAS_METRIC_IDS = SAAS.map((m) => m.id);
|
|
12084
|
+
GATING_LAYER_VISUAL = {
|
|
12085
|
+
kind: "layer_stack",
|
|
12086
|
+
caption: "Gating order. First red in layer order wins. If none, first yellow. If none, lowest score.",
|
|
12087
|
+
layers: [
|
|
12088
|
+
{ label: "L1 Freshness", highlight: true },
|
|
12089
|
+
{ label: "L2 Flow Rate \xB7 Drop Rate" },
|
|
12090
|
+
{ label: "L3 Signal:Noise" },
|
|
12091
|
+
{ label: "L4 Thread Depth" }
|
|
12092
|
+
]
|
|
12093
|
+
};
|
|
12231
12094
|
}
|
|
12232
|
-
|
|
12095
|
+
});
|
|
12096
|
+
|
|
12097
|
+
// src/data/guide-slides.ts
|
|
12098
|
+
function normalize(raw) {
|
|
12099
|
+
return raw.trim().toLowerCase().replace(/\s+/g, "-").replace(/_/g, "-");
|
|
12233
12100
|
}
|
|
12234
|
-
function
|
|
12235
|
-
const
|
|
12236
|
-
|
|
12237
|
-
const termW = termWidth();
|
|
12238
|
-
const outerPad = " ".repeat(Math.max(0, Math.floor((termW - width) / 2)));
|
|
12239
|
-
const out = [];
|
|
12240
|
-
out.push("");
|
|
12241
|
-
out.push(`${outerPad}${border(`\u256D${"\u2500".repeat(width - 2)}\u256E`)}`);
|
|
12242
|
-
out.push(
|
|
12243
|
-
`${outerPad}${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`
|
|
12244
|
-
);
|
|
12245
|
-
out.push(`${outerPad}${border(`\u251C${"\u2500".repeat(width - 2)}\u2524`)}`);
|
|
12246
|
-
for (const row of lines) {
|
|
12247
|
-
out.push(
|
|
12248
|
-
`${outerPad}${border("\u2502 ")}${padRight(truncateVisible(row, inner), inner)}${border(" \u2502")}`
|
|
12249
|
-
);
|
|
12250
|
-
}
|
|
12251
|
-
const footer = opts.footer ?? (opts.deepdive ? `\u23CE next \xB7 b back \xB7 q quit` : `\u23CE next \xB7 /deepdive more \xB7 b back \xB7 q quit`);
|
|
12252
|
-
out.push(`${outerPad}${border(`\u251C${"\u2500".repeat(width - 2)}\u2524`)}`);
|
|
12253
|
-
out.push(
|
|
12254
|
-
`${outerPad}${border("\u2502 ")}${padRight(chalk9.dim(truncateVisible(footer, inner)), inner)}${border(" \u2502")}`
|
|
12255
|
-
);
|
|
12256
|
-
out.push(`${outerPad}${border(`\u2570${"\u2500".repeat(width - 2)}\u256F`)}`);
|
|
12257
|
-
out.push("");
|
|
12258
|
-
if (opts.asLines) return out;
|
|
12259
|
-
for (const line of out) console.log(line);
|
|
12101
|
+
function isGuideSectionQuery(raw) {
|
|
12102
|
+
const q = normalize(raw);
|
|
12103
|
+
return GUIDE_SECTION_ALIASES.includes(q);
|
|
12260
12104
|
}
|
|
12261
|
-
function
|
|
12262
|
-
const
|
|
12263
|
-
|
|
12264
|
-
|
|
12105
|
+
function resolveGuideId(raw) {
|
|
12106
|
+
const q = normalize(raw);
|
|
12107
|
+
if (!q) return null;
|
|
12108
|
+
if (isGuideSectionQuery(q)) return GUIDE_DECK_IDS[0];
|
|
12109
|
+
const underscored = q.replace(/-/g, "_");
|
|
12110
|
+
if (GUIDE_DECK_IDS.includes(underscored)) {
|
|
12111
|
+
return underscored;
|
|
12265
12112
|
}
|
|
12266
|
-
return
|
|
12267
|
-
}
|
|
12268
|
-
function printExplainerCatalogLine(explainer) {
|
|
12269
|
-
const kind = explainer.kind === "vital" ? paint("accent", "vital") : chalk9.dim("saas ");
|
|
12270
|
-
console.log(
|
|
12271
|
-
` ${kind} ${bold(explainer.id.padEnd(20))} ${chalk9.dim(explainer.label)} \u2014 ${chalk9.dim(explainer.tagline)}`
|
|
12272
|
-
);
|
|
12113
|
+
return SLIDE_ALIASES[q] ?? SLIDE_ALIASES[underscored] ?? null;
|
|
12273
12114
|
}
|
|
12274
|
-
function
|
|
12275
|
-
|
|
12276
|
-
` ${paint("accent", "how ")} ${bold(slide.id.padEnd(20))} ${chalk9.dim(slide.label)} \u2014 ${chalk9.dim(slide.tagline)}`
|
|
12277
|
-
);
|
|
12115
|
+
function getGuideSlide(id) {
|
|
12116
|
+
return BY_ID2.get(id);
|
|
12278
12117
|
}
|
|
12279
|
-
function
|
|
12280
|
-
|
|
12281
|
-
console.log(
|
|
12282
|
-
" " + chalk9.dim("How this number works: ") + paint("accent", `/deepdive ${metricId}`) + chalk9.dim(` \u2014 ${name}`)
|
|
12283
|
-
);
|
|
12284
|
-
console.log();
|
|
12118
|
+
function listGuideSlides() {
|
|
12119
|
+
return GUIDE_DECK_IDS.map((id) => BY_ID2.get(id));
|
|
12285
12120
|
}
|
|
12286
|
-
var
|
|
12287
|
-
|
|
12121
|
+
var GUIDE_DECK_IDS, GUIDE_SECTION_ALIASES, SLIDE_ALIASES, TALK, ASK, HANDOFF, LOOP, BY_ID2;
|
|
12122
|
+
var init_guide_slides = __esm({
|
|
12123
|
+
"src/data/guide-slides.ts"() {
|
|
12288
12124
|
"use strict";
|
|
12289
|
-
|
|
12290
|
-
|
|
12125
|
+
GUIDE_DECK_IDS = ["talk", "ask", "handoff", "loop"];
|
|
12126
|
+
GUIDE_SECTION_ALIASES = [
|
|
12127
|
+
"guide",
|
|
12128
|
+
"how",
|
|
12129
|
+
"howto",
|
|
12130
|
+
"how-to",
|
|
12131
|
+
"how_to",
|
|
12132
|
+
"use",
|
|
12133
|
+
"using",
|
|
12134
|
+
"nav",
|
|
12135
|
+
"navigate",
|
|
12136
|
+
"userflow",
|
|
12137
|
+
"user-guide",
|
|
12138
|
+
"userguide"
|
|
12139
|
+
];
|
|
12140
|
+
SLIDE_ALIASES = {
|
|
12141
|
+
talk: "talk",
|
|
12142
|
+
chat: "talk",
|
|
12143
|
+
type: "talk",
|
|
12144
|
+
speak: "talk",
|
|
12145
|
+
conversation: "talk",
|
|
12146
|
+
ask: "ask",
|
|
12147
|
+
connect: "ask",
|
|
12148
|
+
glossary: "ask",
|
|
12149
|
+
keyless: "ask",
|
|
12150
|
+
enter: "ask",
|
|
12151
|
+
definitions: "ask",
|
|
12152
|
+
handoff: "handoff",
|
|
12153
|
+
inbox: "handoff",
|
|
12154
|
+
skill: "handoff",
|
|
12155
|
+
claude: "handoff",
|
|
12156
|
+
ship: "handoff",
|
|
12157
|
+
pickup: "handoff",
|
|
12158
|
+
chatgpt: "handoff",
|
|
12159
|
+
cursor: "handoff",
|
|
12160
|
+
export: "handoff",
|
|
12161
|
+
loop: "loop",
|
|
12162
|
+
strategy: "loop",
|
|
12163
|
+
strategist: "loop",
|
|
12164
|
+
playbook: "loop",
|
|
12165
|
+
remember: "loop",
|
|
12166
|
+
sessions: "loop",
|
|
12167
|
+
progress: "loop"
|
|
12168
|
+
};
|
|
12169
|
+
TALK = {
|
|
12170
|
+
id: "talk",
|
|
12171
|
+
label: "Talk to NTRP",
|
|
12172
|
+
tagline: "Type English. Confirm the scope. Load data. Then listen.",
|
|
12173
|
+
visual: {
|
|
12174
|
+
kind: "funnel",
|
|
12175
|
+
caption: "Bare Enter submits the dim \u23CE hint at these gates. If nothing is armed, Enter does nothing.",
|
|
12176
|
+
funnel: [
|
|
12177
|
+
{ label: "Type a question", widthPct: 100 },
|
|
12178
|
+
{ label: "\u23CE yes (scope)", widthPct: 78 },
|
|
12179
|
+
{ label: "Load data", widthPct: 56 },
|
|
12180
|
+
{ label: "\u23CE go ahead", widthPct: 34 }
|
|
12181
|
+
]
|
|
12182
|
+
},
|
|
12183
|
+
lines: [
|
|
12184
|
+
'You do not need a slash. Type the question you need. Examples: "is our retention real for the board?" or "pipeline health".',
|
|
12185
|
+
"",
|
|
12186
|
+
"NTRP restates the question as a scope card. Confirm with \u23CE yes, or type yes. NTRP listens. NTRP does not invent a different question.",
|
|
12187
|
+
"",
|
|
12188
|
+
"When the dataset is empty, type \u23CE use demo data. Or paste a CSV path. Or type /ingest. When the gap card shows that the formulas can compute, type \u23CE go ahead.",
|
|
12189
|
+
"",
|
|
12190
|
+
"Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it."
|
|
12191
|
+
],
|
|
12192
|
+
deepdive: [
|
|
12193
|
+
"Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
|
|
12194
|
+
'Type "use demo data" to load the hidden_crisis scenario. Company profile is optional. In scripts, type /demo --no-profile.',
|
|
12195
|
+
'After compute, the prompt becomes ask \u203A. Brief is the default depth. Type "go deep" when you want the long read.',
|
|
12196
|
+
"Type /home to show phase status. Type /help to list conversation shortcuts. /help does not list every command."
|
|
12197
|
+
]
|
|
12198
|
+
};
|
|
12199
|
+
ASK = {
|
|
12200
|
+
id: "ask",
|
|
12201
|
+
label: "After the numbers",
|
|
12202
|
+
tagline: 'Glossary is free. "Our ARR" is compute. Narrative needs /connect.',
|
|
12203
|
+
visual: {
|
|
12204
|
+
kind: "split",
|
|
12205
|
+
caption: "Possessives such as our, my, and the team's skip the glossary. They go to compute or scope.",
|
|
12206
|
+
bars: [
|
|
12207
|
+
{ label: "what is ARR?", value: 55, tone: "accent" },
|
|
12208
|
+
{ label: "our ARR", value: 45, tone: "neutral" }
|
|
12209
|
+
]
|
|
12210
|
+
},
|
|
12211
|
+
lines: [
|
|
12212
|
+
'"what is ARR?" and "how is freshness calculated?" answer from the built-in glossary. No key. No data.',
|
|
12213
|
+
"",
|
|
12214
|
+
'"what is our ARR?" and "why is this red?" need a loaded dataset. AI findings and /ask need a stored key. Type /connect and paste any provider key. NTRP detects it.',
|
|
12215
|
+
"",
|
|
12216
|
+
"Type /deepdive to replay this tour. Type /deepdive freshness for one slide. You can also type nrr, arr, or another id. Type /deepdive list for the catalog. Type /deepdive guide for this how-to section.",
|
|
12217
|
+
"",
|
|
12218
|
+
"When the prompt shows a dim \u23CE hint, bare Enter submits that action. Examples: \u23CE yes, \u23CE use demo data, \u23CE go ahead, \u23CE /connect. If there is no hint, Enter does nothing."
|
|
12219
|
+
],
|
|
12220
|
+
deepdive: [
|
|
12221
|
+
"Type /connect ollama for a keyless local model. Type /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
|
|
12222
|
+
"Type /model refresh to re-discover models. A retired model self-heals on the first 404.",
|
|
12223
|
+
"First-run skip of this tour does not complete it. A home \u2691 chip can bring you back after the first analysis.",
|
|
12224
|
+
"Tab completes /deepdive <metric>. Finding cards and playbook triggers also link here."
|
|
12225
|
+
]
|
|
12226
|
+
};
|
|
12227
|
+
HANDOFF = {
|
|
12228
|
+
id: "handoff",
|
|
12229
|
+
label: "Ship work to Claude",
|
|
12230
|
+
tagline: "Teach the inbox once. Later, tell Claude to pick it up.",
|
|
12231
|
+
visual: {
|
|
12232
|
+
kind: "layer_stack",
|
|
12233
|
+
caption: "You and Claude find and open the file. NTRP only writes.",
|
|
12234
|
+
layers: [
|
|
12235
|
+
{ label: "Set a pickup folder (demo, onboard, or /inbox set)" },
|
|
12236
|
+
{ label: "Paste the finder skill once (/inbox skill)", highlight: true },
|
|
12237
|
+
{ label: "/handoff writes files. There is no paste block." },
|
|
12238
|
+
{ label: '"Pick up the latest NTRP handoff."' }
|
|
12239
|
+
]
|
|
12240
|
+
},
|
|
12241
|
+
lines: [
|
|
12242
|
+
'Type "ship a board deck" or type /handoff. Files land in ~/Documents/Claude/ntrp-inbox by default. Type /inbox set to change the folder.',
|
|
12243
|
+
"",
|
|
12244
|
+
"During demo or company setup, or any time, type /inbox skill. Paste a standing finder into Claude, ChatGPT, or Cursor once. That skill tells the tool to follow latest-handoff.md and INDEX.md.",
|
|
12245
|
+
"",
|
|
12246
|
+
'After that, each /handoff prints "Handoff ready". You will not get a paste block every write. Tell Claude: pick up the latest NTRP handoff.',
|
|
12247
|
+
"",
|
|
12248
|
+
"If you skipped this step, NTRP asks once when you load your own data. Skip then, and type /inbox set ~/Documents/Claude/ntrp-inbox. Then type /inbox skill. Type /handoff skill to reprint the same finder."
|
|
12249
|
+
],
|
|
12250
|
+
deepdive: [
|
|
12251
|
+
"Inbox copies: latest-handoff.md, latest-pickup.md, SKILL.md, INDEX.md. Dated files also live under ~/.ntrp/exports/.",
|
|
12252
|
+
"Type /exports to list writes. Type /inbox show to print the folder and the latest pointer. Type /handoff --print to show the prompt body in the terminal.",
|
|
12253
|
+
"Audience-framed Metric definitions append to decks and reports. Claude then has the same glossary you walked.",
|
|
12254
|
+
"Type /end to close a session. NTRP writes a transcript plus a 1-page context brief under ~/.ntrp/sessions/."
|
|
12255
|
+
]
|
|
12256
|
+
};
|
|
12257
|
+
LOOP = {
|
|
12258
|
+
id: "loop",
|
|
12259
|
+
label: "Stay in the loop",
|
|
12260
|
+
tagline: "Diagnose \u2192 plan \u2192 review \u2192 remember. NTRP learns your business.",
|
|
12261
|
+
visual: {
|
|
12262
|
+
kind: "layer_stack",
|
|
12263
|
+
caption: "Stethoscope, not hospital. Observe and recommend. Never prescribe surgery.",
|
|
12264
|
+
layers: [
|
|
12265
|
+
{ label: "Listen (/diagnose, /metrics)" },
|
|
12266
|
+
{ label: 'Plan ("how should we fix this?" / /strategy)', highlight: true },
|
|
12267
|
+
{ label: "Review (/strategy review, /playbook)" },
|
|
12268
|
+
{ label: "Remember (/remember, /rate, ANALYST.md)" }
|
|
12269
|
+
]
|
|
12270
|
+
},
|
|
12271
|
+
lines: [
|
|
12272
|
+
'Type "how should we fix this?" or type /strategy. NTRP builds a measurable plan with milestones and dollar-anchored ranges. Type /strategy review to check those against later vital signs.',
|
|
12273
|
+
"",
|
|
12274
|
+
"When a vital sign is red, type /playbook. NTRP names the matching play. Outcomes from reviews annotate the catalog with what hit here.",
|
|
12275
|
+
"",
|
|
12276
|
+
"Type /remember to store a durable fact. Type /rate bad <reason> to write a calibration. Optional ~/.ntrp/ANALYST.md is standing voice and priorities. It never overrides safety rules.",
|
|
12277
|
+
"",
|
|
12278
|
+
"Commands: /sessions show, /session <id> pickup, /end (transcript plus brief), /home, /progress, /help."
|
|
12279
|
+
],
|
|
12280
|
+
deepdive: [
|
|
12281
|
+
'Type "build me a game plan" before analysis. NTRP queues the strategist and resumes after compute.',
|
|
12282
|
+
"Interactive sessions distill 5 or fewer durable facts on close when an LLM key is stored. One-shot commands do not bank hours or distill.",
|
|
12283
|
+
"Type /scratch to factory-reset data and config. progress.json and install.json survive unless you pass --include-progress.",
|
|
12284
|
+
"Credits in /progress accrue in interactive ntrp only."
|
|
12285
|
+
]
|
|
12286
|
+
};
|
|
12287
|
+
BY_ID2 = /* @__PURE__ */ new Map([
|
|
12288
|
+
[TALK.id, TALK],
|
|
12289
|
+
[ASK.id, ASK],
|
|
12290
|
+
[HANDOFF.id, HANDOFF],
|
|
12291
|
+
[LOOP.id, LOOP]
|
|
12292
|
+
]);
|
|
12291
12293
|
}
|
|
12292
12294
|
});
|
|
12293
12295
|
|
|
@@ -12296,6 +12298,7 @@ var metric_tour_exports = {};
|
|
|
12296
12298
|
__export(metric_tour_exports, {
|
|
12297
12299
|
METRICS_TOUR_MILESTONE_ID: () => METRICS_TOUR_MILESTONE_ID,
|
|
12298
12300
|
describeTourDeck: () => describeTourDeck,
|
|
12301
|
+
firstRunTourDefaultChoice: () => firstRunTourDefaultChoice,
|
|
12299
12302
|
getDeepdiveNudge: () => getDeepdiveNudge,
|
|
12300
12303
|
hasAnalysisForDeepdiveNudge: () => hasAnalysisForDeepdiveNudge,
|
|
12301
12304
|
hasCompletedMetricsTour: () => hasCompletedMetricsTour,
|
|
@@ -12343,6 +12346,7 @@ function hasAnalysisForDeepdiveNudge(ctx) {
|
|
|
12343
12346
|
}
|
|
12344
12347
|
}
|
|
12345
12348
|
function getDeepdiveNudge(ctx) {
|
|
12349
|
+
if (!hasValidLicense()) return null;
|
|
12346
12350
|
if (hasCompletedMetricsTour() || hasSeenDeepdiveHomeNudge()) return null;
|
|
12347
12351
|
if (!hasAnalysisForDeepdiveNudge(ctx)) return null;
|
|
12348
12352
|
return {
|
|
@@ -12622,6 +12626,9 @@ function printGuideCard(slideId, opts = {}) {
|
|
|
12622
12626
|
});
|
|
12623
12627
|
return true;
|
|
12624
12628
|
}
|
|
12629
|
+
function firstRunTourDefaultChoice() {
|
|
12630
|
+
return loadProgress().milestones_unlocked.includes(METRICS_TOUR_MILESTONE_ID) ? "skip" : "tour";
|
|
12631
|
+
}
|
|
12625
12632
|
async function offerFirstRunTour(ctx) {
|
|
12626
12633
|
if (hasCompletedMetricsTour()) return false;
|
|
12627
12634
|
console.log();
|
|
@@ -12629,6 +12636,10 @@ async function offerFirstRunTour(ctx) {
|
|
|
12629
12636
|
console.log(
|
|
12630
12637
|
" " + chalk10.dim("SaaS numbers, five vitals, then how to talk to NTRP and ship work to Claude.")
|
|
12631
12638
|
);
|
|
12639
|
+
const tourDefault = firstRunTourDefaultChoice();
|
|
12640
|
+
if (tourDefault === "skip") {
|
|
12641
|
+
console.log(" " + chalk10.dim("You have taken this tour before."));
|
|
12642
|
+
}
|
|
12632
12643
|
console.log();
|
|
12633
12644
|
const session = createPromptSession(ctx.rl, ctx);
|
|
12634
12645
|
try {
|
|
@@ -12646,7 +12657,7 @@ async function offerFirstRunTour(ctx) {
|
|
|
12646
12657
|
description: "Re-open anytime with /deepdive or /deepdive guide"
|
|
12647
12658
|
}
|
|
12648
12659
|
],
|
|
12649
|
-
{ default:
|
|
12660
|
+
{ default: tourDefault }
|
|
12650
12661
|
);
|
|
12651
12662
|
if (choice === "skip") {
|
|
12652
12663
|
markMetricsTourSkipped();
|
|
@@ -12671,6 +12682,7 @@ var init_metric_tour = __esm({
|
|
|
12671
12682
|
init_prompts();
|
|
12672
12683
|
init_store();
|
|
12673
12684
|
init_progress();
|
|
12685
|
+
init_activation();
|
|
12674
12686
|
init_profile();
|
|
12675
12687
|
init_metric_definitions();
|
|
12676
12688
|
init_guide_slides();
|
|
@@ -15284,7 +15296,7 @@ var init_explore_mode = __esm({
|
|
|
15284
15296
|
|
|
15285
15297
|
// src/conversation/recommended-action.ts
|
|
15286
15298
|
function resolveRecommendedAction(ctx) {
|
|
15287
|
-
if (!hasValidLicense()) return { submit: "/
|
|
15299
|
+
if (!hasValidLicense()) return { submit: "/activate", hint: "/activate" };
|
|
15288
15300
|
const phase = resolveConversationPhase(ctx);
|
|
15289
15301
|
switch (phase) {
|
|
15290
15302
|
case "explore":
|
|
@@ -15345,6 +15357,9 @@ function consumeOrientEmptyEnterCoach(ctx) {
|
|
|
15345
15357
|
if (resolveConversationPhase(ctx) !== "orient") return null;
|
|
15346
15358
|
if (ctx.orientEmptyEnterSeen) return null;
|
|
15347
15359
|
ctx.orientEmptyEnterSeen = true;
|
|
15360
|
+
if (!hasValidLicense()) {
|
|
15361
|
+
return "Type /activate to paste a key. Type /checkout if you need to sign up.";
|
|
15362
|
+
}
|
|
15348
15363
|
return "Type a question, type use demo data, or type /deepdive. Enter alone does not start a step here.";
|
|
15349
15364
|
}
|
|
15350
15365
|
function formatPhaseLabel(phase) {
|
|
@@ -15405,6 +15420,7 @@ var init_phase = __esm({
|
|
|
15405
15420
|
init_explore_mode();
|
|
15406
15421
|
init_session_state();
|
|
15407
15422
|
init_theme();
|
|
15423
|
+
init_activation();
|
|
15408
15424
|
init_recommended_action();
|
|
15409
15425
|
PROMPT_LABELS = {
|
|
15410
15426
|
orient: "\u203A",
|
|
@@ -19738,9 +19754,11 @@ var inbox_setup_exports = {};
|
|
|
19738
19754
|
__export(inbox_setup_exports, {
|
|
19739
19755
|
maybeOfferInboxOnProduction: () => maybeOfferInboxOnProduction,
|
|
19740
19756
|
offerInboxSkillSetup: () => offerInboxSkillSetup,
|
|
19757
|
+
reuseInboxFolderIfPresent: () => reuseInboxFolderIfPresent,
|
|
19741
19758
|
shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
|
|
19742
19759
|
});
|
|
19743
19760
|
import chalk19 from "chalk";
|
|
19761
|
+
import { existsSync as existsSync22 } from "fs";
|
|
19744
19762
|
function markDemoOffered() {
|
|
19745
19763
|
setConfigValue("ai-inbox-nudge-seen", "true");
|
|
19746
19764
|
}
|
|
@@ -19769,6 +19787,23 @@ function printSkipHint(beat) {
|
|
|
19769
19787
|
" " + chalk19.dim("Skipped. NTRP will not ask again. Type ") + setCmd + chalk19.dim(" then ") + skillCmd + chalk19.dim(" at any time.")
|
|
19770
19788
|
);
|
|
19771
19789
|
}
|
|
19790
|
+
async function reuseInboxFolderIfPresent(session, beat, folderPath = defaultAiInboxDir()) {
|
|
19791
|
+
if (getAiInboxDir()) return false;
|
|
19792
|
+
if (!existsSync22(folderPath)) return false;
|
|
19793
|
+
console.log(" " + chalk19.dim("Pickup folder still on disk: ") + folderPath);
|
|
19794
|
+
const reuse = await session.confirm("Reuse this pickup folder?", true);
|
|
19795
|
+
if (!reuse) return false;
|
|
19796
|
+
const resolved = setAiInboxDir(folderPath);
|
|
19797
|
+
markDemoOffered();
|
|
19798
|
+
if (beat === "production") markProductionOffered();
|
|
19799
|
+
console.log();
|
|
19800
|
+
console.log(" " + paint("accent", "Inbox ready") + chalk19.dim(" ") + resolved);
|
|
19801
|
+
console.log(
|
|
19802
|
+
" " + chalk19.dim("Folder reused. Type ") + paint("accent", "/inbox skill") + chalk19.dim(" to print the finder again.")
|
|
19803
|
+
);
|
|
19804
|
+
console.log();
|
|
19805
|
+
return true;
|
|
19806
|
+
}
|
|
19772
19807
|
async function offerInboxSkillSetup(session, opts = {}) {
|
|
19773
19808
|
const beat = opts.beat ?? "production";
|
|
19774
19809
|
if (!shouldOfferInboxSkillSetup(beat)) return;
|
|
@@ -19783,6 +19818,7 @@ async function offerInboxSkillSetup(session, opts = {}) {
|
|
|
19783
19818
|
console.log(" " + chalk19.dim("You skipped this during demo."));
|
|
19784
19819
|
}
|
|
19785
19820
|
console.log();
|
|
19821
|
+
if (await reuseInboxFolderIfPresent(session, beat)) return;
|
|
19786
19822
|
const want = await session.confirm("Set a pickup folder for Claude, ChatGPT, or Cursor?", true);
|
|
19787
19823
|
if (!want) {
|
|
19788
19824
|
if (beat === "demo") markDemoOffered();
|
|
@@ -19853,7 +19889,7 @@ __export(ingest_exports, {
|
|
|
19853
19889
|
handler: () => handler2
|
|
19854
19890
|
});
|
|
19855
19891
|
import chalk20 from "chalk";
|
|
19856
|
-
import { readFileSync as readFileSync19, existsSync as
|
|
19892
|
+
import { readFileSync as readFileSync19, existsSync as existsSync23 } from "fs";
|
|
19857
19893
|
import { basename as basename6 } from "path";
|
|
19858
19894
|
async function handler2(args, ctx) {
|
|
19859
19895
|
const { positional, flags } = parseArgs2(args, [
|
|
@@ -19877,7 +19913,7 @@ async function handler2(args, ctx) {
|
|
|
19877
19913
|
console.error(chalk20.dim(" /ingest --demo [--scenario <name>]"));
|
|
19878
19914
|
process.exit(1);
|
|
19879
19915
|
}
|
|
19880
|
-
if (!
|
|
19916
|
+
if (!existsSync23(file)) {
|
|
19881
19917
|
console.error(chalk20.red(` File not found: ${file}`));
|
|
19882
19918
|
process.exit(1);
|
|
19883
19919
|
}
|
|
@@ -21783,9 +21819,9 @@ async function handleGetSessionBrief(input) {
|
|
|
21783
21819
|
if (!target) {
|
|
21784
21820
|
return { error: `No session matching "${raw}".` };
|
|
21785
21821
|
}
|
|
21786
|
-
const { existsSync:
|
|
21822
|
+
const { existsSync: existsSync34, readFileSync: readFileSync24 } = await import("fs");
|
|
21787
21823
|
const briefPath = contextDocPathForSession2(target.id);
|
|
21788
|
-
if (!
|
|
21824
|
+
if (!existsSync34(briefPath)) {
|
|
21789
21825
|
return {
|
|
21790
21826
|
session_id: target.id,
|
|
21791
21827
|
error: "No context brief on disk for this session (created before brief storage existed).",
|
|
@@ -24192,7 +24228,7 @@ __export(new_exports, {
|
|
|
24192
24228
|
handler: () => handler6
|
|
24193
24229
|
});
|
|
24194
24230
|
import chalk25 from "chalk";
|
|
24195
|
-
import { existsSync as
|
|
24231
|
+
import { existsSync as existsSync24 } from "fs";
|
|
24196
24232
|
import { basename as basename7 } from "path";
|
|
24197
24233
|
async function handler6(args, ctx) {
|
|
24198
24234
|
const { positional, flags } = parseArgs2(args, ["demo", "empty", "list-scenarios", "regen-taxonomy"]);
|
|
@@ -24214,7 +24250,7 @@ async function handler6(args, ctx) {
|
|
|
24214
24250
|
console.error(chalk25.red(" Usage: /new <file.csv> | --demo [--scenario <name>] | --empty [--lens health|metrics]"));
|
|
24215
24251
|
return;
|
|
24216
24252
|
}
|
|
24217
|
-
if (source.kind === "file" && !
|
|
24253
|
+
if (source.kind === "file" && !existsSync24(source.path)) {
|
|
24218
24254
|
console.error(chalk25.red(` File not found: ${source.path}`));
|
|
24219
24255
|
return;
|
|
24220
24256
|
}
|
|
@@ -24441,7 +24477,7 @@ __export(end_exports, {
|
|
|
24441
24477
|
handler: () => handler7
|
|
24442
24478
|
});
|
|
24443
24479
|
import chalk26 from "chalk";
|
|
24444
|
-
import { existsSync as
|
|
24480
|
+
import { existsSync as existsSync25 } from "fs";
|
|
24445
24481
|
async function handler7(args, ctx) {
|
|
24446
24482
|
if (args.length > 0) {
|
|
24447
24483
|
console.error(chalk26.red(" Usage: /end"));
|
|
@@ -24478,10 +24514,10 @@ async function handler7(args, ctx) {
|
|
|
24478
24514
|
if (summary) {
|
|
24479
24515
|
console.log(" " + chalk26.dim(summary));
|
|
24480
24516
|
}
|
|
24481
|
-
if (
|
|
24517
|
+
if (existsSync25(transcriptPathForSession(endedId))) {
|
|
24482
24518
|
console.log(" " + chalk26.dim("Transcript: ") + chalk26.dim(transcriptPathForSession(endedId)));
|
|
24483
24519
|
}
|
|
24484
|
-
if (
|
|
24520
|
+
if (existsSync25(contextDocPathForSession(endedId))) {
|
|
24485
24521
|
console.log(" " + chalk26.dim("Context brief: ") + chalk26.dim(contextDocPathForSession(endedId)));
|
|
24486
24522
|
}
|
|
24487
24523
|
console.log();
|
|
@@ -24503,7 +24539,7 @@ __export(session_exports, {
|
|
|
24503
24539
|
});
|
|
24504
24540
|
import chalk27 from "chalk";
|
|
24505
24541
|
import { join as join23 } from "path";
|
|
24506
|
-
import { existsSync as
|
|
24542
|
+
import { existsSync as existsSync26 } from "fs";
|
|
24507
24543
|
async function handler8(args, ctx) {
|
|
24508
24544
|
const sub = args[0];
|
|
24509
24545
|
if (!sub) return listSessionsView(ctx);
|
|
@@ -24641,7 +24677,7 @@ async function pickUp(idArg, ctx) {
|
|
|
24641
24677
|
);
|
|
24642
24678
|
}
|
|
24643
24679
|
const contextPath = contextDocPathForSession(target.id);
|
|
24644
|
-
if (
|
|
24680
|
+
if (existsSync26(contextPath)) {
|
|
24645
24681
|
console.log(" " + chalk27.dim("Context brief: ") + chalk27.dim(contextPath));
|
|
24646
24682
|
}
|
|
24647
24683
|
console.log();
|
|
@@ -30053,13 +30089,36 @@ __export(activate_exports, {
|
|
|
30053
30089
|
handler: () => handler23
|
|
30054
30090
|
});
|
|
30055
30091
|
import chalk45 from "chalk";
|
|
30092
|
+
async function afterLicenseSuccess(ctx) {
|
|
30093
|
+
if (ctx.oneShot) return;
|
|
30094
|
+
const { resumeSetupAfterLicense: resumeSetupAfterLicense2 } = await Promise.resolve().then(() => (init_first_run(), first_run_exports));
|
|
30095
|
+
await resumeSetupAfterLicense2(ctx);
|
|
30096
|
+
}
|
|
30056
30097
|
async function handler23(args, ctx) {
|
|
30057
30098
|
const { positional } = parseArgs2(args);
|
|
30058
30099
|
const key = positional[0];
|
|
30059
30100
|
if (!key) {
|
|
30060
|
-
|
|
30061
|
-
|
|
30062
|
-
|
|
30101
|
+
if (ctx.oneShot || !process.stdin.isTTY) {
|
|
30102
|
+
console.error(chalk45.red("\n Usage: /activate <key>"));
|
|
30103
|
+
console.error(chalk45.dim(" Or type /activate in ntrp to paste a key.\n"));
|
|
30104
|
+
if (ctx.oneShot) process.exit(1);
|
|
30105
|
+
return;
|
|
30106
|
+
}
|
|
30107
|
+
if (hasValidLicense()) {
|
|
30108
|
+
console.log();
|
|
30109
|
+
console.log(chalk45.green(" A license is already active."));
|
|
30110
|
+
console.log();
|
|
30111
|
+
await afterLicenseSuccess(ctx);
|
|
30112
|
+
return;
|
|
30113
|
+
}
|
|
30114
|
+
console.log();
|
|
30115
|
+
console.log(
|
|
30116
|
+
" " + chalk45.dim("Paste a trial key or a Pro key. Type ") + paint("accent", "/checkout") + chalk45.dim(" if you need to sign up.")
|
|
30117
|
+
);
|
|
30118
|
+
console.log();
|
|
30119
|
+
const activated = await promptForLicenseKey(ctx);
|
|
30120
|
+
if (!activated) return;
|
|
30121
|
+
await afterLicenseSuccess(ctx);
|
|
30063
30122
|
return;
|
|
30064
30123
|
}
|
|
30065
30124
|
try {
|
|
@@ -30074,10 +30133,7 @@ async function handler23(args, ctx) {
|
|
|
30074
30133
|
console.log(chalk45.green(`
|
|
30075
30134
|
License activated: ${result.message}
|
|
30076
30135
|
`));
|
|
30077
|
-
|
|
30078
|
-
const { replayPendingBlockedLine: replayPendingBlockedLine2 } = await Promise.resolve().then(() => (init_dispatch(), dispatch_exports));
|
|
30079
|
-
await replayPendingBlockedLine2(ctx);
|
|
30080
|
-
}
|
|
30136
|
+
await afterLicenseSuccess(ctx);
|
|
30081
30137
|
} catch (err) {
|
|
30082
30138
|
const message = err instanceof Error ? err.message : "License activation failed";
|
|
30083
30139
|
console.error(chalk45.red(`
|
|
@@ -30092,6 +30148,9 @@ var init_activate = __esm({
|
|
|
30092
30148
|
"use strict";
|
|
30093
30149
|
init_verify();
|
|
30094
30150
|
init_argparse();
|
|
30151
|
+
init_activation();
|
|
30152
|
+
init_upgrade();
|
|
30153
|
+
init_theme();
|
|
30095
30154
|
}
|
|
30096
30155
|
});
|
|
30097
30156
|
|
|
@@ -30130,8 +30189,8 @@ async function handler24(args, ctx) {
|
|
|
30130
30189
|
return;
|
|
30131
30190
|
}
|
|
30132
30191
|
if (!ctx.oneShot) {
|
|
30133
|
-
const {
|
|
30134
|
-
await
|
|
30192
|
+
const { resumeSetupAfterLicense: resumeSetupAfterLicense2 } = await Promise.resolve().then(() => (init_first_run(), first_run_exports));
|
|
30193
|
+
await resumeSetupAfterLicense2(ctx);
|
|
30135
30194
|
}
|
|
30136
30195
|
}
|
|
30137
30196
|
var init_upgrade2 = __esm({
|
|
@@ -30159,7 +30218,7 @@ var init_checkout = __esm({
|
|
|
30159
30218
|
});
|
|
30160
30219
|
|
|
30161
30220
|
// src/services/setup.ts
|
|
30162
|
-
import { existsSync as
|
|
30221
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync19 } from "fs";
|
|
30163
30222
|
import { join as join29 } from "path";
|
|
30164
30223
|
function setupCheck() {
|
|
30165
30224
|
const home = ntrpHome();
|
|
@@ -32157,7 +32216,7 @@ __export(sessions_exports, {
|
|
|
32157
32216
|
handler: () => handler35
|
|
32158
32217
|
});
|
|
32159
32218
|
import chalk61 from "chalk";
|
|
32160
|
-
import { existsSync as
|
|
32219
|
+
import { existsSync as existsSync28 } from "fs";
|
|
32161
32220
|
async function handler35(args, _ctx) {
|
|
32162
32221
|
const sub = args[0] ?? "list";
|
|
32163
32222
|
if (sub === "list" || !args[0]) {
|
|
@@ -32264,12 +32323,12 @@ function showSession(idArg) {
|
|
|
32264
32323
|
console.log();
|
|
32265
32324
|
const transcriptPath = transcriptPathForSession(session.id);
|
|
32266
32325
|
const contextPath = contextDocPathForSession(session.id);
|
|
32267
|
-
if (
|
|
32326
|
+
if (existsSync28(transcriptPath) || existsSync28(contextPath)) {
|
|
32268
32327
|
console.log(" " + chalk61.dim("\u2500".repeat(40)));
|
|
32269
|
-
if (
|
|
32328
|
+
if (existsSync28(contextPath)) {
|
|
32270
32329
|
console.log(" " + chalk61.dim("Context brief: ") + chalk61.dim(contextPath));
|
|
32271
32330
|
}
|
|
32272
|
-
if (
|
|
32331
|
+
if (existsSync28(transcriptPath)) {
|
|
32273
32332
|
console.log(" " + chalk61.dim("Full transcript: ") + chalk61.dim(transcriptPath));
|
|
32274
32333
|
}
|
|
32275
32334
|
console.log();
|
|
@@ -33143,27 +33202,20 @@ var init_model = __esm({
|
|
|
33143
33202
|
});
|
|
33144
33203
|
|
|
33145
33204
|
// src/config/update-check.ts
|
|
33146
|
-
|
|
33147
|
-
__export(update_check_exports, {
|
|
33148
|
-
invalidateUpdateCheckCache: () => invalidateUpdateCheckCache,
|
|
33149
|
-
isCacheFresh: () => isCacheFresh,
|
|
33150
|
-
loadUpdateCheckCache: () => loadUpdateCheckCache,
|
|
33151
|
-
saveUpdateCheckCache: () => saveUpdateCheckCache
|
|
33152
|
-
});
|
|
33153
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync21, unlinkSync as unlinkSync5, writeFileSync as writeFileSync20 } from "fs";
|
|
33205
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync18, readFileSync as readFileSync21, unlinkSync as unlinkSync5, writeFileSync as writeFileSync20 } from "fs";
|
|
33154
33206
|
import { join as join33 } from "path";
|
|
33155
33207
|
function cachePath2() {
|
|
33156
33208
|
return join33(ntrpHome(), "update-check.json");
|
|
33157
33209
|
}
|
|
33158
33210
|
function ensureDir7() {
|
|
33159
33211
|
const dir = ntrpHome();
|
|
33160
|
-
if (!
|
|
33212
|
+
if (!existsSync29(dir)) {
|
|
33161
33213
|
mkdirSync18(dir, { recursive: true });
|
|
33162
33214
|
}
|
|
33163
33215
|
}
|
|
33164
33216
|
function loadUpdateCheckCache() {
|
|
33165
33217
|
const path = cachePath2();
|
|
33166
|
-
if (!
|
|
33218
|
+
if (!existsSync29(path)) return null;
|
|
33167
33219
|
try {
|
|
33168
33220
|
const parsed = JSON.parse(readFileSync21(path, "utf-8"));
|
|
33169
33221
|
if (!parsed || typeof parsed !== "object" || typeof parsed.lastCheck !== "number" || typeof parsed.latestVersion !== "string") {
|
|
@@ -33184,7 +33236,7 @@ function isCacheFresh(cache2, ttlMs = CACHE_TTL_MS2) {
|
|
|
33184
33236
|
}
|
|
33185
33237
|
function invalidateUpdateCheckCache() {
|
|
33186
33238
|
const path = cachePath2();
|
|
33187
|
-
if (
|
|
33239
|
+
if (existsSync29(path)) {
|
|
33188
33240
|
unlinkSync5(path);
|
|
33189
33241
|
}
|
|
33190
33242
|
}
|
|
@@ -33198,7 +33250,7 @@ var init_update_check = __esm({
|
|
|
33198
33250
|
});
|
|
33199
33251
|
|
|
33200
33252
|
// src/version.ts
|
|
33201
|
-
import { existsSync as
|
|
33253
|
+
import { existsSync as existsSync30, readFileSync as readFileSync22 } from "fs";
|
|
33202
33254
|
import { dirname as dirname6, join as join34 } from "path";
|
|
33203
33255
|
import { fileURLToPath } from "url";
|
|
33204
33256
|
function getInstalledVersion() {
|
|
@@ -33206,7 +33258,7 @@ function getInstalledVersion() {
|
|
|
33206
33258
|
const start = dirname6(fileURLToPath(import.meta.url));
|
|
33207
33259
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
33208
33260
|
const path = join34(start, rel);
|
|
33209
|
-
if (!
|
|
33261
|
+
if (!existsSync30(path)) continue;
|
|
33210
33262
|
try {
|
|
33211
33263
|
const pkg = JSON.parse(readFileSync22(path, "utf-8"));
|
|
33212
33264
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
@@ -33230,10 +33282,13 @@ var init_version = __esm({
|
|
|
33230
33282
|
var registry_exports = {};
|
|
33231
33283
|
__export(registry_exports, {
|
|
33232
33284
|
NPM_PACKAGE: () => NPM_PACKAGE,
|
|
33285
|
+
applyUpdateCheckResult: () => applyUpdateCheckResult,
|
|
33233
33286
|
checkForUpdate: () => checkForUpdate,
|
|
33234
33287
|
fetchLatestVersion: () => fetchLatestVersion,
|
|
33235
33288
|
formatUpdateNudge: () => formatUpdateNudge,
|
|
33236
|
-
|
|
33289
|
+
hydrateUpdateAvailableFromCache: () => hydrateUpdateAvailableFromCache,
|
|
33290
|
+
isNewerVersion: () => isNewerVersion,
|
|
33291
|
+
startBackgroundUpdateCheck: () => startBackgroundUpdateCheck
|
|
33237
33292
|
});
|
|
33238
33293
|
function registryUrl() {
|
|
33239
33294
|
return process.env.NTRP_REGISTRY_URL ?? "https://registry.npmjs.org/@sonnechasser/ntrp/latest";
|
|
@@ -33252,7 +33307,27 @@ function isNewerVersion(latest, current) {
|
|
|
33252
33307
|
return lPatch > cPatch;
|
|
33253
33308
|
}
|
|
33254
33309
|
function formatUpdateNudge(current, latest) {
|
|
33255
|
-
return `\u26A1 NTRP v${latest} available (
|
|
33310
|
+
return `\u26A1 NTRP v${latest} available (running v${current}) \u2014 type /update`;
|
|
33311
|
+
}
|
|
33312
|
+
function hydrateUpdateAvailableFromCache(current) {
|
|
33313
|
+
const cached2 = loadUpdateCheckCache();
|
|
33314
|
+
if (!cached2?.latestVersion) return void 0;
|
|
33315
|
+
if (!isNewerVersion(cached2.latestVersion, current)) return void 0;
|
|
33316
|
+
return { current, latest: cached2.latestVersion };
|
|
33317
|
+
}
|
|
33318
|
+
function applyUpdateCheckResult(ctx, result) {
|
|
33319
|
+
if (result?.updateAvailable) {
|
|
33320
|
+
ctx.updateAvailable = { current: result.current, latest: result.latest };
|
|
33321
|
+
return;
|
|
33322
|
+
}
|
|
33323
|
+
if (result && !result.updateAvailable) {
|
|
33324
|
+
ctx.updateAvailable = void 0;
|
|
33325
|
+
}
|
|
33326
|
+
}
|
|
33327
|
+
function startBackgroundUpdateCheck(ctx) {
|
|
33328
|
+
const pending = checkForUpdate({ force: true, timeoutMs: 5e3 });
|
|
33329
|
+
ctx.pendingUpdateCheck = pending;
|
|
33330
|
+
void pending.then((result) => applyUpdateCheckResult(ctx, result)).catch(() => void 0);
|
|
33256
33331
|
}
|
|
33257
33332
|
async function fetchLatestVersion(timeoutMs = 5e3) {
|
|
33258
33333
|
try {
|
|
@@ -33299,18 +33374,83 @@ var init_registry = __esm({
|
|
|
33299
33374
|
}
|
|
33300
33375
|
});
|
|
33301
33376
|
|
|
33377
|
+
// src/update/relaunch.ts
|
|
33378
|
+
var relaunch_exports = {};
|
|
33379
|
+
__export(relaunch_exports, {
|
|
33380
|
+
JUST_UPDATED_ENV: () => JUST_UPDATED_ENV,
|
|
33381
|
+
consumeJustUpdatedEnv: () => consumeJustUpdatedEnv,
|
|
33382
|
+
encodeJustUpdated: () => encodeJustUpdated,
|
|
33383
|
+
relaunchIntoHome: () => relaunchIntoHome,
|
|
33384
|
+
updateRestartSummary: () => updateRestartSummary
|
|
33385
|
+
});
|
|
33386
|
+
import { spawnSync } from "child_process";
|
|
33387
|
+
function encodeJustUpdated(fromVersion, toVersion) {
|
|
33388
|
+
return `${fromVersion}\u2192${toVersion}`;
|
|
33389
|
+
}
|
|
33390
|
+
function consumeJustUpdatedEnv() {
|
|
33391
|
+
const raw = process.env[JUST_UPDATED_ENV];
|
|
33392
|
+
if (!raw) return null;
|
|
33393
|
+
delete process.env[JUST_UPDATED_ENV];
|
|
33394
|
+
const sep5 = raw.includes("\u2192") ? "\u2192" : "->";
|
|
33395
|
+
const idx = raw.indexOf(sep5);
|
|
33396
|
+
if (idx <= 0) return null;
|
|
33397
|
+
const from = raw.slice(0, idx);
|
|
33398
|
+
const to = raw.slice(idx + sep5.length);
|
|
33399
|
+
if (!from || !to) return null;
|
|
33400
|
+
return { from, to };
|
|
33401
|
+
}
|
|
33402
|
+
function updateRestartSummary(toVersion) {
|
|
33403
|
+
return `Restart NTRP to use v${toVersion}`;
|
|
33404
|
+
}
|
|
33405
|
+
async function relaunchIntoHome(opts) {
|
|
33406
|
+
const { stopSessionTranscript: stopSessionTranscript2 } = await Promise.resolve().then(() => (init_transcript(), transcript_exports));
|
|
33407
|
+
const { close: close2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
33408
|
+
stopSessionTranscript2();
|
|
33409
|
+
await close2();
|
|
33410
|
+
try {
|
|
33411
|
+
opts.rl?.pause();
|
|
33412
|
+
} catch {
|
|
33413
|
+
}
|
|
33414
|
+
const result = spawnSync(process.execPath, process.argv.slice(1), {
|
|
33415
|
+
stdio: "inherit",
|
|
33416
|
+
env: {
|
|
33417
|
+
...process.env,
|
|
33418
|
+
[JUST_UPDATED_ENV]: encodeJustUpdated(opts.fromVersion, opts.toVersion)
|
|
33419
|
+
}
|
|
33420
|
+
});
|
|
33421
|
+
if (result.error) {
|
|
33422
|
+
try {
|
|
33423
|
+
opts.rl?.resume();
|
|
33424
|
+
} catch {
|
|
33425
|
+
}
|
|
33426
|
+
return "failed";
|
|
33427
|
+
}
|
|
33428
|
+
process.exit(result.status ?? 0);
|
|
33429
|
+
return "failed";
|
|
33430
|
+
}
|
|
33431
|
+
var JUST_UPDATED_ENV;
|
|
33432
|
+
var init_relaunch = __esm({
|
|
33433
|
+
"src/update/relaunch.ts"() {
|
|
33434
|
+
"use strict";
|
|
33435
|
+
JUST_UPDATED_ENV = "NTRP_JUST_UPDATED";
|
|
33436
|
+
}
|
|
33437
|
+
});
|
|
33438
|
+
|
|
33302
33439
|
// src/commands/update.ts
|
|
33303
33440
|
var update_exports = {};
|
|
33304
33441
|
__export(update_exports, {
|
|
33305
33442
|
handler: () => handler43
|
|
33306
33443
|
});
|
|
33307
|
-
import { spawnSync } from "child_process";
|
|
33444
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
33308
33445
|
import chalk69 from "chalk";
|
|
33309
33446
|
function tailLines(text, count = 5) {
|
|
33310
33447
|
return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).slice(-count).join("\n");
|
|
33311
33448
|
}
|
|
33312
33449
|
function runGlobalInstall() {
|
|
33313
|
-
|
|
33450
|
+
if (process.env.NTRP_UPDATE_NPM_STUB === "1") {
|
|
33451
|
+
return { ok: true, output: "stub" };
|
|
33452
|
+
}
|
|
33453
|
+
const result = spawnSync2(
|
|
33314
33454
|
"npm",
|
|
33315
33455
|
["install", "-g", `${NPM_PACKAGE}@latest`],
|
|
33316
33456
|
{
|
|
@@ -33321,7 +33461,7 @@ function runGlobalInstall() {
|
|
|
33321
33461
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
33322
33462
|
return { ok: result.status === 0, output };
|
|
33323
33463
|
}
|
|
33324
|
-
async function handler43(_args,
|
|
33464
|
+
async function handler43(_args, ctx) {
|
|
33325
33465
|
const current = getInstalledVersion();
|
|
33326
33466
|
const latest = await fetchLatestVersion(1e4);
|
|
33327
33467
|
if (!latest) {
|
|
@@ -33342,8 +33482,23 @@ async function handler43(_args, _ctx) {
|
|
|
33342
33482
|
const { ok, output } = runGlobalInstall();
|
|
33343
33483
|
if (ok) {
|
|
33344
33484
|
invalidateUpdateCheckCache();
|
|
33345
|
-
|
|
33485
|
+
if (ctx.oneShot) {
|
|
33486
|
+
console.log(chalk69.green(` \u2713 Updated! ${updateRestartSummary(latest)}`));
|
|
33487
|
+
console.log();
|
|
33488
|
+
return;
|
|
33489
|
+
}
|
|
33346
33490
|
console.log();
|
|
33491
|
+
const failed = await relaunchIntoHome({
|
|
33492
|
+
fromVersion: current,
|
|
33493
|
+
toVersion: latest,
|
|
33494
|
+
rl: ctx.rl
|
|
33495
|
+
});
|
|
33496
|
+
if (failed) {
|
|
33497
|
+
const restart = updateRestartSummary(latest);
|
|
33498
|
+
console.log(chalk69.green(` \u2713 Updated! ${restart}`));
|
|
33499
|
+
console.log();
|
|
33500
|
+
return restart;
|
|
33501
|
+
}
|
|
33347
33502
|
return;
|
|
33348
33503
|
}
|
|
33349
33504
|
const lower = output.toLowerCase();
|
|
@@ -33368,6 +33523,7 @@ var init_update = __esm({
|
|
|
33368
33523
|
"use strict";
|
|
33369
33524
|
init_update_check();
|
|
33370
33525
|
init_registry();
|
|
33526
|
+
init_relaunch();
|
|
33371
33527
|
init_version();
|
|
33372
33528
|
PERMISSIONS_URL = "https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally";
|
|
33373
33529
|
}
|
|
@@ -33680,7 +33836,7 @@ __export(exports_exports, {
|
|
|
33680
33836
|
handler: () => handler46
|
|
33681
33837
|
});
|
|
33682
33838
|
import chalk73 from "chalk";
|
|
33683
|
-
import { existsSync as
|
|
33839
|
+
import { existsSync as existsSync31 } from "fs";
|
|
33684
33840
|
import { join as join35 } from "path";
|
|
33685
33841
|
function usage3() {
|
|
33686
33842
|
console.log(chalk73.dim(" Usage:"));
|
|
@@ -33839,7 +33995,7 @@ function runMove(args, ctx) {
|
|
|
33839
33995
|
}
|
|
33840
33996
|
try {
|
|
33841
33997
|
const destDir = resolveUserPath(dest);
|
|
33842
|
-
if (!
|
|
33998
|
+
if (!existsSync31(destDir)) {
|
|
33843
33999
|
}
|
|
33844
34000
|
const event = moveExport(idOrName, destDir);
|
|
33845
34001
|
console.log();
|
|
@@ -34787,11 +34943,11 @@ Cross-provider IDs are rejected. Type \`/provider\` first to switch.`
|
|
|
34787
34943
|
name: activate
|
|
34788
34944
|
description: Enter a license key
|
|
34789
34945
|
section: Settings
|
|
34790
|
-
args:
|
|
34946
|
+
args: [license]
|
|
34791
34947
|
handler: ../commands/activate.ts
|
|
34792
34948
|
---
|
|
34793
34949
|
|
|
34794
|
-
Activate NTRP with the key from your purchase email. Most commands need a valid license.`
|
|
34950
|
+
Activate NTRP with the key from your purchase email. Type \`/activate\` with no key to paste. Most commands need a valid license.`
|
|
34795
34951
|
},
|
|
34796
34952
|
{
|
|
34797
34953
|
name: "upgrade",
|
|
@@ -34836,7 +34992,7 @@ Remaining nuances merge into a custom_context paragraph that flows into all AI s
|
|
|
34836
34992
|
});
|
|
34837
34993
|
|
|
34838
34994
|
// src/ai/prompt-parts.ts
|
|
34839
|
-
import { existsSync as
|
|
34995
|
+
import { existsSync as existsSync32, readFileSync as readFileSync23 } from "fs";
|
|
34840
34996
|
import { join as join36 } from "path";
|
|
34841
34997
|
function buildCompanyProfileBlock() {
|
|
34842
34998
|
const p = loadProfile();
|
|
@@ -34857,7 +35013,7 @@ function buildCompanyProfileBlock() {
|
|
|
34857
35013
|
function loadAnalystFile() {
|
|
34858
35014
|
const path = join36(ntrpHome(), ANALYST_FILE_NAME);
|
|
34859
35015
|
try {
|
|
34860
|
-
if (!
|
|
35016
|
+
if (!existsSync32(path)) return null;
|
|
34861
35017
|
const raw = sanitizeExternalText(readFileSync23(path, "utf-8").trim());
|
|
34862
35018
|
if (!raw) return null;
|
|
34863
35019
|
if (raw.length <= ANALYST_FILE_MAX_CHARS) return raw;
|
|
@@ -35375,7 +35531,7 @@ __export(ingest_chat_exports, {
|
|
|
35375
35531
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
35376
35532
|
looksLikeFilePath: () => looksLikeFilePath
|
|
35377
35533
|
});
|
|
35378
|
-
import { existsSync as
|
|
35534
|
+
import { existsSync as existsSync33 } from "fs";
|
|
35379
35535
|
import { basename as basename9, resolve as resolve9 } from "path";
|
|
35380
35536
|
import { homedir as homedir8 } from "os";
|
|
35381
35537
|
import chalk75 from "chalk";
|
|
@@ -35395,11 +35551,11 @@ function extractFilePath(input) {
|
|
|
35395
35551
|
const m = trimmed.match(re);
|
|
35396
35552
|
if (m?.[1]) {
|
|
35397
35553
|
const p = expandPath(m[1]);
|
|
35398
|
-
if (
|
|
35554
|
+
if (existsSync33(p)) return p;
|
|
35399
35555
|
}
|
|
35400
35556
|
if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
|
|
35401
35557
|
const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
|
|
35402
|
-
if (
|
|
35558
|
+
if (existsSync33(p)) return p;
|
|
35403
35559
|
}
|
|
35404
35560
|
}
|
|
35405
35561
|
return null;
|
|
@@ -35844,6 +36000,7 @@ __export(first_run_exports, {
|
|
|
35844
36000
|
loadFirstRunDemo: () => loadFirstRunDemo,
|
|
35845
36001
|
markFirstRunCompleted: () => markFirstRunCompleted,
|
|
35846
36002
|
printFirstRunChip: () => printFirstRunChip,
|
|
36003
|
+
resumeSetupAfterLicense: () => resumeSetupAfterLicense,
|
|
35847
36004
|
runFirstRunFork: () => runFirstRunFork,
|
|
35848
36005
|
shouldOfferFirstRunFork: () => shouldOfferFirstRunFork
|
|
35849
36006
|
});
|
|
@@ -35948,9 +36105,6 @@ async function completeInteractiveSetup(ctx, options = {}) {
|
|
|
35948
36105
|
await loadFirstRunDemo(ctx, fork.scenario);
|
|
35949
36106
|
}
|
|
35950
36107
|
}
|
|
35951
|
-
if (!isProfileConfigured()) {
|
|
35952
|
-
printFirstRunChip();
|
|
35953
|
-
}
|
|
35954
36108
|
} catch (err) {
|
|
35955
36109
|
if (err instanceof GlobalReplCommandError) {
|
|
35956
36110
|
if (err.command === "exit") {
|
|
@@ -35966,14 +36120,22 @@ async function completeInteractiveSetup(ctx, options = {}) {
|
|
|
35966
36120
|
await runGlobalAdminCommand2(err.command, `/${err.command}`, ctx);
|
|
35967
36121
|
}
|
|
35968
36122
|
markFirstRunCompleted();
|
|
35969
|
-
if (!isProfileConfigured()) {
|
|
35970
|
-
printFirstRunChip();
|
|
35971
|
-
}
|
|
35972
36123
|
return;
|
|
35973
36124
|
}
|
|
35974
36125
|
throw err;
|
|
35975
36126
|
}
|
|
35976
36127
|
}
|
|
36128
|
+
async function resumeSetupAfterLicense(ctx) {
|
|
36129
|
+
if (!hasValidLicense()) return false;
|
|
36130
|
+
if (!isProfileConfigured() && shouldOfferFirstRunFork()) {
|
|
36131
|
+
ctx.pendingBlockedLine = void 0;
|
|
36132
|
+
await completeInteractiveSetup(ctx, { skipBrand: true });
|
|
36133
|
+
return true;
|
|
36134
|
+
}
|
|
36135
|
+
const { replayPendingBlockedLine: replayPendingBlockedLine2 } = await Promise.resolve().then(() => (init_dispatch(), dispatch_exports));
|
|
36136
|
+
await replayPendingBlockedLine2(ctx);
|
|
36137
|
+
return false;
|
|
36138
|
+
}
|
|
35977
36139
|
async function loadFirstRunDemo(ctx, scenario) {
|
|
35978
36140
|
const { handler: demo } = await Promise.resolve().then(() => (init_generate(), generate_exports));
|
|
35979
36141
|
const ok = await demo(["--no-profile", "--scenario", scenario, "--brief"], ctx);
|
|
@@ -36007,6 +36169,7 @@ var init_first_run = __esm({
|
|
|
36007
36169
|
init_scenarios();
|
|
36008
36170
|
init_repl_globals();
|
|
36009
36171
|
init_global_admin();
|
|
36172
|
+
init_activation();
|
|
36010
36173
|
FIRST_RUN_KEY = "first-run-completed";
|
|
36011
36174
|
}
|
|
36012
36175
|
});
|
|
@@ -36021,7 +36184,7 @@ function printScratchPreamble(includeProgress) {
|
|
|
36021
36184
|
console.log();
|
|
36022
36185
|
console.log(" " + chalk78.yellow.bold("This will permanently remove:"));
|
|
36023
36186
|
console.log(" " + chalk78.dim(" \u2022 API key and all config.json settings"));
|
|
36024
|
-
console.log(" " + chalk78.dim(" \u2022 Company profile (you
|
|
36187
|
+
console.log(" " + chalk78.dim(" \u2022 Company profile (you will set up again in this session)"));
|
|
36025
36188
|
console.log(" " + chalk78.dim(" \u2022 All sessions and datasets"));
|
|
36026
36189
|
console.log(" " + chalk78.dim(" \u2022 Demo taxonomy cache"));
|
|
36027
36190
|
if (includeProgress) {
|
|
@@ -36052,6 +36215,7 @@ function resetContextAfterScratch(ctx) {
|
|
|
36052
36215
|
ctx.deliverIntent = false;
|
|
36053
36216
|
ctx.computeInProgress = false;
|
|
36054
36217
|
ctx.lastExchange = void 0;
|
|
36218
|
+
ctx.pendingBlockedLine = void 0;
|
|
36055
36219
|
}
|
|
36056
36220
|
async function handler48(args, ctx) {
|
|
36057
36221
|
const { flags } = parseArgs2(args, ["confirm", "include-progress"]);
|
|
@@ -36071,18 +36235,17 @@ async function handler48(args, ctx) {
|
|
|
36071
36235
|
resetContextAfterScratch(ctx);
|
|
36072
36236
|
await rotateToFreshSession(ctx);
|
|
36073
36237
|
await initSchema();
|
|
36074
|
-
console.log();
|
|
36075
|
-
const detail = includeProgress ? " \u2014 local config, data, and progress wiped." : " \u2014 local config and data wiped.";
|
|
36076
|
-
console.log(" " + paint("accent", "\u2713 Scratch complete") + chalk78.dim(detail));
|
|
36077
|
-
console.log();
|
|
36078
36238
|
if (ctx.oneShot) {
|
|
36239
|
+
console.log();
|
|
36240
|
+
const detail = includeProgress ? " \u2014 local config, data, and progress wiped." : " \u2014 local config and data wiped.";
|
|
36241
|
+
console.log(" " + paint("accent", "\u2713 Scratch complete") + chalk78.dim(detail));
|
|
36242
|
+
console.log();
|
|
36079
36243
|
console.log(
|
|
36080
36244
|
" " + chalk78.dim("Run ") + paint("accent", "ntrp") + chalk78.dim(" interactively to complete onboarding.\n")
|
|
36081
36245
|
);
|
|
36082
36246
|
return "Scratch complete";
|
|
36083
36247
|
}
|
|
36084
|
-
|
|
36085
|
-
console.log();
|
|
36248
|
+
clearSlideScreen();
|
|
36086
36249
|
const shown = await ensureLicenseActivated(ctx, { exitOnCancel: false });
|
|
36087
36250
|
if (!hasValidLicense()) {
|
|
36088
36251
|
return "Scratch complete \u2014 license required";
|
|
@@ -36100,6 +36263,7 @@ var init_scratch = __esm({
|
|
|
36100
36263
|
init_scratch_wipe();
|
|
36101
36264
|
init_schema();
|
|
36102
36265
|
init_theme();
|
|
36266
|
+
init_slides();
|
|
36103
36267
|
init_activation();
|
|
36104
36268
|
}
|
|
36105
36269
|
});
|
|
@@ -36151,8 +36315,13 @@ function resolvePostAction(input) {
|
|
|
36151
36315
|
if (cancelled || isCancelledSummary(summary)) return "none";
|
|
36152
36316
|
if (command === "onboard" && !ctx.replStarted) return "none";
|
|
36153
36317
|
if (HOME_COMMANDS.has(command)) return "home";
|
|
36318
|
+
if (command === "update" && isUpdateRestartFallback(summary)) return "home";
|
|
36154
36319
|
return "none";
|
|
36155
36320
|
}
|
|
36321
|
+
function isUpdateRestartFallback(summary) {
|
|
36322
|
+
if (!summary) return false;
|
|
36323
|
+
return /^Restart NTRP to use v/.test(summary);
|
|
36324
|
+
}
|
|
36156
36325
|
var HOME_COMMANDS;
|
|
36157
36326
|
var init_post_action = __esm({
|
|
36158
36327
|
"src/cli/post-action.ts"() {
|
|
@@ -36605,11 +36774,26 @@ function formatHomeEntityCounts(counts) {
|
|
|
36605
36774
|
if (acts > 0) parts.push(`${acts} ${acts === 1 ? "activity" : "activities"}`);
|
|
36606
36775
|
return parts.join(" \xB7 ");
|
|
36607
36776
|
}
|
|
36608
|
-
function formatEmptyDataHomeHint(savedSessionCount) {
|
|
36777
|
+
function formatEmptyDataHomeHint(savedSessionCount, opts = {}) {
|
|
36778
|
+
const onboard = opts.includeOnboard === true ? chalk82.dim(", or ") + paint("accent", "/onboard") + chalk82.dim(" to calibrate") : "";
|
|
36609
36779
|
if (savedSessionCount > 0) {
|
|
36610
|
-
return chalk82.dim("Type ") + paint("accent", "use demo data") + chalk82.dim(" to load a sample. Type ") + paint("accent", "/session") + chalk82.dim(" to open a saved session");
|
|
36780
|
+
return chalk82.dim("Type ") + paint("accent", "use demo data") + chalk82.dim(" to load a sample") + onboard + chalk82.dim(". Type ") + paint("accent", "/session") + chalk82.dim(" to open a saved session");
|
|
36781
|
+
}
|
|
36782
|
+
return chalk82.dim("Type ") + paint("accent", "use demo data") + chalk82.dim(" to load a sample pipeline") + onboard;
|
|
36783
|
+
}
|
|
36784
|
+
function ntrpStatusRow(version, update) {
|
|
36785
|
+
if (update && isNewerVersion(update.latest, version)) {
|
|
36786
|
+
return {
|
|
36787
|
+
label: "ntrp",
|
|
36788
|
+
state: badge("UPDATE", "warning"),
|
|
36789
|
+
detail: `v${update.latest} \xB7 type /update`
|
|
36790
|
+
};
|
|
36611
36791
|
}
|
|
36612
|
-
return
|
|
36792
|
+
return {
|
|
36793
|
+
label: "ntrp",
|
|
36794
|
+
state: chalk82.dim(`v${version}`),
|
|
36795
|
+
detail: ""
|
|
36796
|
+
};
|
|
36613
36797
|
}
|
|
36614
36798
|
function resolveSessionSummary(input) {
|
|
36615
36799
|
if (input.scope?.intent_summary?.trim()) return input.scope.intent_summary.trim();
|
|
@@ -36665,7 +36849,7 @@ function formatActiveSessionLine(s, colW, ctx, opts) {
|
|
|
36665
36849
|
function resolveWelcomeNextAction(input) {
|
|
36666
36850
|
const { profileReady, hasData, ctx, unfinishedCount, licenseValid } = input;
|
|
36667
36851
|
if (!licenseValid) {
|
|
36668
|
-
return { label: "Next:", command: "/
|
|
36852
|
+
return { label: "Next:", command: "/activate", detail: "paste a trial or Pro key" };
|
|
36669
36853
|
}
|
|
36670
36854
|
if (!profileReady && !hasData) {
|
|
36671
36855
|
return { label: "Try:", command: "", detail: 'type what you want to investigate (e.g. "pipeline health")' };
|
|
@@ -36760,6 +36944,8 @@ function buildActiveSessionsLines(colW, ctx, activeSessions) {
|
|
|
36760
36944
|
return lines;
|
|
36761
36945
|
}
|
|
36762
36946
|
async function printWelcome(ctx, version) {
|
|
36947
|
+
const updateForPaint = ctx.updateAvailable;
|
|
36948
|
+
ctx.updateHomePaintedLatest = updateForPaint?.latest;
|
|
36763
36949
|
const width = termWidth();
|
|
36764
36950
|
const cardW = resolveCardWidth({ min: 72, max: CARD_MAX_W, margin: CARD_SIDE_MARGIN * 2 });
|
|
36765
36951
|
const innerW = cardW - 2;
|
|
@@ -36816,7 +37002,7 @@ async function printWelcome(ctx, version) {
|
|
|
36816
37002
|
let licenseDetail;
|
|
36817
37003
|
if (!license.valid) {
|
|
36818
37004
|
licenseState = badge("NOT SET", "warning");
|
|
36819
|
-
licenseDetail = "type /
|
|
37005
|
+
licenseDetail = "type /activate";
|
|
36820
37006
|
} else if (license.edition === "trial" && license.trialPhase === "grace") {
|
|
36821
37007
|
licenseState = badge("GRACE", "warning");
|
|
36822
37008
|
licenseDetail = `${license.daysUntilLockout ?? 0} day${license.daysUntilLockout === 1 ? "" : "s"} left on trial`;
|
|
@@ -36832,6 +37018,7 @@ async function printWelcome(ctx, version) {
|
|
|
36832
37018
|
licenseDetail = license.message;
|
|
36833
37019
|
}
|
|
36834
37020
|
const statusRows = [
|
|
37021
|
+
ntrpStatusRow(version, updateForPaint),
|
|
36835
37022
|
{
|
|
36836
37023
|
label: "license",
|
|
36837
37024
|
state: licenseState,
|
|
@@ -36860,7 +37047,7 @@ async function printWelcome(ctx, version) {
|
|
|
36860
37047
|
unfinishedCount: unfinishedSessions.length,
|
|
36861
37048
|
licenseValid: license.valid
|
|
36862
37049
|
});
|
|
36863
|
-
const emptyDataHint = !hasData ? formatEmptyDataHomeHint(savedSessions.length) : null;
|
|
37050
|
+
const emptyDataHint = !hasData && license.valid ? formatEmptyDataHomeHint(savedSessions.length, { includeOnboard: !profileReady }) : null;
|
|
36864
37051
|
const colW = useWideLayout ? leftW : contentW;
|
|
36865
37052
|
const rightColW = useWideLayout ? rightW : contentW;
|
|
36866
37053
|
const systemLines = buildSystemLines(colW, statusRows, recent);
|
|
@@ -36908,7 +37095,7 @@ async function printWelcome(ctx, version) {
|
|
|
36908
37095
|
push(border(`\u2570${"\u2500".repeat(innerW)}\u256F`));
|
|
36909
37096
|
push(
|
|
36910
37097
|
truncateVisible(
|
|
36911
|
-
` ${paint("accent", "/help")}${chalk82.dim(" commands \xB7 ")}${paint("accent", "/deepdive")}${chalk82.dim(" tour \xB7 ")}${paint("accent", "/progress")}${chalk82.dim(" hours \xB7 ")}${paint("accent", "/session")}${chalk82.dim(" resume")}`,
|
|
37098
|
+
license.valid ? ` ${paint("accent", "/help")}${chalk82.dim(" commands \xB7 ")}${paint("accent", "/deepdive")}${chalk82.dim(" tour \xB7 ")}${paint("accent", "/progress")}${chalk82.dim(" hours \xB7 ")}${paint("accent", "/session")}${chalk82.dim(" resume")}` : ` ${paint("accent", "/help")}${chalk82.dim(" commands \xB7 ")}${paint("accent", "/activate")}${chalk82.dim(" paste a key \xB7 ")}${paint("accent", "/checkout")}${chalk82.dim(" signup \xB7 ")}${paint("accent", "/progress")}${chalk82.dim(" hours")}`,
|
|
36912
37099
|
cardW
|
|
36913
37100
|
)
|
|
36914
37101
|
);
|
|
@@ -36945,6 +37132,7 @@ var init_welcome = __esm({
|
|
|
36945
37132
|
init_verify();
|
|
36946
37133
|
init_queries();
|
|
36947
37134
|
init_schema();
|
|
37135
|
+
init_registry();
|
|
36948
37136
|
NO_SUMMARY = "(no summary)";
|
|
36949
37137
|
CARD_MAX_W = 128;
|
|
36950
37138
|
CARD_SIDE_MARGIN = 6;
|
|
@@ -37168,10 +37356,10 @@ async function runRepl(ctx, version) {
|
|
|
37168
37356
|
console.log();
|
|
37169
37357
|
if (ctx.pendingUpdateCheck) {
|
|
37170
37358
|
void ctx.pendingUpdateCheck.then((result) => {
|
|
37171
|
-
if (result?.updateAvailable)
|
|
37172
|
-
|
|
37173
|
-
|
|
37174
|
-
|
|
37359
|
+
if (!result?.updateAvailable) return;
|
|
37360
|
+
if (result.latest === ctx.updateHomePaintedLatest) return;
|
|
37361
|
+
console.log(` ${formatUpdateNudge(result.current, result.latest)}`);
|
|
37362
|
+
console.log();
|
|
37175
37363
|
});
|
|
37176
37364
|
}
|
|
37177
37365
|
let pendingSuggestionRender = null;
|
|
@@ -37353,6 +37541,7 @@ function printHelp() {
|
|
|
37353
37541
|
["/end", "Close the session"],
|
|
37354
37542
|
["/recap", "Write a recap"],
|
|
37355
37543
|
["/connect", "Connect a key"],
|
|
37544
|
+
["/activate", "Paste a license key"],
|
|
37356
37545
|
["/upgrade", "Change to Pro"],
|
|
37357
37546
|
["/checkout", "Open signup"],
|
|
37358
37547
|
["/update", "Install the latest version"],
|
|
@@ -37597,28 +37786,22 @@ async function main() {
|
|
|
37597
37786
|
ctx.datasetPath = datasetPathForSession2(ctx.sessionId);
|
|
37598
37787
|
await setActiveDbPath2(ctx.datasetPath);
|
|
37599
37788
|
const { completeInteractiveSetup: completeInteractiveSetup2 } = await Promise.resolve().then(() => (init_first_run(), first_run_exports));
|
|
37600
|
-
await completeInteractiveSetup2(ctx, { skipBrand: showedActivation });
|
|
37601
|
-
const {
|
|
37602
|
-
isCacheFresh: isCacheFresh2,
|
|
37603
|
-
loadUpdateCheckCache: loadUpdateCheckCache2
|
|
37604
|
-
} = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
|
|
37605
37789
|
const {
|
|
37606
|
-
|
|
37607
|
-
|
|
37608
|
-
isNewerVersion: isNewerVersion2
|
|
37790
|
+
hydrateUpdateAvailableFromCache: hydrateUpdateAvailableFromCache2,
|
|
37791
|
+
startBackgroundUpdateCheck: startBackgroundUpdateCheck2
|
|
37609
37792
|
} = await Promise.resolve().then(() => (init_registry(), registry_exports));
|
|
37610
|
-
const
|
|
37611
|
-
|
|
37612
|
-
|
|
37613
|
-
|
|
37614
|
-
console.log(formatUpdateNudge2(currentVersion, cachedUpdate.latestVersion));
|
|
37615
|
-
}
|
|
37616
|
-
} else {
|
|
37617
|
-
ctx.pendingUpdateCheck = checkForUpdate2({ timeoutMs: 200 });
|
|
37618
|
-
}
|
|
37793
|
+
const { consumeJustUpdatedEnv: consumeJustUpdatedEnv2 } = await Promise.resolve().then(() => (init_relaunch(), relaunch_exports));
|
|
37794
|
+
ctx.updateAvailable = hydrateUpdateAvailableFromCache2(VERSION);
|
|
37795
|
+
startBackgroundUpdateCheck2(ctx);
|
|
37796
|
+
await completeInteractiveSetup2(ctx, { skipBrand: showedActivation });
|
|
37619
37797
|
const { startSessionTranscript: startSessionTranscript2, stopSessionTranscript: stopSessionTranscript2 } = await Promise.resolve().then(() => (init_transcript(), transcript_exports));
|
|
37620
37798
|
startSessionTranscript2(ctx);
|
|
37621
37799
|
const { printWelcome: printWelcome2 } = await Promise.resolve().then(() => (init_welcome(), welcome_exports));
|
|
37800
|
+
const justUpdated = consumeJustUpdatedEnv2();
|
|
37801
|
+
if (justUpdated) {
|
|
37802
|
+
console.log();
|
|
37803
|
+
console.log(" " + paint("accent", "\u2713") + " " + chalk84.dim(`Now running v${justUpdated.to}`));
|
|
37804
|
+
}
|
|
37622
37805
|
await printWelcome2(ctx, VERSION);
|
|
37623
37806
|
await runRepl(ctx, VERSION);
|
|
37624
37807
|
stopSessionTranscript2();
|