kob-cli 1.0.22 → 1.0.24

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ui/code-tui.tsx +165 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kob-cli",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "KOB CLI — AI-powered code generation tool. Built by Kob AI, made in Thailand.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1248,10 +1248,148 @@ function InputBox({ onSubmit, mode, onModeChange, visionSupported, placeholder,
1248
1248
  );
1249
1249
  }
1250
1250
 
1251
+ // ============================================================================
1252
+ // HELP SCREEN — full-screen overlay shown by /help
1253
+ // A self-contained beginner's manual. Closes on Esc, Enter, or q.
1254
+ // ============================================================================
1255
+ function HelpScreen({ onClose }: { onClose: () => void }) {
1256
+ useInput((input, key) => {
1257
+ if (key.escape || key.return || input === 'q') {
1258
+ onClose();
1259
+ }
1260
+ }, { isActive: true });
1261
+
1262
+ return (
1263
+ <Box
1264
+ flexDirection="column"
1265
+ borderStyle="round"
1266
+ borderColor={c.brand}
1267
+ paddingX={2}
1268
+ paddingY={1}
1269
+ marginTop={1}
1270
+ >
1271
+ {/* Header */}
1272
+ <Box>
1273
+ <Text color={c.brand} bold>? /help</Text>
1274
+ <Text color={c.textDim}> </Text>
1275
+ <Text color={c.text} bold>KOB CLI · beginner's guide</Text>
1276
+ <Box flexGrow={1} />
1277
+ <Text color={c.textDim}>Esc / Enter / q to close</Text>
1278
+ </Box>
1279
+
1280
+ {/* Getting started */}
1281
+ <Box marginTop={1} flexDirection="column">
1282
+ <Text color={c.accent} bold>◆ Getting started</Text>
1283
+ <Box marginLeft={2} flexDirection="column">
1284
+ <Box>
1285
+ <Text color={c.brand}>/config</Text>
1286
+ <Text color={c.textMuted}> set API key, model, base URL → </Text>
1287
+ <Text color={c.text}>.env</Text>
1288
+ </Box>
1289
+ <Box>
1290
+ <Text color={c.brand}>/models</Text>
1291
+ <Text color={c.textMuted}> pick a model from the catalog</Text>
1292
+ </Box>
1293
+ <Box>
1294
+ <Text color={c.brand}>/help</Text>
1295
+ <Text color={c.textMuted}> show this guide</Text>
1296
+ </Box>
1297
+ </Box>
1298
+ </Box>
1299
+
1300
+ {/* Modes */}
1301
+ <Box marginTop={1} flexDirection="column">
1302
+ <Text color={c.accent} bold>◆ Modes (press </Text>
1303
+ <Text color={c.pink}>Tab</Text>
1304
+ <Text color={c.accent} bold> to cycle)</Text>
1305
+ <Box marginLeft={2} flexDirection="column">
1306
+ {MODES.map((m) => (
1307
+ <Box key={m.key}>
1308
+ <Text color={m.color} bold>{m.icon} {m.label}</Text>
1309
+ <Text color={c.textDim}> [{m.shortcut}] </Text>
1310
+ <Text color={c.textMuted}>{m.description}</Text>
1311
+ </Box>
1312
+ ))}
1313
+ </Box>
1314
+ </Box>
1315
+
1316
+ {/* While typing */}
1317
+ <Box marginTop={1} flexDirection="column">
1318
+ <Text color={c.accent} bold>◆ While typing</Text>
1319
+ <Box marginLeft={2} flexDirection="column">
1320
+ <Box>
1321
+ <Text color={c.yellow}>Esc</Text>
1322
+ <Text color={c.textMuted}> clear input</Text>
1323
+ </Box>
1324
+ <Box>
1325
+ <Text color={c.green}>⏎</Text>
1326
+ <Text color={c.textMuted}> submit</Text>
1327
+ </Box>
1328
+ <Box>
1329
+ <Text color={c.pink}>↑</Text>
1330
+ <Text color={c.textMuted}> </Text>
1331
+ <Text color={c.pink}>↓</Text>
1332
+ <Text color={c.textMuted}> scroll history (line by line)</Text>
1333
+ </Box>
1334
+ <Box>
1335
+ <Text color={c.pink}>PgUp</Text>
1336
+ <Text color={c.textMuted}> </Text>
1337
+ <Text color={c.pink}>PgDn</Text>
1338
+ <Text color={c.textMuted}> scroll history (page)</Text>
1339
+ </Box>
1340
+ <Box>
1341
+ <Text color={c.pink}>g</Text>
1342
+ <Text color={c.textMuted}> </Text>
1343
+ <Text color={c.pink}>G</Text>
1344
+ <Text color={c.textMuted}> jump to oldest / latest</Text>
1345
+ </Box>
1346
+ </Box>
1347
+ </Box>
1348
+
1349
+ {/* Session */}
1350
+ <Box marginTop={1} flexDirection="column">
1351
+ <Text color={c.accent} bold>◆ Session</Text>
1352
+ <Box marginLeft={2} flexDirection="column">
1353
+ <Box>
1354
+ <Text color={c.brand}>/clear</Text>
1355
+ <Text color={c.textMuted}> forget chat history</Text>
1356
+ </Box>
1357
+ <Box>
1358
+ <Text color={c.brand}>/reset</Text>
1359
+ <Text color={c.textMuted}> reset model to .env default</Text>
1360
+ </Box>
1361
+ <Box>
1362
+ <Text color={c.brand}>/exit</Text>
1363
+ <Text color={c.textMuted}> quit KOB CLI</Text>
1364
+ </Box>
1365
+ </Box>
1366
+ </Box>
1367
+
1368
+ {/* Tips */}
1369
+ <Box marginTop={1} flexDirection="column">
1370
+ <Text color={c.yellow} bold>◆ First time?</Text>
1371
+ <Box marginLeft={2} flexDirection="column">
1372
+ <Text color={c.textMuted}>1. Run </Text>
1373
+ <Text color={c.brand}>/config</Text>
1374
+ <Text color={c.textMuted}> to paste your KOB API key and pick a model.</Text>
1375
+ <Text color={c.textMuted}>2. Type a task, pick a mode with </Text>
1376
+ <Text color={c.pink}>Tab</Text>
1377
+ <Text color={c.textMuted}>, press </Text>
1378
+ <Text color={c.green}>⏎</Text>
1379
+ <Text color={c.textMuted}>.</Text>
1380
+ <Text color={c.textMuted}>3. In </Text>
1381
+ <Text color={c.green}>Code</Text>
1382
+ <Text color={c.textMuted}> mode the agent can write files and run shell commands.</Text>
1383
+ </Box>
1384
+ </Box>
1385
+ </Box>
1386
+ );
1387
+ }
1388
+
1251
1389
  // ============================================================================
