@cspeach/cli 1.1.18 → 1.1.20
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/README.md +3 -4
- package/dist/agent/anthropic-provider.js +30 -10
- package/dist/agent/cache-keepalive.js +162 -0
- package/dist/agent/cold-prune.js +116 -0
- package/dist/agent/loop.js +804 -159
- package/dist/agent/provider-shape.js +263 -0
- package/dist/agent/providers/ai-hub-provider.js +17 -2
- package/dist/agent/providers/byok-provider.js +33 -3
- package/dist/agent/providers/local-provider.js +12 -2
- package/dist/agent/repair-partial.js +125 -7
- package/dist/agent/summarise-via-provider.js +6 -1
- package/dist/agent/system-prompt.js +38 -0
- package/dist/agent/tool-dispatch.js +66 -21
- package/dist/agent/tool-loading-pin.js +100 -0
- package/dist/agent/turn-error-ux.js +77 -26
- package/dist/agent/turn-stream.js +25 -3
- package/dist/approvals/adt-type.js +27 -0
- package/dist/approvals/advisory-prompt.js +48 -0
- package/dist/approvals/advisory-render.js +25 -11
- package/dist/approvals/approval-prompt.js +74 -24
- package/dist/approvals/jwt.js +2 -1
- package/dist/approvals/render.js +64 -36
- package/dist/approvals/risk-floor.js +53 -2
- package/dist/auth/api-key.js +30 -16
- package/dist/auth/keychain.js +0 -0
- package/dist/auth/me.js +25 -7
- package/dist/cli.js +36 -6
- package/dist/commands/auto-compact.js +33 -16
- package/dist/commands/compact.js +49 -11
- package/dist/commands/config-set.js +16 -3
- package/dist/commands/config-show.js +18 -5
- package/dist/commands/cost.js +54 -35
- package/dist/commands/help.js +88 -50
- package/dist/commands/login.js +28 -19
- package/dist/commands/logout.js +7 -3
- package/dist/commands/plan-audit.js +1 -0
- package/dist/commands/plan-chain.js +71 -77
- package/dist/commands/plan-continue.js +1 -0
- package/dist/commands/plan-resume.js +20 -12
- package/dist/commands/whoami.js +23 -9
- package/dist/config/loader.js +70 -5
- package/dist/cost/cost-log.js +62 -2
- package/dist/cost/pricing.js +11 -6
- package/dist/doctor/checks/forge-rules.js +13 -3
- package/dist/doctor/checks/keychain.js +6 -4
- package/dist/doctor/run.js +101 -20
- package/dist/index.js +5 -2
- package/dist/lib/graceful-exit.js +56 -0
- package/dist/lib/piped-prompt.js +65 -0
- package/dist/lib/spill-labels.js +13 -0
- package/dist/lock-contention.js +2 -2
- package/dist/models/resolve.js +93 -2
- package/dist/models/server-config.js +158 -3
- package/dist/one-shot.js +150 -45
- package/dist/projects/answer-blockers.js +6 -1
- package/dist/projects/handover-md.js +15 -14
- package/dist/projects/image-attachments.js +15 -2
- package/dist/projects/plan-run.js +97 -54
- package/dist/projects/promote-command.js +11 -2
- package/dist/projects/save-command.js +3 -2
- package/dist/projects/status.js +2 -1
- package/dist/projects/yes-no.js +12 -0
- package/dist/renderer/abap-inline.js +18 -14
- package/dist/renderer/answer-trim.js +69 -0
- package/dist/renderer/banners.js +8 -8
- package/dist/renderer/brand-settled.js +19 -0
- package/dist/renderer/change-card.js +156 -0
- package/dist/renderer/checklist-format.js +130 -0
- package/dist/renderer/color-mode.js +45 -0
- package/dist/renderer/error-detail.js +104 -0
- package/dist/renderer/fence-state.js +46 -0
- package/dist/renderer/footer-line.js +100 -0
- package/dist/renderer/glyphs.js +25 -0
- package/dist/renderer/goodbye.js +38 -0
- package/dist/renderer/legacy-palette.js +41 -0
- package/dist/renderer/look.js +30 -0
- package/dist/renderer/markdown.js +299 -92
- package/dist/renderer/notice-log.js +72 -0
- package/dist/renderer/notice-shape.js +48 -0
- package/dist/renderer/notices.js +40 -8
- package/dist/renderer/panel-rows.js +16 -0
- package/dist/renderer/pipeline.js +66 -0
- package/dist/renderer/progress-chatter.js +23 -17
- package/dist/renderer/rendering-mode.js +34 -0
- package/dist/renderer/routing-line.js +14 -0
- package/dist/renderer/sanitize.js +12 -0
- package/dist/renderer/severity.js +2 -7
- package/dist/renderer/startup-lines.js +154 -0
- package/dist/renderer/status-footer.js +52 -34
- package/dist/renderer/steering-echo.js +41 -0
- package/dist/renderer/syntax.js +31 -12
- package/dist/renderer/tables.js +5 -1
- package/dist/renderer/theme.js +44 -0
- package/dist/renderer/thinking-heartbeat.js +33 -2
- package/dist/renderer/todo-block.js +10 -49
- package/dist/renderer/tool-labels.js +269 -0
- package/dist/renderer/tool-widget.js +245 -51
- package/dist/renderer/trace.js +42 -0
- package/dist/renderer/transcript-flow.js +132 -0
- package/dist/renderer/tty.js +34 -1
- package/dist/renderer/ui-width.js +55 -0
- package/dist/renderer/verify-chain.js +4 -2
- package/dist/renderer/widget-fallback.js +58 -65
- package/dist/repl/bracketed-paste.js +7 -1
- package/dist/repl/builtin-commands.js +19 -8
- package/dist/repl/credential-handover-gate.js +28 -0
- package/dist/repl/current-transport.js +13 -0
- package/dist/repl/early-line-buffer.js +5 -2
- package/dist/repl/file-picker.js +54 -12
- package/dist/repl/ink-stdin-guard.js +66 -0
- package/dist/repl/inquirer-guard.js +59 -16
- package/dist/repl/inquirer-theme.js +27 -33
- package/dist/repl/paste-marker.js +19 -0
- package/dist/repl/plan-turn-end.js +20 -0
- package/dist/repl/post-turn-status.js +43 -11
- package/dist/repl/reroute-turn.js +21 -0
- package/dist/repl/reset-tty-stdin.js +44 -0
- package/dist/repl/restore-guard.js +22 -0
- package/dist/repl/resume-standalone.js +63 -0
- package/dist/repl/rule8-detector.js +11 -3
- package/dist/repl/safety-confirm.js +161 -93
- package/dist/repl/session-spend-line.js +8 -4
- package/dist/repl/themed-prompts.js +19 -0
- package/dist/repl/ui-look-command.js +75 -0
- package/dist/repl/update-method-preview-hook.js +48 -5
- package/dist/repl.js +863 -339
- package/dist/rewind/cli.js +3 -2
- package/dist/rewind/format.js +14 -9
- package/dist/rewind/restore.js +11 -0
- package/dist/router/classifier.js +47 -4
- package/dist/router/intent-extractor.js +24 -8
- package/dist/router/piped-routing.js +23 -0
- package/dist/router/resume-routing.js +16 -0
- package/dist/router/routing-failure.js +57 -0
- package/dist/sap/first-run-choice.js +1 -1
- package/dist/sap/onboarding.js +3 -2
- package/dist/sap/standalone-onboarding.js +1 -1
- package/dist/sap/system-info.js +4 -2
- package/dist/sap/unreachable.js +28 -0
- package/dist/sap-errors/clean-error-text.js +182 -0
- package/dist/session/interrupt-reason.js +39 -0
- package/dist/session/recap.js +36 -24
- package/dist/session/repin-model.js +18 -0
- package/dist/session/resume.js +104 -36
- package/dist/session/store.js +84 -28
- package/dist/session/user-prompt.js +21 -0
- package/dist/skill-catalog.js +7 -0
- package/dist/skills/bundled-skills.js +76 -69
- package/dist/skills/preamble.js +75 -0
- package/dist/skills/source-manifest.js +11 -1
- package/dist/standards/standards-init.js +1 -1
- package/dist/test-helpers/answer-any.js +46 -0
- package/dist/tools/approval.js +27 -11
- package/dist/tools/ask-question.js +47 -5
- package/dist/tools/filesystem/file-read.js +11 -1
- package/dist/tools/filesystem/file-write.js +19 -2
- package/dist/tools/fiori/fe-extend.js +16 -1
- package/dist/tools/fiori/fe-scaffold.js +105 -3
- package/dist/tools/fiori/html-escapes.js +28 -0
- package/dist/tools/fiori/metadata/audit.js +605 -0
- package/dist/tools/fiori/metadata/references.js +104 -0
- package/dist/tools/fiori/metadata/types.js +8 -0
- package/dist/tools/fiori/preview/app-guard.js +215 -0
- package/dist/tools/fiori/preview/env.js +193 -0
- package/dist/tools/fiori/preview/readiness.js +127 -0
- package/dist/tools/fiori/preview/registry.js +412 -0
- package/dist/tools/fiori/preview/start.js +469 -0
- package/dist/tools/fiori/samples/loader.js +20 -5
- package/dist/tools/fiori/scaffold.js +18 -6
- package/dist/tools/fiori/smoke/app-driver-page.js +372 -0
- package/dist/tools/fiori/smoke/app-driver.js +100 -0
- package/dist/tools/fiori/smoke/assertions.js +161 -24
- package/dist/tools/fiori/smoke/audit-columns.js +21 -0
- package/dist/tools/fiori/smoke/batch.js +273 -0
- package/dist/tools/fiori/smoke/draft-safety.js +241 -0
- package/dist/tools/fiori/smoke/draft-smoke.js +473 -0
- package/dist/tools/fiori/smoke/driver.js +171 -4
- package/dist/tools/fiori/smoke/evidence.js +35 -0
- package/dist/tools/fiori/smoke/labels-codes.js +207 -0
- package/dist/tools/fiori/smoke/nav-error.js +26 -0
- package/dist/tools/fiori/smoke/run-smoke.js +155 -12
- package/dist/tools/fiori/tools.js +651 -7
- package/dist/tools/fiori/ui5-version.js +16 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/local-build.js +6 -0
- package/dist/tools/result-spill.js +238 -0
- package/dist/tools/sap-read.js +74 -15
- package/dist/tools/sap-write.js +122 -10
- package/dist/tools/shell/shell_exec.js +39 -12
- package/dist/tools/subagent/adt-serial.js +33 -0
- package/dist/tools/subagent/agent_run.js +2 -0
- package/dist/tools/subagent/read_agent.js +178 -0
- package/dist/tools/subagent/reader-prompt.js +48 -0
- package/dist/tools/syntax-state.js +67 -0
- package/dist/tools/todo.js +26 -19
- package/dist/tools/tool-loading.js +255 -0
- package/dist/tools/tool-output-read.js +117 -0
- package/dist/tools/transport-fit.js +462 -0
- package/dist/tools/transport-resolution.js +3 -1
- package/dist/tools/transport.js +39 -8
- package/dist/tools/verify.js +184 -8
- package/dist/ui/app.js +284 -146
- package/dist/ui/approval-modal.js +41 -15
- package/dist/ui/ask-answer-text.js +17 -0
- package/dist/ui/body.js +25 -6
- package/dist/ui/card-slot.js +98 -0
- package/dist/ui/checklist.js +9 -0
- package/dist/ui/coaching-picker-classic.js +8 -5
- package/dist/ui/command-palette.js +6 -4
- package/dist/ui/confirm-request.js +18 -0
- package/dist/ui/context-grid.js +2 -1
- package/dist/ui/file-palette.js +6 -4
- package/dist/ui/files-card-emitter.js +19 -0
- package/dist/ui/footer-line.js +16 -0
- package/dist/ui/footer.js +105 -104
- package/dist/ui/input-wrap.js +122 -0
- package/dist/ui/interrupt.js +103 -0
- package/dist/ui/key-burst.js +117 -0
- package/dist/ui/line-resolution.js +4 -3
- package/dist/ui/live-area.js +71 -0
- package/dist/ui/login-banner.js +54 -43
- package/dist/ui/rewind-panel.js +76 -24
- package/dist/ui/role-props.js +47 -0
- package/dist/ui/sap-state-store.js +2 -1
- package/dist/ui/session-timeline.js +49 -3
- package/dist/ui/skill-picker.js +5 -47
- package/dist/ui/text-input.js +128 -24
- package/dist/ui/todo-emitter.js +19 -0
- package/dist/ui/turn-status-emitter.js +65 -2
- package/dist/ui/turn-status.js +16 -19
- package/dist/ui/use-card-request.js +44 -0
- package/dist/ui/widgets/ask-card.js +521 -0
- package/dist/ui/widgets/bar-chart.js +11 -5
- package/dist/ui/widgets/coaching-picker.js +50 -55
- package/dist/ui/widgets/confirm-card.js +193 -0
- package/dist/ui/widgets/dep-graph.js +3 -2
- package/dist/ui/widgets/diff-viewer.js +6 -5
- package/dist/ui/widgets/files-card.js +97 -0
- package/dist/ui/widgets/question-card.js +2 -1
- package/dist/ui/widgets/reclaim-raw-stdin.js +24 -0
- package/dist/ui/widgets/shortcuts-sheet.js +25 -0
- package/dist/ui/widgets/stack-frames.js +2 -1
- package/dist/upgrade-check.js +39 -8
- package/package.json +12 -2
- package/test-harness/fe-draft/app.ts +188 -0
- package/test-harness/fe-draft/fe-draft.e2e.test.ts +461 -0
- package/test-harness/fe-draft/manifest-enhancer.unit.test.ts +51 -0
- package/test-harness/fe-draft/routes.ts +224 -0
- package/test-harness/fe-draft/routes.unit.test.ts +45 -0
- package/test-harness/fe-draft/server.ts +225 -0
- package/test-harness/fe-draft/variants.ts +196 -0
- package/vitest.smoke-e2e.config.ts +20 -0
package/dist/commands/help.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { SKILL_CATALOG } from '../skill-catalog.js';
|
|
3
|
+
import { costCommandDescription } from '../repl/builtin-commands.js';
|
|
4
|
+
import { panelHeading } from '../renderer/panel-rows.js';
|
|
5
|
+
import { paint } from '../renderer/theme.js';
|
|
6
|
+
import { uiWidth, truncateToWidth } from '../renderer/ui-width.js';
|
|
7
|
+
// I2's shortcuts table and note — one source for the `?` sheet and /help.
|
|
8
|
+
import { SHORTCUT_ROWS, SHORTCUTS_NOTE } from '../ui/widgets/shortcuts-sheet.js';
|
|
3
9
|
// Workflow order: plan → understand → modify → build → upgrade → ship.
|
|
4
10
|
const CATEGORY_ORDER = [
|
|
5
11
|
'Pre-Coding',
|
|
@@ -9,13 +15,45 @@ const CATEGORY_ORDER = [
|
|
|
9
15
|
'Upgrade / Migrate',
|
|
10
16
|
'Ship / Release',
|
|
11
17
|
];
|
|
12
|
-
const NAME_COL_PAD = 28; // padded width of
|
|
13
|
-
const
|
|
14
|
-
const DESC_GUTTER = 1; // 1 space between name col and description
|
|
15
|
-
const NAME_COL_WIDTH = NAME_GUTTER + NAME_COL_PAD + DESC_GUTTER; // 31
|
|
18
|
+
const NAME_COL_PAD = 28; // padded width of the command/skill column
|
|
19
|
+
const NAME_COL_WIDTH = 2 + NAME_COL_PAD + 1; // indent + column + gap = 31
|
|
16
20
|
const MAX_DESC_LINES = 2;
|
|
17
|
-
const MIN_DESC_WIDTH =
|
|
18
|
-
const
|
|
21
|
+
const MIN_DESC_WIDTH = 20;
|
|
22
|
+
const fixed = (s) => () => s;
|
|
23
|
+
/**
|
|
24
|
+
* Piece 2 §10 — /help grouped by what you want to do. Every palette command
|
|
25
|
+
* is listed (a test enforces it), plus /files and /ui look. The /cost text
|
|
26
|
+
* follows the cost display mode (credits for managed customers).
|
|
27
|
+
*/
|
|
28
|
+
export const HELP_GROUPS = [
|
|
29
|
+
{ title: 'Work on SAP', rows: [
|
|
30
|
+
{ name: '/transport [TR]', description: fixed('View or set the transport for this session') },
|
|
31
|
+
{ name: '/mode [advisory|gated|auto]', description: fixed('Set the write mode (Shift+Tab also cycles)') },
|
|
32
|
+
{ name: '/safety-mode on|off|status', description: fixed('Turn the Forge Rule confirmation cards on or off') },
|
|
33
|
+
{ name: '/rewind', description: fixed('Undo a write from this session (Esc Esc)') },
|
|
34
|
+
{ name: '/reroute <skill>', description: fixed('Re-run your last prompt with a different skill') },
|
|
35
|
+
] },
|
|
36
|
+
{ title: 'Local files', rows: [
|
|
37
|
+
{ name: '/files', description: fixed('Pick a saved project file and what to do with it') },
|
|
38
|
+
{ name: '@<file>', description: fixed('Attach a file from this folder to your prompt') },
|
|
39
|
+
{ name: '/export audit [file]', description: fixed('Write the signable session audit record to a file') },
|
|
40
|
+
] },
|
|
41
|
+
{ title: 'Session', rows: [
|
|
42
|
+
{ name: '/new', description: fixed('Next prompt picks a skill afresh (also /reset)') },
|
|
43
|
+
{ name: '/recap', description: fixed('Show the recap (last session, open tasks, plan)') },
|
|
44
|
+
{ name: '/cost', description: costCommandDescription },
|
|
45
|
+
{ name: '/context', description: fixed("Show what fills the model's context window") },
|
|
46
|
+
{ name: '/compact', description: fixed('Shrink a long session and keep the facts') },
|
|
47
|
+
{ name: '/help', description: fixed('This screen (also /skills)') },
|
|
48
|
+
{ name: '/exit', description: fixed('Quit CSPeach (also /quit)') },
|
|
49
|
+
] },
|
|
50
|
+
{ title: 'Look and settings', rows: [
|
|
51
|
+
{ name: '/ui [auto|ink]', description: fixed('Change the UI mode (applies after a relaunch)') },
|
|
52
|
+
{ name: '/ui look [panels|legacy]', description: fixed('Switch between the panels and legacy looks') },
|
|
53
|
+
] },
|
|
54
|
+
];
|
|
55
|
+
/** /help lists the `?` key itself first, then I2's sheet rows in their order. */
|
|
56
|
+
const SHORTCUTS = [['?', 'shortcuts'], ...SHORTCUT_ROWS];
|
|
19
57
|
/**
|
|
20
58
|
* Word-wrap a description into at most `maxLines` lines of `width` chars each.
|
|
21
59
|
* If content overflows, the last line is suffixed with `…`.
|
|
@@ -28,7 +66,6 @@ function wrapDescription(desc, width, maxLines) {
|
|
|
28
66
|
while (i < words.length && lines.length < maxLines) {
|
|
29
67
|
const word = words[i];
|
|
30
68
|
if (current.length === 0) {
|
|
31
|
-
// Word longer than width — hard-truncate
|
|
32
69
|
current = word.length <= width ? word : word.slice(0, width);
|
|
33
70
|
i++;
|
|
34
71
|
}
|
|
@@ -41,65 +78,66 @@ function wrapDescription(desc, width, maxLines) {
|
|
|
41
78
|
current = '';
|
|
42
79
|
}
|
|
43
80
|
}
|
|
44
|
-
if (current.length > 0 && lines.length < maxLines)
|
|
81
|
+
if (current.length > 0 && lines.length < maxLines)
|
|
45
82
|
lines.push(current);
|
|
46
|
-
}
|
|
47
|
-
// Overflow → suffix last line with " …"
|
|
48
83
|
if (i < words.length && lines.length > 0) {
|
|
49
84
|
const last = lines[lines.length - 1];
|
|
50
|
-
|
|
51
|
-
lines[lines.length - 1] = last + ' …';
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
lines[lines.length - 1] = last.slice(0, Math.max(0, width - 2)) + ' …';
|
|
55
|
-
}
|
|
85
|
+
lines[lines.length - 1] = last.length + 2 <= width ? last + ' …' : last.slice(0, Math.max(0, width - 2)) + ' …';
|
|
56
86
|
}
|
|
57
87
|
return lines;
|
|
58
88
|
}
|
|
59
|
-
|
|
60
|
-
* Prints the flat-list /help screen grouped by category.
|
|
61
|
-
* Cyan category headings, bold skill names, default-color descriptions.
|
|
62
|
-
* Descriptions wrap to MAX_DESC_LINES lines max, aligned to the name column.
|
|
63
|
-
*/
|
|
89
|
+
const groupTitle = (t) => paint('sap', chalk.bold(t));
|
|
64
90
|
/**
|
|
65
91
|
* Phase D5 (2026-05-17) — accepts an optional emit callback so Ink mode
|
|
66
92
|
* can route output through chunkEmitter instead of console.log (which
|
|
67
93
|
* corrupts the React frame). Default keeps the classic-mode behaviour.
|
|
68
94
|
*/
|
|
69
95
|
export function printHelp(emit = (s) => console.log(s)) {
|
|
70
|
-
const
|
|
71
|
-
const descWidth = Math.max(MIN_DESC_WIDTH,
|
|
96
|
+
const width = uiWidth();
|
|
97
|
+
const descWidth = Math.max(MIN_DESC_WIDTH, width - NAME_COL_WIDTH);
|
|
72
98
|
const continuationIndent = ' '.repeat(NAME_COL_WIDTH);
|
|
99
|
+
const row = (name, desc) => ` ${chalk.bold(truncateToWidth(name, NAME_COL_PAD).padEnd(NAME_COL_PAD))} ${paint('dim', truncateToWidth(desc, descWidth))}`;
|
|
73
100
|
emit('');
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
emit(panelHeading('Commands', 'type / to search'));
|
|
102
|
+
for (const group of HELP_GROUPS) {
|
|
103
|
+
emit('');
|
|
104
|
+
emit(groupTitle(group.title));
|
|
105
|
+
for (const r of group.rows)
|
|
106
|
+
emit(row(r.name, r.description()));
|
|
107
|
+
if (group.title === 'Work on SAP') {
|
|
108
|
+
// Skills live under "Work on SAP", one dim sub-heading per category.
|
|
109
|
+
for (const category of CATEGORY_ORDER) {
|
|
110
|
+
const skills = SKILL_CATALOG.filter((s) => s.category === category);
|
|
111
|
+
if (skills.length === 0)
|
|
112
|
+
continue;
|
|
113
|
+
emit(` ${paint('dim', category)}`);
|
|
114
|
+
for (const skill of skills) {
|
|
115
|
+
const descLines = wrapDescription(skill.description, descWidth, MAX_DESC_LINES);
|
|
116
|
+
emit(` ${chalk.bold(('/' + skill.name).padEnd(NAME_COL_PAD))} ${descLines[0] ?? ''}`);
|
|
117
|
+
for (let j = 1; j < descLines.length; j++)
|
|
118
|
+
emit(`${continuationIndent}${descLines[j]}`);
|
|
119
|
+
}
|
|
86
120
|
}
|
|
87
121
|
}
|
|
88
|
-
emit('');
|
|
89
122
|
}
|
|
90
|
-
emit(
|
|
91
|
-
emit(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
emit('');
|
|
124
|
+
emit(groupTitle('Shortcuts'));
|
|
125
|
+
// Pack "key action" pairs into lines no wider than the UI. The plain
|
|
126
|
+
// length is tracked alongside the painted line (no ANSI stripping).
|
|
127
|
+
let line = ' ';
|
|
128
|
+
let lineLen = 1;
|
|
129
|
+
for (const [key, action] of SHORTCUTS) {
|
|
130
|
+
const cellLen = 2 + key.length + 2 + action.length;
|
|
131
|
+
if (lineLen + cellLen > width && lineLen > 1) {
|
|
132
|
+
emit(line);
|
|
133
|
+
line = ' ';
|
|
134
|
+
lineLen = 1;
|
|
135
|
+
}
|
|
136
|
+
line += ` ${chalk.bold(key)} ${paint('dim', action)}`;
|
|
137
|
+
lineLen += cellLen;
|
|
138
|
+
}
|
|
139
|
+
if (lineLen > 1)
|
|
140
|
+
emit(line);
|
|
141
|
+
emit(` ${paint('dim', truncateToWidth(SHORTCUTS_NOTE, width - 2))}`);
|
|
104
142
|
emit('');
|
|
105
143
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3) Print user_code prominently so customer can read it
|
|
8
8
|
* 4) Poll /v1/auth/device/poll every interval until verified or maxAttempts
|
|
9
9
|
* 5) Save api_key + customer_id to ~/.cspeach/auth.json
|
|
10
|
-
* 6) Call GET /v1/me — if trial_mode === 'byok', prompt for
|
|
10
|
+
* 6) Call GET /v1/me — if trial_mode === 'byok', prompt for the user's own API key
|
|
11
11
|
* 7) Re-save auth file with byokAnthropicKey if provided
|
|
12
12
|
*
|
|
13
13
|
* Dependencies (open, print, prompt) are injected so the command is
|
|
@@ -16,8 +16,16 @@
|
|
|
16
16
|
import keychain from '../auth/keychain.js';
|
|
17
17
|
import { writeAuthFile } from '../auth/auth-file.js';
|
|
18
18
|
import { fetchOrgAnthropicKey } from '../auth/org-anthropic-key.js';
|
|
19
|
+
import { formatFailure, formatAttention, formatInfo } from '../renderer/notice-shape.js';
|
|
20
|
+
import { panelRow } from '../renderer/panel-rows.js';
|
|
21
|
+
import { paint } from '../renderer/theme.js';
|
|
22
|
+
import { getGlyphs } from '../renderer/glyphs.js';
|
|
23
|
+
import { sanitizeControlChars } from '../renderer/sanitize.js';
|
|
19
24
|
const ANTHROPIC_KEYCHAIN_SERVICE = 'cspeach.anthropic';
|
|
20
25
|
const ANTHROPIC_KEYCHAIN_ACCOUNT = 'api-key';
|
|
26
|
+
/** Server-supplied text (user code, URL, email, plan) is one line with no control characters. */
|
|
27
|
+
const serverText = (v) => sanitizeControlChars(String(v ?? '')).replace(/\s*\r?\n\s*/g, ' ').trim();
|
|
28
|
+
const done = (text) => `${paint('ok', getGlyphs().done)} ${text}`;
|
|
21
29
|
export async function runLoginCommand(deps) {
|
|
22
30
|
const proxy = deps.proxyUrl.replace(/\/$/, '');
|
|
23
31
|
const pollInterval = deps.pollIntervalMs ?? 5000;
|
|
@@ -27,26 +35,26 @@ export async function runLoginCommand(deps) {
|
|
|
27
35
|
try {
|
|
28
36
|
const r = await fetch(`${proxy}/v1/auth/device/start`, { method: 'POST' });
|
|
29
37
|
if (!r.ok) {
|
|
30
|
-
deps.print(
|
|
38
|
+
deps.print(formatFailure({ what: 'Could not start sign-in', detail: [`HTTP ${r.status}`], next: 'check your network, then run cspeach login again' }).trimEnd());
|
|
31
39
|
return 2;
|
|
32
40
|
}
|
|
33
41
|
start = await r.json();
|
|
34
42
|
}
|
|
35
43
|
catch (err) {
|
|
36
|
-
deps.print(
|
|
44
|
+
deps.print(formatFailure({ what: 'Could not start sign-in', detail: [err instanceof Error ? err.message : String(err)], next: 'check your network, then run cspeach login again' }).trimEnd());
|
|
37
45
|
return 2;
|
|
38
46
|
}
|
|
39
47
|
// 2 + 3) Browser + user_code. The next line auto-opens the browser, so
|
|
40
48
|
// there's nothing for the user to do — just print honest "opening" wording.
|
|
41
49
|
deps.print('');
|
|
42
|
-
deps.print(
|
|
43
|
-
deps.print(
|
|
50
|
+
deps.print(panelRow('One-time code', serverText(start.user_code), 14));
|
|
51
|
+
deps.print(panelRow('Opening', serverText(start.verification_url_complete), 14));
|
|
44
52
|
deps.print('');
|
|
45
53
|
try {
|
|
46
54
|
await deps.open(start.verification_url_complete);
|
|
47
55
|
}
|
|
48
56
|
catch {
|
|
49
|
-
deps.print(
|
|
57
|
+
deps.print(formatAttention({ text: "The browser didn't open", next: `open ${start.verification_url_complete} yourself` }).trimEnd());
|
|
50
58
|
}
|
|
51
59
|
// 4) Poll.
|
|
52
60
|
let polled = null;
|
|
@@ -67,19 +75,19 @@ export async function runLoginCommand(deps) {
|
|
|
67
75
|
if (errBody.error === 'authorization_pending')
|
|
68
76
|
continue;
|
|
69
77
|
if (errBody.error === 'expired_token') {
|
|
70
|
-
deps.print(
|
|
78
|
+
deps.print(formatFailure({ what: 'The sign-in code expired', next: 'cspeach login' }).trimEnd());
|
|
71
79
|
return 3;
|
|
72
80
|
}
|
|
73
|
-
deps.print(
|
|
81
|
+
deps.print(formatFailure({ what: 'Sign-in failed', detail: [`HTTP ${r.status} · ${errBody.error ?? 'unknown'}`], next: 'cspeach login' }).trimEnd());
|
|
74
82
|
return 4;
|
|
75
83
|
}
|
|
76
84
|
catch (err) {
|
|
77
|
-
deps.print(`
|
|
85
|
+
deps.print(formatInfo(`Network blip, retrying (${err instanceof Error ? err.message : String(err)})`).trimEnd());
|
|
78
86
|
continue;
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
if (!polled) {
|
|
82
|
-
deps.print(
|
|
90
|
+
deps.print(formatFailure({ what: 'Timed out waiting for the browser sign-in', next: 'cspeach login' }).trimEnd());
|
|
83
91
|
return 5;
|
|
84
92
|
}
|
|
85
93
|
// 5) Save (non-BYOK part).
|
|
@@ -88,13 +96,13 @@ export async function runLoginCommand(deps) {
|
|
|
88
96
|
customerId: polled.customer_id,
|
|
89
97
|
byokAnthropicKey: null,
|
|
90
98
|
});
|
|
91
|
-
deps.print(
|
|
99
|
+
deps.print(done('Saved your CSPeach key to ~/.cspeach/auth.json'));
|
|
92
100
|
// 6) /v1/me — figure out if BYOK is required.
|
|
93
101
|
try {
|
|
94
102
|
const r = await fetch(`${proxy}/v1/me`, { headers: { Authorization: `Bearer ${polled.api_key}` } });
|
|
95
103
|
if (r.ok) {
|
|
96
104
|
const me = await r.json();
|
|
97
|
-
deps.print(
|
|
105
|
+
deps.print(done(`Signed in as ${serverText(me.email)} · ${serverText(me.plan)}`));
|
|
98
106
|
if (me.trial_mode === 'byok' || me.plan === 'byo-key') {
|
|
99
107
|
// Try to fetch the org's Anthropic key first — non-fatal if unavailable.
|
|
100
108
|
const orgKey = await fetchOrgAnthropicKey(proxy, polled.api_key, {
|
|
@@ -104,14 +112,14 @@ export async function runLoginCommand(deps) {
|
|
|
104
112
|
((svc, acct, k) => keychain.setPassword(svc, acct, k));
|
|
105
113
|
if (orgKey !== null) {
|
|
106
114
|
await setKey(ANTHROPIC_KEYCHAIN_SERVICE, ANTHROPIC_KEYCHAIN_ACCOUNT, orgKey);
|
|
107
|
-
deps.print(
|
|
115
|
+
deps.print(done("Using your organization's API key"));
|
|
108
116
|
}
|
|
109
117
|
else {
|
|
110
118
|
// Fall back to the manual paste prompt — behaviour unchanged.
|
|
111
119
|
deps.print('');
|
|
112
|
-
deps.print('This account is BYOK
|
|
113
|
-
|
|
114
|
-
const ak = (await deps.prompt('Paste your Anthropic API key (sk-ant
|
|
120
|
+
deps.print(formatInfo('This account is BYOK: you provide your own API key').trimEnd());
|
|
121
|
+
// The key-entry row — the one place the vendor and its key page are named.
|
|
122
|
+
const ak = (await deps.prompt('Paste your Anthropic API key (sk-ant-..., from https://console.anthropic.com/settings/keys): ')).trim();
|
|
115
123
|
if (ak.startsWith('sk-ant-') && ak.length > 20) {
|
|
116
124
|
// The non-BYOK save above just persisted these exact values,
|
|
117
125
|
// so reading them back from disk would be redundant.
|
|
@@ -120,18 +128,19 @@ export async function runLoginCommand(deps) {
|
|
|
120
128
|
customerId: polled.customer_id,
|
|
121
129
|
byokAnthropicKey: ak,
|
|
122
130
|
});
|
|
123
|
-
deps.print('
|
|
131
|
+
deps.print(done('API key saved'));
|
|
124
132
|
}
|
|
125
133
|
else {
|
|
126
|
-
deps.print(
|
|
134
|
+
deps.print(formatFailure({ what: 'That does not look like your own API key', detail: ['it must start with sk-ant-', 'skipped'], next: 'run cspeach login again later' }).trimEnd());
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
catch (err) {
|
|
133
|
-
deps.print(`
|
|
141
|
+
deps.print(formatInfo(`Could not read your account details (${err instanceof Error ? err.message : String(err)}) · sign-in still worked`).trimEnd());
|
|
134
142
|
}
|
|
143
|
+
deps.print(` ${paint('dim', 'Next:')} run cspeach to start`);
|
|
135
144
|
return 0;
|
|
136
145
|
}
|
|
137
146
|
function sleep(ms) {
|
package/dist/commands/logout.js
CHANGED
|
@@ -3,15 +3,19 @@
|
|
|
3
3
|
* will fall back to the keytar-stored API key if one is present.
|
|
4
4
|
*/
|
|
5
5
|
import { deleteAuthFile, readAuthFile } from '../auth/auth-file.js';
|
|
6
|
+
import { formatInfo } from '../renderer/notice-shape.js';
|
|
7
|
+
import { paint } from '../renderer/theme.js';
|
|
8
|
+
import { getGlyphs } from '../renderer/glyphs.js';
|
|
6
9
|
export async function runLogoutCommand(deps) {
|
|
7
10
|
const before = await readAuthFile();
|
|
8
11
|
await deleteAuthFile();
|
|
9
12
|
if (before) {
|
|
10
|
-
deps.print('
|
|
11
|
-
deps.print(
|
|
13
|
+
deps.print(`${paint('ok', getGlyphs().done)} Signed out · removed ~/.cspeach/auth.json`);
|
|
14
|
+
deps.print(` ${paint('dim', 'The REPL uses the keychain-stored key if you have one.')}`);
|
|
15
|
+
deps.print(` ${paint('dim', 'Next:')} cspeach login (to sign in again)`);
|
|
12
16
|
}
|
|
13
17
|
else {
|
|
14
|
-
deps.print('Already signed out
|
|
18
|
+
deps.print(formatInfo('Already signed out · no auth.json present').trimEnd());
|
|
15
19
|
}
|
|
16
20
|
return 0;
|
|
17
21
|
}
|
|
@@ -635,6 +635,7 @@ export async function runPhaseAudit(args) {
|
|
|
635
635
|
// read-only filter (which still exposes file_read/grep/glob).
|
|
636
636
|
toolFilter: () => false,
|
|
637
637
|
modelOverride: auditModel,
|
|
638
|
+
modelRole: 'audit',
|
|
638
639
|
suppressSaveHook: true,
|
|
639
640
|
signal: controller.signal,
|
|
640
641
|
});
|