xibecode 1.3.7 → 1.3.11
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/commands/cloud-pull.d.ts +5 -0
- package/dist/commands/cloud-pull.d.ts.map +1 -1
- package/dist/commands/cloud-pull.js +98 -4
- package/dist/commands/cloud-pull.js.map +1 -1
- package/dist/commands/resume.js +1 -0
- package/dist/commands/resume.js.map +1 -1
- package/dist/index.js +36 -16
- package/dist/index.js.map +1 -1
- package/dist/ui/claude-style-chat.d.ts +2 -0
- package/dist/ui/claude-style-chat.d.ts.map +1 -1
- package/dist/ui/claude-style-chat.js +229 -11
- package/dist/ui/claude-style-chat.js.map +1 -1
- package/dist/utils/cloud-sync-feedback.js +1 -1
- package/dist/utils/cloud-sync-feedback.js.map +1 -1
- package/dist/utils/list-files.d.ts +10 -0
- package/dist/utils/list-files.d.ts.map +1 -0
- package/dist/utils/list-files.js +118 -0
- package/dist/utils/list-files.js.map +1 -0
- package/package.json +2 -2
|
@@ -23,6 +23,7 @@ import { loadImageAttachment, mimeFromExtension } from '../utils/image-attachmen
|
|
|
23
23
|
import { SessionManager } from 'xibecode-core';
|
|
24
24
|
import { AutoMemoryManager, HooksManager, SettingsManager as CoreSettingsManager } from 'xibecode-core';
|
|
25
25
|
import { cloudPullCommand } from '../commands/cloud-pull.js';
|
|
26
|
+
import { listWorkspaceFiles } from '../utils/list-files.js';
|
|
26
27
|
import { attachRemoteExecution, codingToolExecutorRemoteOptions, getRuntimeStatusLabel, remoteToolSandboxIdForAgent, remoteToolWorkspaceRootForAgent, resolveRemoteExecutionConfig, } from '../utils/remote-execution.js';
|
|
27
28
|
import { syncWorkspaceToSandbox } from '../utils/sandbox-sync.js';
|
|
28
29
|
import { withCloudWorkspaceSyncSpinner } from '../utils/cloud-sync-feedback.js';
|
|
@@ -96,6 +97,52 @@ function summarizeToolResultContent(content) {
|
|
|
96
97
|
}
|
|
97
98
|
return raw.replace(/\s+/g, ' ').trim().slice(0, 300);
|
|
98
99
|
}
|
|
100
|
+
/** Mathem. brackets — wrap @-file picks so each pick is one immutable token */
|
|
101
|
+
const AT_MENTION_OPEN = '\u27e6'; // ⟦
|
|
102
|
+
const AT_MENTION_CLOSE = '\u27e7'; // ⟧
|
|
103
|
+
/** Drop unknown ⟦…⟧ groups; keep only exact picker-produced tokens still registered in `locked` */
|
|
104
|
+
function reconcileTaggedAtMentions(next, locked) {
|
|
105
|
+
let out = '';
|
|
106
|
+
let i = 0;
|
|
107
|
+
while (i < next.length) {
|
|
108
|
+
if (next.startsWith(AT_MENTION_OPEN, i)) {
|
|
109
|
+
const closeIdx = next.indexOf(AT_MENTION_CLOSE, i + AT_MENTION_OPEN.length);
|
|
110
|
+
if (closeIdx === -1) {
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
const block = next.slice(i, closeIdx + AT_MENTION_CLOSE.length);
|
|
114
|
+
if (locked.current.has(block)) {
|
|
115
|
+
out += block;
|
|
116
|
+
}
|
|
117
|
+
i = closeIdx + AT_MENTION_CLOSE.length;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
out += next[i];
|
|
121
|
+
i += 1;
|
|
122
|
+
}
|
|
123
|
+
for (const t of [...locked.current]) {
|
|
124
|
+
if (!out.includes(t))
|
|
125
|
+
locked.current.delete(t);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/** Strip pick wrappers before commands / history / prompts — core expects plain `@path` */
|
|
130
|
+
function flattenTaggedAtMentions(s) {
|
|
131
|
+
return s.replace(/\u27e6(@[A-Za-z0-9._\-\/]+)\u27e7/g, (_, inner) => {
|
|
132
|
+
let body = inner.slice(1);
|
|
133
|
+
if (body.startsWith('/'))
|
|
134
|
+
body = body.slice(1);
|
|
135
|
+
return `@${body}`;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** Inserted path for dirs: `/relative/` — files stay repo-relative without a leading slash */
|
|
139
|
+
function mentionPathForPick(entry) {
|
|
140
|
+
if (entry.isDirectory) {
|
|
141
|
+
const trimmed = entry.relativePath.replace(/\/+$/, '');
|
|
142
|
+
return trimmed ? `/${trimmed}/` : '/';
|
|
143
|
+
}
|
|
144
|
+
return entry.relativePath;
|
|
145
|
+
}
|
|
99
146
|
function transcriptLinesFromMessage(message) {
|
|
100
147
|
if (typeof message.content === 'string') {
|
|
101
148
|
const text = message.content.trim();
|
|
@@ -155,7 +202,7 @@ const CHAT_COMMANDS = [
|
|
|
155
202
|
{ name: '/config', description: 'Show current config and quick config hints' },
|
|
156
203
|
{ name: '/memory', description: 'Show auto-memories for this project' },
|
|
157
204
|
{ name: '/hooks', description: 'Show registered lifecycle hooks' },
|
|
158
|
-
{ name: '/cpull', description: 'Pull
|
|
205
|
+
{ name: '/cpull', description: 'Pull sandbox workspace; /cpull --apply merges only new/changed files (use --full to replace all)' },
|
|
159
206
|
{ name: '/commit', description: 'Stage all changes and commit (auto message or custom text)' },
|
|
160
207
|
{ name: '/donate', description: 'Open the donation page in your browser' },
|
|
161
208
|
{ name: '/sponsor', description: 'Open the sponsorship page in your browser' },
|
|
@@ -279,6 +326,15 @@ function XibeCodeChatApp(props) {
|
|
|
279
326
|
const [configProviderIndex, setConfigProviderIndex] = useState(0);
|
|
280
327
|
const [configCostModePickerOpen, setConfigCostModePickerOpen] = useState(false);
|
|
281
328
|
const [configCostModeIndex, setConfigCostModeIndex] = useState(0);
|
|
329
|
+
// File picker state (@-triggered)
|
|
330
|
+
const [filePickerOpen, setFilePickerOpen] = useState(false);
|
|
331
|
+
const [filteredFileEntries, setFilteredFileEntries] = useState([]);
|
|
332
|
+
const [selectedFileIndex, setSelectedFileIndex] = useState(0);
|
|
333
|
+
const [fileQuery, setFileQuery] = useState('');
|
|
334
|
+
const [atPos, setAtPos] = useState(-1);
|
|
335
|
+
const [filePickerLoading, setFilePickerLoading] = useState(false);
|
|
336
|
+
/** Bump when the file picker inserts text so Ink TextInput remounts with the caret at the end */
|
|
337
|
+
const [chatInputMountKey, setChatInputMountKey] = useState(0);
|
|
282
338
|
const [questionsState, setQuestionsState] = useState(null);
|
|
283
339
|
const [workSpinnerFrame, setWorkSpinnerFrame] = useState(0);
|
|
284
340
|
const [workVerbIndex, setWorkVerbIndex] = useState(0);
|
|
@@ -322,6 +378,9 @@ function XibeCodeChatApp(props) {
|
|
|
322
378
|
// Keep a much larger in-memory transcript so context doesn't vanish quickly.
|
|
323
379
|
setLines((prev) => [...prev.slice(-5000), withId]);
|
|
324
380
|
}, [props]);
|
|
381
|
+
const handleChatInputChange = useCallback((next) => {
|
|
382
|
+
setInput(reconcileTaggedAtMentions(next, lockedPickTagsRef));
|
|
383
|
+
}, []);
|
|
325
384
|
useEffect(() => {
|
|
326
385
|
props.registerUiSink?.(pushLine);
|
|
327
386
|
}, [props, pushLine]);
|
|
@@ -341,6 +400,12 @@ function XibeCodeChatApp(props) {
|
|
|
341
400
|
const lastVisibleOutputAtRef = useRef(Date.now());
|
|
342
401
|
const currentPromptRef = useRef(null);
|
|
343
402
|
const restartAttemptsRef = useRef(0);
|
|
403
|
+
const promptHistoryRef = useRef([]);
|
|
404
|
+
const historyIndexRef = useRef(-1);
|
|
405
|
+
const savedInputRef = useRef('');
|
|
406
|
+
const cachedFileEntriesRef = useRef([]);
|
|
407
|
+
/** Exact ⟦@path⟧ strings inserted via the file picker — edits inside remove the whole token */
|
|
408
|
+
const lockedPickTagsRef = useRef(new Set());
|
|
344
409
|
const sessionMessagesRef = useRef(props.initialMessages || []);
|
|
345
410
|
const lastBgLineByTask = useRef(new Map());
|
|
346
411
|
useEffect(() => {
|
|
@@ -433,6 +498,64 @@ function XibeCodeChatApp(props) {
|
|
|
433
498
|
}, WORK_VERB_ROTATE_MS);
|
|
434
499
|
return () => clearInterval(id);
|
|
435
500
|
}, [isRunning]);
|
|
501
|
+
// Load workspace file list once on mount for @-picker
|
|
502
|
+
useEffect(() => {
|
|
503
|
+
let cancelled = false;
|
|
504
|
+
setFilePickerLoading(true);
|
|
505
|
+
(async () => {
|
|
506
|
+
try {
|
|
507
|
+
const entries = await listWorkspaceFiles(process.cwd());
|
|
508
|
+
if (!cancelled) {
|
|
509
|
+
cachedFileEntriesRef.current = entries;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
// silently fail
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
if (!cancelled)
|
|
517
|
+
setFilePickerLoading(false);
|
|
518
|
+
}
|
|
519
|
+
})();
|
|
520
|
+
return () => { cancelled = true; };
|
|
521
|
+
}, []);
|
|
522
|
+
// Detect '@' in input to open file picker
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
const lastAtIndex = input.lastIndexOf('@');
|
|
525
|
+
if (lastAtIndex === -1) {
|
|
526
|
+
setFilePickerOpen(false);
|
|
527
|
+
setSelectedFileIndex(0);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
// '@' must be at a word boundary
|
|
531
|
+
if (lastAtIndex > 0) {
|
|
532
|
+
const prev = input[lastAtIndex - 1];
|
|
533
|
+
if (prev !== ' ' && prev !== '(' && prev !== AT_MENTION_CLOSE) {
|
|
534
|
+
setFilePickerOpen(false);
|
|
535
|
+
setSelectedFileIndex(0);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const afterAt = input.slice(lastAtIndex + 1);
|
|
540
|
+
// Space ends the active @-mention — hide suggestions (typing past first word breaks mention mode)
|
|
541
|
+
if (afterAt.includes(' ')) {
|
|
542
|
+
setFilePickerOpen(false);
|
|
543
|
+
setSelectedFileIndex(0);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const query = afterAt;
|
|
547
|
+
setAtPos(lastAtIndex);
|
|
548
|
+
setFileQuery(query);
|
|
549
|
+
setFilePickerOpen(true);
|
|
550
|
+
const lowerQuery = query.toLowerCase();
|
|
551
|
+
const filtered = cachedFileEntriesRef.current.filter((e) => {
|
|
552
|
+
const rel = e.relativePath.toLowerCase();
|
|
553
|
+
const label = mentionPathForPick(e).toLowerCase();
|
|
554
|
+
return rel.includes(lowerQuery) || label.includes(lowerQuery);
|
|
555
|
+
});
|
|
556
|
+
setFilteredFileEntries(filtered);
|
|
557
|
+
setSelectedFileIndex((prev) => Math.min(prev, Math.max(0, filtered.length - 1)));
|
|
558
|
+
}, [input]);
|
|
436
559
|
const ensureModelsLoaded = useCallback(async () => {
|
|
437
560
|
if (availableModels.length > 0) {
|
|
438
561
|
return availableModels;
|
|
@@ -603,17 +726,22 @@ function XibeCodeChatApp(props) {
|
|
|
603
726
|
if (questionsState) {
|
|
604
727
|
return;
|
|
605
728
|
}
|
|
729
|
+
// If file picker is open, ignore Enter (handled by useInput)
|
|
730
|
+
if (filePickerOpen) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
const flat = flattenTaggedAtMentions(trimmed);
|
|
606
734
|
if (isRunning) {
|
|
607
|
-
queuedPromptRef.current =
|
|
735
|
+
queuedPromptRef.current = flat;
|
|
608
736
|
setInput('');
|
|
609
737
|
pushLine({ type: 'info', text: 'Queued message — will run next.' });
|
|
610
738
|
return;
|
|
611
739
|
}
|
|
612
|
-
const commandMatches = CHAT_COMMANDS.filter((command) => command.name.toLowerCase().startsWith(
|
|
613
|
-
const exactMatch = CHAT_COMMANDS.some((command) => command.name.toLowerCase() ===
|
|
614
|
-
const resolvedInput =
|
|
740
|
+
const commandMatches = CHAT_COMMANDS.filter((command) => command.name.toLowerCase().startsWith(flat.toLowerCase()));
|
|
741
|
+
const exactMatch = CHAT_COMMANDS.some((command) => command.name.toLowerCase() === flat.toLowerCase());
|
|
742
|
+
const resolvedInput = flat.startsWith('/') && !exactMatch && commandMatches[selectedCommandIndex]
|
|
615
743
|
? commandMatches[selectedCommandIndex].name
|
|
616
|
-
:
|
|
744
|
+
: flat;
|
|
617
745
|
setInput('');
|
|
618
746
|
// Setup wizard input capture
|
|
619
747
|
if (setupStep !== 'idle') {
|
|
@@ -965,6 +1093,14 @@ function XibeCodeChatApp(props) {
|
|
|
965
1093
|
const anyErr = err;
|
|
966
1094
|
return anyErr.name === 'AbortError' || String(anyErr.message || '').toLowerCase().includes('aborted');
|
|
967
1095
|
};
|
|
1096
|
+
// Save to prompt history (avoid duplicates for consecutive same prompts)
|
|
1097
|
+
if (resolvedInput && !resolvedInput.startsWith('/')) {
|
|
1098
|
+
const hist = promptHistoryRef.current;
|
|
1099
|
+
if (hist[hist.length - 1] !== resolvedInput) {
|
|
1100
|
+
hist.push(resolvedInput);
|
|
1101
|
+
}
|
|
1102
|
+
historyIndexRef.current = hist.length;
|
|
1103
|
+
}
|
|
968
1104
|
// Watchdog auto-restart loop (up to 2 restarts)
|
|
969
1105
|
restartAttemptsRef.current = 0;
|
|
970
1106
|
let promptToRun = resolvedInput;
|
|
@@ -1004,9 +1140,11 @@ function XibeCodeChatApp(props) {
|
|
|
1004
1140
|
configPrompt.kind,
|
|
1005
1141
|
ensureModelsLoaded,
|
|
1006
1142
|
exit,
|
|
1143
|
+
filePickerOpen,
|
|
1007
1144
|
isRunning,
|
|
1008
1145
|
props,
|
|
1009
1146
|
pushLine,
|
|
1147
|
+
questionsState,
|
|
1010
1148
|
requestOpenAIModelsFrom,
|
|
1011
1149
|
selectedCommandIndex,
|
|
1012
1150
|
activeMode,
|
|
@@ -1015,7 +1153,6 @@ function XibeCodeChatApp(props) {
|
|
|
1015
1153
|
wireFormat,
|
|
1016
1154
|
applyMode,
|
|
1017
1155
|
startSetupWizard,
|
|
1018
|
-
isRunning,
|
|
1019
1156
|
]);
|
|
1020
1157
|
const normalizedInput = input.trim().toLowerCase();
|
|
1021
1158
|
const isSlashMode = input.startsWith('/');
|
|
@@ -1035,6 +1172,9 @@ function XibeCodeChatApp(props) {
|
|
|
1035
1172
|
const visibleModelOptions = filteredModels.slice(modelPickerStart, modelPickerStart + MODEL_PICKER_WINDOW);
|
|
1036
1173
|
const setupModelPickerStart = computeWindowStart(setupModels.length, setupSelectedModelIndex, MODEL_PICKER_WINDOW);
|
|
1037
1174
|
const visibleSetupModelOptions = setupModels.slice(setupModelPickerStart, setupModelPickerStart + MODEL_PICKER_WINDOW);
|
|
1175
|
+
const FILE_PICKER_WINDOW = 14;
|
|
1176
|
+
const filePickerStart = computeWindowStart(filteredFileEntries.length, selectedFileIndex, FILE_PICKER_WINDOW);
|
|
1177
|
+
const visibleFileEntries = filteredFileEntries.slice(filePickerStart, filePickerStart + FILE_PICKER_WINDOW);
|
|
1038
1178
|
useInput((inputKey, key) => {
|
|
1039
1179
|
if (key.ctrl && inputKey === 'c') {
|
|
1040
1180
|
exit();
|
|
@@ -1424,6 +1564,69 @@ function XibeCodeChatApp(props) {
|
|
|
1424
1564
|
return;
|
|
1425
1565
|
}
|
|
1426
1566
|
}
|
|
1567
|
+
// File picker arrow navigation (@-triggered)
|
|
1568
|
+
if (filePickerOpen && !isRunning && !isSlashMode) {
|
|
1569
|
+
if (key.escape) {
|
|
1570
|
+
setFilePickerOpen(false);
|
|
1571
|
+
setSelectedFileIndex(0);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
if (key.upArrow) {
|
|
1575
|
+
setSelectedFileIndex((prev) => prev === 0 ? filteredFileEntries.length - 1 : prev - 1);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (key.downArrow) {
|
|
1579
|
+
setSelectedFileIndex((prev) => prev >= filteredFileEntries.length - 1 ? 0 : prev + 1);
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (key.return) {
|
|
1583
|
+
const selected = filteredFileEntries[selectedFileIndex];
|
|
1584
|
+
if (selected) {
|
|
1585
|
+
const beforeAt = input.slice(0, atPos);
|
|
1586
|
+
const afterAtPart = input.slice(atPos + 1);
|
|
1587
|
+
const spaceIdx = afterAtPart.indexOf(' ');
|
|
1588
|
+
const rest = spaceIdx === -1 ? '' : afterAtPart.slice(spaceIdx);
|
|
1589
|
+
const pickedPath = mentionPathForPick(selected);
|
|
1590
|
+
const tag = `${AT_MENTION_OPEN}@${pickedPath}${AT_MENTION_CLOSE}`;
|
|
1591
|
+
lockedPickTagsRef.current.add(tag);
|
|
1592
|
+
const newInput = `${beforeAt}${tag}${rest} `;
|
|
1593
|
+
setInput(reconcileTaggedAtMentions(newInput, lockedPickTagsRef));
|
|
1594
|
+
setChatInputMountKey((k) => k + 1);
|
|
1595
|
+
}
|
|
1596
|
+
setFilePickerOpen(false);
|
|
1597
|
+
setSelectedFileIndex(0);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
// Prompt history browsing with UP/DOWN arrows (only in normal chat input)
|
|
1602
|
+
if (!isSlashMode && !isRunning && !questionsState && !configMenuOpen && !modePickerOpen && !modelPickerOpen && !setupModelPickerOpen && !setupProviderPickerOpen && !configProviderPickerOpen && !configCostModePickerOpen) {
|
|
1603
|
+
const hist = promptHistoryRef.current;
|
|
1604
|
+
if (key.upArrow) {
|
|
1605
|
+
if (hist.length === 0)
|
|
1606
|
+
return;
|
|
1607
|
+
// Save current input before browsing (only the first time we press up)
|
|
1608
|
+
if (historyIndexRef.current >= hist.length) {
|
|
1609
|
+
savedInputRef.current = input;
|
|
1610
|
+
}
|
|
1611
|
+
if (historyIndexRef.current > 0) {
|
|
1612
|
+
historyIndexRef.current -= 1;
|
|
1613
|
+
setInput(hist[historyIndexRef.current]);
|
|
1614
|
+
}
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
if (key.downArrow) {
|
|
1618
|
+
if (historyIndexRef.current < hist.length) {
|
|
1619
|
+
historyIndexRef.current += 1;
|
|
1620
|
+
if (historyIndexRef.current >= hist.length) {
|
|
1621
|
+
setInput(savedInputRef.current);
|
|
1622
|
+
}
|
|
1623
|
+
else {
|
|
1624
|
+
setInput(hist[historyIndexRef.current]);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1427
1630
|
if (!isSlashMode || isRunning || filteredCommands.length === 0) {
|
|
1428
1631
|
return;
|
|
1429
1632
|
}
|
|
@@ -1504,11 +1707,15 @@ function XibeCodeChatApp(props) {
|
|
|
1504
1707
|
const isSelected = otherIdx === selectedOptionIndex;
|
|
1505
1708
|
return (_jsxs(Text, { children: [_jsx(Text, { bold: true, color: isSelected ? 'green' : 'inactive', children: isSelected ? ' ▸ ' : ' ' }), _jsxs(Text, { bold: true, color: isSelected ? 'green' : 'yellow', children: [optLetters[otherIdx], ")"] }), _jsx(Text, { color: isSelected ? 'green' : 'inactive', children: " type yourself" })] }));
|
|
1506
1709
|
})()] }), _jsx(Text, { color: "inactive", dimColor: true, children: "\u2191/\u2193 to navigate, Enter to select, Esc to cancel" })] }));
|
|
1507
|
-
})(), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: questionsState ? (questionsState.isTypingCustom ? 'green' : 'yellow') : 'claude', paddingX: 1, children: [_jsx(Text, { color: questionsState ? (questionsState.isTypingCustom ? 'green' : 'yellow') : 'claude', children: '> ' }), _jsx(TextInput, { value: input, onChange:
|
|
1710
|
+
})(), _jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: questionsState ? (questionsState.isTypingCustom ? 'green' : 'yellow') : 'claude', paddingX: 1, children: [_jsx(Text, { color: questionsState ? (questionsState.isTypingCustom ? 'green' : 'yellow') : 'claude', children: '> ' }), _jsx(TextInput, { value: input, onChange: handleChatInputChange, onSubmit: onSubmit, placeholder: questionsState
|
|
1508
1711
|
? questionsState.isTypingCustom
|
|
1509
1712
|
? `Type your answer for Q${questionsState.currentIndex + 1} and press Enter`
|
|
1510
1713
|
: `Use ↑/↓ and Enter to pick an option (Q${questionsState.currentIndex + 1}/${questionsState.questions.length})`
|
|
1511
|
-
: isRunning ? 'Waiting for response…' : 'Message XibeCode…' })] }),
|
|
1714
|
+
: isRunning ? 'Waiting for response…' : 'Message XibeCode…' }, chatInputMountKey)] }), filePickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "suggestion", children: ["Files", filePickerLoading ? ' (loading...)' : ''] }), filePickerLoading ? (_jsx(Text, { color: "inactive", children: "Scanning workspace\u2026" })) : filteredFileEntries.length === 0 ? (_jsx(Text, { color: "inactive", children: fileQuery ? `No files match "${fileQuery}"` : 'No files in workspace' })) : (visibleFileEntries.map((entry, index) => {
|
|
1715
|
+
const absoluteIndex = filePickerStart + index;
|
|
1716
|
+
const isSelected = absoluteIndex === selectedFileIndex;
|
|
1717
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: isSelected ? 'claude' : 'inactive', children: isSelected ? '▸ ' : ' ' }), _jsx(Text, { color: isSelected ? 'claude' : (entry.isDirectory ? 'yellow' : 'text'), children: mentionPathForPick(entry) })] }, entry.relativePath));
|
|
1718
|
+
})), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter inserts locked \u27E6@path\u27E7 \u2022 Space ends mention \u2022 Esc close" })] })), isSlashMode && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "suggestion", bold: true, children: "Commands" }), filteredCommands.length === 0 ? (_jsxs(Text, { color: "inactive", children: ["No commands match \"", input, "\""] })) : (filteredCommands.map((command, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === selectedCommandIndex ? 'claude' : 'inactive', children: index === selectedCommandIndex ? '▸ ' : ' ' }), _jsx(Text, { bold: true, color: index === selectedCommandIndex ? 'claude' : 'text', children: command.name }), _jsxs(Text, { color: "inactive", children: [" \u2014 ", command.description] })] }) }, command.name)))), _jsx(Text, { color: "subtle", children: "Use \u2191/\u2193 to navigate, Tab to autocomplete." })] })), modelPickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "claude", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "claude", children: "Select model" }), isModelListLoading ? (_jsx(Text, { color: "inactive", children: "Loading models from provider..." })) : filteredModels.length === 0 ? (_jsx(Text, { color: "inactive", children: "No models matched current filter." })) : (visibleModelOptions.map((modelName, index) => {
|
|
1512
1719
|
const absoluteIndex = modelPickerStart + index;
|
|
1513
1720
|
const isSelected = absoluteIndex === selectedModelIndex;
|
|
1514
1721
|
return (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: isSelected ? 'claude' : 'inactive', children: isSelected ? '▸ ' : ' ' }), _jsx(Text, { color: isSelected ? 'claude' : 'text', children: modelName })] }) }, modelName));
|
|
@@ -1542,6 +1749,11 @@ function XibeCodeChatApp(props) {
|
|
|
1542
1749
|
].map((label, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === configProviderIndex ? 'claude' : 'inactive', children: index === configProviderIndex ? '▸ ' : ' ' }), _jsx(Text, { color: index === configProviderIndex ? 'claude' : 'text', children: label })] }) }, label))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc close" })] })), configCostModePickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "suggestion", children: "Cost mode" }), ['normal', 'economy'].map((label, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === configCostModeIndex ? 'claude' : 'inactive', children: index === configCostModeIndex ? '▸ ' : ' ' }), _jsx(Text, { color: index === configCostModeIndex ? 'claude' : 'text', children: label })] }) }, label))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc close" })] })), modePickerOpen && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "suggestion", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "suggestion", children: "Select mode" }), filteredModeOptions.length === 0 ? (_jsx(Text, { color: "inactive", children: "No modes matched current filter." })) : (filteredModeOptions.map((mode, index) => (_jsx(React.Fragment, { children: _jsxs(Text, { children: [_jsx(Text, { color: index === selectedModeIndex ? 'claude' : 'inactive', children: index === selectedModeIndex ? '▸ ' : ' ' }), _jsx(Text, { color: index === selectedModeIndex ? 'claude' : 'text', children: mode.id }), _jsxs(Text, { color: "inactive", children: [" \u2014 ", mode.description] })] }) }, mode.id)))), _jsx(Text, { color: "subtle", children: "\u2191/\u2193 navigate \u2022 Enter apply \u2022 Esc close" })] }))] }));
|
|
1543
1750
|
}
|
|
1544
1751
|
export async function launchClaudeStyleChat(options) {
|
|
1752
|
+
if (options.forceLocalRuntime) {
|
|
1753
|
+
process.env.XIBECODE_SANDBOX_MODE = 'local';
|
|
1754
|
+
delete process.env.XIBECODE_SANDBOX_SESSION_ID;
|
|
1755
|
+
delete process.env.XIBECODE_SANDBOX_SKIP_SYNC;
|
|
1756
|
+
}
|
|
1545
1757
|
const config = new ConfigManager(options.profile);
|
|
1546
1758
|
const apiKey = options.apiKey || config.getApiKey() || '';
|
|
1547
1759
|
const needsFirstRunSetup = !apiKey;
|
|
@@ -2079,6 +2291,7 @@ export async function launchClaudeStyleChat(options) {
|
|
|
2079
2291
|
const tokens = argsRaw ? argsRaw.split(/\s+/).filter(Boolean) : [];
|
|
2080
2292
|
let apply = false;
|
|
2081
2293
|
let force = false;
|
|
2294
|
+
let full = false;
|
|
2082
2295
|
let output;
|
|
2083
2296
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
2084
2297
|
const token = tokens[i];
|
|
@@ -2086,6 +2299,10 @@ export async function launchClaudeStyleChat(options) {
|
|
|
2086
2299
|
apply = true;
|
|
2087
2300
|
continue;
|
|
2088
2301
|
}
|
|
2302
|
+
if (token === '--full') {
|
|
2303
|
+
full = true;
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2089
2306
|
if (token === '--force') {
|
|
2090
2307
|
force = true;
|
|
2091
2308
|
continue;
|
|
@@ -2093,18 +2310,19 @@ export async function launchClaudeStyleChat(options) {
|
|
|
2093
2310
|
if (token === '--output') {
|
|
2094
2311
|
const next = tokens[i + 1];
|
|
2095
2312
|
if (!next || next.startsWith('--')) {
|
|
2096
|
-
throw new Error('Usage: /cpull [--apply] [--force] [--output <path>]');
|
|
2313
|
+
throw new Error('Usage: /cpull [--apply] [--full] [--force] [--output <path>]');
|
|
2097
2314
|
}
|
|
2098
2315
|
output = next;
|
|
2099
2316
|
i += 1;
|
|
2100
2317
|
continue;
|
|
2101
2318
|
}
|
|
2102
|
-
throw new Error(`Unknown /cpull option "${token}". Usage: /cpull [--apply] [--force] [--output <path>]`);
|
|
2319
|
+
throw new Error(`Unknown /cpull option "${token}". Usage: /cpull [--apply] [--full] [--force] [--output <path>]`);
|
|
2103
2320
|
}
|
|
2104
2321
|
await cloudPullCommand({
|
|
2105
2322
|
profile: options.profile,
|
|
2106
2323
|
session: sessionId,
|
|
2107
2324
|
apply,
|
|
2325
|
+
full,
|
|
2108
2326
|
force,
|
|
2109
2327
|
output,
|
|
2110
2328
|
onStatus: (text) => pushLine({ type: 'info', text }),
|