miantiao.me 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # miantiao.me
2
2
 
3
- 在终端浏览面条的文章、项目和标签,也可以直接向 AI 助手提问。界面使用 assistant-ui Ink,与 [miantiao.me](https://miantiao.me) 的主题、提示符和命令保持一致。
3
+ 在终端浏览面条的文章、项目和标签,也可以直接向 AI 助手提问。界面使用 assistant-ui Ink:固定窗口、底部输入框和独立滚动的内容区,与 [miantiao.me](https://miantiao.me) 的主题、欢迎页和会话块保持一致。
4
4
 
5
5
  ```sh
6
6
  npx miantiao.me
@@ -24,7 +24,7 @@ npx miantiao.me
24
24
  | `/clear` | 清空 AI 对话上下文 |
25
25
  | `/quit` | 退出 |
26
26
 
27
- Tab 在输入框和列表之间切换;↑↓ 选择条目,Enter 打开;Esc 取消加载或停止回复,Ctrl+C 退出。编号对应当前页面的结果列表,可用于 `/read 1` 或 `/open 1`。已完成的内容保存在终端滚动记录中。`/clear` 重置 AI 会话,终端已有的滚动记录仍可查看。
27
+ PgUp/PgDn 滚动正文,输入框始终固定在底部。Tab 在输入框和内容区之间切换;列表中用 ↑↓ 选择、Enter 打开,文章中用 ↑↓、Home、End 滚动。Esc 取消加载或停止回复,Ctrl+C 退出。编号对应当前页面的结果列表,可用于 `/read 1` 或 `/open 1`。`/clear` 清空 AI 会话。退出后恢复原终端画面。
28
28
 
29
29
  支持 GrokNight、GrokDay、TokyoNight、RosePine Moon、Oscura Midnight 和 `auto`。自动模式参考终端的 `COLORFGBG`;未提供时使用 GrokNight。可用 `/theme light` 或 `/theme dark` 显式选择。
30
30
 
@@ -4,8 +4,8 @@ import {
4
4
 
5
5
  // packages/cli/src/app.tsx
6
6
  import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
7
- import { Box as Box3, render, Text as Text3, useApp as useApp2, useInput as useInput3, useStdout } from "ink";
8
- import { useCallback as useCallback3, useEffect as useEffect4, useState as useState4, useSyncExternalStore } from "react";
7
+ import { Box as Box8, render, Text as Text8, useApp as useApp2, useInput as useInput4, useWindowSize as useWindowSize2 } from "ink";
8
+ import { useEffect as useEffect4, useState as useState5 } from "react";
9
9
 
10
10
  // src/terminal/schema.ts
11
11
  import { z } from "zod";
@@ -52,12 +52,22 @@ var terminalIndexSchema = z.object({
52
52
  tags: z.array(z.object({ name: z.string(), path: z.string(), count: z.number().int().nonnegative() })),
53
53
  searchIndexUrl: z.string()
54
54
  });
55
+ var sectionSchema = z.discriminatedUnion("kind", [
56
+ z.object({
57
+ kind: z.literal("welcome"),
58
+ markdown: z.string(),
59
+ logo: z.string().optional(),
60
+ menu: z.array(linkSchema.extend({ command: z.string() })).optional()
61
+ }),
62
+ z.object({ kind: z.literal("turn"), prompt: z.string(), markdown: z.string() })
63
+ ]);
55
64
  var terminalDocumentSchema = z.object({
56
65
  schemaVersion: z.literal(1),
57
66
  revision: z.string().min(1),
58
67
  path: z.string(),
59
68
  title: z.string(),
60
- markdown: z.string().min(1)
69
+ markdown: z.string().min(1),
70
+ sections: z.array(sectionSchema).optional()
61
71
  });
62
72
 
63
73
  // src/utils/search-core.ts
@@ -276,49 +286,12 @@ var TerminalClient = class {
276
286
  };
277
287
 
278
288
  // packages/cli/src/console.tsx
279
- import {
280
- ComposerPrimitive,
281
- LoadingPrimitive,
282
- ThreadPrimitive as ThreadPrimitive2,
283
- useAui,
284
- useAuiState as useAuiState2
285
- } from "@assistant-ui/react-ink";
286
- import { Box as Box2, Static, Text as Text2, useFocus, useInput } from "ink";
287
- import { useState } from "react";
288
-
289
- // src/agent/commands.ts
290
- var commands = [
291
- { id: "help", description: "\u67E5\u770B\u53EF\u7528\u547D\u4EE4" },
292
- { id: "theme", args: "[\u4E3B\u9898]", description: "\u5207\u6362\u4E3B\u9898\uFF0C\u4E0D\u5E26\u53C2\u6570\u65F6\u6253\u5F00\u9009\u62E9\u5668" },
293
- { id: "clear", description: "\u6E05\u7A7A\u5F53\u524D\u5BF9\u8BDD" },
294
- { id: "home", description: "\u6253\u5F00\u9996\u9875" },
295
- { id: "blog", description: "\u6D4F\u89C8\u5168\u90E8\u6587\u7AE0" },
296
- { id: "projects", description: "\u67E5\u770B\u6298\u817E\u7684\u9879\u76EE" },
297
- { id: "tags", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
298
- { id: "search", args: "[\u5173\u952E\u8BCD]", description: "\u641C\u7D22\u6587\u7AE0" },
299
- { id: "about", description: "\u5173\u4E8E\u672C\u7AD9" },
300
- { id: "rss", description: "\u8BA2\u9605 RSS" }
301
- ];
302
- var pageCommands = {
303
- home: "/",
304
- blog: "/blog/",
305
- projects: "/projects/",
306
- tags: "/tags/",
307
- about: "/about/"
308
- };
309
- function parseCommand(input) {
310
- const match = input.trim().match(/^\/(\S+)(?:\s+([\s\S]*))?$/);
311
- if (!match)
312
- return null;
313
- return { name: match[1].toLowerCase(), args: (match[2] ?? "").trim() };
314
- }
315
- function getSearchPath(query) {
316
- return query ? `/search/?q=${encodeURIComponent(query)}` : "/search/";
317
- }
289
+ import { ThreadPrimitive as ThreadPrimitive2 } from "@assistant-ui/react-ink";
290
+ import { useRef as useRef4 } from "react";
318
291
 
319
292
  // packages/cli/src/chat.tsx
320
293
  import { ThreadPrimitive, useAuiState } from "@assistant-ui/react-ink";
321
- import { Box, Text } from "ink";
294
+ import { Box as Box4, Text as Text4 } from "ink";
322
295
 
323
296
  // packages/cli/src/theme.tsx
324
297
  import { createContext, useContext, useMemo } from "react";
@@ -423,43 +396,225 @@ function useTerminalStyle() {
423
396
  if (!style) throw new Error("TerminalTheme is required");
424
397
  return style;
425
398
  }
426
- function Markdown({ text }) {
399
+ function Markdown({ text, width: availableWidth }) {
427
400
  const { width, markdownTheme, highlighter } = useTerminalStyle();
428
401
  return /* @__PURE__ */ jsx(
429
402
  MarkdownText,
430
403
  {
431
404
  text,
432
- width: Math.max(10, width - 6),
405
+ width: Math.max(10, availableWidth ?? width - 3),
433
406
  theme: markdownTheme,
434
407
  highlighter,
435
408
  codeWrap: true,
409
+ hyperlinks: true,
436
410
  tableTruncate: false
437
411
  }
438
412
  );
439
413
  }
440
414
 
441
- // packages/cli/src/chat.tsx
415
+ // packages/cli/src/blocks.tsx
416
+ import { Box as Box3, Text as Text3, useBoxMetrics as useBoxMetrics3 } from "ink";
417
+ import { useRef as useRef3 } from "react";
418
+
419
+ // packages/cli/src/page-list.tsx
420
+ import { Box as Box2, Text as Text2, useBoxMetrics as useBoxMetrics2, useInput as useInput2 } from "ink";
421
+ import { useEffect, useRef as useRef2, useState as useState2 } from "react";
422
+
423
+ // packages/cli/src/scroll-area.tsx
424
+ import {
425
+ Box,
426
+ Text,
427
+ measureElement,
428
+ useBoxMetrics,
429
+ useFocus,
430
+ useInput
431
+ } from "ink";
432
+ import {
433
+ createContext as createContext2,
434
+ useContext as useContext2,
435
+ useImperativeHandle,
436
+ useMemo as useMemo2,
437
+ useRef,
438
+ useState
439
+ } from "react";
442
440
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
441
+ var ScrollContext = createContext2(null);
442
+ function useScrollArea() {
443
+ const value = useContext2(ScrollContext);
444
+ if (!value) throw new Error("ScrollArea is required");
445
+ return value;
446
+ }
447
+ function ScrollArea({
448
+ children,
449
+ selection,
450
+ ref
451
+ }) {
452
+ const { colors } = useTerminalStyle();
453
+ const { isFocused } = useFocus({ id: "content" });
454
+ const viewport = useRef(null);
455
+ const content = useRef(null);
456
+ const { clientHeight: height } = useBoxMetrics(viewport);
457
+ const { height: contentHeight } = useBoxMetrics(content);
458
+ const [anchor, setAnchor] = useState(0);
459
+ const maximum = Math.max(0, contentHeight - height);
460
+ const offset = anchor === "end" ? maximum : Math.min(anchor, maximum);
461
+ const context = useMemo2(() => ({ focused: isFocused, height }), [isFocused, height]);
462
+ useImperativeHandle(ref, () => ({
463
+ end: () => setAnchor("end"),
464
+ reveal: (element) => {
465
+ if (!viewport.current) return false;
466
+ const item = measureElement(element);
467
+ const top = item.y - measureElement(viewport.current).y;
468
+ const visible = top >= offset && top < offset + height;
469
+ if (top < offset || item.height > height) setAnchor(Math.max(0, top));
470
+ else if (top + item.height > offset + height)
471
+ setAnchor(Math.min(maximum, top + item.height - height));
472
+ return visible;
473
+ }
474
+ }), [offset, height, maximum]);
475
+ useInput((_input, key) => {
476
+ let next = offset;
477
+ if (key.pageUp) next -= Math.max(1, height - 1);
478
+ else if (key.pageDown) next += Math.max(1, height - 1);
479
+ else if (isFocused && !selection && key.upArrow) next--;
480
+ else if (isFocused && !selection && key.downArrow) next++;
481
+ else if (isFocused && !selection && key.home) next = 0;
482
+ else if (isFocused && !selection && key.end) next = maximum;
483
+ else return;
484
+ setAnchor(next >= maximum ? "end" : Math.max(0, next));
485
+ });
486
+ const track = Math.max(0, height);
487
+ const thumbSize = Math.min(track, Math.max(1, Math.round(height * height / Math.max(1, contentHeight))));
488
+ const thumbTop = maximum > 0 ? Math.round(offset / maximum * (height - thumbSize)) : 0;
489
+ const before = Array(thumbTop).fill("\u2502").join("\n");
490
+ const thumb = Array(thumbSize).fill("\u2503").join("\n");
491
+ const after = Array(Math.max(0, track - thumbTop - thumbSize)).fill("\u2502").join("\n");
492
+ return /* @__PURE__ */ jsx2(ScrollContext, { value: context, children: /* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexShrink: 1, minHeight: 1, marginX: 2, children: [
493
+ /* @__PURE__ */ jsx2(
494
+ Box,
495
+ {
496
+ ref: viewport,
497
+ flexDirection: "column",
498
+ flexGrow: 1,
499
+ flexShrink: 1,
500
+ overflow: "hidden",
501
+ contentOffsetY: offset,
502
+ children: /* @__PURE__ */ jsx2(Box, { ref: content, flexDirection: "column", flexShrink: 0, width: "100%", children })
503
+ }
504
+ ),
505
+ /* @__PURE__ */ jsx2(Box, { width: 1, flexShrink: 0, "aria-hidden": true, children: /* @__PURE__ */ jsxs(Text, { color: colors.line, children: [
506
+ before,
507
+ before && "\n",
508
+ /* @__PURE__ */ jsx2(Text, { color: isFocused ? colors.accent : colors.muted, children: thumb }),
509
+ after && "\n",
510
+ after
511
+ ] }) })
512
+ ] }) });
513
+ }
514
+
515
+ // packages/cli/src/page-list.tsx
516
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
517
+ function PageList({ items, onSelect, scroll, compact = false }) {
518
+ const { focused } = useScrollArea();
519
+ const { colors } = useTerminalStyle();
520
+ const [selected, setSelected] = useState2(0);
521
+ const elements = useRef2(/* @__PURE__ */ new Map());
522
+ const container = useRef2(null);
523
+ const { hasMeasured } = useBoxMetrics2(container);
524
+ useEffect(() => {
525
+ const element = elements.current.get(items[selected].path);
526
+ if (focused && hasMeasured && element) scroll.current?.reveal(element);
527
+ }, [focused, hasMeasured, selected, items, scroll]);
528
+ useInput2((_input, key) => {
529
+ let next = selected;
530
+ if (key.upArrow) next--;
531
+ else if (key.downArrow) next++;
532
+ else if (key.home) next = 0;
533
+ else if (key.end) next = items.length - 1;
534
+ else if (key.return) {
535
+ const item = items[selected];
536
+ const element = elements.current.get(item.path);
537
+ if (element && scroll.current?.reveal(element) === false) return;
538
+ onSelect(item.path, item.external);
539
+ return;
540
+ } else return;
541
+ next = Math.max(0, Math.min(items.length - 1, next));
542
+ setSelected(next);
543
+ }, { isActive: focused });
544
+ return /* @__PURE__ */ jsx3(Box2, { ref: container, flexDirection: "column", marginTop: 1, children: items.map((item, index) => {
545
+ const active = focused && index === selected;
546
+ return /* @__PURE__ */ jsxs2(
547
+ Box2,
548
+ {
549
+ flexDirection: compact ? "row" : "column",
550
+ justifyContent: compact ? "space-between" : void 0,
551
+ marginBottom: compact ? 0 : 1,
552
+ backgroundColor: active ? colors.band : void 0,
553
+ ref: (element) => {
554
+ if (element) elements.current.set(item.path, element);
555
+ else elements.current.delete(item.path);
556
+ },
557
+ children: [
558
+ /* @__PURE__ */ jsxs2(Text2, { color: active ? colors.accent : colors.fg, bold: active, children: [
559
+ active ? "\u25B8" : compact ? " " : "\u2022",
560
+ " ",
561
+ compact ? "" : `${index + 1}. `,
562
+ cleanText(item.title)
563
+ ] }),
564
+ item.description && /* @__PURE__ */ jsx3(Text2, { color: colors.muted, children: cleanText(item.description) })
565
+ ]
566
+ },
567
+ item.path
568
+ );
569
+ }) });
570
+ }
571
+
572
+ // packages/cli/src/blocks.tsx
573
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
574
+ function Prompt({ text, command = false }) {
575
+ const { colors } = useTerminalStyle();
576
+ return /* @__PURE__ */ jsxs3(Box3, { backgroundColor: colors.band, paddingX: 2, children: [
577
+ /* @__PURE__ */ jsx4(Text3, { color: colors.prompt, children: "\u276F " }),
578
+ /* @__PURE__ */ jsx4(Text3, { color: command ? colors.skill : colors.fg, children: cleanText(text) })
579
+ ] });
580
+ }
581
+ function Welcome({ section, ...selection }) {
582
+ const { colors, width } = useTerminalStyle();
583
+ const content = useRef3(null);
584
+ const { clientWidth } = useBoxMetrics3(content);
585
+ return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "round", borderColor: colors.line, paddingX: 2, paddingY: 1, gap: 4, children: [
586
+ section.logo && width >= 90 && /* @__PURE__ */ jsx4(Text3, { color: colors.skill, children: cleanText(section.logo) }),
587
+ /* @__PURE__ */ jsxs3(Box3, { ref: content, flexDirection: "column", flexGrow: 1, flexShrink: 1, minWidth: 0, children: [
588
+ /* @__PURE__ */ jsx4(Markdown, { text: section.markdown, width: clientWidth || width - 6 }),
589
+ section.menu && selection.items.length > 0 && /* @__PURE__ */ jsx4(PageList, { ...selection, compact: true })
590
+ ] })
591
+ ] });
592
+ }
593
+ function HomeSections({ page, ...selection }) {
594
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", gap: 1, children: page.sections?.map((section) => section.kind === "welcome" ? /* @__PURE__ */ jsx4(Welcome, { section, items: page.items, ...selection }, "welcome") : /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
595
+ /* @__PURE__ */ jsx4(Prompt, { text: section.prompt }),
596
+ /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx4(Markdown, { text: section.markdown }) })
597
+ ] }, section.prompt)) });
598
+ }
599
+
600
+ // packages/cli/src/chat.tsx
601
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
443
602
  var toolTitles = { set_theme: "Theme", open_page: "Open" };
444
603
  function MessageBody({ message }) {
445
604
  const { colors } = useTerminalStyle();
446
605
  if (message.role === "user") {
447
- return /* @__PURE__ */ jsx2(Box, { marginY: 1, children: /* @__PURE__ */ jsxs(Text, { color: colors.prompt, children: [
448
- "\u276F",
449
- " ",
450
- message.content.filter((part) => part.type === "text").map((part) => cleanText(part.text)).join("\n")
451
- ] }) });
606
+ return /* @__PURE__ */ jsx5(Box4, { marginY: 1, children: /* @__PURE__ */ jsx5(Prompt, { text: message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n") }) });
452
607
  }
453
608
  if (message.role !== "assistant") return null;
454
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
609
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
455
610
  message.content.map((part, index) => {
456
611
  if (part.type === "text")
457
- return /* @__PURE__ */ jsx2(Markdown, { text: part.text }, index);
612
+ return /* @__PURE__ */ jsx5(Markdown, { text: part.text }, index);
458
613
  if (part.type === "reasoning") {
459
614
  const label = message.status.type === "running" ? "\u601D\u8003\u4E2D\u2026" : "\u601D\u8003";
460
615
  return (
461
616
  // eslint-disable-next-line react-doctor/no-array-index-as-key
462
- /* @__PURE__ */ jsxs(Text, { color: colors.muted, children: [
617
+ /* @__PURE__ */ jsxs4(Text4, { color: colors.muted, children: [
463
618
  "\u25C6 ",
464
619
  label
465
620
  ] }, index)
@@ -467,7 +622,7 @@ function MessageBody({ message }) {
467
622
  }
468
623
  if (part.type === "tool-call") {
469
624
  const title = toolTitles[part.toolName] ?? part.toolName;
470
- return /* @__PURE__ */ jsxs(Text, { color: colors.skill, children: [
625
+ return /* @__PURE__ */ jsxs4(Text4, { color: colors.skill, children: [
471
626
  "\u25C6 ",
472
627
  cleanText(title),
473
628
  " ",
@@ -476,116 +631,85 @@ function MessageBody({ message }) {
476
631
  }
477
632
  return null;
478
633
  }),
479
- message.status.type === "incomplete" && message.status.reason === "error" && /* @__PURE__ */ jsx2(Text, { color: colors.err, children: "\u25C6 AI \u6682\u65F6\u65E0\u6CD5\u56DE\u590D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" })
634
+ message.status.type === "incomplete" && message.status.reason === "error" && /* @__PURE__ */ jsx5(Text4, { color: colors.err, children: "\u25C6 AI \u6682\u65F6\u65E0\u6CD5\u56DE\u590D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" })
480
635
  ] });
481
636
  }
482
- function LiveMessage() {
637
+ function Message() {
483
638
  const message = useAuiState((state) => state.message);
484
- return message.role === "assistant" && message.status.type === "running" ? /* @__PURE__ */ jsx2(MessageBody, { message }) : null;
639
+ return /* @__PURE__ */ jsx5(MessageBody, { message });
485
640
  }
486
641
  function ChatThread() {
487
- return /* @__PURE__ */ jsx2(ThreadPrimitive.Messages, { components: { Message: LiveMessage } });
642
+ return /* @__PURE__ */ jsx5(ThreadPrimitive.Messages, { components: { Message } });
488
643
  }
489
644
 
490
- // packages/cli/src/console.tsx
491
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
492
- var pageSize = 8;
493
- function PageOutput({ page, prompt }) {
494
- const { colors } = useTerminalStyle();
495
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginY: 1, children: [
496
- /* @__PURE__ */ jsxs2(Text2, { color: colors.prompt, children: [
497
- "\u276F ",
498
- cleanText(prompt)
499
- ] }),
500
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: [
501
- /* @__PURE__ */ jsxs2(Text2, { color: colors.skill, children: [
502
- "\u25C6 ",
503
- page.verb,
504
- " ",
505
- /* @__PURE__ */ jsx3(Text2, { color: colors.path, children: cleanText(page.title) })
506
- ] }),
507
- page.markdown && /* @__PURE__ */ jsx3(Markdown, { text: page.markdown })
508
- ] })
509
- ] });
645
+ // packages/cli/src/composer.tsx
646
+ import { ComposerPrimitive, LoadingPrimitive, useAui, useAuiState as useAuiState2 } from "@assistant-ui/react-ink";
647
+ import { Box as Box5, Text as Text5, useFocusManager } from "ink";
648
+
649
+ // src/agent/appearance.ts
650
+ var composerPlaceholder = "\u95EE\u70B9\u4EC0\u4E48\uFF0C\u6216\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4";
651
+ var composerBrand = { mark: "<MT/>", model: "V3.14 Pro" };
652
+
653
+ // src/agent/commands.ts
654
+ var commands = [
655
+ { id: "help", description: "\u67E5\u770B\u53EF\u7528\u547D\u4EE4" },
656
+ { id: "theme", args: "[\u4E3B\u9898]", description: "\u5207\u6362\u4E3B\u9898\uFF0C\u4E0D\u5E26\u53C2\u6570\u65F6\u6253\u5F00\u9009\u62E9\u5668" },
657
+ { id: "clear", description: "\u6E05\u7A7A\u5F53\u524D\u5BF9\u8BDD" },
658
+ { id: "home", description: "\u6253\u5F00\u9996\u9875" },
659
+ { id: "blog", description: "\u6D4F\u89C8\u5168\u90E8\u6587\u7AE0" },
660
+ { id: "projects", description: "\u67E5\u770B\u6298\u817E\u7684\u9879\u76EE" },
661
+ { id: "tags", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
662
+ { id: "search", args: "[\u5173\u952E\u8BCD]", description: "\u641C\u7D22\u6587\u7AE0" },
663
+ { id: "about", description: "\u5173\u4E8E\u672C\u7AD9" },
664
+ { id: "rss", description: "\u8BA2\u9605 RSS" }
665
+ ];
666
+ var pageCommands = {
667
+ home: "/",
668
+ blog: "/blog/",
669
+ projects: "/projects/",
670
+ tags: "/tags/",
671
+ about: "/about/"
672
+ };
673
+ function parseCommand(input) {
674
+ const match = input.trim().match(/^\/(\S+)(?:\s+([\s\S]*))?$/);
675
+ if (!match)
676
+ return null;
677
+ return { name: match[1].toLowerCase(), args: (match[2] ?? "").trim() };
510
678
  }
511
- function PageList({ page, onSelect }) {
512
- const { isFocused } = useFocus();
513
- const { colors } = useTerminalStyle();
514
- const [selected, setSelected] = useState(0);
515
- const start = Math.floor(selected / pageSize) * pageSize;
516
- useInput(
517
- (_input, key) => {
518
- if (key.upArrow) setSelected((value) => Math.max(0, value - 1));
519
- if (key.downArrow) setSelected((value) => Math.min(page.items.length - 1, value + 1));
520
- if (key.return) {
521
- const item = page.items[selected];
522
- if (item) onSelect(item.path, item.external);
523
- }
524
- },
525
- { isActive: isFocused }
526
- );
527
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
528
- page.items.slice(start, start + pageSize).map((item, offset) => {
529
- const active = isFocused && start + offset === selected;
530
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", color: active ? colors.accent : colors.fg, children: [
531
- active ? "\u25B8" : "\u2022",
532
- " ",
533
- start + offset + 1,
534
- ". ",
535
- cleanText(item.title),
536
- item.description && /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
537
- "\n ",
538
- cleanText(item.description)
539
- ] })
540
- ] }, item.path);
541
- }),
542
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
543
- start + 1,
544
- "\u2013",
545
- Math.min(start + pageSize, page.items.length),
546
- " / ",
547
- page.items.length,
548
- " \xB7 Tab",
549
- " ",
550
- isFocused ? "\u56DE\u5230\u8F93\u5165" : "\u9009\u62E9\u5217\u8868"
551
- ] })
552
- ] });
679
+ function getSearchPath(query) {
680
+ return query ? `/search/?q=${encodeURIComponent(query)}` : "/search/";
553
681
  }
554
- function Console({
682
+
683
+ // packages/cli/src/composer.tsx
684
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
685
+ function Composer({
555
686
  runtime,
556
687
  page,
557
688
  revision,
558
- output,
559
689
  loading,
560
690
  notice,
561
691
  onCommand,
562
- onSelect
692
+ onSubmit
563
693
  }) {
564
694
  const { colors } = useTerminalStyle();
565
695
  const aui = useAui();
566
- const isRunning = useAuiState2((state) => state.thread.isRunning);
567
- return /* @__PURE__ */ jsxs2(ThreadPrimitive2.Root, { children: [
568
- /* @__PURE__ */ jsx3(Static, { items: output, children: (item) => "page" in item ? /* @__PURE__ */ jsx3(PageOutput, { page: item.page, prompt: item.prompt }, item.id) : /* @__PURE__ */ jsx3(MessageBody, { message: item.message }, item.id) }),
569
- page.items.length > 0 && /* @__PURE__ */ jsx3(PageList, { page, onSelect }, `${page.path}:${revision}`),
570
- /* @__PURE__ */ jsx3(ChatThread, {}),
571
- (loading || isRunning) && /* @__PURE__ */ jsxs2(Box2, { children: [
572
- /* @__PURE__ */ jsx3(LoadingPrimitive.Spinner, {}),
573
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
574
- " ",
575
- loading ? "\u6B63\u5728\u8BFB\u53D6\u5B98\u7F51\u2026" : "\u56DE\u590D\u4E2D\u2026",
576
- " \xB7 Esc \u505C\u6B62"
577
- ] })
696
+ const { activeId } = useFocusManager();
697
+ const running = useAuiState2((state) => state.thread.isRunning);
698
+ const focused = activeId !== "content";
699
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginX: 2, flexShrink: 0, children: [
700
+ (loading || running || notice) && /* @__PURE__ */ jsxs5(Box5, { paddingLeft: 2, height: 1, overflow: "hidden", children: [
701
+ (loading || running) && /* @__PURE__ */ jsx6(LoadingPrimitive.Spinner, {}),
702
+ /* @__PURE__ */ jsx6(Text5, { color: notice ? colors.warn : colors.muted, wrap: "truncate-end", children: notice ? cleanText(notice) : ` ${loading ? "\u6B63\u5728\u8BFB\u53D6\u5B98\u7F51\u2026" : "\u56DE\u590D\u4E2D\u2026"} \xB7 Esc \u505C\u6B62` })
578
703
  ] }),
579
- notice && /* @__PURE__ */ jsx3(Text2, { color: colors.warn, children: cleanText(notice) }),
580
- /* @__PURE__ */ jsxs2(Box2, { borderStyle: "single", borderColor: colors.line, paddingX: 1, children: [
581
- /* @__PURE__ */ jsx3(Text2, { color: colors.prompt, children: "\u276F " }),
582
- /* @__PURE__ */ jsx3(
704
+ /* @__PURE__ */ jsxs5(Box5, { borderStyle: "round", borderColor: focused ? colors["line-strong"] : colors.line, paddingX: 1, children: [
705
+ /* @__PURE__ */ jsx6(Text5, { color: colors.prompt, children: "\u276F " }),
706
+ /* @__PURE__ */ jsx6(
583
707
  ComposerPrimitive.Input,
584
708
  {
585
709
  autoFocus: true,
586
710
  submitOnEnter: true,
587
711
  flexGrow: 1,
588
- placeholder: "\u95EE\u6211\u4EFB\u4F55\u95EE\u9898\uFF0C\u6216\u8F93\u5165 /help",
712
+ placeholder: composerPlaceholder,
589
713
  onSubmit: (text) => {
590
714
  const input = text.trim();
591
715
  if (!input) return;
@@ -593,19 +717,166 @@ function Console({
593
717
  if (command) {
594
718
  aui.composer.setText("");
595
719
  void onCommand(command.name, command.args);
596
- } else if (!isRunning) runtime.thread.composer.send();
720
+ } else if (!running) {
721
+ onSubmit();
722
+ runtime.thread.composer.send();
723
+ }
597
724
  }
598
725
  },
599
- page.path
600
- )
726
+ `${page.path}:${revision}`
727
+ ),
728
+ /* @__PURE__ */ jsx6(Box5, { position: "absolute", bottom: -1, right: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: colors.muted, backgroundColor: colors.bg, children: [
729
+ " ",
730
+ composerBrand.mark,
731
+ " \xB7 ",
732
+ composerBrand.model,
733
+ " "
734
+ ] }) })
601
735
  ] }),
602
- /* @__PURE__ */ jsxs2(Text2, { color: colors.muted, children: [
603
- cleanText(page.path),
604
- " \xB7 /help \xB7 Tab \u5207\u6362 \xB7 Ctrl+C \u9000\u51FA"
736
+ /* @__PURE__ */ jsxs5(Text5, { color: colors.muted, wrap: "truncate-end", children: [
737
+ /* @__PURE__ */ jsx6(Text5, { color: colors["fg-soft"], bold: true, children: "Tab" }),
738
+ ":\u5207\u6362 \u2502 ",
739
+ /* @__PURE__ */ jsx6(Text5, { bold: true, children: "PgUp/PgDn" }),
740
+ ":\u6EDA\u52A8 \u2502 ",
741
+ /* @__PURE__ */ jsx6(Text5, { bold: true, children: "/help" }),
742
+ " \u2502 Ctrl+C:\u9000\u51FA"
605
743
  ] })
606
744
  ] });
607
745
  }
608
746
 
747
+ // packages/cli/src/page-content.tsx
748
+ import { Box as Box6, Text as Text6 } from "ink";
749
+ import { memo } from "react";
750
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
751
+ var PageContent = memo(function PageContent2({
752
+ page,
753
+ prompt,
754
+ onSelect,
755
+ scroll
756
+ }) {
757
+ const { colors } = useTerminalStyle();
758
+ if (page.sections) return /* @__PURE__ */ jsx7(HomeSections, { page, onSelect, scroll });
759
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 1, children: [
760
+ page.path !== "/" && /* @__PURE__ */ jsx7(Prompt, { text: prompt, command: true }),
761
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, paddingTop: 1, children: [
762
+ page.path !== "/" && /* @__PURE__ */ jsxs6(Text6, { children: [
763
+ /* @__PURE__ */ jsx7(Text6, { color: colors.skill, children: "\u25C6 " }),
764
+ /* @__PURE__ */ jsx7(Text6, { color: colors.fg, bold: true, children: page.verb }),
765
+ /* @__PURE__ */ jsxs6(Text6, { color: colors.path, children: [
766
+ " ",
767
+ cleanText(page.title)
768
+ ] })
769
+ ] }),
770
+ page.markdown && /* @__PURE__ */ jsx7(Markdown, { text: page.markdown }),
771
+ page.items.length > 0 && /* @__PURE__ */ jsx7(PageList, { items: page.items, onSelect, scroll })
772
+ ] })
773
+ ] });
774
+ });
775
+
776
+ // packages/cli/src/window.tsx
777
+ import { Box as Box7, Text as Text7, useWindowSize } from "ink";
778
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
779
+ function windowWidth(columns) {
780
+ return Math.min(120, columns >= 84 ? columns - 4 : columns);
781
+ }
782
+ function Window({
783
+ name,
784
+ path,
785
+ nav = [],
786
+ children
787
+ }) {
788
+ const { columns, rows } = useWindowSize();
789
+ const { colors } = useTerminalStyle();
790
+ const width = windowWidth(columns);
791
+ const margin = columns >= 80 && rows >= 24 ? 1 : 0;
792
+ const directory = `~/miantiao.me${path === "/" ? "" : path.replace(/\/$/, "")}`;
793
+ return /* @__PURE__ */ jsx8(
794
+ Box7,
795
+ {
796
+ width: columns,
797
+ height: rows,
798
+ flexDirection: "column",
799
+ alignItems: "center",
800
+ paddingY: margin,
801
+ backgroundColor: colors.page,
802
+ children: /* @__PURE__ */ jsxs7(
803
+ Box7,
804
+ {
805
+ width,
806
+ height: Math.max(1, rows - margin * 2),
807
+ flexDirection: "column",
808
+ borderStyle: "round",
809
+ borderColor: colors.line,
810
+ backgroundColor: colors.bg,
811
+ overflow: "hidden",
812
+ children: [
813
+ /* @__PURE__ */ jsxs7(
814
+ Box7,
815
+ {
816
+ paddingX: 2,
817
+ flexShrink: 0,
818
+ borderStyle: "single",
819
+ borderTop: false,
820
+ borderLeft: false,
821
+ borderRight: false,
822
+ borderColor: colors.line,
823
+ children: [
824
+ /* @__PURE__ */ jsx8(Text7, { color: colors.err, children: "\u25CF " }),
825
+ /* @__PURE__ */ jsx8(Text7, { color: colors.warn, children: "\u25CF " }),
826
+ /* @__PURE__ */ jsx8(Text7, { color: colors.ok, children: "\u25CF " }),
827
+ /* @__PURE__ */ jsx8(Box7, { flexGrow: 1, justifyContent: "center", paddingRight: 6, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, wrap: "truncate-end", children: [
828
+ cleanText(name),
829
+ " \u2014 miantiao.me"
830
+ ] }) })
831
+ ]
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsxs7(Box7, { paddingX: 2, paddingTop: 1, flexShrink: 0, children: [
835
+ /* @__PURE__ */ jsx8(Box7, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx8(Text7, { color: colors.muted, wrap: "truncate-middle", children: cleanText(directory) }) }),
836
+ width >= 70 && nav.map((link) => /* @__PURE__ */ jsx8(Box7, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text7, { color: path.startsWith(link.href) ? colors.fg : colors.muted, children: cleanText(link.title) }) }, link.href))
837
+ ] }),
838
+ children
839
+ ]
840
+ }
841
+ )
842
+ }
843
+ );
844
+ }
845
+
846
+ // packages/cli/src/console.tsx
847
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
848
+ function Console({
849
+ runtime,
850
+ page,
851
+ prompt,
852
+ revision,
853
+ site,
854
+ loading,
855
+ notice,
856
+ onCommand,
857
+ onSelect
858
+ }) {
859
+ const scroll = useRef4(null);
860
+ return /* @__PURE__ */ jsx9(Window, { name: site.name, path: page.path, nav: site.nav, children: /* @__PURE__ */ jsxs8(ThreadPrimitive2.Root, { flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
861
+ /* @__PURE__ */ jsxs8(ScrollArea, { ref: scroll, selection: page.items.length > 0, children: [
862
+ /* @__PURE__ */ jsx9(PageContent, { page, prompt, onSelect, scroll }),
863
+ /* @__PURE__ */ jsx9(ChatThread, {})
864
+ ] }, `${page.path}:${revision}`),
865
+ /* @__PURE__ */ jsx9(
866
+ Composer,
867
+ {
868
+ runtime,
869
+ page,
870
+ revision,
871
+ loading,
872
+ notice,
873
+ onCommand,
874
+ onSubmit: () => scroll.current?.end()
875
+ }
876
+ )
877
+ ] }) });
878
+ }
879
+
609
880
  // packages/cli/src/pages.ts
610
881
  var terminalCommands = [
611
882
  ...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
@@ -622,7 +893,7 @@ function helpPage() {
622
893
  items: [],
623
894
  markdown: terminalCommands.map(
624
895
  (command) => `- \`/${command.id}${command.args ? ` ${command.args}` : ""}\` \u2014 ${command.description}`
625
- ).join("\n") + "\n\nTab \u5207\u6362\u8F93\u5165\u548C\u5217\u8868\uFF1B\u2191\u2193 \u9009\u62E9\uFF0CEnter \u6253\u5F00\uFF1BEsc \u505C\u6B62\u56DE\u590D\u6216\u53D6\u6D88\u52A0\u8F7D\uFF1BCtrl+C \u9000\u51FA\u3002"
896
+ ).join("\n") + "\n\nPgUp/PgDn \u6EDA\u52A8\u6B63\u6587\uFF1BTab \u5207\u6362\u8F93\u5165\u548C\u5185\u5BB9\uFF1B\u2191\u2193 \u9009\u62E9\uFF0CEnter \u6253\u5F00\uFF1BEsc \u505C\u6B62\uFF1BCtrl+C \u9000\u51FA\u3002"
626
897
  };
