@cspeach/cli 1.1.8 → 1.1.10

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/repl.js CHANGED
@@ -384,9 +384,9 @@ export async function runRepl(opts) {
384
384
  // Having aliases configured does NOT override the request.
385
385
  // A session that WAS standalone stays standalone on `--resume`: its
386
386
  // sap_system is the reserved 'none' alias, which has no entry in cfg.sap.
387
- const standalone = resolveStandalone(cfg, opts?.noSap === true) ||
387
+ let standalone = resolveStandalone(cfg, opts?.noSap === true) ||
388
388
  opts?.resumedSession?.sap_system === STANDALONE_ALIAS;
389
- if (standalone) {
389
+ const enterStandalone = async () => {
390
390
  console.log(chalk.dim('\nStandalone mode: no SAP system connected — skills work from pasted code. ' +
391
391
  'Connect later with: cspeach config add-sap'));
392
392
  // Ask the profile questions ONCE — they become the <sap_system> element
@@ -396,11 +396,30 @@ export async function runRepl(opts) {
396
396
  await runStandaloneProfileWizard({ cwd: process.cwd() });
397
397
  cfg = await loadConfig();
398
398
  }
399
+ };
400
+ if (standalone) {
401
+ await enterStandalone();
399
402
  }
400
403
  else if (Object.keys(cfg.sap).length === 0) {
401
- console.log(chalk.yellow('\nNo SAP systems configured. Starting onboarding wizard.'));
402
- await runAddSapWizard();
403
- cfg = await loadConfig();
404
+ // First run with nothing configured (2026-09-17): let the user choose.
405
+ // Someone without SAP access used to be pushed into the SAP wizard with
406
+ // no way out except knowing --no-sap. Non-TTY runs skip the question and
407
+ // keep the previous behaviour exactly.
408
+ const { chooseFirstRunMode } = await import('./sap/first-run-choice.js');
409
+ const mode = await chooseFirstRunMode();
410
+ if (mode === 'standalone') {
411
+ // Persist so the next plain `cspeach` is standalone too — same effect
412
+ // as `cspeach config set no_sap on`, which the user can undo any time.
413
+ cfg.no_sap = true;
414
+ await saveConfig(cfg);
415
+ standalone = true;
416
+ await enterStandalone();
417
+ }
418
+ else {
419
+ console.log(chalk.yellow('\nNo SAP systems configured. Starting onboarding wizard.'));
420
+ await runAddSapWizard();
421
+ cfg = await loadConfig();
422
+ }
404
423
  }
405
424
  // model-governance step 2d — fire-and-forget fetch of the served model-config
406
425
  // (managed mode only). NEVER blocks startup: a turn that starts before the
@@ -0,0 +1,34 @@
1
+ /**
2
+ * First-run choice (2026-09-17). Before this, a fresh install that started
3
+ * with plain `cspeach` (no --no-sap flag, no persisted no_sap) was pushed
4
+ * straight into the "Add SAP system" wizard — someone with no SAP access had
5
+ * no way forward except to know the flag. Seen live on Laeeq's dry run.
6
+ *
7
+ * Now, when no SAP alias is configured, the user picks how to start. The
8
+ * standalone answer is listed first: it needs nothing, and it is the path a
9
+ * trial user is on. Non-interactive runs (piped stdin, CI) never see the
10
+ * prompt and keep the exact pre-existing behaviour (the SAP wizard branch).
11
+ */
12
+ import { select } from '@inquirer/prompts';
13
+ export const FIRST_RUN_CHOICES = [
14
+ {
15
+ name: 'Work standalone — no SAP connection',
16
+ value: 'standalone',
17
+ description: 'Paste code or a requirement in; get code, reviews, designs and step-by-step implementation guides out. Connect a system later with: cspeach config add-sap',
18
+ },
19
+ {
20
+ name: 'Connect to an SAP system now',
21
+ value: 'sap',
22
+ description: 'Host, client, user and password for an SAP system CSPeach may read and write through ADT.',
23
+ },
24
+ ];
25
+ export async function chooseFirstRunMode(deps = {}) {
26
+ const isTTY = deps.isTTY ?? process.stdin.isTTY === true;
27
+ if (!isTTY)
28
+ return 'sap';
29
+ const ask = deps.select ?? select;
30
+ return ask({
31
+ message: 'No SAP system is set up yet. How do you want to start?',
32
+ choices: FIRST_RUN_CHOICES,
33
+ });
34
+ }
@@ -1,6 +1,6 @@
1
1
  // GENERATED FILE — bundled skills for local mode.
2
2
  // Source: https://manifest.cspeach.dev/v1.json
3
- // Generated: 2026-09-17T15:45:29.783Z
3
+ // Generated: 2026-09-17T17:10:00.533Z
4
4
  // Manifest signature verified at build time.
5
5
  export const BUNDLED_SKILLS = {
6
6
  "abap-atc-fix": {
package/dist/ui/footer.js CHANGED
@@ -27,6 +27,8 @@ function appendHistory(line, history) {
27
27
  const PEACH = '#FFCBA4';
28
28
  /** The input prompt — its width feeds the display budget below. */
29
29
  const PROMPT = 'CSPeach › ';
30
+ /** Held multi-line draft: how many of the newest lines are shown above the input. */
31
+ const HELD_LINES_SHOWN = 3;
30
32
  export function Footer({ onSubmit, onSlashTrigger, onHistoryTrigger, onValueChange, prefill, disabled, onNotice, onBufferChange }) {
31
33
  const [value, setValue] = useState('');
32
34
  // Wave1 live-fix (Bug 1, statusline duplication) — the input row must
@@ -203,5 +205,5 @@ export function Footer({ onSubmit, onSlashTrigger, onHistoryTrigger, onValueChan
203
205
  if (disabled) {
204
206
  return (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: PEACH, children: PROMPT }), _jsx(Text, { dimColor: true, children: "(answer above to continue)" })] }));
205
207
  }
206
- return (_jsxs(Box, { flexDirection: "column", children: [buffer.length > 0 && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["\u2026 ", buffer.length, " line", buffer.length === 1 ? '' : 's', " pending (Enter sends, \\ continues)"] })), _jsxs(Box, { children: [_jsx(Text, { bold: true, color: PEACH, children: PROMPT }), _jsx(TextInput, { value: value, onChange: setValue, onSubmit: handleSubmit, onPasteLines: handlePasteLines, maxDisplayWidth: inputBudget })] })] }));
208
+ return (_jsxs(Box, { flexDirection: "column", children: [buffer.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [buffer.length > HELD_LINES_SHOWN && (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["\u2026 ", buffer.length - HELD_LINES_SHOWN, " earlier line", buffer.length - HELD_LINES_SHOWN === 1 ? '' : 's'] })), buffer.slice(-HELD_LINES_SHOWN).map((line, i) => (_jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ['│ ', line] }, `held-${buffer.length}-${i}`))), _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [buffer.length, " line", buffer.length === 1 ? '' : 's', " pending \u2014 Enter sends all, Esc discards, \\ continues"] })] })), _jsxs(Box, { children: [_jsx(Text, { bold: true, color: PEACH, children: PROMPT }), _jsx(TextInput, { value: value, onChange: setValue, onSubmit: handleSubmit, onPasteLines: handlePasteLines, maxDisplayWidth: inputBudget })] })] }));
207
209
  }
