xiaotime 0.5.1 → 0.5.4
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/__tests__/ansi_tags.test.d.ts +9 -0
- package/dist/__tests__/ansi_tags.test.d.ts.map +1 -0
- package/dist/__tests__/ansi_tags.test.js +58 -0
- package/dist/__tests__/ansi_tags.test.js.map +1 -0
- package/dist/__tests__/paste_transform_submit.test.d.ts +2 -0
- package/dist/__tests__/paste_transform_submit.test.d.ts.map +1 -0
- package/dist/__tests__/paste_transform_submit.test.js +74 -0
- package/dist/__tests__/paste_transform_submit.test.js.map +1 -0
- package/dist/__tests__/session_match.test.d.ts +10 -0
- package/dist/__tests__/session_match.test.d.ts.map +1 -0
- package/dist/__tests__/session_match.test.js +55 -0
- package/dist/__tests__/session_match.test.js.map +1 -0
- package/dist/ansi_tags.d.ts +20 -0
- package/dist/ansi_tags.d.ts.map +1 -0
- package/dist/ansi_tags.js +38 -0
- package/dist/ansi_tags.js.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/display.d.ts +53 -22
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +750 -47
- package/dist/display.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -3
- package/dist/index.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +5 -2
- package/dist/session.js.map +1 -1
- package/dist/session_match.d.ts +19 -0
- package/dist/session_match.d.ts.map +1 -0
- package/dist/session_match.js +9 -0
- package/dist/session_match.js.map +1 -0
- package/dist/sidebar.d.ts +84 -0
- package/dist/sidebar.d.ts.map +1 -0
- package/dist/sidebar.js +310 -0
- package/dist/sidebar.js.map +1 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +25 -66
- package/dist/tools.js.map +1 -1
- package/dist/ui_state.d.ts +41 -96
- package/dist/ui_state.d.ts.map +1 -1
- package/dist/ui_state.js +226 -213
- package/dist/ui_state.js.map +1 -1
- package/dist/ws.d.ts +10 -14
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +240 -119
- package/dist/ws.js.map +1 -1
- package/package.json +10 -5
package/dist/display.js
CHANGED
|
@@ -1,81 +1,784 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Output formatting — streaming text, session list,
|
|
3
|
+
* Output formatting — streaming text, session list, status bar, tool lines.
|
|
4
|
+
*
|
|
5
|
+
* SPRINT-CLI-UX-POLISH-5:
|
|
6
|
+
* - Thinking spinner (animates between submit and first token)
|
|
7
|
+
* - Animated tool lines (spinner → ✓ with elapsed on completion)
|
|
8
|
+
* - Diff view for write_file tool calls
|
|
9
|
+
* - Approval prompt styled as yellow bordered box
|
|
10
|
+
* - Keyboard shortcut bar shown ONCE on welcome only
|
|
11
|
+
* - Session resume shows last message preview
|
|
12
|
+
* - "Xiaotime Labs" wordmark in status bar
|
|
13
|
+
* - Word-wrap at terminal width (no mid-word cuts)
|
|
14
|
+
* - Code fence double-render fix
|
|
15
|
+
* - Sticky bottom status bar (no bg, connected dot, turn counter, last tool)
|
|
16
|
+
* - No cost in status bar
|
|
17
|
+
* - Double-border input box (╭─╮ top, you › inside, ╰─╯ on submit)
|
|
18
|
+
* - Red user/Junior text (#c0384b)
|
|
19
|
+
* - Claude Code-style turn headers (Junior ›)
|
|
20
|
+
* - Code blocks with full-width border + language tag
|
|
21
|
+
* - Turn separators between responses
|
|
22
|
+
* - Tool call lines with elapsed time + icons
|
|
23
|
+
*
|
|
24
|
+
* SPRINT-CLI-UX-POLISH-6:
|
|
25
|
+
* - Restored "Junior is thinking..." label.
|
|
26
|
+
* - Inline code backtick spans get subtle background tint
|
|
27
|
+
* - Auto-detect file paths in output — underlined + OSC 8 hyperlinks (iTerm2/Termius)
|
|
28
|
+
* - Tool call errors render as red-bordered box instead of inline text
|
|
29
|
+
* - Removed "Junior is thinking..." spinner label
|
|
4
30
|
*/
|
|
5
31
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
32
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
33
|
};
|
|
8
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
35
|
+
exports.initStatusBar = initStatusBar;
|
|
36
|
+
exports.setConnected = setConnected;
|
|
37
|
+
exports.incrementTurn = incrementTurn;
|
|
38
|
+
exports.setLastTool = setLastTool;
|
|
39
|
+
exports.updateStatusBarCost = updateStatusBarCost;
|
|
40
|
+
exports.startSpinner = startSpinner;
|
|
41
|
+
exports.stopSpinner = stopSpinner;
|
|
42
|
+
exports.startToolLine = startToolLine;
|
|
43
|
+
exports.finishToolLine = finishToolLine;
|
|
44
|
+
exports.printToolError = printToolError;
|
|
45
|
+
exports.printFileDiff = printFileDiff;
|
|
46
|
+
exports.printApprovalPrompt = printApprovalPrompt;
|
|
10
47
|
exports.printTextDelta = printTextDelta;
|
|
48
|
+
exports.printTurnStart = printTurnStart;
|
|
49
|
+
exports.printThinkingStart = printThinkingStart;
|
|
50
|
+
exports.printThinkingStop = printThinkingStop;
|
|
11
51
|
exports.printTurnEnd = printTurnEnd;
|
|
52
|
+
exports.consumeFirstPrompt = consumeFirstPrompt;
|
|
53
|
+
exports.printPrompt = printPrompt;
|
|
54
|
+
exports.printPromptEnd = printPromptEnd;
|
|
55
|
+
exports.printWelcome = printWelcome;
|
|
12
56
|
exports.printError = printError;
|
|
13
57
|
exports.printSessionList = printSessionList;
|
|
14
|
-
exports.
|
|
58
|
+
exports.friendlyToolLabel = friendlyToolLabel;
|
|
59
|
+
exports.friendlyError = friendlyError;
|
|
60
|
+
exports.printHelp = printHelp;
|
|
61
|
+
exports.printDisconnectBanner = printDisconnectBanner;
|
|
62
|
+
exports.printExitConfirmation = printExitConfirmation;
|
|
63
|
+
exports.printToolCallLine = printToolCallLine;
|
|
15
64
|
exports.printServerToolCall = printServerToolCall;
|
|
16
65
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
66
|
+
const cli_highlight_1 = require("cli-highlight");
|
|
67
|
+
// Force color output — Termius and some SSH clients report level 0
|
|
68
|
+
// even when the terminal supports 256 colors.
|
|
69
|
+
if (chalk_1.default.level === 0 &&
|
|
70
|
+
(process.env.TERM?.includes("256color") ||
|
|
71
|
+
process.env.TERM?.includes("xterm") ||
|
|
72
|
+
process.env.COLORTERM ||
|
|
73
|
+
process.env.FORCE_COLOR)) {
|
|
74
|
+
chalk_1.default.level = 2;
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Terminal geometry helpers
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
function termWidth() {
|
|
80
|
+
return process.stdout.columns || 80;
|
|
81
|
+
}
|
|
82
|
+
function termRows() {
|
|
83
|
+
return process.stdout.rows || 24;
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Word wrap — break at word boundaries, never mid-word
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
function wordWrap(text, width) {
|
|
89
|
+
if (width <= 0)
|
|
90
|
+
return [text];
|
|
91
|
+
const words = text.split(" ");
|
|
92
|
+
const lines = [];
|
|
93
|
+
let current = "";
|
|
94
|
+
for (const word of words) {
|
|
95
|
+
if (word.length >= width) {
|
|
96
|
+
if (current.length > 0) {
|
|
97
|
+
lines.push(current);
|
|
98
|
+
current = "";
|
|
99
|
+
}
|
|
100
|
+
lines.push(word);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const candidate = current.length === 0 ? word : current + " " + word;
|
|
104
|
+
if (candidate.length > width) {
|
|
105
|
+
lines.push(current);
|
|
106
|
+
current = word;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
current = candidate;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (current.length > 0)
|
|
113
|
+
lines.push(current);
|
|
114
|
+
return lines;
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// OSC 8 hyperlink helper (iTerm2, Termius, WezTerm)
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
function osc8Link(url, text) {
|
|
120
|
+
return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
|
|
121
|
+
}
|
|
122
|
+
// Regex: absolute paths (/foo/bar), relative with ./ or ../, or paths with
|
|
123
|
+
// a common extension — conservative to avoid false positives in prose.
|
|
124
|
+
const FILE_PATH_RE = /(?<![`\w])((?:\/[\w.\-]+)+(?:\/[\w.\-]+)?|\.{1,2}\/[\w.\-/]+|[\w.\-]+\/[\w.\-]+\.(?:ts|js|tsx|jsx|json|md|py|go|rs|sh|yaml|yml|env|toml|lock))(?![`\w])/g;
|
|
125
|
+
function _linkifyPaths(text) {
|
|
126
|
+
return text.replace(FILE_PATH_RE, (match) => {
|
|
127
|
+
const url = match.startsWith("/") ? `file://${match}` : `file://${process.cwd()}/${match}`;
|
|
128
|
+
return chalk_1.default.underline(osc8Link(url, match));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Lightweight markdown renderer — chalk only, no external markdown deps
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
function renderMarkdownLine(line) {
|
|
135
|
+
const h3 = line.match(/^### (.+)$/);
|
|
136
|
+
if (h3)
|
|
137
|
+
return chalk_1.default.bold.hex("#94e2d5")(h3[1]);
|
|
138
|
+
const h2 = line.match(/^## (.+)$/);
|
|
139
|
+
if (h2)
|
|
140
|
+
return chalk_1.default.bold.hex("#89b4fa")(h2[1]);
|
|
141
|
+
const h1 = line.match(/^# (.+)$/);
|
|
142
|
+
if (h1)
|
|
143
|
+
return chalk_1.default.bold.hex("#c0384b")(h1[1]);
|
|
144
|
+
if (/^---+$/.test(line) || /^\*\*\*+$/.test(line)) {
|
|
145
|
+
return chalk_1.default.dim("─".repeat(termWidth()));
|
|
146
|
+
}
|
|
147
|
+
if (line.startsWith("> ")) {
|
|
148
|
+
return chalk_1.default.dim.italic(" │ " + line.slice(2));
|
|
149
|
+
}
|
|
150
|
+
const bullet = line.match(/^(\s*)[*\-+] (.+)$/);
|
|
151
|
+
if (bullet) {
|
|
152
|
+
return `${bullet[1]}${chalk_1.default.hex("#f38ba8")("•")} ${_renderInline(bullet[2])}`;
|
|
153
|
+
}
|
|
154
|
+
const numbered = line.match(/^(\s*)(\d+)\. (.+)$/);
|
|
155
|
+
if (numbered) {
|
|
156
|
+
return `${numbered[1]}${chalk_1.default.dim(numbered[2] + ".")} ${_renderInline(numbered[3])}`;
|
|
157
|
+
}
|
|
158
|
+
return _renderInline(line);
|
|
159
|
+
}
|
|
160
|
+
function _renderInline(text) {
|
|
161
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, (_m, t) => chalk_1.default.bold.italic(t));
|
|
162
|
+
text = text.replace(/\*\*(.+?)\*\*/g, (_m, t) => chalk_1.default.bold(t));
|
|
163
|
+
text = text.replace(/\*(.+?)\*/g, (_m, t) => chalk_1.default.italic(t));
|
|
164
|
+
text = text.replace(/_(.+?)_/g, (_m, t) => chalk_1.default.italic(t));
|
|
165
|
+
// Inline code — subtle background tint (#313244 = Catppuccin surface0, text stays readable)
|
|
166
|
+
text = text.replace(/`([^`]+)`/g, (_m, t) => chalk_1.default.bgHex("#313244").hex("#cdd6f4")(` ${t} `));
|
|
167
|
+
text = text.replace(/\[(.+?)\]\((.+?)\)/g, (_m, label, url) => `${chalk_1.default.hex("#89b4fa")(label)} ${chalk_1.default.dim(`(${url})`)}`);
|
|
168
|
+
// Linkify file paths (only outside code spans — already consumed above)
|
|
169
|
+
text = _linkifyPaths(text);
|
|
170
|
+
return text;
|
|
171
|
+
}
|
|
172
|
+
let _statusBar = {
|
|
173
|
+
tenant: process.env.XIAOTIME_TENANT_ID ??
|
|
174
|
+
process.env.TENANT_ID ??
|
|
175
|
+
"craig",
|
|
176
|
+
sessionId: "",
|
|
177
|
+
sessionName: null,
|
|
178
|
+
connected: false,
|
|
179
|
+
turnCount: 0,
|
|
180
|
+
lastTool: null,
|
|
181
|
+
};
|
|
182
|
+
/** Naively strip ANSI codes for length calculation. */
|
|
183
|
+
function stripAnsi(s) {
|
|
184
|
+
// eslint-disable-next-line no-control-regex
|
|
185
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").replace(/\x1b\]8;;.*?\x07.*?\x1b\]8;;\x07/g, (m) => {
|
|
186
|
+
// Extract visible text from OSC 8 links for length calc
|
|
187
|
+
const inner = m.match(/\x07(.+?)\x1b/);
|
|
188
|
+
return inner ? inner[1] : "";
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function _renderStatusBarLine() {
|
|
192
|
+
const w = termWidth();
|
|
193
|
+
const dot = _statusBar.connected
|
|
194
|
+
? chalk_1.default.hex("#a6e3a1")("●")
|
|
195
|
+
: chalk_1.default.dim("○");
|
|
196
|
+
const tenant = chalk_1.default.hex("#c0384b")(_statusBar.tenant);
|
|
197
|
+
const session = _statusBar.sessionName
|
|
198
|
+
? chalk_1.default.hex("#89b4fa")(_statusBar.sessionName)
|
|
199
|
+
: chalk_1.default.dim(_statusBar.sessionId.slice(0, 8) || "…");
|
|
200
|
+
const turns = chalk_1.default.dim(`${_statusBar.turnCount} turns`);
|
|
201
|
+
const lastTool = _statusBar.lastTool
|
|
202
|
+
? chalk_1.default.dim(`↳ ${_statusBar.lastTool}`)
|
|
203
|
+
: chalk_1.default.dim("ready");
|
|
204
|
+
const sep = chalk_1.default.dim(" · ");
|
|
205
|
+
const left = ` ${dot} ${tenant}${sep}${session}${sep}${turns}${sep}${lastTool}`;
|
|
206
|
+
const right = chalk_1.default.hex("#c0384b")("Xiaotime Labs") + " ";
|
|
207
|
+
const leftRaw = stripAnsi(left);
|
|
208
|
+
const rightRaw = stripAnsi(right);
|
|
209
|
+
const pad = Math.max(0, w - leftRaw.length - rightRaw.length);
|
|
210
|
+
return left + " ".repeat(pad) + right;
|
|
211
|
+
}
|
|
212
|
+
// The persistent bottom status bar is intentionally NOT drawn in the terminal.
|
|
213
|
+
//
|
|
214
|
+
// A truly sticky bottom bar requires a DECSTBM scroll region. That approach
|
|
215
|
+
// (shipped briefly in 0.5.3) forced the input box to the bottom of the screen
|
|
216
|
+
// with a large empty gap on real SSH clients (Termius); without the region the
|
|
217
|
+
// absolute-positioned bar leaks a copy into scrollback on every redraw — the
|
|
218
|
+
// "Xiaotime Labs repeating up the window" symptom. Neither is acceptable, and
|
|
219
|
+
// terminal-control iteration can't be verified headless.
|
|
220
|
+
//
|
|
221
|
+
// Session context already appears in the welcome header. The proper live status
|
|
222
|
+
// bar belongs in the portal terminal, where it is a real pinned DOM element.
|
|
223
|
+
// State is still tracked below so a future renderer (or the portal) can use it.
|
|
224
|
+
function _drawStickyBar() {
|
|
225
|
+
// no-op — see note above. Status state is tracked; nothing is painted.
|
|
226
|
+
}
|
|
227
|
+
function initStatusBar(sessionId, sessionName, tenant) {
|
|
228
|
+
_statusBar.sessionId = sessionId;
|
|
229
|
+
_statusBar.sessionName = sessionName;
|
|
230
|
+
if (tenant)
|
|
231
|
+
_statusBar.tenant = tenant;
|
|
232
|
+
_statusBar.connected = false;
|
|
233
|
+
_drawStickyBar();
|
|
234
|
+
}
|
|
235
|
+
function setConnected(connected) {
|
|
236
|
+
_statusBar.connected = connected;
|
|
237
|
+
_drawStickyBar();
|
|
238
|
+
}
|
|
239
|
+
function incrementTurn() {
|
|
240
|
+
_statusBar.turnCount += 1;
|
|
241
|
+
_drawStickyBar();
|
|
242
|
+
}
|
|
243
|
+
function setLastTool(toolName) {
|
|
244
|
+
_statusBar.lastTool = toolName;
|
|
245
|
+
_drawStickyBar();
|
|
246
|
+
}
|
|
247
|
+
function updateStatusBarCost(_turnCostUsd) {
|
|
248
|
+
// no-op — cost not shown in bar
|
|
249
|
+
}
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Thinking spinner
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
254
|
+
let _spinnerInterval = null;
|
|
255
|
+
let _spinnerFrame = 0;
|
|
256
|
+
let _spinnerLabel = "";
|
|
257
|
+
let _spinnerActive = false;
|
|
258
|
+
function _clearSpinnerLine() {
|
|
259
|
+
process.stdout.write("\x1b[2K\r");
|
|
260
|
+
}
|
|
261
|
+
function startSpinner(label) {
|
|
262
|
+
if (_spinnerActive)
|
|
263
|
+
stopSpinner();
|
|
264
|
+
_spinnerLabel = label;
|
|
265
|
+
_spinnerActive = true;
|
|
266
|
+
_spinnerFrame = 0;
|
|
267
|
+
_spinnerInterval = setInterval(() => {
|
|
268
|
+
if (!_spinnerActive)
|
|
269
|
+
return;
|
|
270
|
+
const frame = chalk_1.default.hex("#c0384b")(SPINNER_FRAMES[_spinnerFrame % SPINNER_FRAMES.length]);
|
|
271
|
+
_spinnerFrame++;
|
|
272
|
+
const labelPart = _spinnerLabel ? ` ${chalk_1.default.dim(_spinnerLabel)}` : "";
|
|
273
|
+
process.stdout.write(`\r ${frame}${labelPart}`);
|
|
274
|
+
}, 80);
|
|
275
|
+
}
|
|
276
|
+
function stopSpinner(finalLabel, success = true) {
|
|
277
|
+
if (_spinnerInterval) {
|
|
278
|
+
clearInterval(_spinnerInterval);
|
|
279
|
+
_spinnerInterval = null;
|
|
280
|
+
}
|
|
281
|
+
_spinnerActive = false;
|
|
282
|
+
_clearSpinnerLine();
|
|
283
|
+
if (finalLabel) {
|
|
284
|
+
const icon = success ? chalk_1.default.hex("#a6e3a1")("✓") : chalk_1.default.hex("#f38ba8")("✗");
|
|
285
|
+
process.stdout.write(` ${icon} ${chalk_1.default.dim(finalLabel)}\n`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
let _activeTool = null;
|
|
289
|
+
function startToolLine(name, detail) {
|
|
290
|
+
if (_activeTool)
|
|
291
|
+
finishToolLine(true);
|
|
292
|
+
setLastTool(name);
|
|
293
|
+
let frame = 0;
|
|
294
|
+
const spinnerInterval = setInterval(() => {
|
|
295
|
+
const f = chalk_1.default.hex("#89b4fa")(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
|
|
296
|
+
const friendly = friendlyToolLabel(name);
|
|
297
|
+
const detailStr = detail ? chalk_1.default.dim(` ${detail}`) : "";
|
|
298
|
+
process.stdout.write(`\r ${f} ${chalk_1.default.dim(friendly)}${detailStr}`);
|
|
299
|
+
frame++;
|
|
300
|
+
}, 80);
|
|
301
|
+
_activeTool = {
|
|
302
|
+
name,
|
|
303
|
+
detail,
|
|
304
|
+
startedAt: Date.now(),
|
|
305
|
+
spinnerInterval,
|
|
306
|
+
frame: 0,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function finishToolLine(success = true, errorMessage) {
|
|
310
|
+
if (!_activeTool)
|
|
311
|
+
return;
|
|
312
|
+
clearInterval(_activeTool.spinnerInterval);
|
|
313
|
+
const elapsed = Date.now() - _activeTool.startedAt;
|
|
314
|
+
if (!success && errorMessage) {
|
|
315
|
+
// Clear the spinner line, then render the red error box
|
|
316
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
317
|
+
printToolError(_activeTool.name, errorMessage, elapsed);
|
|
21
318
|
}
|
|
22
319
|
else {
|
|
23
|
-
|
|
320
|
+
const icon = success ? chalk_1.default.hex("#a6e3a1")("✓") : chalk_1.default.hex("#f38ba8")("✗");
|
|
321
|
+
const detailStr = _activeTool.detail ? chalk_1.default.dim(` ${_activeTool.detail}`) : "";
|
|
322
|
+
const dur = chalk_1.default.dim(` ${elapsed}ms`);
|
|
323
|
+
const friendlyLabel = friendlyToolLabel(_activeTool.name);
|
|
324
|
+
process.stdout.write(`\r\x1b[2K ${icon} ${chalk_1.default.dim(friendlyLabel)}${detailStr}${dur}\n`);
|
|
325
|
+
}
|
|
326
|
+
_activeTool = null;
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Tool error — red bordered box
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
function printToolError(toolName, errorMessage, durationMs) {
|
|
332
|
+
const w = termWidth();
|
|
333
|
+
const top = chalk_1.default.hex("#f38ba8")("╭") +
|
|
334
|
+
chalk_1.default.hex("#f38ba8")("─".repeat(w - 2)) +
|
|
335
|
+
chalk_1.default.hex("#f38ba8")("╮");
|
|
336
|
+
const bottom = chalk_1.default.hex("#f38ba8")("╰") +
|
|
337
|
+
chalk_1.default.hex("#f38ba8")("─".repeat(w - 2)) +
|
|
338
|
+
chalk_1.default.hex("#f38ba8")("╯");
|
|
339
|
+
const dur = durationMs !== undefined ? chalk_1.default.dim(` ${durationMs}ms`) : "";
|
|
340
|
+
const titleLine = chalk_1.default.bold.hex("#f38ba8")(` ✗ ${toolName}${dur}`);
|
|
341
|
+
const bodyLines = wordWrap(errorMessage, w - 4).map((l) => chalk_1.default.hex("#f38ba8")(" " + l));
|
|
342
|
+
process.stdout.write("\n" + top + "\n");
|
|
343
|
+
process.stdout.write(titleLine + "\n");
|
|
344
|
+
for (const l of bodyLines) {
|
|
345
|
+
process.stdout.write(l + "\n");
|
|
24
346
|
}
|
|
25
|
-
|
|
347
|
+
process.stdout.write(bottom + "\n\n");
|
|
26
348
|
}
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Diff view for write_file
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
function printFileDiff(path, before, after) {
|
|
353
|
+
const w = termWidth();
|
|
354
|
+
const label = chalk_1.default.dim(` write_file ${path} `);
|
|
355
|
+
const labelRaw = ` write_file ${path} `;
|
|
356
|
+
const rule = chalk_1.default.dim("─".repeat(Math.max(0, w - labelRaw.length)));
|
|
357
|
+
process.stdout.write("\n" + rule + label + "\n");
|
|
358
|
+
if (!before) {
|
|
359
|
+
// New file — show all lines as added
|
|
360
|
+
const lines = after.split("\n").slice(0, 20);
|
|
361
|
+
for (const line of lines) {
|
|
362
|
+
process.stdout.write(chalk_1.default.hex("#a6e3a1")("+ " + line) + "\n");
|
|
363
|
+
}
|
|
364
|
+
if (after.split("\n").length > 20) {
|
|
365
|
+
process.stdout.write(chalk_1.default.dim(` … ${after.split("\n").length - 20} more lines\n`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
const beforeLines = before.split("\n");
|
|
370
|
+
const afterLines = after.split("\n");
|
|
371
|
+
let shown = 0;
|
|
372
|
+
const maxLines = 30;
|
|
373
|
+
for (let i = 0; i < Math.max(beforeLines.length, afterLines.length) && shown < maxLines; i++) {
|
|
374
|
+
const b = beforeLines[i];
|
|
375
|
+
const a = afterLines[i];
|
|
376
|
+
if (b === undefined) {
|
|
377
|
+
process.stdout.write(chalk_1.default.hex("#a6e3a1")("+ " + a) + "\n");
|
|
378
|
+
shown++;
|
|
379
|
+
}
|
|
380
|
+
else if (a === undefined) {
|
|
381
|
+
process.stdout.write(chalk_1.default.hex("#f38ba8")("- " + b) + "\n");
|
|
382
|
+
shown++;
|
|
383
|
+
}
|
|
384
|
+
else if (b !== a) {
|
|
385
|
+
process.stdout.write(chalk_1.default.hex("#f38ba8")("- " + b) + "\n");
|
|
386
|
+
process.stdout.write(chalk_1.default.hex("#a6e3a1")("+ " + a) + "\n");
|
|
387
|
+
shown += 2;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const totalDiff = Math.abs(afterLines.length - beforeLines.length);
|
|
391
|
+
if (totalDiff > maxLines) {
|
|
392
|
+
process.stdout.write(chalk_1.default.dim(` … ${totalDiff - maxLines} more changes\n`));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
process.stdout.write(chalk_1.default.dim("─".repeat(w)) + "\n\n");
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Approval prompt — yellow bordered box
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
function printApprovalPrompt(title, body) {
|
|
401
|
+
const w = termWidth();
|
|
402
|
+
const top = chalk_1.default.hex("#f9e2af")("╭") +
|
|
403
|
+
chalk_1.default.hex("#f9e2af")("─".repeat(w - 2)) +
|
|
404
|
+
chalk_1.default.hex("#f9e2af")("╮");
|
|
405
|
+
const bottom = chalk_1.default.hex("#f9e2af")("╰") +
|
|
406
|
+
chalk_1.default.hex("#f9e2af")("─".repeat(w - 2)) +
|
|
407
|
+
chalk_1.default.hex("#f9e2af")("╯");
|
|
408
|
+
const titleLine = chalk_1.default.bold.hex("#f9e2af")(" " + title);
|
|
409
|
+
const bodyLines = wordWrap(body, w - 4).map((l) => chalk_1.default.hex("#f9e2af")(" " + l));
|
|
410
|
+
process.stdout.write("\n" + top + "\n");
|
|
411
|
+
process.stdout.write(titleLine + "\n");
|
|
412
|
+
for (const l of bodyLines) {
|
|
413
|
+
process.stdout.write(l + "\n");
|
|
414
|
+
}
|
|
415
|
+
process.stdout.write(bottom + "\n");
|
|
416
|
+
}
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Streaming buffer — handle code fences in real-time, markdown on flush
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
let _streamBuf = "";
|
|
421
|
+
let _inCodeFence = false;
|
|
422
|
+
let _codeFenceLang = "";
|
|
423
|
+
let _codeFenceBuffer = [];
|
|
424
|
+
let _turnHasContent = false;
|
|
27
425
|
function printTextDelta(text) {
|
|
28
|
-
|
|
426
|
+
// Stop spinner on first token
|
|
427
|
+
if (_spinnerActive)
|
|
428
|
+
stopSpinner();
|
|
429
|
+
if (_activeTool)
|
|
430
|
+
finishToolLine(true);
|
|
431
|
+
_streamBuf += text;
|
|
432
|
+
const lines = _streamBuf.split("\n");
|
|
433
|
+
const complete = lines.slice(0, -1);
|
|
434
|
+
_streamBuf = lines[lines.length - 1];
|
|
435
|
+
for (const line of complete) {
|
|
436
|
+
_writeLine(line);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function _writeLine(line) {
|
|
440
|
+
_turnHasContent = true;
|
|
441
|
+
if (!_inCodeFence) {
|
|
442
|
+
const fenceMatch = line.match(/^```(\w*)$/);
|
|
443
|
+
if (fenceMatch) {
|
|
444
|
+
_inCodeFence = true;
|
|
445
|
+
_codeFenceLang = fenceMatch[1] || "plaintext";
|
|
446
|
+
_codeFenceBuffer = [];
|
|
447
|
+
const w = termWidth();
|
|
448
|
+
const lang = _codeFenceLang !== "plaintext" ? _codeFenceLang : "";
|
|
449
|
+
const label = lang ? chalk_1.default.dim(` ${lang} `) : "";
|
|
450
|
+
const labelRaw = lang ? ` ${lang} ` : "";
|
|
451
|
+
const rule = chalk_1.default.dim("─".repeat(Math.max(0, w - labelRaw.length)));
|
|
452
|
+
process.stdout.write(rule + label + "\n");
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const w = termWidth();
|
|
456
|
+
const rendered = renderMarkdownLine(line);
|
|
457
|
+
const rawLen = stripAnsi(rendered).length;
|
|
458
|
+
if (rawLen <= w) {
|
|
459
|
+
process.stdout.write(rendered + "\n");
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
const segments = wordWrap(line, w);
|
|
463
|
+
for (const seg of segments) {
|
|
464
|
+
process.stdout.write(renderMarkdownLine(seg) + "\n");
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
if (line === "```") {
|
|
470
|
+
const code = _codeFenceBuffer.join("\n");
|
|
471
|
+
const language = _codeFenceLang && (0, cli_highlight_1.supportsLanguage)(_codeFenceLang)
|
|
472
|
+
? _codeFenceLang
|
|
473
|
+
: "plaintext";
|
|
474
|
+
try {
|
|
475
|
+
process.stdout.write((0, cli_highlight_1.highlight)(code, { language, ignoreIllegals: true }) + "\n");
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
process.stdout.write(chalk_1.default.dim(code) + "\n");
|
|
479
|
+
}
|
|
480
|
+
process.stdout.write(chalk_1.default.dim("─".repeat(termWidth())) + "\n");
|
|
481
|
+
_inCodeFence = false;
|
|
482
|
+
_codeFenceLang = "";
|
|
483
|
+
_codeFenceBuffer = [];
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
_codeFenceBuffer.push(line);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// ---------------------------------------------------------------------------
|
|
491
|
+
// Turn lifecycle
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
function printTurnStart() {
|
|
494
|
+
process.stdout.write("\x1b[?25l"); // hide cursor
|
|
495
|
+
process.stdout.write("\n" +
|
|
496
|
+
chalk_1.default.hex("#c0384b")("Junior") +
|
|
497
|
+
chalk_1.default.dim(" › ") +
|
|
498
|
+
"\n");
|
|
499
|
+
_turnHasContent = false;
|
|
500
|
+
}
|
|
501
|
+
function printThinkingStart() {
|
|
502
|
+
startSpinner("");
|
|
503
|
+
}
|
|
504
|
+
function printThinkingStop() {
|
|
505
|
+
stopSpinner();
|
|
29
506
|
}
|
|
30
507
|
function printTurnEnd(costUsd) {
|
|
31
|
-
|
|
32
|
-
|
|
508
|
+
process.stdout.write("\x1b[?25h"); // restore cursor
|
|
509
|
+
// Stop any lingering spinner or tool line
|
|
510
|
+
if (_spinnerActive)
|
|
511
|
+
stopSpinner();
|
|
512
|
+
if (_activeTool)
|
|
513
|
+
finishToolLine(true);
|
|
514
|
+
if (_streamBuf.length > 0) {
|
|
515
|
+
_writeLine(_streamBuf);
|
|
516
|
+
_streamBuf = "";
|
|
517
|
+
}
|
|
518
|
+
_inCodeFence = false;
|
|
519
|
+
_codeFenceBuffer = [];
|
|
520
|
+
incrementTurn();
|
|
521
|
+
process.stdout.write("\n" + chalk_1.default.dim("─".repeat(termWidth())) + "\n");
|
|
522
|
+
}
|
|
523
|
+
// ---------------------------------------------------------------------------
|
|
524
|
+
// Input prompt — top border only, no repeated shortcut bar
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
// ---------------------------------------------------------------------------
|
|
527
|
+
// First-launch state — show placeholder hint on very first prompt
|
|
528
|
+
// ---------------------------------------------------------------------------
|
|
529
|
+
let _isFirstPrompt = true;
|
|
530
|
+
function consumeFirstPrompt() {
|
|
531
|
+
const was = _isFirstPrompt;
|
|
532
|
+
_isFirstPrompt = false;
|
|
533
|
+
return was;
|
|
534
|
+
}
|
|
535
|
+
function printPrompt(_showHint = false) {
|
|
536
|
+
const w = termWidth();
|
|
537
|
+
const topBorder = chalk_1.default.dim("╭") +
|
|
538
|
+
chalk_1.default.dim("─".repeat(w - 2)) +
|
|
539
|
+
chalk_1.default.dim("╮");
|
|
540
|
+
// No inline placeholder hint. readline echoes typed input immediately after
|
|
541
|
+
// the "you › " prefix, so a static hint here does not clear on focus/typing —
|
|
542
|
+
// it just gets text appended to it ("…exampleshey junior"). The "type a
|
|
543
|
+
// message, or 'help' for examples" guidance lives in the welcome header
|
|
544
|
+
// instead, where nothing is typed over it.
|
|
545
|
+
process.stdout.write("\n" +
|
|
546
|
+
topBorder + "\n" +
|
|
547
|
+
chalk_1.default.bold.hex("#c0384b")("you") +
|
|
548
|
+
chalk_1.default.dim(" › "));
|
|
549
|
+
}
|
|
550
|
+
function printPromptEnd() {
|
|
551
|
+
const w = termWidth();
|
|
552
|
+
const bottomBorder = chalk_1.default.dim("╰") +
|
|
553
|
+
chalk_1.default.dim("─".repeat(w - 2)) +
|
|
554
|
+
chalk_1.default.dim("╯");
|
|
555
|
+
process.stdout.write("\n" + bottomBorder + "\n");
|
|
556
|
+
}
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
// Welcome + session list
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
function printWelcome(sessionId, sessionName, resumed, messageCount, lastMessagePreview) {
|
|
561
|
+
initStatusBar(sessionId, sessionName);
|
|
562
|
+
const name = sessionName || sessionId.slice(0, 8);
|
|
563
|
+
// Shortcut bar — shown ONCE on welcome only
|
|
564
|
+
const w = termWidth();
|
|
565
|
+
const shortcuts = [
|
|
566
|
+
chalk_1.default.dim("ctrl+c") + " " + chalk_1.default.hex("#6c7086")("exit"),
|
|
567
|
+
chalk_1.default.dim("ctrl+r") + " " + chalk_1.default.hex("#6c7086")("sessions"),
|
|
568
|
+
chalk_1.default.dim("ctrl+l") + " " + chalk_1.default.hex("#6c7086")("clear"),
|
|
569
|
+
chalk_1.default.dim("↑↓") + " " + chalk_1.default.hex("#6c7086")("history"),
|
|
570
|
+
].join(chalk_1.default.dim(" · "));
|
|
571
|
+
const shortcutRaw = stripAnsi(shortcuts);
|
|
572
|
+
const shortcutPad = Math.max(0, Math.floor((w - shortcutRaw.length) / 2));
|
|
573
|
+
if (resumed) {
|
|
574
|
+
process.stdout.write("\n" +
|
|
575
|
+
chalk_1.default.hex("#89b4fa")("↩ " + name) +
|
|
576
|
+
chalk_1.default.dim(` ${messageCount} turns`) +
|
|
577
|
+
"\n");
|
|
578
|
+
if (lastMessagePreview) {
|
|
579
|
+
const truncated = lastMessagePreview.length > 60
|
|
580
|
+
? lastMessagePreview.slice(0, 60) + "…"
|
|
581
|
+
: lastMessagePreview;
|
|
582
|
+
process.stdout.write(chalk_1.default.dim(' last: "' + truncated + '"') + "\n");
|
|
583
|
+
}
|
|
584
|
+
process.stdout.write("\n" +
|
|
585
|
+
" ".repeat(shortcutPad) + shortcuts + "\n\n");
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
process.stdout.write("\n" +
|
|
589
|
+
chalk_1.default.hex("#c0384b").bold("✦ Junior") +
|
|
590
|
+
chalk_1.default.dim(` ${name}`) +
|
|
591
|
+
"\n\n" +
|
|
592
|
+
" ".repeat(shortcutPad) + shortcuts + "\n\n");
|
|
593
|
+
}
|
|
33
594
|
}
|
|
34
595
|
function printError(message) {
|
|
35
|
-
|
|
596
|
+
process.stderr.write(chalk_1.default.red("\n✗ ") +
|
|
597
|
+
chalk_1.default.bold.red("Error: ") +
|
|
598
|
+
chalk_1.default.red(message) +
|
|
599
|
+
"\n\n");
|
|
36
600
|
}
|
|
37
601
|
function printSessionList(sessions) {
|
|
38
602
|
if (sessions.length === 0) {
|
|
39
603
|
console.log(chalk_1.default.dim("No sessions found."));
|
|
40
604
|
return;
|
|
41
605
|
}
|
|
42
|
-
|
|
606
|
+
const w = termWidth();
|
|
607
|
+
process.stdout.write("\n" +
|
|
608
|
+
chalk_1.default.bold.hex("#c0384b")("Sessions") +
|
|
609
|
+
"\n" +
|
|
610
|
+
chalk_1.default.dim("─".repeat(w)) +
|
|
611
|
+
"\n");
|
|
43
612
|
for (const s of sessions) {
|
|
44
|
-
const name = s.session_name
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
613
|
+
const name = s.session_name
|
|
614
|
+
? chalk_1.default.hex("#89b4fa")(s.session_name)
|
|
615
|
+
: chalk_1.default.dim("(unnamed)");
|
|
616
|
+
// Session ID hidden — not useful to non-technical users
|
|
617
|
+
const turns = chalk_1.default.dim(`${s.message_count} turns`);
|
|
618
|
+
const cost = chalk_1.default.dim(`$${Number(s.total_cost_usd).toFixed(4)}`);
|
|
619
|
+
const lastActive = chalk_1.default.dim(new Date(s.last_active_at).toLocaleString());
|
|
620
|
+
const status = s.status === "active"
|
|
621
|
+
? chalk_1.default.hex("#a6e3a1")("●")
|
|
622
|
+
: chalk_1.default.dim("○");
|
|
623
|
+
process.stdout.write(` ${status} ${name}\n`);
|
|
624
|
+
process.stdout.write(` ${turns} ${cost} ${lastActive}\n\n`);
|
|
53
625
|
}
|
|
54
|
-
|
|
626
|
+
process.stdout.write(chalk_1.default.dim("Resume: xiaotime dev --session <id>\n\n"));
|
|
55
627
|
}
|
|
56
|
-
|
|
57
|
-
|
|
628
|
+
// ---------------------------------------------------------------------------
|
|
629
|
+
// Tool call indicators
|
|
630
|
+
// ---------------------------------------------------------------------------
|
|
631
|
+
const TOOL_ICONS = {
|
|
632
|
+
read_file: "▸",
|
|
633
|
+
write_file: "▸",
|
|
634
|
+
list_directory: "▸",
|
|
635
|
+
shell_command: "▸",
|
|
636
|
+
git_status: "▸",
|
|
637
|
+
git_diff: "▸",
|
|
638
|
+
git_commit: "▸",
|
|
639
|
+
};
|
|
640
|
+
const SERVER_TOOL_ICONS = {
|
|
641
|
+
web_search: "▸",
|
|
642
|
+
lookup_entity: "▸",
|
|
643
|
+
remember_entity: "▸",
|
|
644
|
+
signal_query: "▸",
|
|
645
|
+
research_request: "▸",
|
|
646
|
+
note_ingest: "▸",
|
|
647
|
+
check_calendar: "▸",
|
|
648
|
+
meeting_prep: "▸",
|
|
649
|
+
list_entities: "▸",
|
|
650
|
+
};
|
|
651
|
+
// ---------------------------------------------------------------------------
|
|
652
|
+
// Human-friendly tool status labels for non-developer users
|
|
653
|
+
// ---------------------------------------------------------------------------
|
|
654
|
+
const TOOL_FRIENDLY_NAMES = {
|
|
655
|
+
web_search: "searching the web...",
|
|
656
|
+
lookup_entity: "looking that up...",
|
|
657
|
+
remember_entity: "saving that...",
|
|
658
|
+
signal_query: "checking signals...",
|
|
659
|
+
research_request: "doing some research...",
|
|
660
|
+
note_ingest: "saving your note...",
|
|
661
|
+
check_calendar: "checking your calendar...",
|
|
662
|
+
meeting_prep: "preparing your meeting brief...",
|
|
663
|
+
list_entities: "pulling up your contacts...",
|
|
664
|
+
list_pipeline_accounts: "checking your pipeline...",
|
|
665
|
+
entity_history: "reviewing history...",
|
|
666
|
+
account_deep_view: "digging into that account...",
|
|
667
|
+
stale_contacts: "finding stale contacts...",
|
|
668
|
+
resolve_person: "looking up that person...",
|
|
669
|
+
link_entities: "making that connection...",
|
|
670
|
+
get_connections: "finding connections...",
|
|
671
|
+
precall_brief: "prepping your brief...",
|
|
672
|
+
objection_prep: "preparing objection handling...",
|
|
673
|
+
competitive_intel: "pulling competitive intel...",
|
|
674
|
+
habit_log: "logging that habit...",
|
|
675
|
+
habit_streak: "checking your streaks...",
|
|
676
|
+
schedule_job: "scheduling that...",
|
|
677
|
+
list_scheduled_jobs: "loading scheduled jobs...",
|
|
678
|
+
cancel_scheduled_job: "cancelling that job...",
|
|
679
|
+
log_decision: "recording that decision...",
|
|
680
|
+
signal_stats: "pulling signal stats...",
|
|
681
|
+
read_file: "reading file...",
|
|
682
|
+
write_file: "writing file...",
|
|
683
|
+
list_directory: "listing files...",
|
|
684
|
+
shell_command: "running command...",
|
|
685
|
+
git_status: "checking git status...",
|
|
686
|
+
git_diff: "reading changes...",
|
|
687
|
+
git_commit: "committing...",
|
|
688
|
+
generate_image: "generating image...",
|
|
689
|
+
run_background_task: "starting background task...",
|
|
690
|
+
summarize_url: "reading that page...",
|
|
691
|
+
fetch_url: "fetching that page...",
|
|
692
|
+
};
|
|
693
|
+
function friendlyToolLabel(name) {
|
|
694
|
+
return TOOL_FRIENDLY_NAMES[name] ?? `working on it...`;
|
|
58
695
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
696
|
+
// ---------------------------------------------------------------------------
|
|
697
|
+
// Friendly plain-English error messages
|
|
698
|
+
// ---------------------------------------------------------------------------
|
|
699
|
+
const ERROR_PATTERNS = [
|
|
700
|
+
[/ECONNREFUSED/i, "Can't reach the server. Check your connection and try again."],
|
|
701
|
+
[/ETIMEDOUT|TIMEOUT/i, "The request timed out. The server might be busy — try again in a moment."],
|
|
702
|
+
[/ENOTFOUND/i, "Couldn't connect — check your internet connection."],
|
|
703
|
+
[/401|unauthorized/i, "Not signed in. Run `xiaotime login` to authenticate."],
|
|
704
|
+
[/403|forbidden/i, "You don't have permission to do that."],
|
|
705
|
+
[/404|not found/i, "That resource wasn't found."],
|
|
706
|
+
[/500|internal server/i, "Something went wrong on the server. Try again shortly."],
|
|
707
|
+
[/EPIPE/i, "Connection was interrupted. Reconnect and try again."],
|
|
708
|
+
[/WEBSOCKET|ws error/i, "Lost the live connection. Reconnecting..."],
|
|
709
|
+
];
|
|
710
|
+
function friendlyError(raw) {
|
|
711
|
+
for (const [pattern, friendly] of ERROR_PATTERNS) {
|
|
712
|
+
if (pattern.test(raw))
|
|
713
|
+
return friendly;
|
|
714
|
+
}
|
|
715
|
+
// Fall back — strip stack trace noise, just show first line
|
|
716
|
+
const firstLine = raw.split("\n")[0].replace(/^Error:\s*/i, "").trim();
|
|
717
|
+
return firstLine.length > 0 && firstLine.length < 200 ? firstLine : "Something went wrong. Try again.";
|
|
718
|
+
}
|
|
719
|
+
// ---------------------------------------------------------------------------
|
|
720
|
+
// Help panel
|
|
721
|
+
// ---------------------------------------------------------------------------
|
|
722
|
+
function printHelp() {
|
|
723
|
+
const w = termWidth();
|
|
724
|
+
const top = chalk_1.default.hex("#f9e2af")("╭") +
|
|
725
|
+
chalk_1.default.hex("#f9e2af")("─".repeat(w - 2)) +
|
|
726
|
+
chalk_1.default.hex("#f9e2af")("╮");
|
|
727
|
+
const bottom = chalk_1.default.hex("#f9e2af")("╰") +
|
|
728
|
+
chalk_1.default.hex("#f9e2af")("─".repeat(w - 2)) +
|
|
729
|
+
chalk_1.default.hex("#f9e2af")("╯");
|
|
730
|
+
const lines = [
|
|
731
|
+
chalk_1.default.bold.hex("#f9e2af")(" Junior — your AI chief of staff"),
|
|
732
|
+
"",
|
|
733
|
+
chalk_1.default.dim(" Just type naturally. Some examples:"),
|
|
734
|
+
"",
|
|
735
|
+
` ${chalk_1.default.hex("#89b4fa")("what's on my calendar today")}`,
|
|
736
|
+
` ${chalk_1.default.hex("#89b4fa")("draft a follow-up email to Acme")}`,
|
|
737
|
+
` ${chalk_1.default.hex("#89b4fa")("look up Banner Health")}`,
|
|
738
|
+
` ${chalk_1.default.hex("#89b4fa")("what signals do I have this week")}`,
|
|
739
|
+
` ${chalk_1.default.hex("#89b4fa")("research CrowdStrike")}`,
|
|
740
|
+
"",
|
|
741
|
+
chalk_1.default.dim(" Keyboard shortcuts:"),
|
|
742
|
+
` ${chalk_1.default.dim("ctrl+c")} exit ${chalk_1.default.dim("ctrl+r")} switch sessions`,
|
|
743
|
+
` ${chalk_1.default.dim("ctrl+l")} clear screen ${chalk_1.default.dim("ctrl+u")} scroll history`,
|
|
744
|
+
` ${chalk_1.default.dim("↑ ↓")} message history`,
|
|
745
|
+
"",
|
|
746
|
+
chalk_1.default.dim(" Type anything to get started. Junior will figure out the rest."),
|
|
747
|
+
];
|
|
748
|
+
process.stdout.write("\n" + top + "\n");
|
|
749
|
+
for (const l of lines) {
|
|
750
|
+
process.stdout.write(l + "\n");
|
|
751
|
+
}
|
|
752
|
+
process.stdout.write(bottom + "\n\n");
|
|
753
|
+
}
|
|
754
|
+
// ---------------------------------------------------------------------------
|
|
755
|
+
// Disconnect banner
|
|
756
|
+
// ---------------------------------------------------------------------------
|
|
757
|
+
function printDisconnectBanner(reconnecting = true) {
|
|
758
|
+
const msg = reconnecting
|
|
759
|
+
? " Lost connection — trying to reconnect..."
|
|
760
|
+
: " Disconnected. Resume with: xiaotime dev";
|
|
761
|
+
process.stdout.write("\n" + chalk_1.default.dim(msg) + "\n");
|
|
762
|
+
}
|
|
763
|
+
// ---------------------------------------------------------------------------
|
|
764
|
+
// Exit confirmation
|
|
765
|
+
// ---------------------------------------------------------------------------
|
|
766
|
+
function printExitConfirmation() {
|
|
767
|
+
process.stdout.write("\n" + chalk_1.default.hex("#f9e2af")(" Session paused. Your conversation is saved.") + "\n");
|
|
768
|
+
process.stdout.write(chalk_1.default.dim(" Resume anytime with: xiaotime dev") + "\n\n");
|
|
769
|
+
}
|
|
770
|
+
function printToolCallLine(name, detail, durationMs, success = true) {
|
|
771
|
+
setLastTool(name);
|
|
772
|
+
const icon = success ? chalk_1.default.hex("#a6e3a1")("✓") : chalk_1.default.hex("#f38ba8")("✗");
|
|
773
|
+
const dur = durationMs !== undefined ? chalk_1.default.dim(` ${durationMs}ms`) : "";
|
|
774
|
+
const detailStr = detail ? chalk_1.default.dim(` ${detail}`) : "";
|
|
775
|
+
process.stdout.write(` ${icon} ${chalk_1.default.dim(name)}${detailStr}${dur}\n`);
|
|
776
|
+
}
|
|
777
|
+
function printServerToolCall(name, inputSummary, durationMs) {
|
|
778
|
+
setLastTool(name);
|
|
779
|
+
const icon = chalk_1.default.hex("#a6e3a1")("✓");
|
|
780
|
+
const tail = inputSummary ? chalk_1.default.dim(` ${inputSummary}`) : "";
|
|
781
|
+
const dur = durationMs !== undefined ? chalk_1.default.dim(` ${durationMs}ms`) : "";
|
|
782
|
+
process.stdout.write(` ${icon} ${chalk_1.default.dim(name)}${tail}${dur}\n`);
|
|
80
783
|
}
|
|
81
784
|
//# sourceMappingURL=display.js.map
|