@sonnechasser/ntrp 1.4.8 → 1.4.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 CHANGED
@@ -12330,7 +12330,7 @@ var init_guide_slides = __esm({
12330
12330
  tagline: "Start with the question you care about \u2014 we'll mirror it back before we compute.",
12331
12331
  visual: {
12332
12332
  kind: "funnel",
12333
- caption: "When a dim \u23CE hint shows, bare Enter submits it. No hint means Enter does nothing.",
12333
+ caption: "When a dim \u23CE hint shows, bare Enter submits it. Type b (or back) to step back one gate. No hint means Enter does nothing.",
12334
12334
  funnel: [
12335
12335
  { label: "Type a question", widthPct: 100 },
12336
12336
  { label: "\u23CE yes (scope)", widthPct: 78 },
@@ -12341,6 +12341,8 @@ var init_guide_slides = __esm({
12341
12341
  lines: [
12342
12342
  `We designed the conversation so you don't need a slash to start. Type the question you actually need \u2014 "is our retention real for the board?" or "pipeline health" \u2014 and NTRP restates it as a scope card so you can confirm we're answering the right thing.`,
12343
12343
  "",
12344
+ "Hit Enter by accident on \u23CE yes? Type b or back \u2014 that rewinds to the focus card. The same word leaves a strategy or think overlay. It does not unload demo data or undo compute; /scratch or a new session is the blunt reset.",
12345
+ "",
12344
12346
  "Empty dataset? Load a sample with \u23CE use demo data, paste a CSV path, or /ingest. When the gap card says the formulas can compute, \u23CE go ahead runs the local math.",
12345
12347
  "",
12346
12348
  "Vital signs and SaaS metrics compute without an AI key. Every score that has a dollar translation shows it \u2014 that's the point of the stethoscope."
@@ -12349,7 +12351,7 @@ var init_guide_slides = __esm({
12349
12351
  "Power-user slash commands still work. They stay hidden from /help: /new, /diagnose, /metrics, /ingest, /session.",
12350
12352
  '"use demo data" loads a fitted sample book. Company profile is optional. In scripts, /demo --no-profile.',
12351
12353
  'After compute, the prompt returns to \u203A. Brief is the default depth; type "go deep" for the long read.',
12352
- "/home shows phase status. /help lists conversation shortcuts \u2014 not every command."
12354
+ "/home shows phase status. /help lists conversation shortcuts and the Keys legend (\u23CE, b/back, cancel)."
12353
12355
  ]
12354
12356
  };
12355
12357
  ASK = {
@@ -12369,7 +12371,7 @@ var init_guide_slides = __esm({
12369
12371
  "",
12370
12372
  '"what is our ARR?" or "why is this red?" need a loaded dataset. Narrative findings and /ask need a stored key \u2014 /connect pastes any provider key and we detect it.',
12371
12373
  "",
12372
- "Replay this tour with /deepdive, jump to one slide with /deepdive freshness (or nrr, arr, \u2026), or /deepdive guide for this how-to section. When the prompt shows a dim \u23CE hint (yes, use demo data, go ahead, /connect), bare Enter submits it."
12374
+ "Replay this tour with /deepdive, jump to one slide with /deepdive freshness (or nrr, arr, \u2026), or /deepdive guide for this how-to section. When the prompt shows a dim \u23CE hint (yes, use demo data, go ahead, /connect), bare Enter submits it \u2014 and b / back steps back one confirm gate (or the previous tour slide while you're in /deepdive)."
12373
12375
  ],
12374
12376
  deepdive: [
12375
12377
  "/connect ollama for a keyless local model. /connect --base-url <url> --id <name> for any OpenAI-compatible endpoint.",
@@ -12597,7 +12599,9 @@ function paintIntro(opts) {
12597
12599
  extraLines: [
12598
12600
  ...TOUR_INTRO_LINES,
12599
12601
  "",
12600
- chalk10.dim("No AI key needed for this tour. Enter advances; /deepdive (or d) opens more on any slide.")
12602
+ chalk10.dim(
12603
+ "No AI key needed for this tour. Enter advances; b goes to the previous slide; /deepdive (or d) opens more on any slide. After the tour, b / back steps back one confirm gate in the REPL."
12604
+ )
12601
12605
  ],
12602
12606
  footer: `${progressDots(opts.index, opts.total)} \u23CE next \xB7 q skip`
12603
12607
  });
@@ -12796,7 +12800,7 @@ async function offerFirstRunTour(ctx) {
12796
12800
  {
12797
12801
  value: "tour",
12798
12802
  label: "Take the tour",
12799
- description: "\u23CE through slides \u2014 /deepdive on any slide for more"
12803
+ description: "\u23CE next \xB7 b back on slides \u2014 /deepdive on any slide for more"
12800
12804
  },
12801
12805
  {
12802
12806
  value: "skip",
@@ -12862,7 +12866,7 @@ var init_metric_tour = __esm({
12862
12866
  "",
12863
12867
  "We're a stethoscope, not a hospital: we listen hard, translate scores into dollars, and point at plays \u2014 you decide the surgery."
12864
12868
  ];
12865
- TOUR_OFFER_BLURB = "How we think about SaaS numbers and five vitals \u2014 then how to talk to NTRP and ship work to your AI.";
12869
+ TOUR_OFFER_BLURB = "How we think about SaaS numbers and five vitals \u2014 then how to talk to NTRP and ship work to your AI. On slides, b goes back; after the tour, the same word steps back at confirm gates.";
12866
12870
  }
12867
12871
  });
12868
12872
 
@@ -15593,7 +15597,8 @@ function buildConversationPrompt(ctx) {
15593
15597
  const enterHint2 = action ? chalk11.dim(` \xB7 \u23CE ${action.hint}`) : "";
15594
15598
  return accentPromptHead(phase, sessionName) + chalk11.dim(stack) + enterHint2 + " ";
15595
15599
  }
15596
- const enterHint = action ? chalk11.dim(`\u23CE ${action.hint} `) : "";
15600
+ const modalBack = phase === "scope" || phase === "awaiting_data" || phase === "strategize" ? " \xB7 b back" : "";
15601
+ const enterHint = action ? chalk11.dim(`\u23CE ${action.hint}${modalBack} `) : "";
15597
15602
  return accentPromptHead(phase, sessionName) + enterHint;
15598
15603
  }
15599
15604
  var PROMPT_LABELS;
@@ -21813,10 +21818,12 @@ var init_agent_context = __esm({
21813
21818
  var scope_exports = {};
21814
21819
  __export(scope_exports, {
21815
21820
  confirmScope: () => confirmScope,
21821
+ isBackInput: () => isBackInput,
21816
21822
  isScopeAdjustInput: () => isScopeAdjustInput,
21817
21823
  isScopeConfirmInput: () => isScopeConfirmInput,
21818
21824
  isSkipClarifier: () => isSkipClarifier,
21819
- proposeScopeFromIntent: () => proposeScopeFromIntent
21825
+ proposeScopeFromIntent: () => proposeScopeFromIntent,
21826
+ unconfirmScope: () => unconfirmScope
21820
21827
  });
21821
21828
  function proposeScopeFromIntent(input) {
21822
21829
  const text = input.trim();
@@ -21861,12 +21868,20 @@ function confirmScope(ctx) {
21861
21868
  if (ctx.scope.primary_lens === "revenue_metrics" || ctx.scope.primary_lens === "gtm_health") {
21862
21869
  }
21863
21870
  }
21871
+ function unconfirmScope(ctx) {
21872
+ if (!ctx.scope?.confirmed_at) return;
21873
+ const { confirmed_at: _dropped, ...rest } = ctx.scope;
21874
+ ctx.scope = rest;
21875
+ }
21864
21876
  function isScopeConfirmInput(input) {
21865
21877
  return /^(yes|y|yeah|yep|correct|looks good|that'?s right|confirm|ok|okay|go)$/i.test(input.trim());
21866
21878
  }
21867
21879
  function isScopeAdjustInput(input) {
21868
21880
  return /^(adjust|change|refine|edit|no|nope)$/i.test(input.trim());
21869
21881
  }
21882
+ function isBackInput(input) {
21883
+ return /^(b|back|prev)\s*[.!]?\s*$/i.test(input.trim());
21884
+ }
21870
21885
  function isSkipClarifier(input) {
21871
21886
  return /^skip$/i.test(input.trim());
21872
21887
  }
@@ -23300,7 +23315,7 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
23300
23315
  `Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
23301
23316
  ];
23302
23317
  if (channel === "think") {
23303
- lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
23318
+ lines.push("Channel: think \u2014 socratic partner; type done/cancel/b back returns to \u203A");
23304
23319
  lines.push("Response mode: deep (tools on)");
23305
23320
  } else if (channel === "strategist") {
23306
23321
  lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
@@ -23344,11 +23359,13 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
23344
23359
  } else if (phase === "awaiting_data") {
23345
23360
  lines.push("Already done: scope confirmed; analysis not ready");
23346
23361
  lines.push(
23347
- 'Available now: tell the user to paste a CSV path, type "use demo data", or \u23CE go ahead when can_compute \u2014 you cannot open files'
23362
+ 'Available now: tell the user to paste a CSV path, type "use demo data", \u23CE go ahead when can_compute, or b/back to rewind the focus card \u2014 you cannot open files'
23348
23363
  );
23349
23364
  } else if (phase === "scope") {
23350
23365
  lines.push("Already done: intent proposed; waiting on confirm");
23351
- lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
23366
+ lines.push(
23367
+ "Available now: wait for CLI confirm (\u23CE yes) or b/back/adjust \u2014 do not invent a different question"
23368
+ );
23352
23369
  } else if (phase === "orient") {
23353
23370
  lines.push("Already done: nothing locked yet");
23354
23371
  lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
@@ -23421,13 +23438,13 @@ var init_situation = __esm({
23421
23438
  FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
23422
23439
  PHASE_MEANING = {
23423
23440
  orient: "setup \u2014 open chat; user has not locked a scope yet",
23424
- scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
23425
- awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
23441
+ scope: "scope confirm \u2014 CLI owns yes / b back / adjust; do not re-propose unless asked",
23442
+ awaiting_data: "data gate \u2014 load CSV/demo or compute; b/back rewinds to scope confirm (does not unload data); CLI owns the gate",
23426
23443
  compute: "compute in progress \u2014 wait",
23427
23444
  explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
23428
- think: "socratic think channel \u2014 return to \u203A via done/cancel",
23429
- strategize: "strategist flow \u2014 objective confirm or plan drafting",
23430
- deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
23445
+ think: "socratic think channel \u2014 return to \u203A via done/cancel/b back",
23446
+ strategize: "strategist flow \u2014 objective confirm (yes / b back / adjust) or plan drafting",
23447
+ deliver: "ship/handoff wizard \u2014 CLI owns write confirms; b/back or cancel leaves the draft"
23431
23448
  };
23432
23449
  }
23433
23450
  });
@@ -29957,7 +29974,7 @@ async function handleStrategizeFlow(input, ctx) {
29957
29974
  " " + chalk37.dim("That looks like a question. A strategy objective is waiting.")
29958
29975
  );
29959
29976
  console.log(
29960
- " " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to answer questions first.")
29977
+ " " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("b") + chalk37.dim(" / ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to answer questions first.")
29961
29978
  );
29962
29979
  console.log();
29963
29980
  return "Awaiting confirm";
@@ -29970,7 +29987,7 @@ async function handleStrategizeFlow(input, ctx) {
29970
29987
  }
29971
29988
  console.log();
29972
29989
  console.log(
29973
- " " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to stop.")
29990
+ " " + chalk37.dim("Type ") + chalk37.cyan("yes") + chalk37.dim(" to make the plan. Type ") + chalk37.cyan("b") + chalk37.dim(" / ") + chalk37.cyan("adjust") + chalk37.dim(" to change the objective. Type ") + chalk37.cyan("cancel") + chalk37.dim(" to stop.")
29974
29991
  );
29975
29992
  console.log();
29976
29993
  return "Awaiting confirm";
@@ -30147,7 +30164,7 @@ function printObjectiveCard(ctx, objective, proposed) {
30147
30164
  );
30148
30165
  console.log();
30149
30166
  console.log(
30150
- " " + chalk37.dim("Confirm? ") + chalk37.cyan("\u23CE yes") + chalk37.dim(" \xB7 ") + chalk37.cyan("adjust") + chalk37.dim(" \xB7 ") + chalk37.cyan("cancel")
30167
+ " " + chalk37.dim("Confirm? ") + chalk37.cyan("\u23CE yes") + chalk37.dim(" \xB7 ") + chalk37.cyan("b back") + chalk37.dim(" \xB7 ") + chalk37.cyan("adjust") + chalk37.dim(" \xB7 ") + chalk37.cyan("cancel")
30151
30168
  );
30152
30169
  console.log();
30153
30170
  }
@@ -32515,7 +32532,7 @@ function printScopeProposal(ctx) {
32515
32532
  }
32516
32533
  console.log();
32517
32534
  console.log(
32518
- " " + chalk52.dim("Confirm? ") + chalk52.cyan("\u23CE yes") + chalk52.dim(" \xB7 ") + chalk52.cyan("adjust")
32535
+ " " + chalk52.dim("Confirm? ") + chalk52.cyan("\u23CE yes") + chalk52.dim(" \xB7 ") + chalk52.cyan("b back") + chalk52.dim(" \xB7 ") + chalk52.cyan("adjust")
32519
32536
  );
32520
32537
  console.log();
32521
32538
  }
@@ -37032,6 +37049,7 @@ CLI slide deck: SaaS refresher, five vital signs (definition, formula, visual, d
37032
37049
  Bare \`/deepdive\` runs the full onboarding tour. Type \`/deepdive guide\` to jump to how to use NTRP (includes teaching the inbox once).
37033
37050
  Type \`/deepdive handoff\` for the ship-to-AI slide. Type \`/deepdive <metric>\` to jump to one metric card.
37034
37051
  Type \`/deepdive list\` to print the catalog. Works without an AI key. Re-run anytime from the homescreen.
37052
+ During the tour: Enter advances, \`b\` / \`back\` goes to the previous slide, \`q\` quits. After the tour, the same \`b\` / \`back\` word steps back one confirm gate in the REPL (scope, data, strategy) \u2014 it does not unload data or undo compute.
37035
37053
  Live values overlay when an analysis exists.`
37036
37054
  },
37037
37055
  {
@@ -37046,7 +37064,7 @@ handler: ../commands/thinkwithme.ts
37046
37064
 
37047
37065
  Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
37048
37066
  Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
37049
- Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to \u203A.
37067
+ Requires an analysis and an AI key (\`/connect\`). Type \`done\`, \`cancel\`, or \`b\` / \`back\` to return to \u203A.
37050
37068
  When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
37051
37069
  },
37052
37070
  {
@@ -38792,6 +38810,83 @@ function popModalState(ctx) {
38792
38810
  }
38793
38811
  return popped;
38794
38812
  }
38813
+ function handleBackNavigation(ctx, line) {
38814
+ if (!isBackInput(line)) return null;
38815
+ const phase = resolveConversationPhase(ctx);
38816
+ if (phase === "orient" || phase === "explore" || phase === "compute") {
38817
+ return null;
38818
+ }
38819
+ if (phase === "scope") {
38820
+ console.log();
38821
+ console.log(" " + chalk84.dim("What should we focus on instead?"));
38822
+ ctx.scope = void 0;
38823
+ clearPendingAsk(ctx);
38824
+ saveSessionState(ctx);
38825
+ recordMessage(ctx, "user", line);
38826
+ recordMessage(ctx, "agent", "Scope cleared (back)");
38827
+ console.log();
38828
+ return { handled: true, summary: "Scope cleared" };
38829
+ }
38830
+ if (phase === "awaiting_data") {
38831
+ if (!ctx.scope?.confirmed_at) return null;
38832
+ const hadData = sessionHasData(ctx);
38833
+ const hadAnalysis = ctx.stage === "analyzed" || isAnalysisReady(ctx);
38834
+ unconfirmScope(ctx);
38835
+ saveSessionState(ctx);
38836
+ recordMessage(ctx, "user", line);
38837
+ recordMessage(ctx, "agent", "Scope unconfirmed (back)");
38838
+ printScopeProposal(ctx);
38839
+ if (hadData || hadAnalysis) {
38840
+ console.log(
38841
+ " " + chalk84.dim(
38842
+ "Focus gate rewound \u2014 loaded data and any analysis stay. `b` does not unload demo data or undo compute."
38843
+ )
38844
+ );
38845
+ console.log();
38846
+ }
38847
+ return { handled: true, summary: "Scope unconfirmed" };
38848
+ }
38849
+ if (phase === "strategize") {
38850
+ const state2 = ctx.strategistState;
38851
+ if (state2?.step === "objective_confirm") {
38852
+ state2.step = "objective_input";
38853
+ saveSessionState(ctx);
38854
+ recordMessage(ctx, "user", line);
38855
+ recordMessage(ctx, "agent", "Strategy objective adjust (back)");
38856
+ console.log();
38857
+ console.log(" " + chalk84.dim("What is the objective? State a finish line."));
38858
+ console.log();
38859
+ return { handled: true, summary: "Awaiting objective" };
38860
+ }
38861
+ const popped = popModalState(ctx);
38862
+ if (popped.length === 0) return null;
38863
+ saveSessionState(ctx);
38864
+ recordMessage(ctx, "user", line);
38865
+ recordMessage(ctx, "agent", `Back \u2014 dropped ${popped.join(", ")}`);
38866
+ const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
38867
+ console.log();
38868
+ console.log(
38869
+ " " + chalk84.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk84.cyan(backTo) + chalk84.dim(".")
38870
+ );
38871
+ console.log();
38872
+ return { handled: true, summary: "Back" };
38873
+ }
38874
+ if (phase === "think" || phase === "deliver") {
38875
+ const popped = popModalState(ctx);
38876
+ if (popped.length === 0) return null;
38877
+ saveSessionState(ctx);
38878
+ recordMessage(ctx, "user", line);
38879
+ recordMessage(ctx, "agent", `Back \u2014 dropped ${popped.join(", ")}`);
38880
+ const backTo = formatPhaseLabel(resolveConversationPhase(ctx));
38881
+ console.log();
38882
+ console.log(
38883
+ " " + chalk84.dim(`Back \u2014 dropped ${popped.join(" and ")}. At `) + chalk84.cyan(backTo) + chalk84.dim(".")
38884
+ );
38885
+ console.log();
38886
+ return { handled: true, summary: "Back" };
38887
+ }
38888
+ return null;
38889
+ }
38795
38890
  async function conversationRouter(input, ctx) {
38796
38891
  if (ctx.oneShot || (ctx.wizardDepth ?? 0) > 0) {
38797
38892
  return { handled: false };
@@ -38821,6 +38916,8 @@ async function conversationRouter(input, ctx) {
38821
38916
  return { handled: true, summary: "Cancelled" };
38822
38917
  }
38823
38918
  }
38919
+ const backResult = handleBackNavigation(ctx, line);
38920
+ if (backResult) return backResult;
38824
38921
  if (/^\S+$/.test(line) && !/^\d$/.test(line)) {
38825
38922
  const { resolveSessionByToken: resolveSessionByToken2 } = await Promise.resolve().then(() => (init_context2(), context_exports));
38826
38923
  if (resolveSessionByToken2(line, { printErrors: false }) !== void 0) {
@@ -38925,6 +39022,8 @@ var init_router = __esm({
38925
39022
  init_think_flow();
38926
39023
  init_context2();
38927
39024
  init_demo();
39025
+ init_scope();
39026
+ init_pending_ask();
38928
39027
  FRESH_START_RE = /\b(start (over|fresh)|new analysis|start again|reset session)\b/i;
38929
39028
  CANCEL_ESCAPE_RE = /^(cancel|stop|abort|quit|never\s?mind|nevermind|forget it)\s*[.!]?\s*$/i;
38930
39029
  }
@@ -39148,17 +39247,17 @@ var init_loop_guard2 = __esm({
39148
39247
  PHASE_GUIDES = {
39149
39248
  scope: {
39150
39249
  mode: "scope confirmation",
39151
- accepts: "\u23CE or yes (run with this focus) \xB7 adjust (restate it) \xB7 a new focus statement",
39152
- leave: "cancel drops the proposal \xB7 /home shows the dashboard"
39250
+ accepts: "\u23CE or yes (run with this focus) \xB7 b / back (clear it) \xB7 adjust (restate it) \xB7 a new focus statement",
39251
+ leave: "b / back or cancel drops the proposal \xB7 /home shows the dashboard"
39153
39252
  },
39154
39253
  awaiting_data: {
39155
39254
  mode: "data loading",
39156
39255
  accepts: 'a CSV path \xB7 "use demo data" \xB7 "go ahead" (compute once data is loaded) \xB7 \u23CE runs the action shown at the prompt',
39157
- leave: "cancel drops the scope \xB7 /home shows the dashboard"
39256
+ leave: "b / back rewinds to the focus confirm card (data stays) \xB7 cancel \xB7 /home shows the dashboard"
39158
39257
  },
39159
39258
  strategize: {
39160
39259
  mode: "strategy objective confirm",
39161
- accepts: "\u23CE or yes (build the plan) \xB7 adjust (restate the objective) \xB7 cancel",
39260
+ accepts: "\u23CE or yes (build the plan) \xB7 b / back or adjust (restate the objective) \xB7 cancel",
39162
39261
  leave: "cancel drops the strategy session and returns to Q&A"
39163
39262
  }
39164
39263
  };
@@ -40086,6 +40185,20 @@ function printHelp() {
40086
40185
  console.log(" " + chalk88.dim("Type ") + paint("accent", '"ship a board deck"') + chalk88.dim(" to write a handoff."));
40087
40186
  console.log(" " + chalk88.dim("The ") + paint("accent", "\u203A") + chalk88.dim(" prompt shows brief or deep after analysis. Brief is the default."));
40088
40187
  console.log();
40188
+ console.log(" " + sectionHeading("Keys"));
40189
+ console.log(
40190
+ " " + paint("accent", "\u23CE") + chalk88.dim(" Runs the armed action shown at the prompt (yes, use demo data, go ahead, /connect).")
40191
+ );
40192
+ console.log(
40193
+ " " + paint("accent", "b") + chalk88.dim(" / ") + paint("accent", "back") + chalk88.dim(" Steps back one confirm gate, or leaves a modal. Does not unload data or undo compute.")
40194
+ );
40195
+ console.log(
40196
+ " " + paint("accent", "cancel") + chalk88.dim(" Drops the current modal overlay (strategy, think, ship, unconfirmed scope).")
40197
+ );
40198
+ console.log(
40199
+ " " + chalk88.dim("On ") + paint("accent", "/deepdive") + chalk88.dim(" slides, ") + paint("accent", "b") + chalk88.dim(" moves to the previous slide.")
40200
+ );
40201
+ console.log();
40089
40202
  console.log(" " + sectionHeading("Shortcuts"));
40090
40203
  const shortcuts = [
40091
40204
  ["/home", "Show status"],
@@ -10317,6 +10317,7 @@ CLI slide deck: SaaS refresher, five vital signs (definition, formula, visual, d
10317
10317
  Bare \`/deepdive\` runs the full onboarding tour. Type \`/deepdive guide\` to jump to how to use NTRP (includes teaching the inbox once).
10318
10318
  Type \`/deepdive handoff\` for the ship-to-AI slide. Type \`/deepdive <metric>\` to jump to one metric card.
10319
10319
  Type \`/deepdive list\` to print the catalog. Works without an AI key. Re-run anytime from the homescreen.
10320
+ During the tour: Enter advances, \`b\` / \`back\` goes to the previous slide, \`q\` quits. After the tour, the same \`b\` / \`back\` word steps back one confirm gate in the REPL (scope, data, strategy) \u2014 it does not unload data or undo compute.
10320
10321
  Live values overlay when an analysis exists.`
10321
10322
  },
10322
10323
  {
@@ -10331,7 +10332,7 @@ handler: ../commands/thinkwithme.ts
10331
10332
 
10332
10333
  Open a dedicated \`think \u203A\` channel to explore ideas, challenge assumptions, and pull evidence from your data.
10333
10334
  Bare \`/thinkwithme\` enters the channel. Type \`/thinkwithme <topic>\` to seed the first turn.
10334
- Requires an analysis and an AI key (\`/connect\`). Type \`done\` or \`cancel\` to return to \u203A.
10335
+ Requires an analysis and an AI key (\`/connect\`). Type \`done\`, \`cancel\`, or \`b\` / \`back\` to return to \u203A.
10335
10336
  When you are ready to commit to a plan, say so or the partner can hand off to \`/strategy\`.`
10336
10337
  },
10337
10338
  {
@@ -13242,10 +13243,12 @@ var init_agent_context = __esm({
13242
13243
  var scope_exports = {};
13243
13244
  __export(scope_exports, {
13244
13245
  confirmScope: () => confirmScope,
13246
+ isBackInput: () => isBackInput,
13245
13247
  isScopeAdjustInput: () => isScopeAdjustInput,
13246
13248
  isScopeConfirmInput: () => isScopeConfirmInput,
13247
13249
  isSkipClarifier: () => isSkipClarifier,
13248
- proposeScopeFromIntent: () => proposeScopeFromIntent
13250
+ proposeScopeFromIntent: () => proposeScopeFromIntent,
13251
+ unconfirmScope: () => unconfirmScope
13249
13252
  });
13250
13253
  function proposeScopeFromIntent(input) {
13251
13254
  const text = input.trim();
@@ -13290,12 +13293,20 @@ function confirmScope(ctx) {
13290
13293
  if (ctx.scope.primary_lens === "revenue_metrics" || ctx.scope.primary_lens === "gtm_health") {
13291
13294
  }
13292
13295
  }
13296
+ function unconfirmScope(ctx) {
13297
+ if (!ctx.scope?.confirmed_at) return;
13298
+ const { confirmed_at: _dropped, ...rest } = ctx.scope;
13299
+ ctx.scope = rest;
13300
+ }
13293
13301
  function isScopeConfirmInput(input) {
13294
13302
  return /^(yes|y|yeah|yep|correct|looks good|that'?s right|confirm|ok|okay|go)$/i.test(input.trim());
13295
13303
  }
13296
13304
  function isScopeAdjustInput(input) {
13297
13305
  return /^(adjust|change|refine|edit|no|nope)$/i.test(input.trim());
13298
13306
  }
13307
+ function isBackInput(input) {
13308
+ return /^(b|back|prev)\s*[.!]?\s*$/i.test(input.trim());
13309
+ }
13299
13310
  function isSkipClarifier(input) {
13300
13311
  return /^skip$/i.test(input.trim());
13301
13312
  }
@@ -14246,7 +14257,8 @@ function buildConversationPrompt(ctx) {
14246
14257
  const enterHint2 = action ? chalk8.dim(` \xB7 \u23CE ${action.hint}`) : "";
14247
14258
  return accentPromptHead(phase, sessionName) + chalk8.dim(stack) + enterHint2 + " ";
14248
14259
  }
14249
- const enterHint = action ? chalk8.dim(`\u23CE ${action.hint} `) : "";
14260
+ const modalBack = phase === "scope" || phase === "awaiting_data" || phase === "strategize" ? " \xB7 b back" : "";
14261
+ const enterHint = action ? chalk8.dim(`\u23CE ${action.hint}${modalBack} `) : "";
14250
14262
  return accentPromptHead(phase, sessionName) + enterHint;
14251
14263
  }
14252
14264
  var PROMPT_LABELS;
@@ -18837,7 +18849,7 @@ async function handleStrategizeFlow(input, ctx) {
18837
18849
  " " + chalk17.dim("That looks like a question. A strategy objective is waiting.")
18838
18850
  );
18839
18851
  console.log(
18840
- " " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to answer questions first.")
18852
+ " " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("b") + chalk17.dim(" / ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to answer questions first.")
18841
18853
  );
18842
18854
  console.log();
18843
18855
  return "Awaiting confirm";
@@ -18850,7 +18862,7 @@ async function handleStrategizeFlow(input, ctx) {
18850
18862
  }
18851
18863
  console.log();
18852
18864
  console.log(
18853
- " " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to stop.")
18865
+ " " + chalk17.dim("Type ") + chalk17.cyan("yes") + chalk17.dim(" to make the plan. Type ") + chalk17.cyan("b") + chalk17.dim(" / ") + chalk17.cyan("adjust") + chalk17.dim(" to change the objective. Type ") + chalk17.cyan("cancel") + chalk17.dim(" to stop.")
18854
18866
  );
18855
18867
  console.log();
18856
18868
  return "Awaiting confirm";
@@ -19027,7 +19039,7 @@ function printObjectiveCard(ctx, objective, proposed) {
19027
19039
  );
19028
19040
  console.log();
19029
19041
  console.log(
19030
- " " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel")
19042
+ " " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("b back") + chalk17.dim(" \xB7 ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel")
19031
19043
  );
19032
19044
  console.log();
19033
19045
  }
@@ -19170,7 +19182,7 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
19170
19182
  `Phase: ${phase} (${formatPhaseLabel(phase)}) \u2014 ${PHASE_MEANING[phase]}`
19171
19183
  ];
19172
19184
  if (channel === "think") {
19173
- lines.push("Channel: think \u2014 socratic partner; type done/cancel returns to \u203A");
19185
+ lines.push("Channel: think \u2014 socratic partner; type done/cancel/b back returns to \u203A");
19174
19186
  lines.push("Response mode: deep (tools on)");
19175
19187
  } else if (channel === "strategist") {
19176
19188
  lines.push("Channel: strategist \u2014 measurable plan engine owns sequencing");
@@ -19214,11 +19226,13 @@ function buildSituationalAwarenessBlock(ctx, opts = {}) {
19214
19226
  } else if (phase === "awaiting_data") {
19215
19227
  lines.push("Already done: scope confirmed; analysis not ready");
19216
19228
  lines.push(
19217
- 'Available now: tell the user to paste a CSV path, type "use demo data", or \u23CE go ahead when can_compute \u2014 you cannot open files'
19229
+ 'Available now: tell the user to paste a CSV path, type "use demo data", \u23CE go ahead when can_compute, or b/back to rewind the focus card \u2014 you cannot open files'
19218
19230
  );
19219
19231
  } else if (phase === "scope") {
19220
19232
  lines.push("Already done: intent proposed; waiting on confirm");
19221
- lines.push("Available now: wait for CLI confirm (\u23CE yes) \u2014 do not invent a different question");
19233
+ lines.push(
19234
+ "Available now: wait for CLI confirm (\u23CE yes) or b/back/adjust \u2014 do not invent a different question"
19235
+ );
19222
19236
  } else if (phase === "orient") {
19223
19237
  lines.push("Already done: nothing locked yet");
19224
19238
  lines.push("Available now: help the user name a focus; CLI will propose scope from their words");
@@ -19291,13 +19305,13 @@ var init_situation = __esm({
19291
19305
  FUNNEL_SPINE = "Funnel: orient \u2192 scope \u2192 data \u2192 compute \u2192 explore | side doors: think, strategy, ship";
19292
19306
  PHASE_MEANING = {
19293
19307
  orient: "setup \u2014 open chat; user has not locked a scope yet",
19294
- scope: "scope confirm \u2014 CLI owns yes/adjust; do not re-propose unless asked",
19295
- awaiting_data: "data gate \u2014 load CSV/demo or compute; CLI owns the gate",
19308
+ scope: "scope confirm \u2014 CLI owns yes / b back / adjust; do not re-propose unless asked",
19309
+ awaiting_data: "data gate \u2014 load CSV/demo or compute; b/back rewinds to scope confirm (does not unload data); CLI owns the gate",
19296
19310
  compute: "compute in progress \u2014 wait",
19297
19311
  explore: "analysis complete \u2014 free-form Q&A on this session's artifact",
19298
- think: "socratic think channel \u2014 return to \u203A via done/cancel",
19299
- strategize: "strategist flow \u2014 objective confirm or plan drafting",
19300
- deliver: "ship/handoff wizard \u2014 CLI owns write confirms"
19312
+ think: "socratic think channel \u2014 return to \u203A via done/cancel/b back",
19313
+ strategize: "strategist flow \u2014 objective confirm (yes / b back / adjust) or plan drafting",
19314
+ deliver: "ship/handoff wizard \u2014 CLI owns write confirms; b/back or cancel leaves the draft"
19301
19315
  };
19302
19316
  }
19303
19317
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonnechasser/ntrp",
3
- "version": "1.4.8",
3
+ "version": "1.4.9",
4
4
  "description": "GTM Health Diagnostic CLI — local pipeline analysis tool",
5
5
  "homepage": "https://ntrp.sonnechasser.com",
6
6
  "repository": {