@@ -181,11 +181,22 @@ export function PasteSafeTextInput({ value: originalValue, placeholder = '', foc
181
181
  nextCursorOffset = Math.max(0, cursorRef.current + cleanInput.length - (lastNl + 1));
182
182
  }
183
183
  else {
184
+ // 2026-09-17 — a single-line field (question form, question modal:
185
+ // no onPasteLines) receiving a multi-line paste. Windows Terminal
186
+ // delivers pasted newlines as bare \r in raw mode; left in the value
187
+ // they render as carriage returns (each line drawn over the last, so
188
+ // only the tail of the final line is visible) and reach the model as
189
+ // stray control characters. Join the lines with one space instead:
190
+ // the whole paste is visible and the answer is clean. Fields that do
191
+ // handle lines (the main prompt) never take this branch.
192
+ const insert = (input.includes('\n') || input.includes('\r'))
193
+ ? input.replace(/\r\n/g, '\n').replace(/\r/g, '\n').replace(/\n+/g, ' ')
194
+ : input;
184
195
  nextValue =
185
196
  currentValue.slice(0, cursorRef.current) +
186
- input +
197
+ insert +
187
198
  currentValue.slice(cursorRef.current, currentValue.length);
188
- nextCursorOffset += input.length;
199
+ nextCursorOffset += insert.length;
189
200
  }
190
201
  if (nextCursorOffset < 0)
191
202
  nextCursorOffset = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cspeach/cli",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "CSPeach — AI-assisted ABAP development CLI. Direct SAP access, 34 skills, safety gates.",
5
5
  "keywords": [
6
6
  "abap",