@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.50
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash-classifier.js +400 -4
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/diff-capture.js +112 -3
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +86 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/budgets.js +98 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +860 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1045 -36
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +284 -2
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1897 -37
- package/dist/core/repl/slash-commands.js +430 -15
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +80 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +3241 -343
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +412 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +32 -0
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +203 -4
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +51 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +218 -3
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +313 -35
- package/dist/tui/repl-splash-art.js +1 -1
- package/dist/tui/repl-splash-mascot.js +32 -8
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +85 -5
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +52 -3
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +12 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `/feedback` interactive prompt — Leak L21 (2026-05-27).
|
|
4
|
+
*
|
|
5
|
+
* Five-step wizard mounted from both the top-level `pugi feedback`
|
|
6
|
+
* shell handler and the in-REPL `/feedback` slash. Steps:
|
|
7
|
+
*
|
|
8
|
+
* 1. category — bug / feature / general / praise (numeric pick 1-4)
|
|
9
|
+
* 2. rating — 1-5 stars (numeric pick 1-5)
|
|
10
|
+
* 3. comment — free-text, multi-line, Ctrl-D submits, Esc cancels
|
|
11
|
+
* 4. context — y/n include last 5 turns (redacted)? default no
|
|
12
|
+
* 5. confirm — y/n submit?
|
|
13
|
+
*
|
|
14
|
+
* Brand voice gate: ASCII glyphs only, no em-dashes, no banned brand
|
|
15
|
+
* words. Copy is intentionally power-word neutral so the future i18n
|
|
16
|
+
* landing localises cleanly.
|
|
17
|
+
*
|
|
18
|
+
* The component is PURE in the Ink sense — props in, one terminal
|
|
19
|
+
* `onResolve` event out. The caller owns the submit + queue logic.
|
|
20
|
+
*
|
|
21
|
+
* The verdict the operator submits is:
|
|
22
|
+
* - `{ cancelled: true }` when the operator presses Esc at any step
|
|
23
|
+
* - `{ cancelled: false, draft }` when the wizard completes
|
|
24
|
+
*
|
|
25
|
+
* `draft` is the assembled `FeedbackDraft` — NOT yet a full envelope
|
|
26
|
+
* (the caller still injects `ts` + `cliVersion` + maybe `tier`).
|
|
27
|
+
*/
|
|
28
|
+
import { useState } from 'react';
|
|
29
|
+
import { Box, Text, render, useApp, useInput } from 'ink';
|
|
30
|
+
const CATEGORIES = [
|
|
31
|
+
{ value: 'bug', label: 'bug', gloss: 'something broke or behaves unexpectedly' },
|
|
32
|
+
{ value: 'feature', label: 'feature', gloss: 'request a new capability' },
|
|
33
|
+
{ value: 'general', label: 'general', gloss: 'observation, idea, comment' },
|
|
34
|
+
{ value: 'praise', label: 'praise', gloss: 'positive note for the team' },
|
|
35
|
+
];
|
|
36
|
+
export function FeedbackPrompt(props) {
|
|
37
|
+
const [step, setStep] = useState('category');
|
|
38
|
+
const [category, setCategory] = useState(null);
|
|
39
|
+
const [rating, setRating] = useState(null);
|
|
40
|
+
const [commentBuffer, setCommentBuffer] = useState('');
|
|
41
|
+
const [includeContext, setIncludeContext] = useState(false);
|
|
42
|
+
useInput((input, key) => {
|
|
43
|
+
if (key.escape) {
|
|
44
|
+
props.onResolve({ cancelled: true });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (step === 'category') {
|
|
48
|
+
const n = Number.parseInt(input, 10);
|
|
49
|
+
if (!Number.isNaN(n) && n >= 1 && n <= CATEGORIES.length) {
|
|
50
|
+
const picked = CATEGORIES[n - 1];
|
|
51
|
+
if (picked) {
|
|
52
|
+
setCategory(picked.value);
|
|
53
|
+
setStep('rating');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (step === 'rating') {
|
|
59
|
+
const n = Number.parseInt(input, 10);
|
|
60
|
+
if (!Number.isNaN(n) && n >= 1 && n <= 5) {
|
|
61
|
+
setRating(n);
|
|
62
|
+
setStep('comment');
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (step === 'comment') {
|
|
67
|
+
// Ctrl-D submits the comment (even when empty — rating-only
|
|
68
|
+
// feedback is allowed). Enter inserts a newline so the
|
|
69
|
+
// operator can write multi-line bug repros without the wizard
|
|
70
|
+
// ending the buffer prematurely.
|
|
71
|
+
if (key.ctrl && (input === 'd' || input === '')) {
|
|
72
|
+
setStep('context');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (key.return) {
|
|
76
|
+
setCommentBuffer((prev) => prev + '\n');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (key.backspace || key.delete) {
|
|
80
|
+
setCommentBuffer((prev) => prev.slice(0, -1));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Printable characters land in the buffer. We accept anything
|
|
84
|
+
// non-empty + non-control. Ink's useInput sometimes delivers
|
|
85
|
+
// multi-char paste bursts as one `input` — we append the whole
|
|
86
|
+
// burst so paste lands intact.
|
|
87
|
+
if (input && !key.ctrl && !key.meta) {
|
|
88
|
+
setCommentBuffer((prev) => prev + input);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (step === 'context') {
|
|
93
|
+
if (input === 'y' || input === 'Y') {
|
|
94
|
+
setIncludeContext(true);
|
|
95
|
+
setStep('confirm');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (input === 'n' || input === 'N' || key.return) {
|
|
99
|
+
// Default no — Enter at the context prompt picks "no" so
|
|
100
|
+
// the operator can blast through with all defaults.
|
|
101
|
+
setIncludeContext(false);
|
|
102
|
+
setStep('confirm');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (step === 'confirm') {
|
|
108
|
+
if (input === 'y' || input === 'Y' || key.return) {
|
|
109
|
+
if (category != null && rating != null) {
|
|
110
|
+
props.onResolve({
|
|
111
|
+
cancelled: false,
|
|
112
|
+
draft: {
|
|
113
|
+
category,
|
|
114
|
+
rating,
|
|
115
|
+
comment: commentBuffer,
|
|
116
|
+
includeSessionContext: includeContext,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (input === 'n' || input === 'N') {
|
|
123
|
+
props.onResolve({ cancelled: true });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}, { isActive: !props.inert });
|
|
129
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Pugi /feedback" }), _jsx(Text, { children: " \u2014 share what you saw. Esc cancels at any step." })] }), step === 'category' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "1. Category" }), CATEGORIES.map((c, idx) => (_jsx(Text, { children: ` ${idx + 1}. ${c.label.padEnd(8)} ${c.gloss}` }, c.value))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Pick 1-4." }) })] })), step === 'rating' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "2. Rating" }), _jsx(Text, { children: ` 1=poor, 5=excellent. Picked category: ${category ?? '?'}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Pick 1-5." }) })] })), step === 'comment' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "3. Comment" }), _jsx(Text, { dimColor: true, children: "Multi-line. Enter inserts a newline. Ctrl-D submits." }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: commentBuffer.length === 0 ? (_jsx(Text, { dimColor: true, children: "(empty \u2014 Ctrl-D submits without a comment)" })) : (commentBuffer.split('\n').map((line, idx) => (_jsx(Text, { children: `> ${line}` }, idx)))) })] })), step === 'context' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "4. Include session context?" }), _jsx(Text, { children: ' Last 5 turns, redacted (tokens / *_KEY=* values stripped).' }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "y / n \u2014 default n (Enter)." }) })] })), step === 'confirm' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "5. Submit?" }), _jsx(Text, { children: ` category=${category ?? '?'} rating=${rating ?? '?'} context=${includeContext ? 'yes' : 'no'}` }), _jsx(Text, { children: ` comment=${commentBuffer.length} chars` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "y / n \u2014 default y (Enter)." }) })] }))] }));
|
|
130
|
+
}
|
|
131
|
+
export async function renderFeedbackPrompt() {
|
|
132
|
+
let resolveOuter;
|
|
133
|
+
const outerPromise = new Promise((resolve) => {
|
|
134
|
+
resolveOuter = resolve;
|
|
135
|
+
});
|
|
136
|
+
function App() {
|
|
137
|
+
const { exit } = useApp();
|
|
138
|
+
return (_jsx(FeedbackPrompt, { onResolve: (verdict) => {
|
|
139
|
+
resolveOuter(verdict);
|
|
140
|
+
// Mirror renderAskCli: tiny delay so Ink flushes unmount
|
|
141
|
+
// before the caller prints the toast line.
|
|
142
|
+
setTimeout(() => exit(), 16);
|
|
143
|
+
} }));
|
|
144
|
+
}
|
|
145
|
+
const instance = render(_jsx(App, {}));
|
|
146
|
+
const verdict = await outerPromise;
|
|
147
|
+
try {
|
|
148
|
+
await instance.waitUntilExit();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ink can throw when exit() races with a re-render — the verdict
|
|
152
|
+
// is already captured so we ignore.
|
|
153
|
+
}
|
|
154
|
+
return verdict;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=feedback-prompt.js.map
|
package/dist/tui/input-box.js
CHANGED
|
@@ -33,6 +33,14 @@ import { SlashPalette, completePalette, filterPalette, } from './slash-palette.j
|
|
|
33
33
|
import { EMPTY_KILL_RING, killToLineEnd, killToLineStart, killWordBackward, yankAtCursor, } from '../core/repl/kill-ring.js';
|
|
34
34
|
import { readClipboard } from '../core/repl/clipboard-read.js';
|
|
35
35
|
const CTRL_C_DOUBLE_TAP_MS = 1_000;
|
|
36
|
+
/**
|
|
37
|
+
* Wave 6 BT 8 (Claude Code parity): Esc-Esc walks the conversation back
|
|
38
|
+
* one turn. 500ms is tight enough that an operator clearing the buffer +
|
|
39
|
+
* later changing their mind does NOT accidentally pop a turn, while
|
|
40
|
+
* still feeling like one motion. Matches Claude Code's documented
|
|
41
|
+
* double-Esc window.
|
|
42
|
+
*/
|
|
43
|
+
const ESCAPE_DOUBLE_TAP_MS = 500;
|
|
36
44
|
/** Width subtracted from the terminal width so the border + padding fit. */
|
|
37
45
|
const FRAME_OVERHEAD_COLUMNS = 4;
|
|
38
46
|
/** Fallback width when ink cannot read stdout (e.g. test harness). */
|
|
@@ -75,6 +83,18 @@ export function InputBox(props) {
|
|
|
75
83
|
const [history, setHistory] = useState(seededHistory);
|
|
76
84
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
77
85
|
const [lastCtrlCAt, setLastCtrlCAt] = useState(undefined);
|
|
86
|
+
// CEO P0 #2 (2026-05-29): Claude Code parity — surface а visible
|
|
87
|
+
// "Press Ctrl+C again to exit" toast on the first Ctrl+C press so
|
|
88
|
+
// the operator knows the second press will terminate the REPL.
|
|
89
|
+
// Auto-clears after CTRL_C_DOUBLE_TAP_MS so it never lingers past
|
|
90
|
+
// the double-tap window.
|
|
91
|
+
const [ctrlCToast, setCtrlCToast] = useState(null);
|
|
92
|
+
const ctrlCToastTimerRef = useRef(null);
|
|
93
|
+
// Wave 6 BT 8: Esc-Esc walkback double-tap window. Tracks the epoch
|
|
94
|
+
// ms of the most recent Esc press so the next Esc within
|
|
95
|
+
// ESCAPE_DOUBLE_TAP_MS triggers the walkback handler instead of
|
|
96
|
+
// re-clearing the buffer.
|
|
97
|
+
const [lastEscapeAt, setLastEscapeAt] = useState(undefined);
|
|
78
98
|
const [cursorVisible, setCursorVisible] = useState(true);
|
|
79
99
|
// Ctrl+R / Ctrl+S reverse-search mode. Undefined when idle, a
|
|
80
100
|
// HistorySearchState while the operator is searching.
|
|
@@ -94,6 +114,11 @@ export function InputBox(props) {
|
|
|
94
114
|
// panes when Ctrl+L wipes the terminal (the parent React tree is
|
|
95
115
|
// otherwise stable and would not redraw on a stdout.write alone).
|
|
96
116
|
const [, setRedrawTick] = useState(0);
|
|
117
|
+
// Wave 7 Shift+Tab toast — flashed for 2s after a mode cycle so the
|
|
118
|
+
// operator sees `Mode → acceptEdits` под the input divider. Cleared
|
|
119
|
+
// by a setTimeout so a quick second Shift+Tab refreshes the toast.
|
|
120
|
+
const [modeCycleToast, setModeCycleToast] = useState(null);
|
|
121
|
+
const modeCycleTimerRef = useRef(null);
|
|
97
122
|
const now = props.now ?? Date.now;
|
|
98
123
|
const { stdout } = useStdout();
|
|
99
124
|
const columns = stdout?.columns ?? FALLBACK_COLUMNS;
|
|
@@ -173,6 +198,28 @@ export function InputBox(props) {
|
|
|
173
198
|
return;
|
|
174
199
|
}
|
|
175
200
|
setLastCtrlCAt(t);
|
|
201
|
+
// CEO P0 #2 (2026-05-29): surface the "Press Ctrl+C again to
|
|
202
|
+
// exit" toast on the first press so the operator sees the
|
|
203
|
+
// double-tap semantics in the UI, not just в the bottom hint
|
|
204
|
+
// line. Mirrors Claude Code's exit affordance verbatim. The
|
|
205
|
+
// toast string varies by which branch fired (cancel vs idle
|
|
206
|
+
// clear) so the operator learns what the press just did:
|
|
207
|
+
//
|
|
208
|
+
// - cancelResult === true → "Aborted. Press Ctrl+C again to exit."
|
|
209
|
+
// - cancelResult === false → "Press Ctrl+C again to exit."
|
|
210
|
+
//
|
|
211
|
+
// (The undefined branch already returned above — а modal owns
|
|
212
|
+
// input и the toast is suppressed.)
|
|
213
|
+
const toastCopy = cancelResult === true
|
|
214
|
+
? 'Aborted. Press Ctrl+C again to exit.'
|
|
215
|
+
: 'Press Ctrl+C again to exit.';
|
|
216
|
+
setCtrlCToast(toastCopy);
|
|
217
|
+
if (ctrlCToastTimerRef.current)
|
|
218
|
+
clearTimeout(ctrlCToastTimerRef.current);
|
|
219
|
+
ctrlCToastTimerRef.current = setTimeout(() => {
|
|
220
|
+
setCtrlCToast(null);
|
|
221
|
+
ctrlCToastTimerRef.current = null;
|
|
222
|
+
}, CTRL_C_DOUBLE_TAP_MS);
|
|
176
223
|
// Legacy behaviour: on idle (or no onCancel wired), clear the
|
|
177
224
|
// buffer + reset search so the operator's screen is calm before
|
|
178
225
|
// they confirm exit. When we DID cancel a live dispatch, keep
|
|
@@ -184,6 +231,24 @@ export function InputBox(props) {
|
|
|
184
231
|
}
|
|
185
232
|
return;
|
|
186
233
|
}
|
|
234
|
+
// Wave 7 — Claude Code parity: Shift+Tab cycles permission mode.
|
|
235
|
+
// The host owns the cycle logic + persistence; we just intercept
|
|
236
|
+
// the chord and surface a one-line toast on success. Place this
|
|
237
|
+
// BEFORE the search-mode and palette branches so a Shift+Tab fires
|
|
238
|
+
// even while reverse-search is active (operator habit-driven).
|
|
239
|
+
if (key.shift && key.tab && props.onCyclePermissionMode) {
|
|
240
|
+
const nextMode = props.onCyclePermissionMode();
|
|
241
|
+
if (nextMode) {
|
|
242
|
+
setModeCycleToast(`Mode → ${nextMode}`);
|
|
243
|
+
if (modeCycleTimerRef.current)
|
|
244
|
+
clearTimeout(modeCycleTimerRef.current);
|
|
245
|
+
modeCycleTimerRef.current = setTimeout(() => {
|
|
246
|
+
setModeCycleToast(null);
|
|
247
|
+
modeCycleTimerRef.current = null;
|
|
248
|
+
}, 2_000);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
187
252
|
// Search-mode key handling. Ctrl+R / Ctrl+S cycle, Enter accepts,
|
|
188
253
|
// Esc cancels (restoring the pre-search draft), backspace shortens
|
|
189
254
|
// the query, typed characters extend it.
|
|
@@ -202,7 +267,9 @@ export function InputBox(props) {
|
|
|
202
267
|
setCursor(draftBeforeSearch.length);
|
|
203
268
|
return;
|
|
204
269
|
}
|
|
205
|
-
|
|
270
|
+
// Bare LF accepts the focused match, same as CR (`key.return`).
|
|
271
|
+
// See the post-search block below for the rationale.
|
|
272
|
+
if (key.return || (input === '\n' && !key.meta && !key.ctrl && !key.shift)) {
|
|
206
273
|
const picked = currentBrief(search);
|
|
207
274
|
setSearch(undefined);
|
|
208
275
|
if (picked !== null) {
|
|
@@ -221,6 +288,11 @@ export function InputBox(props) {
|
|
|
221
288
|
return;
|
|
222
289
|
}
|
|
223
290
|
if (input && !key.meta && !key.ctrl) {
|
|
291
|
+
// Drop a bare LF from the search query — the Enter-accept
|
|
292
|
+
// branch above already handled it; falling through here would
|
|
293
|
+
// splice a newline into the search string.
|
|
294
|
+
if (input === '\n')
|
|
295
|
+
return;
|
|
224
296
|
const nextQuery = search.query + input;
|
|
225
297
|
setSearch(applyQuery(search, nextQuery, history));
|
|
226
298
|
return;
|
|
@@ -243,6 +315,118 @@ export function InputBox(props) {
|
|
|
243
315
|
setSearch(initialSearchState(history));
|
|
244
316
|
return;
|
|
245
317
|
}
|
|
318
|
+
// P0 fix (CEO 2026-05-29 dogfood, second iteration): bare LF (`\n`)
|
|
319
|
+
// MUST submit the brief, same as bare CR (`\r`). Ink's parseKeypress
|
|
320
|
+
// maps `\r` to `key.return` and `\n` to `key.name === 'enter'`
|
|
321
|
+
// WITHOUT setting `key.return`. Most real terminals deliver CR for
|
|
322
|
+
// Enter (ICRNL on by default), so the `key.return` branch below
|
|
323
|
+
// catches them. But when stdin is a PTY whose parent writes raw
|
|
324
|
+
// `\n` (Python's `pty.fork` + `os.write(fd, b"\n")`, automation
|
|
325
|
+
// harnesses, certain SSH multiplexers), the LF arrives as a
|
|
326
|
+
// printable char.
|
|
327
|
+
//
|
|
328
|
+
// PR #697 (beta.45) fixed the case where `input === '\n'` exactly.
|
|
329
|
+
// CEO PTY smoke 2026-05-29 surfaced the REAL shape: when the parent
|
|
330
|
+
// writes the brief AND the Enter as separate `os.write` calls (or
|
|
331
|
+
// even when it doesn't), Node's stdin buffer COALESCES them into
|
|
332
|
+
// ONE chunk before Ink delivers the `useInput` event. The repro
|
|
333
|
+
// confirmed via stderr instrumentation: typing `hi\n` arrives in
|
|
334
|
+
// input-box as `bytes=[68 69 0a] len=3 flags=-` — a SINGLE 3-char
|
|
335
|
+
// chunk "hi\n" with no key flags. The PR #697 branch (`input ===
|
|
336
|
+
// '\n'`) does not match, so `hi\n` falls through to the printable-
|
|
337
|
+
// char branch and the literal newline lands in the buffer as
|
|
338
|
+
// `› hi\n █` (multi-line composer, brief never dispatches, status
|
|
339
|
+
// stays `idle` forever).
|
|
340
|
+
//
|
|
341
|
+
// Fix: detect a TRAILING `\n` in a printable chunk with no
|
|
342
|
+
// modifiers — type the prefix into the buffer, then submit. The
|
|
343
|
+
// discriminator that keeps multi-line paste working: the chunk
|
|
344
|
+
// must contain EXACTLY ONE `\n` (the trailing one) and no other
|
|
345
|
+
// newlines. Multi-line pastes have ≥2 `\n` characters (or arrive
|
|
346
|
+
// wrapped in bracketed-paste markers handled below), so they
|
|
347
|
+
// still preserve interior newlines via the printable-char branch.
|
|
348
|
+
//
|
|
349
|
+
// Detection contract:
|
|
350
|
+
// - `input` ends with `\n`
|
|
351
|
+
// - no Ctrl / Meta / Shift modifiers
|
|
352
|
+
// - exactly ONE `\n` in the chunk (the trailing one)
|
|
353
|
+
// - chunk is not bracketed-paste wrapped (markers stripped below)
|
|
354
|
+
//
|
|
355
|
+
// Edge cases covered by `test/input-box-lf-submit.spec.tsx`:
|
|
356
|
+
// - bare `\n` → submit empty (no-op on empty buf)
|
|
357
|
+
// - `hi\n` → splice `hi` + submit
|
|
358
|
+
// - `hi\nthere\n` (multi-line) → printable branch, preserves \n
|
|
359
|
+
// - `\r` (CR) → key.return branch unchanged
|
|
360
|
+
// - `hi\r\n` (CRLF) → key.return branch (CR wins first)
|
|
361
|
+
const endsWithLf = input.length > 0 && input.charCodeAt(input.length - 1) === 0x0a;
|
|
362
|
+
const newlineCount = (input.match(/\n/g) || []).length;
|
|
363
|
+
if (endsWithLf
|
|
364
|
+
&& newlineCount === 1
|
|
365
|
+
&& !key.meta
|
|
366
|
+
&& !key.ctrl
|
|
367
|
+
&& !key.shift) {
|
|
368
|
+
// Splice the prefix (everything before the trailing `\n`) into
|
|
369
|
+
// the buffer at the cursor, then run the canonical submit path.
|
|
370
|
+
// Refs (cursorRef / lineRef) hold the latest committed values so
|
|
371
|
+
// the splice runs against the operator's most recent edits even
|
|
372
|
+
// if a previous async paste / setState is still mid-flight.
|
|
373
|
+
const prefix = input.slice(0, -1);
|
|
374
|
+
let mergedLine = lineRef.current;
|
|
375
|
+
let mergedCursor = cursorRef.current;
|
|
376
|
+
if (prefix.length > 0) {
|
|
377
|
+
// Same sanitisation as the printable-char branch below — strip
|
|
378
|
+
// bracketed-paste markers so a stray escape sequence never
|
|
379
|
+
// lands in the submitted brief.
|
|
380
|
+
const stripped = prefix
|
|
381
|
+
.replace(/\x1b\[200~/g, '')
|
|
382
|
+
.replace(/\x1b\[201~/g, '')
|
|
383
|
+
.replace(/\[200~/g, '')
|
|
384
|
+
.replace(/\[201~/g, '');
|
|
385
|
+
if (stripped.length > 0) {
|
|
386
|
+
mergedLine =
|
|
387
|
+
mergedLine.slice(0, mergedCursor) + stripped + mergedLine.slice(mergedCursor);
|
|
388
|
+
mergedCursor = mergedCursor + stripped.length;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Synthesise the same payload-shape the `key.return` branch
|
|
392
|
+
// below uses so palette completion + history dedup + onSubmit
|
|
393
|
+
// dispatch all run identically.
|
|
394
|
+
const paletteHere = !paletteSuppressed
|
|
395
|
+
? filterPalette(mergedLine)
|
|
396
|
+
: { rows: [], totalBeforeLimit: 0 };
|
|
397
|
+
const paletteOpenHere = paletteHere.rows.length > 0;
|
|
398
|
+
const paletteFocusedIndexHere = paletteHere.rows.length === 0
|
|
399
|
+
? 0
|
|
400
|
+
: Math.min(paletteIndex, paletteHere.rows.length - 1);
|
|
401
|
+
let payload = mergedLine;
|
|
402
|
+
if (paletteOpenHere) {
|
|
403
|
+
const completed = completePalette(mergedLine, paletteHere.rows, paletteFocusedIndexHere);
|
|
404
|
+
if (completed !== null)
|
|
405
|
+
payload = completed;
|
|
406
|
+
}
|
|
407
|
+
const trimmed = payload.trim();
|
|
408
|
+
if (trimmed.length > 0) {
|
|
409
|
+
setHistory((prev) => {
|
|
410
|
+
if (prev[prev.length - 1] === trimmed)
|
|
411
|
+
return prev;
|
|
412
|
+
return [...prev, trimmed];
|
|
413
|
+
});
|
|
414
|
+
setHistoryIndex(-1);
|
|
415
|
+
if (props.workspaceSlug) {
|
|
416
|
+
appendHistory({
|
|
417
|
+
home: props.historyHome,
|
|
418
|
+
workspaceSlug: props.workspaceSlug,
|
|
419
|
+
brief: trimmed,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
props.onSubmit(trimmed);
|
|
423
|
+
}
|
|
424
|
+
setLine('');
|
|
425
|
+
setCursor(0);
|
|
426
|
+
setPaletteSuppressed(false);
|
|
427
|
+
setPaletteIndex(0);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
246
430
|
// Readline-style kill ring shortcuts. All four kills push the
|
|
247
431
|
// removed slice onto the ring; Ctrl+Y yanks the most recent.
|
|
248
432
|
if (key.ctrl && input === 'u') {
|
|
@@ -375,10 +559,41 @@ export function InputBox(props) {
|
|
|
375
559
|
if (key.escape) {
|
|
376
560
|
if (paletteOpen) {
|
|
377
561
|
// Close the palette without clearing the buffer so the operator
|
|
378
|
-
// can still send `/help` as plain text if they want.
|
|
562
|
+
// can still send `/help` as plain text if they want. Palette
|
|
563
|
+
// takes precedence over walkback because the operator's mental
|
|
564
|
+
// model is "Esc closes the visible overlay first".
|
|
379
565
|
setPaletteSuppressed(true);
|
|
566
|
+
setLastEscapeAt(undefined);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// Wave 6 BT 8: Esc-Esc walkback. Two presses within
|
|
570
|
+
// ESCAPE_DOUBLE_TAP_MS step the conversation back by one turn.
|
|
571
|
+
// First press still clears the buffer (legacy behaviour for the
|
|
572
|
+
// single-Esc cancel UX); the second press calls the host's
|
|
573
|
+
// walkback handler. Buffer-clear on the first press is what makes
|
|
574
|
+
// the double-tap feel "free" - the operator did not have to
|
|
575
|
+
// memorise a new chord; they just have to keep pressing.
|
|
576
|
+
const tEsc = now();
|
|
577
|
+
const withinEscapeWindow = typeof lastEscapeAt === 'number'
|
|
578
|
+
&& tEsc - lastEscapeAt <= ESCAPE_DOUBLE_TAP_MS;
|
|
579
|
+
if (withinEscapeWindow && props.onWalkback) {
|
|
580
|
+
// Second tap inside the window. Buffer was already cleared on
|
|
581
|
+
// the first press, so the host sees a clean input box AND the
|
|
582
|
+
// walkback result. We clear the window so a third tap restarts
|
|
583
|
+
// the cycle (no run-on walkbacks from a stuck Esc key).
|
|
584
|
+
const verdict = props.onWalkback();
|
|
585
|
+
setLastEscapeAt(undefined);
|
|
586
|
+
if (verdict !== 'walked-back') {
|
|
587
|
+
// Host refused (dispatch in flight, no turns to pop). The
|
|
588
|
+
// host owns the refusal copy via its own writeOutput path;
|
|
589
|
+
// we do not double-message here.
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
380
592
|
return;
|
|
381
593
|
}
|
|
594
|
+
// First Esc (or no walkback wired). Arm the window + clear the
|
|
595
|
+
// buffer per the long-standing single-Esc cancel contract.
|
|
596
|
+
setLastEscapeAt(tEsc);
|
|
382
597
|
setLine('');
|
|
383
598
|
setCursor(0);
|
|
384
599
|
setHistoryIndex(-1);
|
|
@@ -499,7 +714,7 @@ export function InputBox(props) {
|
|
|
499
714
|
: Math.min(paletteIndex, paletteView.rows.length - 1);
|
|
500
715
|
const divider = '─'.repeat(innerWidth);
|
|
501
716
|
const focusedMatch = search ? search.matches[search.focusedIndex] : undefined;
|
|
502
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "
|
|
717
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#3da9fc", dimColor: true, children: divider }), _jsx(Box, { paddingX: 1, flexDirection: "column", children: search ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '(reverse-i-search) ' }), _jsx(Text, { children: `\`${search.query}\`: ` }), _jsx(Text, { color: "yellow", children: focusedMatch ? focusedMatch.brief : '(no match)' })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `Ctrl+R next · Ctrl+S prev · Enter accept · Esc cancel · ${search.matches.length} match${search.matches.length === 1 ? '' : 'es'}` }) })] })) : (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '› ' }), _jsx(Text, { children: renderLineWithCursor(line, cursor, cursorVisible) })] })) }), _jsx(Text, { color: "#3da9fc", dimColor: true, children: divider }), modeCycleToast ? (_jsx(Box, { children: _jsx(Text, { color: "#3da9fc", bold: true, children: ` ${modeCycleToast}` }) })) : null, ctrlCToast ? (_jsx(Box, { children: _jsx(Text, { color: "yellow", bold: true, children: ` ${ctrlCToast}` }) })) : null, line.length > innerWidth - 4 ? (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: '┊ ' }), _jsx(Text, { dimColor: true, children: 'line wraps - Enter still submits' })] })) : null, _jsx(SlashPalette, { rows: paletteView.rows, focusedIndex: clampedPaletteIndex, totalBeforeLimit: paletteView.totalBeforeLimit }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '↑/↓ history · Ctrl+R search · / commands · Shift+Tab mode · Enter brief · Esc cancel · Ctrl+C abort / ×2 exit' }) })] }));
|
|
503
718
|
}
|
|
504
719
|
/**
|
|
505
720
|
* Render the line with the cursor glyph inserted at `cursor`. The cursor
|
|
@@ -107,9 +107,9 @@ function renderBlock(block, key) {
|
|
|
107
107
|
case 'paragraph':
|
|
108
108
|
return (_jsx(Text, { children: renderInline(block.text) }, key));
|
|
109
109
|
case 'bullet':
|
|
110
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: "
|
|
110
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '• ' }), _jsx(Text, { children: renderInline(block.text) })] }, key));
|
|
111
111
|
case 'ordered':
|
|
112
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: "
|
|
112
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: `${block.index}. ` }), _jsx(Text, { children: renderInline(block.text) })] }, key));
|
|
113
113
|
case 'code':
|
|
114
114
|
return renderCodeBlock(block.lang, block.body, key);
|
|
115
115
|
case 'blank':
|
|
@@ -148,7 +148,7 @@ function renderCodeLine(line, keywords) {
|
|
|
148
148
|
spans.push(_jsx(Text, { color: "green", children: tok }, key));
|
|
149
149
|
}
|
|
150
150
|
else if (keywords.includes(tok)) {
|
|
151
|
-
spans.push(_jsx(Text, { color: "
|
|
151
|
+
spans.push(_jsx(Text, { color: "#3da9fc", bold: true, children: tok }, key));
|
|
152
152
|
}
|
|
153
153
|
else {
|
|
154
154
|
spans.push(_jsx(Text, { children: tok }, key));
|
|
@@ -260,7 +260,7 @@ function renderSpan(span, key) {
|
|
|
260
260
|
case 'code':
|
|
261
261
|
return _jsx(Text, { color: "green", children: span.text }, key);
|
|
262
262
|
case 'link':
|
|
263
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: "
|
|
263
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: "#3da9fc", underline: true, children: span.text }), _jsx(Text, { dimColor: true, children: ` (${span.url})` })] }, key));
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
//# sourceMappingURL=markdown-render.js.map
|