miantiao.me 0.1.1 → 0.1.2
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 +3 -3
- package/dist/{app-ZZYXIU6G.js → app-IW4THTCR.js} +221 -160
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# miantiao.me
|
|
2
2
|
|
|
3
|
-
在终端浏览面条的文章、项目和标签,也可以直接向 AI 助手提问。界面使用 assistant-ui Ink
|
|
3
|
+
在终端浏览面条的文章、项目和标签,也可以直接向 AI 助手提问。界面使用 assistant-ui Ink:铺满终端、底部输入框和独立滚动的内容区,与 [miantiao.me](https://miantiao.me) 的主题、欢迎页和会话块保持一致。
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx miantiao.me
|
|
@@ -8,7 +8,7 @@ npx miantiao.me
|
|
|
8
8
|
|
|
9
9
|
需要 Node.js 22.14 或更新版本,以及交互式终端。文章、项目、首页、关于和搜索索引从官网读取;官网部署后,下一次启动、导航或 `/refresh` 就能读取更新内容。已打开的终端无需重装包。TUI 功能升级仍通过 npm 版本发布。
|
|
10
10
|
|
|
11
|
-
输入普通文字可向 AI
|
|
11
|
+
输入普通文字可向 AI 助手提问。输入 `/` 展开命令面板,可按命令名或中文说明筛选,用 ↑↓ 选择、Enter 确认、Esc 关闭。选择 `/search` 或 `/read` 后继续填写参数。斜杠命令在本地执行:
|
|
12
12
|
|
|
13
13
|
| 命令 | 用途 |
|
|
14
14
|
| ------------------------------------ | -------------------------- |
|
|
@@ -24,7 +24,7 @@ npx miantiao.me
|
|
|
24
24
|
| `/clear` | 清空 AI 对话上下文 |
|
|
25
25
|
| `/quit` | 退出 |
|
|
26
26
|
|
|
27
|
-
PgUp/PgDn 滚动正文,输入框始终固定在底部。Tab
|
|
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
|
|
8
|
-
import { useEffect as
|
|
7
|
+
import { Box as Box9, render, Text as Text9, useApp as useApp2, useInput as useInput6, useWindowSize as useWindowSize3 } from "ink";
|
|
8
|
+
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
9
9
|
|
|
10
10
|
// src/terminal/schema.ts
|
|
11
11
|
import { z } from "zod";
|
|
@@ -287,7 +287,7 @@ var TerminalClient = class {
|
|
|
287
287
|
|
|
288
288
|
// packages/cli/src/console.tsx
|
|
289
289
|
import { ThreadPrimitive as ThreadPrimitive2 } from "@assistant-ui/react-ink";
|
|
290
|
-
import { useRef as
|
|
290
|
+
import { useRef as useRef5 } from "react";
|
|
291
291
|
|
|
292
292
|
// packages/cli/src/chat.tsx
|
|
293
293
|
import { ThreadPrimitive, useAuiState } from "@assistant-ui/react-ink";
|
|
@@ -644,7 +644,8 @@ function ChatThread() {
|
|
|
644
644
|
|
|
645
645
|
// packages/cli/src/composer.tsx
|
|
646
646
|
import { ComposerPrimitive, LoadingPrimitive, useAui, useAuiState as useAuiState2 } from "@assistant-ui/react-ink";
|
|
647
|
-
import { Box as
|
|
647
|
+
import { Box as Box6, Text as Text6, useFocusManager, useInput as useInput4 } from "ink";
|
|
648
|
+
import { useEffect as useEffect2, useRef as useRef4 } from "react";
|
|
648
649
|
|
|
649
650
|
// src/agent/appearance.ts
|
|
650
651
|
var composerPlaceholder = "\u95EE\u70B9\u4EC0\u4E48\uFF0C\u6216\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4";
|
|
@@ -680,8 +681,103 @@ function getSearchPath(query) {
|
|
|
680
681
|
return query ? `/search/?q=${encodeURIComponent(query)}` : "/search/";
|
|
681
682
|
}
|
|
682
683
|
|
|
683
|
-
// packages/cli/src/
|
|
684
|
+
// packages/cli/src/command-menu.tsx
|
|
685
|
+
import { Box as Box5, Text as Text5, useInput as useInput3, useWindowSize } from "ink";
|
|
686
|
+
import { useImperativeHandle as useImperativeHandle2, useState as useState3 } from "react";
|
|
687
|
+
|
|
688
|
+
// packages/cli/src/commands.ts
|
|
689
|
+
var terminalCommands = [
|
|
690
|
+
...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
|
|
691
|
+
{ id: "read", args: "<\u7F16\u53F7\u3001slug \u6216\u8DEF\u5F84>", description: "\u9605\u8BFB\u6587\u7AE0\u5168\u6587" },
|
|
692
|
+
{ id: "open", args: "[\u7F16\u53F7\u6216 URL]", description: "\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5F53\u524D\u9875\u9762\u6216\u94FE\u63A5" },
|
|
693
|
+
{ id: "refresh", description: "\u4ECE\u5B98\u7F51\u5237\u65B0\u5F53\u524D\u9875\u9762" },
|
|
694
|
+
{ id: "quit", description: "\u9000\u51FA" }
|
|
695
|
+
];
|
|
696
|
+
function getCommandSuggestions(text) {
|
|
697
|
+
const match = text.match(/^\/([^\s]*)$/);
|
|
698
|
+
if (!match) return null;
|
|
699
|
+
const query = match[1].toLowerCase();
|
|
700
|
+
return terminalCommands.filter((command) => command.id.startsWith(query) || command.description.includes(query));
|
|
701
|
+
}
|
|
702
|
+
function numberedItem(input, page) {
|
|
703
|
+
if (!/^\d+$/.test(input)) return void 0;
|
|
704
|
+
const item = page.items[Number(input) - 1];
|
|
705
|
+
if (!item) throw new Error("\u5F53\u524D\u5217\u8868\u6CA1\u6709\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u8BF7\u4F7F\u7528\u5217\u8868\u4E2D\u663E\u793A\u7684\u7F16\u53F7\u3002");
|
|
706
|
+
return item;
|
|
707
|
+
}
|
|
708
|
+
function resolveReadPath(input, page) {
|
|
709
|
+
if (!input) throw new Error("\u8F93\u5165 /read <\u7F16\u53F7\u3001slug \u6216\u7AD9\u5185\u8DEF\u5F84> \u9605\u8BFB\u6587\u7AE0\u3002");
|
|
710
|
+
const item = numberedItem(input, page);
|
|
711
|
+
if (item) {
|
|
712
|
+
if (item.external || !item.path.startsWith("/posts/"))
|
|
713
|
+
throw new Error("\u8FD9\u4E2A\u7F16\u53F7\u4E0D\u662F\u6587\u7AE0\uFF0C\u8BF7\u4F7F\u7528 /blog \u6216 /search \u67E5\u627E\u6587\u7AE0\u3002");
|
|
714
|
+
return item.path;
|
|
715
|
+
}
|
|
716
|
+
return input.startsWith("/") ? input : `/posts/${input.replace(/\/$/, "")}/`;
|
|
717
|
+
}
|
|
718
|
+
function resolveOpenTarget(input, page) {
|
|
719
|
+
return numberedItem(input, page)?.path ?? (input || page.path);
|
|
720
|
+
}
|
|
721
|
+
function resolveTagPath(input, index) {
|
|
722
|
+
const tag = index.tags.find((tag2) => tag2.name === input);
|
|
723
|
+
if (!tag) throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u6807\u7B7E\uFF0C\u8BF7\u4F7F\u7528 /tags \u67E5\u770B\u53EF\u7528\u6807\u7B7E\u3002");
|
|
724
|
+
return tag.path;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// packages/cli/src/command-menu.tsx
|
|
684
728
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
729
|
+
function CommandMenu({
|
|
730
|
+
text,
|
|
731
|
+
focused,
|
|
732
|
+
onSelect,
|
|
733
|
+
ref
|
|
734
|
+
}) {
|
|
735
|
+
const { colors } = useTerminalStyle();
|
|
736
|
+
const { rows } = useWindowSize();
|
|
737
|
+
const [selected, setSelected] = useState3(0);
|
|
738
|
+
const [dismissed, setDismissed] = useState3(false);
|
|
739
|
+
const suggestions = getCommandSuggestions(text);
|
|
740
|
+
const matches = suggestions ?? [];
|
|
741
|
+
const open = focused && suggestions !== null && !dismissed;
|
|
742
|
+
const visibleCount = Math.max(1, Math.min(8, rows - 10));
|
|
743
|
+
const start = Math.max(0, selected - visibleCount + 1);
|
|
744
|
+
useImperativeHandle2(ref, () => ({
|
|
745
|
+
submit: (input) => {
|
|
746
|
+
if (!open || input !== text || !matches.length) return false;
|
|
747
|
+
onSelect(matches[selected]);
|
|
748
|
+
return true;
|
|
749
|
+
},
|
|
750
|
+
dismiss: () => {
|
|
751
|
+
if (!open) return false;
|
|
752
|
+
setDismissed(true);
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
}));
|
|
756
|
+
useInput3((_input, key) => {
|
|
757
|
+
if (!matches.length) return;
|
|
758
|
+
const direction = Number(key.downArrow) - Number(key.upArrow);
|
|
759
|
+
if (direction) setSelected((index) => (index + direction + matches.length) % matches.length);
|
|
760
|
+
}, { isActive: open });
|
|
761
|
+
if (!open) return null;
|
|
762
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: colors.line, paddingX: 1, marginBottom: 1, children: [
|
|
763
|
+
matches.length === 0 && /* @__PURE__ */ jsx6(Text5, { color: colors.muted, children: "\u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4" }),
|
|
764
|
+
matches.slice(start, start + visibleCount).map((command, index) => {
|
|
765
|
+
const active = start + index === selected;
|
|
766
|
+
return /* @__PURE__ */ jsxs5(Box5, { height: 1, backgroundColor: active ? colors.band : void 0, children: [
|
|
767
|
+
/* @__PURE__ */ jsx6(Box5, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsxs5(Text5, { color: active ? colors.accent : colors.skill, bold: active, children: [
|
|
768
|
+
active ? "\u25B8" : " ",
|
|
769
|
+
" /",
|
|
770
|
+
command.id
|
|
771
|
+
] }) }),
|
|
772
|
+
/* @__PURE__ */ jsx6(Box5, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx6(Text5, { color: colors.muted, wrap: "truncate-end", children: command.description }) })
|
|
773
|
+
] }, command.id);
|
|
774
|
+
}),
|
|
775
|
+
/* @__PURE__ */ jsx6(Text5, { color: colors.muted, wrap: "truncate-end", children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u5173\u95ED" })
|
|
776
|
+
] });
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// packages/cli/src/composer.tsx
|
|
780
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
685
781
|
function Composer({
|
|
686
782
|
runtime,
|
|
687
783
|
page,
|
|
@@ -689,34 +785,69 @@ function Composer({
|
|
|
689
785
|
loading,
|
|
690
786
|
notice,
|
|
691
787
|
onCommand,
|
|
788
|
+
onCancel,
|
|
692
789
|
onSubmit
|
|
693
790
|
}) {
|
|
694
791
|
const { colors } = useTerminalStyle();
|
|
695
792
|
const aui = useAui();
|
|
696
|
-
const { activeId } = useFocusManager();
|
|
793
|
+
const { activeId, focus, focusNext } = useFocusManager();
|
|
697
794
|
const running = useAuiState2((state) => state.thread.isRunning);
|
|
795
|
+
const text = useAuiState2((state) => state.composer.text);
|
|
796
|
+
const menu = useRef4(null);
|
|
797
|
+
const inputFocusId = useRef4(void 0);
|
|
698
798
|
const focused = activeId !== "content";
|
|
699
|
-
|
|
700
|
-
(
|
|
701
|
-
|
|
702
|
-
|
|
799
|
+
useEffect2(() => {
|
|
800
|
+
if (activeId && activeId !== "content") inputFocusId.current = activeId;
|
|
801
|
+
}, [activeId]);
|
|
802
|
+
useInput4((input, key) => {
|
|
803
|
+
if (key.escape) {
|
|
804
|
+
if (!menu.current?.dismiss()) onCancel();
|
|
805
|
+
if (inputFocusId.current) focus(inputFocusId.current);
|
|
806
|
+
}
|
|
807
|
+
if (input === "/" && activeId === "content") {
|
|
808
|
+
focusNext();
|
|
809
|
+
aui.composer.setText("/");
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginX: 2, flexShrink: 0, children: [
|
|
813
|
+
/* @__PURE__ */ jsx7(
|
|
814
|
+
CommandMenu,
|
|
815
|
+
{
|
|
816
|
+
ref: menu,
|
|
817
|
+
text,
|
|
818
|
+
focused,
|
|
819
|
+
onSelect: (command) => {
|
|
820
|
+
if (command.id === "search" || command.id === "read") {
|
|
821
|
+
aui.composer.setText(`/${command.id} `);
|
|
822
|
+
} else {
|
|
823
|
+
aui.composer.setText("");
|
|
824
|
+
void onCommand(command.id, "");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
text
|
|
829
|
+
),
|
|
830
|
+
(loading || running || notice) && /* @__PURE__ */ jsxs6(Box6, { paddingLeft: 2, height: 1, overflow: "hidden", children: [
|
|
831
|
+
(loading || running) && /* @__PURE__ */ jsx7(LoadingPrimitive.Spinner, {}),
|
|
832
|
+
/* @__PURE__ */ jsx7(Text6, { 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` })
|
|
703
833
|
] }),
|
|
704
|
-
/* @__PURE__ */
|
|
705
|
-
/* @__PURE__ */
|
|
706
|
-
/* @__PURE__ */
|
|
834
|
+
/* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: focused ? colors["line-strong"] : colors.line, paddingX: 1, children: [
|
|
835
|
+
/* @__PURE__ */ jsx7(Text6, { color: colors.prompt, children: "\u276F " }),
|
|
836
|
+
/* @__PURE__ */ jsx7(
|
|
707
837
|
ComposerPrimitive.Input,
|
|
708
838
|
{
|
|
709
839
|
autoFocus: true,
|
|
710
840
|
submitOnEnter: true,
|
|
711
841
|
flexGrow: 1,
|
|
712
842
|
placeholder: composerPlaceholder,
|
|
713
|
-
onSubmit: (
|
|
714
|
-
const input =
|
|
843
|
+
onSubmit: (text2) => {
|
|
844
|
+
const input = text2.trim();
|
|
715
845
|
if (!input) return;
|
|
716
|
-
|
|
717
|
-
if (
|
|
846
|
+
if (menu.current?.submit(text2)) return;
|
|
847
|
+
if (input.startsWith("/")) {
|
|
848
|
+
const command = parseCommand(input);
|
|
718
849
|
aui.composer.setText("");
|
|
719
|
-
void onCommand(command
|
|
850
|
+
void onCommand(command?.name ?? "help", command?.args ?? "");
|
|
720
851
|
} else if (!running) {
|
|
721
852
|
onSubmit();
|
|
722
853
|
runtime.thread.composer.send();
|
|
@@ -725,7 +856,7 @@ function Composer({
|
|
|
725
856
|
},
|
|
726
857
|
`${page.path}:${revision}`
|
|
727
858
|
),
|
|
728
|
-
/* @__PURE__ */
|
|
859
|
+
/* @__PURE__ */ jsx7(Box6, { position: "absolute", bottom: -1, right: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: colors.muted, backgroundColor: colors.bg, children: [
|
|
729
860
|
" ",
|
|
730
861
|
composerBrand.mark,
|
|
731
862
|
" \xB7 ",
|
|
@@ -733,21 +864,21 @@ function Composer({
|
|
|
733
864
|
" "
|
|
734
865
|
] }) })
|
|
735
866
|
] }),
|
|
736
|
-
/* @__PURE__ */
|
|
737
|
-
/* @__PURE__ */
|
|
867
|
+
/* @__PURE__ */ jsxs6(Text6, { color: colors.muted, wrap: "truncate-end", children: [
|
|
868
|
+
/* @__PURE__ */ jsx7(Text6, { color: colors["fg-soft"], bold: true, children: "Tab" }),
|
|
738
869
|
":\u5207\u6362 \u2502 ",
|
|
739
|
-
/* @__PURE__ */
|
|
870
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "PgUp/PgDn" }),
|
|
740
871
|
":\u6EDA\u52A8 \u2502 ",
|
|
741
|
-
/* @__PURE__ */
|
|
872
|
+
/* @__PURE__ */ jsx7(Text6, { bold: true, children: "/help" }),
|
|
742
873
|
" \u2502 Ctrl+C:\u9000\u51FA"
|
|
743
874
|
] })
|
|
744
875
|
] });
|
|
745
876
|
}
|
|
746
877
|
|
|
747
878
|
// packages/cli/src/page-content.tsx
|
|
748
|
-
import { Box as
|
|
879
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
749
880
|
import { memo } from "react";
|
|
750
|
-
import { jsx as
|
|
881
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
751
882
|
var PageContent = memo(function PageContent2({
|
|
752
883
|
page,
|
|
753
884
|
prompt,
|
|
@@ -755,96 +886,56 @@ var PageContent = memo(function PageContent2({
|
|
|
755
886
|
scroll
|
|
756
887
|
}) {
|
|
757
888
|
const { colors } = useTerminalStyle();
|
|
758
|
-
if (page.sections) return /* @__PURE__ */
|
|
759
|
-
return /* @__PURE__ */
|
|
760
|
-
page.path !== "/" && /* @__PURE__ */
|
|
761
|
-
/* @__PURE__ */
|
|
762
|
-
page.path !== "/" && /* @__PURE__ */
|
|
763
|
-
/* @__PURE__ */
|
|
764
|
-
/* @__PURE__ */
|
|
765
|
-
/* @__PURE__ */
|
|
889
|
+
if (page.sections) return /* @__PURE__ */ jsx8(HomeSections, { page, onSelect, scroll });
|
|
890
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
891
|
+
page.path !== "/" && /* @__PURE__ */ jsx8(Prompt, { text: prompt, command: true }),
|
|
892
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, paddingTop: 1, children: [
|
|
893
|
+
page.path !== "/" && /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
894
|
+
/* @__PURE__ */ jsx8(Text7, { color: colors.skill, children: "\u25C6 " }),
|
|
895
|
+
/* @__PURE__ */ jsx8(Text7, { color: colors.fg, bold: true, children: page.verb }),
|
|
896
|
+
/* @__PURE__ */ jsxs7(Text7, { color: colors.path, children: [
|
|
766
897
|
" ",
|
|
767
898
|
cleanText(page.title)
|
|
768
899
|
] })
|
|
769
900
|
] }),
|
|
770
|
-
page.markdown && /* @__PURE__ */
|
|
771
|
-
page.items.length > 0 && /* @__PURE__ */
|
|
901
|
+
page.markdown && /* @__PURE__ */ jsx8(Markdown, { text: page.markdown }),
|
|
902
|
+
page.items.length > 0 && /* @__PURE__ */ jsx8(PageList, { items: page.items, onSelect, scroll })
|
|
772
903
|
] })
|
|
773
904
|
] });
|
|
774
905
|
});
|
|
775
906
|
|
|
776
|
-
// packages/cli/src/
|
|
777
|
-
import { Box as
|
|
778
|
-
import { jsx as
|
|
779
|
-
function
|
|
780
|
-
return Math.min(120, columns >= 84 ? columns - 4 : columns);
|
|
781
|
-
}
|
|
782
|
-
function Window({
|
|
783
|
-
name,
|
|
907
|
+
// packages/cli/src/terminal-layout.tsx
|
|
908
|
+
import { Box as Box8, Text as Text8, useWindowSize as useWindowSize2 } from "ink";
|
|
909
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
910
|
+
function TerminalLayout({
|
|
784
911
|
path,
|
|
785
912
|
nav = [],
|
|
786
913
|
children
|
|
787
914
|
}) {
|
|
788
|
-
const { columns, rows } =
|
|
915
|
+
const { columns, rows } = useWindowSize2();
|
|
789
916
|
const { colors } = useTerminalStyle();
|
|
790
|
-
const width = windowWidth(columns);
|
|
791
|
-
const margin = columns >= 80 && rows >= 24 ? 1 : 0;
|
|
792
917
|
const directory = `~/miantiao.me${path === "/" ? "" : path.replace(/\/$/, "")}`;
|
|
793
|
-
return /* @__PURE__ */
|
|
794
|
-
|
|
918
|
+
return /* @__PURE__ */ jsxs8(
|
|
919
|
+
Box8,
|
|
795
920
|
{
|
|
796
921
|
width: columns,
|
|
797
922
|
height: rows,
|
|
798
923
|
flexDirection: "column",
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
)
|
|
924
|
+
backgroundColor: colors.bg,
|
|
925
|
+
overflow: "hidden",
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsxs8(Box8, { paddingX: 2, flexShrink: 0, children: [
|
|
928
|
+
/* @__PURE__ */ jsx9(Box8, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx9(Text8, { color: colors.muted, wrap: "truncate-middle", children: cleanText(directory) }) }),
|
|
929
|
+
columns >= 70 && nav.map((link) => /* @__PURE__ */ jsx9(Box8, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text8, { color: path.startsWith(link.href) ? colors.fg : colors.muted, children: cleanText(link.title) }) }, link.href))
|
|
930
|
+
] }),
|
|
931
|
+
children
|
|
932
|
+
]
|
|
842
933
|
}
|
|
843
934
|
);
|
|
844
935
|
}
|
|
845
936
|
|
|
846
937
|
// packages/cli/src/console.tsx
|
|
847
|
-
import { jsx as
|
|
938
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
848
939
|
function Console({
|
|
849
940
|
runtime,
|
|
850
941
|
page,
|
|
@@ -854,15 +945,16 @@ function Console({
|
|
|
854
945
|
loading,
|
|
855
946
|
notice,
|
|
856
947
|
onCommand,
|
|
948
|
+
onCancel,
|
|
857
949
|
onSelect
|
|
858
950
|
}) {
|
|
859
|
-
const scroll =
|
|
860
|
-
return /* @__PURE__ */
|
|
861
|
-
/* @__PURE__ */
|
|
862
|
-
/* @__PURE__ */
|
|
863
|
-
/* @__PURE__ */
|
|
951
|
+
const scroll = useRef5(null);
|
|
952
|
+
return /* @__PURE__ */ jsx10(TerminalLayout, { path: page.path, nav: site.nav, children: /* @__PURE__ */ jsxs9(ThreadPrimitive2.Root, { flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
|
|
953
|
+
/* @__PURE__ */ jsxs9(ScrollArea, { ref: scroll, selection: page.items.length > 0, children: [
|
|
954
|
+
/* @__PURE__ */ jsx10(PageContent, { page, prompt, onSelect, scroll }),
|
|
955
|
+
/* @__PURE__ */ jsx10(ChatThread, {})
|
|
864
956
|
] }, `${page.path}:${revision}`),
|
|
865
|
-
/* @__PURE__ */
|
|
957
|
+
/* @__PURE__ */ jsx10(
|
|
866
958
|
Composer,
|
|
867
959
|
{
|
|
868
960
|
runtime,
|
|
@@ -871,6 +963,7 @@ function Console({
|
|
|
871
963
|
loading,
|
|
872
964
|
notice,
|
|
873
965
|
onCommand,
|
|
966
|
+
onCancel,
|
|
874
967
|
onSubmit: () => scroll.current?.end()
|
|
875
968
|
}
|
|
876
969
|
)
|
|
@@ -878,13 +971,6 @@ function Console({
|
|
|
878
971
|
}
|
|
879
972
|
|
|
880
973
|
// packages/cli/src/pages.ts
|
|
881
|
-
var terminalCommands = [
|
|
882
|
-
...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
|
|
883
|
-
{ id: "read", args: "<\u7F16\u53F7\u3001slug \u6216\u8DEF\u5F84>", description: "\u9605\u8BFB\u6587\u7AE0\u5168\u6587" },
|
|
884
|
-
{ id: "open", args: "[\u7F16\u53F7\u6216 URL]", description: "\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5F53\u524D\u9875\u9762\u6216\u94FE\u63A5" },
|
|
885
|
-
{ id: "refresh", description: "\u4ECE\u5B98\u7F51\u5237\u65B0\u5F53\u524D\u9875\u9762" },
|
|
886
|
-
{ id: "quit", description: "\u9000\u51FA" }
|
|
887
|
-
];
|
|
888
974
|
function helpPage() {
|
|
889
975
|
return {
|
|
890
976
|
path: "/help/",
|
|
@@ -893,7 +979,7 @@ function helpPage() {
|
|
|
893
979
|
items: [],
|
|
894
980
|
markdown: terminalCommands.map(
|
|
895
981
|
(command) => `- \`/${command.id}${command.args ? ` ${command.args}` : ""}\` \u2014 ${command.description}`
|
|
896
|
-
).join("\n") + "\n\
|
|
982
|
+
).join("\n") + "\n\n/ \u6253\u5F00\u547D\u4EE4\u9762\u677F\uFF1B\u2191\u2193 \u9009\u62E9\u3001Enter \u786E\u8BA4\u3001Esc \u5173\u95ED\u3002PgUp/PgDn \u6EDA\u52A8\u6B63\u6587\uFF1BTab \u5207\u6362\u8F93\u5165\u548C\u5185\u5BB9\uFF1BEsc \u505C\u6B62\uFF1BCtrl+C \u9000\u51FA\u3002"
|
|
897
983
|
};
|
|
898
984
|
}
|
|
899
985
|
function themePage() {
|
|
@@ -987,12 +1073,12 @@ async function loadPage(client, path, signal) {
|
|
|
987
1073
|
}
|
|
988
1074
|
|
|
989
1075
|
// packages/cli/src/session.ts
|
|
990
|
-
import { useApp, useInput as
|
|
991
|
-
import { useState as
|
|
1076
|
+
import { useApp, useInput as useInput5 } from "ink";
|
|
1077
|
+
import { useState as useState5 } from "react";
|
|
992
1078
|
|
|
993
1079
|
// packages/cli/src/agent.ts
|
|
994
1080
|
import { useChatRuntime } from "@assistant-ui/ai-sdk";
|
|
995
|
-
import { useEffect as
|
|
1081
|
+
import { useEffect as useEffect3, useMemo as useMemo3 } from "react";
|
|
996
1082
|
|
|
997
1083
|
// src/agent/transport.ts
|
|
998
1084
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
@@ -1181,7 +1267,7 @@ function useTerminalAgent({ site, index, page, theme, setTheme, navigate }) {
|
|
|
1181
1267
|
]
|
|
1182
1268
|
);
|
|
1183
1269
|
const runtime = useChatRuntime({ transport });
|
|
1184
|
-
|
|
1270
|
+
useEffect3(() => () => runtime.thread.cancelRun(), [runtime]);
|
|
1185
1271
|
return runtime;
|
|
1186
1272
|
}
|
|
1187
1273
|
|
|
@@ -1202,43 +1288,17 @@ async function openBrowser(input, site) {
|
|
|
1202
1288
|
});
|
|
1203
1289
|
}
|
|
1204
1290
|
|
|
1205
|
-
// packages/cli/src/commands.ts
|
|
1206
|
-
function numberedItem(input, page) {
|
|
1207
|
-
if (!/^\d+$/.test(input)) return void 0;
|
|
1208
|
-
const item = page.items[Number(input) - 1];
|
|
1209
|
-
if (!item) throw new Error("\u5F53\u524D\u5217\u8868\u6CA1\u6709\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u8BF7\u4F7F\u7528\u5217\u8868\u4E2D\u663E\u793A\u7684\u7F16\u53F7\u3002");
|
|
1210
|
-
return item;
|
|
1211
|
-
}
|
|
1212
|
-
function resolveReadPath(input, page) {
|
|
1213
|
-
if (!input) throw new Error("\u8F93\u5165 /read <\u7F16\u53F7\u3001slug \u6216\u7AD9\u5185\u8DEF\u5F84> \u9605\u8BFB\u6587\u7AE0\u3002");
|
|
1214
|
-
const item = numberedItem(input, page);
|
|
1215
|
-
if (item) {
|
|
1216
|
-
if (item.external || !item.path.startsWith("/posts/"))
|
|
1217
|
-
throw new Error("\u8FD9\u4E2A\u7F16\u53F7\u4E0D\u662F\u6587\u7AE0\uFF0C\u8BF7\u4F7F\u7528 /blog \u6216 /search \u67E5\u627E\u6587\u7AE0\u3002");
|
|
1218
|
-
return item.path;
|
|
1219
|
-
}
|
|
1220
|
-
return input.startsWith("/") ? input : `/posts/${input.replace(/\/$/, "")}/`;
|
|
1221
|
-
}
|
|
1222
|
-
function resolveOpenTarget(input, page) {
|
|
1223
|
-
return numberedItem(input, page)?.path ?? (input || page.path);
|
|
1224
|
-
}
|
|
1225
|
-
function resolveTagPath(input, index) {
|
|
1226
|
-
const tag = index.tags.find((tag2) => tag2.name === input);
|
|
1227
|
-
if (!tag) throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u6807\u7B7E\uFF0C\u8BF7\u4F7F\u7528 /tags \u67E5\u770B\u53EF\u7528\u6807\u7B7E\u3002");
|
|
1228
|
-
return tag.path;
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
1291
|
// packages/cli/src/navigation.ts
|
|
1232
|
-
import { useCallback, useEffect as
|
|
1292
|
+
import { useCallback, useEffect as useEffect4, useRef as useRef6, useState as useState4 } from "react";
|
|
1233
1293
|
function useNavigation(client, initial) {
|
|
1234
|
-
const [state, setState] =
|
|
1294
|
+
const [state, setState] = useState4({
|
|
1235
1295
|
...initial,
|
|
1236
1296
|
status: "idle",
|
|
1237
1297
|
notice: client.warning,
|
|
1238
1298
|
prompt: "/home"
|
|
1239
1299
|
});
|
|
1240
|
-
const active =
|
|
1241
|
-
|
|
1300
|
+
const active = useRef6(void 0);
|
|
1301
|
+
useEffect4(
|
|
1242
1302
|
() => () => {
|
|
1243
1303
|
active.current?.abort();
|
|
1244
1304
|
active.current = void 0;
|
|
@@ -1293,23 +1353,23 @@ function useNavigation(client, initial) {
|
|
|
1293
1353
|
// packages/cli/src/session.ts
|
|
1294
1354
|
function useTerminalSession(client, initial) {
|
|
1295
1355
|
const { exit } = useApp();
|
|
1296
|
-
const [theme, setTheme] =
|
|
1356
|
+
const [theme, setTheme] = useState5("auto");
|
|
1297
1357
|
const { index, page, prompt, status, notice, navigate, show, cancel, notify } = useNavigation(client, initial);
|
|
1298
1358
|
const runtime = useTerminalAgent({ site: client.site, index, page, theme, setTheme, navigate });
|
|
1299
1359
|
const stop = () => {
|
|
1300
1360
|
cancel();
|
|
1301
1361
|
runtime.thread.cancelRun();
|
|
1302
1362
|
};
|
|
1303
|
-
|
|
1304
|
-
if (key.escape) {
|
|
1305
|
-
stop();
|
|
1306
|
-
notify("\u5DF2\u505C\u6B62\u3002");
|
|
1307
|
-
}
|
|
1363
|
+
useInput5((input, key) => {
|
|
1308
1364
|
if (key.ctrl && input === "c") {
|
|
1309
1365
|
stop();
|
|
1310
1366
|
exit();
|
|
1311
1367
|
}
|
|
1312
1368
|
});
|
|
1369
|
+
const onCancel = () => {
|
|
1370
|
+
stop();
|
|
1371
|
+
notify("\u5DF2\u505C\u6B62\u3002");
|
|
1372
|
+
};
|
|
1313
1373
|
const onCommand = async (name, args) => {
|
|
1314
1374
|
try {
|
|
1315
1375
|
if (pageCommands[name] && !(name === "tags" && args)) {
|
|
@@ -1377,29 +1437,30 @@ function useTerminalSession(client, initial) {
|
|
|
1377
1437
|
loading: status === "loading",
|
|
1378
1438
|
notice,
|
|
1379
1439
|
onCommand,
|
|
1440
|
+
onCancel,
|
|
1380
1441
|
onSelect,
|
|
1381
1442
|
theme
|
|
1382
1443
|
};
|
|
1383
1444
|
}
|
|
1384
1445
|
|
|
1385
1446
|
// packages/cli/src/app.tsx
|
|
1386
|
-
import { jsx as
|
|
1447
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1387
1448
|
function Session({
|
|
1388
1449
|
client,
|
|
1389
1450
|
initial,
|
|
1390
1451
|
width
|
|
1391
1452
|
}) {
|
|
1392
1453
|
const { theme, ...session } = useTerminalSession(client, initial);
|
|
1393
|
-
return /* @__PURE__ */
|
|
1454
|
+
return /* @__PURE__ */ jsx11(TerminalTheme, { preference: theme, width, children: /* @__PURE__ */ jsx11(AssistantRuntimeProvider, { runtime: session.runtime, children: /* @__PURE__ */ jsx11(Console, { ...session }) }) });
|
|
1394
1455
|
}
|
|
1395
1456
|
function App({ site }) {
|
|
1396
1457
|
const { exit } = useApp2();
|
|
1397
|
-
const { columns } =
|
|
1398
|
-
const [client] =
|
|
1399
|
-
const [state, setState] =
|
|
1400
|
-
const [attempt, setAttempt] =
|
|
1401
|
-
const width = Math.max(10,
|
|
1402
|
-
|
|
1458
|
+
const { columns } = useWindowSize3();
|
|
1459
|
+
const [client] = useState6(() => new TerminalClient(site));
|
|
1460
|
+
const [state, setState] = useState6({ status: "loading" });
|
|
1461
|
+
const [attempt, setAttempt] = useState6(0);
|
|
1462
|
+
const width = Math.max(10, columns - 5);
|
|
1463
|
+
useEffect5(() => {
|
|
1403
1464
|
const request = new AbortController();
|
|
1404
1465
|
loadPage(client, "/", request.signal).then((content) => {
|
|
1405
1466
|
if (!request.signal.aborted) setState({ status: "ready", content });
|
|
@@ -1412,7 +1473,7 @@ function App({ site }) {
|
|
|
1412
1473
|
});
|
|
1413
1474
|
return () => request.abort();
|
|
1414
1475
|
}, [client, attempt]);
|
|
1415
|
-
|
|
1476
|
+
useInput6(
|
|
1416
1477
|
(input, key) => {
|
|
1417
1478
|
if (key.ctrl && input === "c" || input === "q") exit();
|
|
1418
1479
|
else if (state.status === "error" && (key.return || input === "r" || input === "/refresh")) {
|
|
@@ -1423,17 +1484,17 @@ function App({ site }) {
|
|
|
1423
1484
|
{ isActive: state.status !== "ready" }
|
|
1424
1485
|
);
|
|
1425
1486
|
if (state.status === "ready")
|
|
1426
|
-
return /* @__PURE__ */
|
|
1427
|
-
return /* @__PURE__ */
|
|
1428
|
-
/* @__PURE__ */
|
|
1487
|
+
return /* @__PURE__ */ jsx11(Session, { client, initial: state.content, width });
|
|
1488
|
+
return /* @__PURE__ */ jsx11(TerminalTheme, { preference: "auto", width, children: /* @__PURE__ */ jsx11(TerminalLayout, { path: "/", children: /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", flexGrow: 1, padding: 2, children: [
|
|
1489
|
+
/* @__PURE__ */ jsxs10(Text9, { children: [
|
|
1429
1490
|
"\u25C6 ",
|
|
1430
1491
|
state.status === "error" ? state.message : "\u6B63\u5728\u8BFB\u53D6\u5B98\u7F51\u2026"
|
|
1431
1492
|
] }),
|
|
1432
|
-
/* @__PURE__ */
|
|
1493
|
+
/* @__PURE__ */ jsx11(Text9, { children: state.status === "error" ? "Enter \u91CD\u8BD5 \xB7 q \u9000\u51FA" : "Ctrl+C \u9000\u51FA" })
|
|
1433
1494
|
] }) }) });
|
|
1434
1495
|
}
|
|
1435
1496
|
async function run(site) {
|
|
1436
|
-
const app = render(/* @__PURE__ */
|
|
1497
|
+
const app = render(/* @__PURE__ */ jsx11(App, { site }), {
|
|
1437
1498
|
exitOnCtrlC: false,
|
|
1438
1499
|
alternateScreen: true,
|
|
1439
1500
|
incrementalRendering: true
|
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.
|
|
19
|
+
console.log("0.1.2");
|
|
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-
|
|
26
|
+
const { run } = await import("./app-IW4THTCR.js");
|
|
27
27
|
await run(site.origin);
|
|
28
28
|
}
|
|
29
29
|
} catch (error) {
|