1252
1390
  // BOTTOM BAR
1253
1391
  // ============================================================================
1254
- function BottomBar({ phase, mode }: { phase: Phase; mode: Mode }) {
1392
+ function BottomBar({ phase, mode, isFirstStart }: { phase: Phase; mode: Mode; isFirstStart: boolean }) {
1255
1393
  const modeInfo = getMode(mode);
1256
1394
  return (
1257
1395
  <Box
@@ -1261,7 +1399,7 @@ function BottomBar({ phase, mode }: { phase: Phase; mode: Mode }) {
1261
1399
  paddingX={1}
1262
1400
  justifyContent="space-between"
1263
1401
  >
1264
- <Box>
1402
+ <Box flexWrap="wrap">
1265
1403
  <Text color={c.green}>⏎ </Text>
1266
1404
  <Text color={c.textDim}>submit</Text>
1267
1405
  <Text color={c.borderDim}> · </Text>
@@ -1274,6 +1412,12 @@ function BottomBar({ phase, mode }: { phase: Phase; mode: Mode }) {
1274
1412
  <Text color={c.brand}>/models</Text>
1275
1413
  <Text color={c.textDim}> switch</Text>
1276
1414
  <Text color={c.borderDim}> · </Text>
1415
+ <Text color={c.accent}>/config</Text>
1416
+ <Text color={c.textDim}> {isFirstStart ? 'for first start' : 'edit'}</Text>
1417
+ <Text color={c.borderDim}> · </Text>
1418
+ <Text color={c.accent}>/help</Text>
1419
+ <Text color={c.textDim}> guide</Text>
1420
+ <Text color={c.borderDim}> · </Text>
1277
1421
  <Text color={c.accent}>Ctrl+C</Text>
1278
1422
  <Text color={c.textDim}> quit</Text>
1279
1423
  </Box>
@@ -1308,12 +1452,26 @@ function CodeEngine() {
1308
1452
  const [model, setModel] = useState<string>(formatV2Model('DeepSeek', initialConfig.modelId));
1309
1453
  const [palette, setPalette] = useState<null | 'models'>(null);
1310
1454
  const [configOpen, setConfigOpen] = useState<boolean>(false);
1455
+ const [helpOpen, setHelpOpen] = useState<boolean>(false);
1311
1456
  const [banner, setBanner] = useState<string | null>(null);
1312
1457
  const messagesRef = useRef<{ role: string; content: string }[]>([]);
1313
1458
  const modeRef = useRef<Mode>(mode);
1314
1459
  const exchangesLenRef = useRef<number>(0);
1315
1460
  const configRef = useRef(initialConfig);
1316
1461
 
1462
+ // "First start" = no .env file at the project root. The user is running
1463
+ // KOB CLI for the very first time (or in a fresh clone), so we surface
1464
+ // the /config hint more prominently.
1465
+ const isFirstStart = (() => {
1466
+ try {
1467
+ const fs = require('fs') as typeof import('fs');
1468
+ const path = require('path') as typeof import('path');
1469
+ return !fs.existsSync(path.join(process.cwd(), '.env'));
1470
+ } catch {
1471
+ return false;
1472
+ }
1473
+ })();
1474
+
1317
1475
  const showBanner = useCallback((msg: string, ms = 2200) => {
1318
1476
  setBanner(msg);
1319
1477
  setTimeout(() => setBanner((cur) => (cur === msg ? null : cur)), ms);
@@ -1420,7 +1578,7 @@ function CodeEngine() {
1420
1578
  return true;
1421
1579
  case 'help':
1422
1580
  case '?':
1423
- showBanner('◆ /ask /plan /code /clear /reset /models /config /help /exit');
1581
+ setHelpOpen(true);
1424
1582
  return true;
1425
1583
  case 'exit':
1426
1584
  case 'quit':
@@ -1578,6 +1736,8 @@ function CodeEngine() {
1578
1736
  }} />
1579
1737
  )}
1580
1738
 
1739
+ {helpOpen && <HelpScreen onClose={() => setHelpOpen(false)} />}
1740
+
1581
1741
  {phase === 'generating' ? (
1582
1742
  <GeneratingPanel messages={getMode(mode).statusMessages} elapsed={now - startMs} />
1583
1743
  ) : (
@@ -1586,11 +1746,11 @@ function CodeEngine() {
1586
1746
  mode={mode}
1587
1747
  onModeChange={setMode}
1588
1748
  visionSupported={modelSupportsVision(model)}
1589
- isActive={palette === null && !configOpen && phase === 'input'}
1749
+ isActive={palette === null && !configOpen && !helpOpen && phase === 'input'}
1590
1750
  />
1591
1751
  )}
1592
1752
 
1593
- <BottomBar phase={phase} mode={mode} />
1753
+ <BottomBar phase={phase} mode={mode} isFirstStart={isFirstStart} />
1594
1754
  </Box>
1595
1755
  );
1596
1756
  }