bingocode 1.1.132 → 1.1.134
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/.claude/settings.local.json +3 -1
- package/package.json +1 -1
- package/src/manager/CliMenuManager.tsx +190 -52
- package/src/utils/config.ts +1 -1
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
4
|
"Read(//c/Users/qi.lin/.claude/**)",
|
|
5
|
-
"Bash(dir \"F:\\\\Leanchy\\\\VirtuosAgent\\\\BingoCode\\\\src\\\\server\\\\proxy\")"
|
|
5
|
+
"Bash(dir \"F:\\\\Leanchy\\\\VirtuosAgent\\\\BingoCode\\\\src\\\\server\\\\proxy\")",
|
|
6
|
+
"Bash(ls src/**/)",
|
|
7
|
+
"Bash(cat config/*.json)"
|
|
6
8
|
]
|
|
7
9
|
}
|
|
8
10
|
}
|
package/package.json
CHANGED
|
@@ -78,34 +78,46 @@ const TOP_H_COMPACT = Number(process.env.CLI_TOP_H_COMPACT || 3);
|
|
|
78
78
|
// Bottom bar height
|
|
79
79
|
const BOTTOM_H = Number(process.env.CLI_BOTTOM_H || 3);
|
|
80
80
|
|
|
81
|
+
const LANG_OPTIONS = [
|
|
82
|
+
{ label: 'English', value: 'en' as const },
|
|
83
|
+
{ label: '中文', value: 'zh' as const },
|
|
84
|
+
{ label: '日本語', value: 'ja' as const },
|
|
85
|
+
];
|
|
86
|
+
|
|
81
87
|
const i18nMap = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
about: 'Bingo CLI - Version Info & About',
|
|
92
|
-
aboutContent: [
|
|
93
|
-
'Bingo is an AI assistant terminal client.',
|
|
94
|
-
'1. API Config: Press "P" or select "API Config" to set up your keys.',
|
|
95
|
-
'2. Model Slots: Configure specific models in the Provider panel.',
|
|
96
|
-
'3. Background Service: Bingo runs a local server to manage sessions.',
|
|
97
|
-
'4. Start Chat: Run `bingocode` or `claude` in any terminal to start.',
|
|
98
|
-
].join('\n'),
|
|
99
|
-
aboutFooter: 'Author: leanchy (leanchy07@outlook.com) · github.com/leanchy/bingo-claude-code-offline-installer',
|
|
100
|
-
mark: '→ Mark Session',
|
|
101
|
-
unmark: '→ Unmark Session',
|
|
102
|
-
tipsSimple: 'L Lang | ESC Back | ←→ Menu | ↩ Enter | ? Help',
|
|
103
|
-
noData: 'No data',
|
|
104
|
-
emptyHistory: 'Nothing here yet. Start a new session?',
|
|
105
|
-
deleting: 'Delete this session? (Irreversible)',
|
|
106
|
-
historyHint: 'Enter to open · j next · k first · q back',
|
|
107
|
-
helpTitle: 'Shortcuts',
|
|
88
|
+
zh: {
|
|
89
|
+
menu: {
|
|
90
|
+
newSession: '新建会话',
|
|
91
|
+
history: '会话历史',
|
|
92
|
+
provider: 'API 配置',
|
|
93
|
+
settings: '设置',
|
|
94
|
+
about: '关于',
|
|
95
|
+
exit: '退出',
|
|
108
96
|
},
|
|
97
|
+
about: 'Bingo CLI 终端 - 版本信息与关于',
|
|
98
|
+
aboutContent: [
|
|
99
|
+
'Bingo 是一款 AI 助手终端客户端。',
|
|
100
|
+
'1. API 配置:按 "P" 或选择「API 配置」来设置你的密钥。',
|
|
101
|
+
'2. 模型槽:在 Provider 面板中配置各模型。',
|
|
102
|
+
'3. 后台服务:Bingo 会运行一个本地服务器来管理会话。',
|
|
103
|
+
'4. 开始聊天:在任意终端中运行 `bingocode` 或 `claude`。',
|
|
104
|
+
].join('\n'),
|
|
105
|
+
aboutFooter: '作者: leanchy (leanchy07@outlook.com) · github.com/leanchy/claude-code-bingo',
|
|
106
|
+
mark: '→ 标记会话',
|
|
107
|
+
unmark: '→ 取消标记',
|
|
108
|
+
tipsSimple: 'L 语言 | ESC 返回 | ←→ 菜单 | ↩ 确认 | ? 帮助',
|
|
109
|
+
noData: '暂无数据',
|
|
110
|
+
emptyHistory: '还没有会话,要新建一个吗?',
|
|
111
|
+
deleting: '确定删除此会话?(不可恢复)',
|
|
112
|
+
historyHint: '↩ 打开 · j 下一页 · k 首页 · q 返回',
|
|
113
|
+
helpTitle: '快捷键',
|
|
114
|
+
// Settings page
|
|
115
|
+
settingsTitle: '设置',
|
|
116
|
+
langLabel: '语言',
|
|
117
|
+
langPickerTitle: '选择语言',
|
|
118
|
+
settingsHint: '↑/k ↓/j 滚动 · ↩ 编辑 · ESC 返回',
|
|
119
|
+
langOptions: LANG_OPTIONS,
|
|
120
|
+
},
|
|
109
121
|
en: {
|
|
110
122
|
menu: {
|
|
111
123
|
newSession: 'New Session',
|
|
@@ -123,7 +135,7 @@ const i18nMap = {
|
|
|
123
135
|
'3. Background Service: Bingo runs a local server to manage sessions.',
|
|
124
136
|
'4. Start Chat: Run `bingocode` or `claude` in any terminal to start.',
|
|
125
137
|
].join('\n'),
|
|
126
|
-
aboutFooter: 'Author: leanchy (leanchy07@outlook.com) · github.com/leanchy/
|
|
138
|
+
aboutFooter: 'Author: leanchy (leanchy07@outlook.com) · github.com/leanchy/claude-code-bingo',
|
|
127
139
|
mark: '→ Mark Session',
|
|
128
140
|
unmark: '→ Unmark Session',
|
|
129
141
|
tipsSimple: 'L Lang | ESC Back | ←→ Menu | ↩ Enter | ? Help',
|
|
@@ -132,7 +144,46 @@ const i18nMap = {
|
|
|
132
144
|
deleting: 'Delete this session? (Irreversible)',
|
|
133
145
|
historyHint: 'Enter to open · j next · k first · q back',
|
|
134
146
|
helpTitle: 'Shortcuts',
|
|
135
|
-
|
|
147
|
+
// Settings page
|
|
148
|
+
settingsTitle: 'Settings',
|
|
149
|
+
langLabel: 'Language',
|
|
150
|
+
langPickerTitle: 'Select Language',
|
|
151
|
+
settingsHint: '↑/k ↓/j scroll · ↩ edit · ESC back',
|
|
152
|
+
langOptions: LANG_OPTIONS,
|
|
153
|
+
},
|
|
154
|
+
ja: {
|
|
155
|
+
menu: {
|
|
156
|
+
newSession: '新規セッション',
|
|
157
|
+
history: 'セッション履歴',
|
|
158
|
+
provider: 'API設定',
|
|
159
|
+
settings: '設定',
|
|
160
|
+
about: 'について',
|
|
161
|
+
exit: '終了',
|
|
162
|
+
},
|
|
163
|
+
about: 'Bingo CLI ターミナル - バージョン情報',
|
|
164
|
+
aboutContent: [
|
|
165
|
+
'BingoはAIアシスタントのターミナルクライアントです。',
|
|
166
|
+
'1. API設定: "P"キーまたは「API設定」を選択してキーを設定。',
|
|
167
|
+
'2. モデルスロット: Providerパネルで各モデルを設定。',
|
|
168
|
+
'3. バックグラウンドサービス: セッション管理用ローカルサーバーを起動。',
|
|
169
|
+
'4. チャット開始: 任意のターミナルで `bingocode` または `claude` を実行。',
|
|
170
|
+
].join('\n'),
|
|
171
|
+
aboutFooter: '作者: leanchy (leanchy07@outlook.com) · github.com/leanchy/claude-code-bingo',
|
|
172
|
+
mark: '→ セッションをマーク',
|
|
173
|
+
unmark: '→ マークを解除',
|
|
174
|
+
tipsSimple: 'L 言語 | ESC 戻る | ←→ メニュー | ↩ 決定 | ? ヘルプ',
|
|
175
|
+
noData: 'データなし',
|
|
176
|
+
emptyHistory: 'まだセッションがありません。新規作成しますか?',
|
|
177
|
+
deleting: 'このセッションを削除しますか?(元に戻せません)',
|
|
178
|
+
historyHint: '↩ 開く · j 次へ · k 最初へ · q 戻る',
|
|
179
|
+
helpTitle: 'ショートカット',
|
|
180
|
+
// Settings page
|
|
181
|
+
settingsTitle: '設定',
|
|
182
|
+
langLabel: '言語',
|
|
183
|
+
langPickerTitle: '言語を選択',
|
|
184
|
+
settingsHint: '↑/k ↓/j スクロール · ↩ 編集 · ESC 戻る',
|
|
185
|
+
langOptions: LANG_OPTIONS,
|
|
186
|
+
},
|
|
136
187
|
};
|
|
137
188
|
|
|
138
189
|
const menuKeys = [
|
|
@@ -243,7 +294,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
243
294
|
if (configReady) {
|
|
244
295
|
try {
|
|
245
296
|
const cfg = getGlobalConfig();
|
|
246
|
-
if (cfg.language && (cfg.language === 'en' || cfg.language === 'zh')) {
|
|
297
|
+
if (cfg.language && (cfg.language === 'en' || cfg.language === 'zh' || cfg.language === 'ja')) {
|
|
247
298
|
setLang(cfg.language as Lang);
|
|
248
299
|
}
|
|
249
300
|
} catch (e) {
|
|
@@ -294,6 +345,8 @@ export const CliMenuManager: React.FC = () => {
|
|
|
294
345
|
const [settingData, setSettingData] = useState<any>(null);
|
|
295
346
|
const [loadingSetting, setLoadingSetting] = useState(false);
|
|
296
347
|
const [setErr, setSetErr] = useState<string | null>(null);
|
|
348
|
+
const [settingsStage, setSettingsStage] = useState<'list' | 'langPicker'>('list');
|
|
349
|
+
const [settingsCursor, setSettingsCursor] = useState(0);
|
|
297
350
|
|
|
298
351
|
// Top toolbar state
|
|
299
352
|
const [animEnabled, setAnimEnabled] = useState(true);
|
|
@@ -373,6 +426,8 @@ export const CliMenuManager: React.FC = () => {
|
|
|
373
426
|
}
|
|
374
427
|
if (page !== 'settings') {
|
|
375
428
|
setSettingsOffset(0);
|
|
429
|
+
setSettingsStage('list');
|
|
430
|
+
setSettingsCursor(0);
|
|
376
431
|
}
|
|
377
432
|
// Close help overlay
|
|
378
433
|
setShowHelp(false);
|
|
@@ -515,9 +570,10 @@ export const CliMenuManager: React.FC = () => {
|
|
|
515
570
|
|
|
516
571
|
// Keyboard interactions
|
|
517
572
|
useInput((input, key) => {
|
|
518
|
-
// Language toggle
|
|
573
|
+
// Language toggle (en → zh → ja → en)
|
|
519
574
|
if (input === 'l' || input === 'L') {
|
|
520
|
-
const
|
|
575
|
+
const langOrder: Lang[] = ['en', 'zh', 'ja'];
|
|
576
|
+
const nextLang = langOrder[(langOrder.indexOf(lang) + 1) % langOrder.length];
|
|
521
577
|
setLang(nextLang);
|
|
522
578
|
try {
|
|
523
579
|
const cfg = getGlobalConfig();
|
|
@@ -563,6 +619,10 @@ export const CliMenuManager: React.FC = () => {
|
|
|
563
619
|
if (key.escape) {
|
|
564
620
|
if (showHelp) { setShowHelp(false); return; }
|
|
565
621
|
if (page === 'provider') return; // Handled internally
|
|
622
|
+
// Settings: langPicker → back to list; list → back to main menu
|
|
623
|
+
if (page === 'settings') {
|
|
624
|
+
if (settingsStage === 'langPicker') { setSettingsStage('list'); return; }
|
|
625
|
+
}
|
|
566
626
|
setPage(null);
|
|
567
627
|
setHistoryMenuStage('list');
|
|
568
628
|
setSelectedHistory(null);
|
|
@@ -670,18 +730,30 @@ export const CliMenuManager: React.FC = () => {
|
|
|
670
730
|
}
|
|
671
731
|
}
|
|
672
732
|
|
|
673
|
-
// Settings
|
|
674
|
-
if (!showHelp && page === 'settings'
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
733
|
+
// Settings interactions
|
|
734
|
+
if (!showHelp && page === 'settings') {
|
|
735
|
+
if (settingsStage === 'list') {
|
|
736
|
+
// +1 for the fixed Language row prepended before settingData entries
|
|
737
|
+
const totalRows = 1 + (settingData && typeof settingData === 'object' ? Object.keys(settingData).length : 0);
|
|
738
|
+
const visible = Math.max(1, MID_H - 2);
|
|
739
|
+
if (key.downArrow || input === 'j') {
|
|
740
|
+
setSettingsCursor(c => Math.min(totalRows - 1, c + 1));
|
|
741
|
+
setSettingsOffset(o => Math.min(Math.max(0, totalRows - visible), o + 1));
|
|
742
|
+
}
|
|
743
|
+
if (key.upArrow || input === 'k') {
|
|
744
|
+
setSettingsCursor(c => Math.max(0, c - 1));
|
|
745
|
+
setSettingsOffset(o => Math.max(0, o - 1));
|
|
746
|
+
}
|
|
747
|
+
if (key.return) {
|
|
748
|
+
// Row 0 is the interactive Language row
|
|
749
|
+
if (settingsCursor === 0) {
|
|
750
|
+
setSettingsStage('langPicker');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
682
753
|
}
|
|
754
|
+
// langPicker stage: ESC handled above; selection via SelectInput onSelect
|
|
683
755
|
}
|
|
684
|
-
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme]);
|
|
756
|
+
}, [menuItems, page, historyMenuStage, historyList, historyHasMore, navIndex, sessionMessages, settingData, MID_H, MSGS_PAGE_SIZE, showHelp, theme, settingsStage, settingsCursor]);
|
|
685
757
|
|
|
686
758
|
function cleanText(text: string): string {
|
|
687
759
|
return String(text ?? '').replace(/[\n\r]+/g, ' ').replace(/\u001b\[[0-9;]*m/g, '').trim();
|
|
@@ -915,7 +987,7 @@ export const CliMenuManager: React.FC = () => {
|
|
|
915
987
|
<Text color="cyan">R</Text><Text> Quick Resume</Text>
|
|
916
988
|
<Text color="cyan">P</Text><Text> Open Provider Config</Text>
|
|
917
989
|
<Text color="cyan">G</Text><Text> Toggle Theme (light/dark/highContrast)</Text>
|
|
918
|
-
<Text color="cyan">L</Text><Text> Toggle Language (en
|
|
990
|
+
<Text color="cyan">L</Text><Text> Toggle Language (en → zh → ja)</Text>
|
|
919
991
|
<Text color="cyan">O</Text><Text> Toggle Top Animation</Text>
|
|
920
992
|
<Text color="cyan">T</Text><Text> Toggle Top Tips</Text>
|
|
921
993
|
<Text color="cyan">?</Text><Text> Toggle Help</Text>
|
|
@@ -1130,18 +1202,84 @@ export const CliMenuManager: React.FC = () => {
|
|
|
1130
1202
|
if (page === 'settings') {
|
|
1131
1203
|
if (loadingSetting) return <StateDisplay type="loading" message="Loading settings..." />;
|
|
1132
1204
|
if (setErr) return <StateDisplay type="error" message={setErr} />;
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1205
|
+
|
|
1206
|
+
const tS = i18nMap[lang];
|
|
1207
|
+
const currentLangLabel = LANG_OPTIONS.find(o => o.value === lang)?.label ?? lang;
|
|
1208
|
+
|
|
1209
|
+
// --- langPicker sub-menu ---
|
|
1210
|
+
if (settingsStage === 'langPicker') {
|
|
1211
|
+
return (
|
|
1212
|
+
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1213
|
+
<Box paddingX={1} marginBottom={1}>
|
|
1214
|
+
<Text color="magenta" bold>{tS.langPickerTitle}</Text>
|
|
1215
|
+
</Box>
|
|
1216
|
+
<Box paddingX={2} flexGrow={1} flexDirection="column">
|
|
1217
|
+
<SelectInput
|
|
1218
|
+
items={tS.langOptions}
|
|
1219
|
+
initialIndex={tS.langOptions.findIndex(o => o.value === lang)}
|
|
1220
|
+
onSelect={(item: { label: string; value: Lang }) => {
|
|
1221
|
+
setLang(item.value);
|
|
1222
|
+
try {
|
|
1223
|
+
const cfg = getGlobalConfig();
|
|
1224
|
+
cfg.language = item.value;
|
|
1225
|
+
saveGlobalConfig(cfg);
|
|
1226
|
+
} catch {}
|
|
1227
|
+
setSettingsStage('list');
|
|
1228
|
+
}}
|
|
1229
|
+
/>
|
|
1230
|
+
</Box>
|
|
1231
|
+
<Box paddingX={1}>
|
|
1232
|
+
<Hint>↩ confirm · ESC back</Hint>
|
|
1233
|
+
</Box>
|
|
1234
|
+
</Box>
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// --- settings list ---
|
|
1239
|
+
type SettingRow = { key: string; label: string; value: string; interactive: boolean };
|
|
1240
|
+
const fixedRows: SettingRow[] = [
|
|
1241
|
+
{ key: '__lang', label: tS.langLabel, value: currentLangLabel, interactive: true },
|
|
1242
|
+
];
|
|
1243
|
+
const dataEntries = settingData && typeof settingData === 'object' ? Object.entries(settingData) : [];
|
|
1244
|
+
const dataRows: SettingRow[] = dataEntries.map(([k, v]) => ({
|
|
1245
|
+
key: k,
|
|
1246
|
+
label: k,
|
|
1247
|
+
value: typeof v === 'object' ? JSON.stringify(v) : String(v),
|
|
1248
|
+
interactive: false,
|
|
1249
|
+
}));
|
|
1250
|
+
const allRows: SettingRow[] = [...fixedRows, ...dataRows];
|
|
1251
|
+
const visible = Math.max(1, MID_H - 2);
|
|
1252
|
+
const start = Math.min(settingsOffset, Math.max(0, allRows.length - visible));
|
|
1253
|
+
const sliced = allRows.slice(start, start + visible);
|
|
1254
|
+
|
|
1138
1255
|
return (
|
|
1139
1256
|
<Box width={VIEW_W} height={MID_H} flexDirection="column">
|
|
1140
|
-
<
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1257
|
+
<Box flexDirection="row" position="relative" flexGrow={1}>
|
|
1258
|
+
<Box flexDirection="column" flexGrow={1} paddingX={1} overflow="hidden">
|
|
1259
|
+
{sliced.map((row, idx) => {
|
|
1260
|
+
const absIdx = start + idx;
|
|
1261
|
+
const isCursor = absIdx === settingsCursor;
|
|
1262
|
+
const prefix = isCursor ? '>' : ' ';
|
|
1263
|
+
const labelColor = isCursor ? 'cyan' : (row.interactive ? 'white' : 'gray');
|
|
1264
|
+
const valueColor = row.interactive ? 'green' : undefined;
|
|
1265
|
+
return (
|
|
1266
|
+
<Box key={row.key} height={1}>
|
|
1267
|
+
<Text color={labelColor}>
|
|
1268
|
+
{prefix} {row.label}:{' '}
|
|
1269
|
+
<Text color={valueColor ?? (isCursor ? 'white' : 'gray')}>
|
|
1270
|
+
{row.value}
|
|
1271
|
+
{row.interactive ? ' ↩' : ''}
|
|
1272
|
+
</Text>
|
|
1273
|
+
</Text>
|
|
1274
|
+
</Box>
|
|
1275
|
+
);
|
|
1276
|
+
})}
|
|
1277
|
+
</Box>
|
|
1278
|
+
<ScrollBar total={allRows.length} offset={start} height={visible - 1} />
|
|
1279
|
+
</Box>
|
|
1280
|
+
<Box paddingX={1}>
|
|
1281
|
+
<Hint>{tS.settingsHint} · {start + 1}-{Math.min(start + visible, allRows.length)}/{allRows.length}</Hint>
|
|
1282
|
+
</Box>
|
|
1145
1283
|
</Box>
|
|
1146
1284
|
);
|
|
1147
1285
|
}
|
package/src/utils/config.ts
CHANGED
|
@@ -242,7 +242,7 @@ export type GlobalConfig = {
|
|
|
242
242
|
hasUsedBackgroundTask?: boolean // Whether the user has backgrounded a task (Ctrl+B)
|
|
243
243
|
queuedCommandUpHintCount?: number // Counter for how many times the user has seen the queued command up hint
|
|
244
244
|
diffTool?: DiffTool // Which tool to use for displaying diffs (terminal or vscode)
|
|
245
|
-
language: 'en' | 'zh' // User's preferred language for CLI menus
|
|
245
|
+
language: 'en' | 'zh' | 'ja' // User's preferred language for CLI menus
|
|
246
246
|
|
|
247
247
|
// Terminal setup state tracking
|
|
248
248
|
iterm2SetupInProgress?: boolean
|