627
898
  }
628
899
  function themePage() {
@@ -651,13 +922,23 @@ async function loadPage(client, path, signal) {
651
922
  page.path = result.document.path + url.search;
652
923
  page.title = result.document.title;
653
924
  page.markdown = result.document.markdown;
925
+ page.sections = result.document.sections;
654
926
  page.verb = "Read";
655
- if (page.path === index.pages.home.path)
656
- page.items = index.site.nav.map((link) => ({
657
- title: link.title,
658
- path: link.href,
659
- external: !link.href.startsWith("/")
660
- }));
927
+ if (page.path === index.pages.home.path) {
928
+ const welcome = page.sections?.find((section) => section.kind === "welcome");
929
+ const menu = welcome?.menu ?? index.site.nav.map((link) => ({ ...link, command: void 0 }));
930
+ const siteOrigins = new Set([client.site, index.site.url].map((site) => new URL(site).origin));
931
+ page.items = menu.map((link) => {
932
+ const destination = new URL(link.href, client.site);
933
+ const external = !siteOrigins.has(destination.origin);
934
+ return {
935
+ title: link.title,
936
+ description: link.command,
937
+ path: external ? destination.href : destination.pathname + destination.search,
938
+ external
939
+ };
940
+ });
941
+ }
661
942
  } else if (pathname === "/blog/") {
662
943
  page.title = "\u5168\u90E8\u6587\u7AE0";
663
944
  page.items = index.posts.map((post) => ({
@@ -706,12 +987,12 @@ async function loadPage(client, path, signal) {
706
987
  }
707
988
 
708
989
  // packages/cli/src/session.ts
709
- import { useApp, useInput as useInput2 } from "ink";
710
- import { useCallback as useCallback2, useEffect as useEffect3, useState as useState3 } from "react";
990
+ import { useApp, useInput as useInput3 } from "ink";
991
+ import { useState as useState4 } from "react";
711
992
 
712
993
  // packages/cli/src/agent.ts
713
994
  import { useChatRuntime } from "@assistant-ui/ai-sdk";
714
- import { useEffect, useMemo as useMemo2 } from "react";
995
+ import { useEffect as useEffect2, useMemo as useMemo3 } from "react";
715
996
 
716
997
  // src/agent/transport.ts
717
998
  import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
@@ -857,7 +1138,7 @@ function createAgentTransport(handlers, options) {
857
1138
 
858
1139
  // packages/cli/src/agent.ts
859
1140
  function useTerminalAgent({ site, index, page, theme, setTheme, navigate }) {
860
- const transport = useMemo2(
1141
+ const transport = useMemo3(
861
1142
  () => createAgentTransport(
862
1143
  {
863
1144
  getContext: () => {
@@ -900,7 +1181,7 @@ function useTerminalAgent({ site, index, page, theme, setTheme, navigate }) {
900
1181
  ]
901
1182
  );
902
1183
  const runtime = useChatRuntime({ transport });
903
- useEffect(() => () => runtime.thread.cancelRun(), [runtime]);
1184
+ useEffect2(() => () => runtime.thread.cancelRun(), [runtime]);
904
1185
  return runtime;
905
1186
  }
906
1187
 
@@ -948,15 +1229,16 @@ function resolveTagPath(input, index) {
948
1229
  }
949
1230
 
950
1231
  // packages/cli/src/navigation.ts
951
- import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
952
- function useNavigation(client, initial, onPage) {
953
- const [state, setState] = useState2({
1232
+ import { useCallback, useEffect as useEffect3, useRef as useRef5, useState as useState3 } from "react";
1233
+ function useNavigation(client, initial) {
1234
+ const [state, setState] = useState3({
954
1235
  ...initial,
955
1236
  status: "idle",
956
- notice: client.warning
1237
+ notice: client.warning,
1238
+ prompt: "/home"
957
1239
  });
958
- const active = useRef(void 0);
959
- useEffect2(
1240
+ const active = useRef5(void 0);
1241
+ useEffect3(
960
1242
  () => () => {
961
1243
  active.current?.abort();
962
1244
  active.current = void 0;
@@ -974,10 +1256,9 @@ function useNavigation(client, initial, onPage) {
974
1256
  const show = useCallback(
975
1257
  (page, prompt) => {
976
1258
  cancel();
977
- setState((current) => ({ ...current, page, notice: "" }));
978
- onPage(page, prompt);
1259
+ setState((current) => ({ ...current, page, prompt, notice: "" }));
979
1260
  },
980
- [cancel, onPage]
1261
+ [cancel]
981
1262
  );
982
1263
  const navigate = useCallback(
983
1264
  async (path, prompt = path) => {
@@ -1001,11 +1282,10 @@ function useNavigation(client, initial, onPage) {
1001
1282
  }
1002
1283
  if (active.current !== request) return false;
1003
1284
  active.current = void 0;
1004
- setState({ ...next, status: "idle", notice: client.warning });
1005
- onPage(next.page, prompt);
1285
+ setState({ ...next, status: "idle", notice: client.warning, prompt });
1006
1286
  return true;
1007
1287
  },
1008
- [client, onPage]
1288
+ [client]
1009
1289
  );
1010
1290
  return { ...state, navigate, show, cancel, notify };
1011
1291
  }
@@ -1013,39 +1293,14 @@ function useNavigation(client, initial, onPage) {
1013
1293
  // packages/cli/src/session.ts
1014
1294
  function useTerminalSession(client, initial) {
1015
1295
  const { exit } = useApp();
1016
- const [theme, setTheme] = useState3("auto");
1017
- const [output, setOutput] = useState3([
1018
- { id: "home", page: initial.page, prompt: "/home" }
1019
- ]);
1020
- const appendPage = useCallback2((page2, prompt) => {
1021
- const entry = { id: crypto.randomUUID(), page: page2, prompt };
1022
- setOutput((items) => [...items, entry]);
1023
- }, []);
1024
- const { index, page, status, notice, navigate, show, cancel, notify } = useNavigation(
1025
- client,
1026
- initial,
1027
- appendPage
1028
- );
1296
+ const [theme, setTheme] = useState4("auto");
1297
+ const { index, page, prompt, status, notice, navigate, show, cancel, notify } = useNavigation(client, initial);
1029
1298
  const runtime = useTerminalAgent({ site: client.site, index, page, theme, setTheme, navigate });
1030
- useEffect3(() => {
1031
- const printed = /* @__PURE__ */ new Set();
1032
- const publish = () => {
1033
- const completed = runtime.thread.getState().messages.filter(
1034
- (message) => !printed.has(message.id) && (message.role !== "assistant" || message.status.type !== "running")
1035
- );
1036
- if (!completed.length) return;
1037
- const entries = completed.map((message) => ({ id: message.id, message }));
1038
- completed.forEach((message) => printed.add(message.id));
1039
- setOutput((items) => [...items, ...entries]);
1040
- };
1041
- publish();
1042
- return runtime.thread.subscribe(publish);
1043
- }, [runtime]);
1044
1299
  const stop = () => {
1045
1300
  cancel();
1046
1301
  runtime.thread.cancelRun();
1047
1302
  };
1048
- useInput2((input, key) => {
1303
+ useInput3((input, key) => {
1049
1304
  if (key.escape) {
1050
1305
  stop();
1051
1306
  notify("\u5DF2\u505C\u6B62\u3002");
@@ -1117,7 +1372,8 @@ function useTerminalSession(client, initial) {
1117
1372
  runtime,
1118
1373
  page,
1119
1374
  revision: index.revision,
1120
- output,
1375
+ prompt,
1376
+ site: index.site,
1121
1377
  loading: status === "loading",
1122
1378
  notice,
1123
1379
  onCommand,
@@ -1127,33 +1383,22 @@ function useTerminalSession(client, initial) {
1127
1383
  }
1128
1384
 
1129
1385
  // packages/cli/src/app.tsx
1130
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1386
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1131
1387
  function Session({
1132
1388
  client,
1133
1389
  initial,
1134
1390
  width
1135
1391
  }) {
1136
1392
  const { theme, ...session } = useTerminalSession(client, initial);
1137
- return /* @__PURE__ */ jsx4(TerminalTheme, { preference: theme, width, children: /* @__PURE__ */ jsx4(AssistantRuntimeProvider, { runtime: session.runtime, children: /* @__PURE__ */ jsx4(Console, { ...session }) }) });
1393
+ return /* @__PURE__ */ jsx10(TerminalTheme, { preference: theme, width, children: /* @__PURE__ */ jsx10(AssistantRuntimeProvider, { runtime: session.runtime, children: /* @__PURE__ */ jsx10(Console, { ...session }) }) });
1138
1394
  }
1139
1395
  function App({ site }) {
1140
1396
  const { exit } = useApp2();
1141
- const { stdout } = useStdout();
1142
- const [client] = useState4(() => new TerminalClient(site));
1143
- const [state, setState] = useState4({ status: "loading" });
1144
- const [attempt, setAttempt] = useState4(0);
1145
- const width = useSyncExternalStore(
1146
- useCallback3(
1147
- (notify) => {
1148
- stdout.on("resize", notify);
1149
- return () => {
1150
- stdout.off("resize", notify);
1151
- };
1152
- },
1153
- [stdout]
1154
- ),
1155
- () => "columns" in stdout && typeof stdout.columns === "number" ? stdout.columns : 80
1156
- );
1397
+ const { columns } = useWindowSize2();
1398
+ const [client] = useState5(() => new TerminalClient(site));
1399
+ const [state, setState] = useState5({ status: "loading" });
1400
+ const [attempt, setAttempt] = useState5(0);
1401
+ const width = Math.max(10, windowWidth(columns) - 7);
1157
1402
  useEffect4(() => {
1158
1403
  const request = new AbortController();
1159
1404
  loadPage(client, "/", request.signal).then((content) => {
@@ -1167,7 +1412,7 @@ function App({ site }) {
1167
1412
  });
1168
1413
  return () => request.abort();
1169
1414
  }, [client, attempt]);
1170
- useInput3(
1415
+ useInput4(
1171
1416
  (input, key) => {
1172
1417
  if (key.ctrl && input === "c" || input === "q") exit();
1173
1418
  else if (state.status === "error" && (key.return || input === "r" || input === "/refresh")) {
@@ -1178,17 +1423,21 @@ function App({ site }) {
1178
1423
  { isActive: state.status !== "ready" }
1179
1424
  );
1180
1425
  if (state.status === "ready")
1181
- return /* @__PURE__ */ jsx4(Session, { client, initial: state.content, width });
1182
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1183
- /* @__PURE__ */ jsxs3(Text3, { children: [
1426
+ return /* @__PURE__ */ jsx10(Session, { client, initial: state.content, width });
1427
+ return /* @__PURE__ */ jsx10(TerminalTheme, { preference: "auto", width, children: /* @__PURE__ */ jsx10(Window, { name: "miantiao.me", path: "/", children: /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", flexGrow: 1, padding: 2, children: [
1428
+ /* @__PURE__ */ jsxs9(Text8, { children: [
1184
1429
  "\u25C6 ",
1185
1430
  state.status === "error" ? state.message : "\u6B63\u5728\u8BFB\u53D6\u5B98\u7F51\u2026"
1186
1431
  ] }),
1187
- /* @__PURE__ */ jsx4(Text3, { children: state.status === "error" ? "Enter \u91CD\u8BD5 \xB7 q \u9000\u51FA" : "Ctrl+C \u9000\u51FA" })
1188
- ] });
1432
+ /* @__PURE__ */ jsx10(Text8, { children: state.status === "error" ? "Enter \u91CD\u8BD5 \xB7 q \u9000\u51FA" : "Ctrl+C \u9000\u51FA" })
1433
+ ] }) }) });
1189
1434
  }
1190
1435
  async function run(site) {
1191
- const app = render(/* @__PURE__ */ jsx4(App, { site }), { exitOnCtrlC: false });
1436
+ const app = render(/* @__PURE__ */ jsx10(App, { site }), {
1437
+ exitOnCtrlC: false,
1438
+ alternateScreen: true,
1439
+ incrementalRendering: true
1440
+ });
1192
1441
  await app.waitUntilExit();
1193
1442
  }
1194
1443
  export {
package/dist/index.js CHANGED
@@ -16,14 +16,14 @@ try {
16
16
  "miantiao.me \u2014 \u7EC8\u7AEF\u91CC\u7684\u9762\u6761\n\n\u7528\u6CD5\uFF1Anpx miantiao.me [--site <URL>]\n\n -h, --help \u67E5\u770B\u5E2E\u52A9\n -v, --version \u67E5\u770B\u7248\u672C\n --site <URL> \u5B98\u7F51\u5730\u5740\uFF0C\u4E5F\u53EF\u6307\u5411\u672C\u5730\u9884\u89C8\n\n\u8FDB\u5165\u540E\u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA\u3002\n\u9700\u8981 Node.js 22.14 \u6216\u66F4\u65B0\u7248\u672C\uFF0C\u4EE5\u53CA\u53EF\u4EA4\u4E92\u7684\u7EC8\u7AEF\u3002"
17
17
  );
18
18
  } else if (values.version) {
19
- console.log("0.1.0");
19
+ console.log("0.1.1");
20
20
  } else {
21
21
  const site = new URL(values.site);
22
22
  if (!["https:", "http:"].includes(site.protocol) || site.username || site.password)
23
23
  throw new Error("--site \u5FC5\u987B\u4E3A\u4E0D\u542B\u51ED\u8BC1\u7684 HTTP \u6216 HTTPS \u5730\u5740\u3002");
24
24
  if (!process.stdin.isTTY || !process.stdout.isTTY)
25
25
  throw new Error("\u8BF7\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u8FD0\u884C npx miantiao.me\uFF1B\u4F7F\u7528 --help \u67E5\u770B\u5E2E\u52A9\u3002");
26
- const { run } = await import("./app-UWAFKI3E.js");
26
+ const { run } = await import("./app-ZZYXIU6G.js");
27
27
  await run(site.origin);
28
28
  }
29
29
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miantiao.me",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "在终端浏览 miantiao.me,与面条的 AI 助手对话。",
5
5
  "type": "module",
6
6
  "bin": {