miantiao.me 0.2.0 → 0.3.0

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.
@@ -293,13 +293,6 @@ import { useRef as useRef8 } from "react";
293
293
  import { ThreadPrimitive, useAuiState } from "@assistant-ui/react-ink";
294
294
  import { Box as Box4, Text as Text4 } from "ink";
295
295
 
296
- // packages/cli/src/theme.tsx
297
- import { createContext, useContext, useMemo } from "react";
298
- import {
299
- MarkdownText,
300
- useShikiHighlighter
301
- } from "@assistant-ui/react-ink-markdown";
302
-
303
296
  // src/theme-data.ts
304
297
  var themes = [
305
298
  { id: "groknight", name: "GrokNight", description: "\u4E2D\u6027\u6697\u8272\uFF0C\u9ED8\u8BA4\u4E3B\u9898", dark: true, color: "#141414" },
@@ -331,7 +324,7 @@ function parseThemePreference(input) {
331
324
  const name = input?.trim().toLowerCase();
332
325
  if (!name)
333
326
  return void 0;
334
- return themeOptions.find((option) => option.id === name)?.id ?? aliases[name];
327
+ return themeOptions.find((option) => option.id === name)?.id ?? (Object.hasOwn(aliases, name) ? aliases[name] : void 0);
335
328
  }
336
329
  function resolveTheme(preference, prefersDark2) {
337
330
  if (preference !== "auto")
@@ -342,7 +335,27 @@ function getThemeName(id) {
342
335
  return themeOptions.find((option) => option.id === id)?.name ?? id;
343
336
  }
344
337
 
338
+ // src/agent/tool-header.ts
339
+ function getToolHeader({ toolName, args }) {
340
+ switch (toolName) {
341
+ case "set_theme": {
342
+ const theme = typeof args?.theme === "string" ? args.theme : "";
343
+ const preference = parseThemePreference(theme);
344
+ return { verb: "Theme", target: preference ? getThemeName(preference) : theme };
345
+ }
346
+ case "open_page":
347
+ return { verb: "Open", target: typeof args?.path === "string" ? args.path : "" };
348
+ default:
349
+ return { verb: toolName, target: "" };
350
+ }
351
+ }
352
+
345
353
  // packages/cli/src/theme.tsx
354
+ import { createContext, useContext, useMemo } from "react";
355
+ import {
356
+ MarkdownText,
357
+ useShikiHighlighter
358
+ } from "@assistant-ui/react-ink-markdown";
346
359
  import { stripVTControlCharacters } from "node:util";
347
360
  import { jsx } from "react/jsx-runtime";
348
361
  function cleanText(text) {
@@ -608,7 +621,6 @@ function HomeSections({ page, ...selection }) {
608
621
 
609
622
  // packages/cli/src/chat.tsx
610
623
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
611
- var toolTitles = { set_theme: "Theme", open_page: "Open" };
612
624
  function MessageBody({ message }) {
613
625
  const { colors } = useTerminalStyle();
614
626
  if (message.role === "user") {
@@ -630,12 +642,13 @@ function MessageBody({ message }) {
630
642
  );
631
643
  }
632
644
  if (part.type === "tool-call") {
633
- const title = toolTitles[part.toolName] ?? part.toolName;
645
+ const { verb, target } = getToolHeader(part);
634
646
  return /* @__PURE__ */ jsxs4(Text4, { color: colors.skill, children: [
635
647
  "\u25C6 ",
636
- cleanText(title),
648
+ cleanText(verb),
637
649
  " ",
638
- cleanText(part.result === void 0 ? part.argsText : String(part.result))
650
+ cleanText(target),
651
+ part.result !== void 0 && ` (${cleanText(String(part.result))})`
639
652
  ] }, part.toolCallId);
640
653
  }
641
654
  return null;
@@ -668,6 +681,14 @@ import { useEffect as useEffect5, useEffectEvent, useRef as useRef6 } from "reac
668
681
  var composerPlaceholder = "\u95EE\u70B9\u4EC0\u4E48\uFF0C\u6216\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4";
669
682
  var composerBrand = { mark: "<MT/>", model: "V3.14 Pro" };
670
683
 
684
+ // src/utils/tags.ts
685
+ function getTagSlug(tag) {
686
+ return encodeURIComponent(tag);
687
+ }
688
+ function getTagPath(tag) {
689
+ return `/tags/${getTagSlug(tag)}/`;
690
+ }
691
+
671
692
  // src/agent/commands.ts
672
693
  var commands = [
673
694
  { id: "help", description: "\u67E5\u770B\u53EF\u7528\u547D\u4EE4" },
@@ -676,7 +697,7 @@ var commands = [
676
697
  { id: "home", description: "\u6253\u5F00\u9996\u9875" },
677
698
  { id: "blog", description: "\u6D4F\u89C8\u5168\u90E8\u6587\u7AE0" },
678
699
  { id: "projects", description: "\u67E5\u770B\u6298\u817E\u7684\u9879\u76EE" },
679
- { id: "tags", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
700
+ { id: "tags", args: "[\u6807\u7B7E]", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
680
701
  { id: "search", args: "[\u5173\u952E\u8BCD]", description: "\u641C\u7D22\u6587\u7AE0" },
681
702
  { id: "about", description: "\u5173\u4E8E\u672C\u7AD9" },
682
703
  { id: "rss", description: "\u8BA2\u9605 RSS" }
@@ -688,6 +709,13 @@ var pageCommands = {
688
709
  tags: "/tags/",
689
710
  about: "/about/"
690
711
  };
712
+ function getCommandPath(name, args = "") {
713
+ if (name === "search")
714
+ return getSearchPath(args);
715
+ if (name === "tags" && args)
716
+ return getTagPath(args);
717
+ return Object.hasOwn(pageCommands, name) ? pageCommands[name] : void 0;
718
+ }
691
719
  function parseCommand(input) {
692
720
  const match = input.trim().match(/^\/(\S+)(?:\s+([\s\S]*))?$/);
693
721
  if (!match)
@@ -704,7 +732,7 @@ import { useEffect as useEffect2, useImperativeHandle as useImperativeHandle2, u
704
732
 
705
733
  // packages/cli/src/commands.ts
706
734
  var terminalCommands = [
707
- ...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
735
+ ...commands,
708
736
  { id: "read", args: "<\u7F16\u53F7\u3001slug \u6216\u8DEF\u5F84>", description: "\u9605\u8BFB\u6587\u7AE0\u5168\u6587" },
709
737
  { id: "open", args: "[\u7F16\u53F7\u6216 URL]", description: "\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5F53\u524D\u9875\u9762\u6216\u94FE\u63A5" },
710
738
  { id: "refresh", description: "\u4ECE\u5B98\u7F51\u5237\u65B0\u5F53\u524D\u9875\u9762" },
@@ -1144,28 +1172,44 @@ function TerminalPanels({
1144
1172
  // packages/cli/src/run-status.tsx
1145
1173
  import { LoadingPrimitive } from "@assistant-ui/react-ink";
1146
1174
  import { Box as Box9, Text as Text9 } from "ink";
1175
+
1176
+ // src/agent/useElapsedTime.ts
1147
1177
  import { useEffect as useEffect4, useState as useState7 } from "react";
1148
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1149
- function RunStatus({ running, loading }) {
1150
- const { colors } = useTerminalStyle();
1178
+ function useElapsedTime(active, live = false) {
1151
1179
  const [seconds, setSeconds] = useState7(0);
1152
1180
  useEffect4(() => {
1153
- if (!running) return;
1154
- const started = Date.now();
1181
+ if (!active)
1182
+ return;
1183
+ const startedAt = Date.now();
1184
+ const elapsed = () => (Date.now() - startedAt) / 1e3;
1185
+ const finish = () => setSeconds(elapsed());
1155
1186
  setSeconds(0);
1156
- const timer = setInterval(() => setSeconds(Math.floor((Date.now() - started) / 1e3)), 1e3);
1157
- return () => clearInterval(timer);
1158
- }, [running]);
1187
+ if (!live)
1188
+ return finish;
1189
+ const timer = setInterval(finish, 1e3);
1190
+ return () => {
1191
+ clearInterval(timer);
1192
+ finish();
1193
+ };
1194
+ }, [active, live]);
1195
+ return seconds;
1196
+ }
1197
+
1198
+ // packages/cli/src/run-status.tsx
1199
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1200
+ function RunStatus({ running, loading }) {
1201
+ const { colors } = useTerminalStyle();
1202
+ const seconds = useElapsedTime(running, true);
1159
1203
  if (!running && !loading) return null;
1160
1204
  return /* @__PURE__ */ jsxs9(Box9, { paddingLeft: 2, height: 1, children: [
1161
1205
  /* @__PURE__ */ jsx10(LoadingPrimitive.Spinner, {}),
1162
1206
  /* @__PURE__ */ jsxs9(Text9, { color: colors.fg, children: [
1163
1207
  " ",
1164
- loading ? "\u601D\u8003\u4E2D\u2026\u2026" : "\u56DE\u590D\u4E2D\u2026"
1208
+ loading ? "\u601D\u8003\u4E2D\u2026" : "\u56DE\u590D\u4E2D\u2026"
1165
1209
  ] }),
1166
1210
  running && /* @__PURE__ */ jsxs9(Text9, { color: colors.muted, children: [
1167
1211
  " ",
1168
- seconds,
1212
+ Math.floor(seconds),
1169
1213
  "s"
1170
1214
  ] }),
1171
1215
  /* @__PURE__ */ jsx10(Box9, { flexGrow: 1 }),
@@ -1535,6 +1579,42 @@ async function loadPage(client, path, signal) {
1535
1579
  import { useApp, useInput as useInput8 } from "ink";
1536
1580
  import { useCallback as useCallback2, useState as useState10 } from "react";
1537
1581
 
1582
+ // src/agent/command-actions.ts
1583
+ async function runSharedCommand(name, args, actions) {
1584
+ const path = getCommandPath(name, args);
1585
+ if (path) {
1586
+ actions.setPanel(null);
1587
+ await actions.navigate(path, `/${name} ${args}`.trim());
1588
+ return true;
1589
+ }
1590
+ switch (name) {
1591
+ case "help":
1592
+ case "rss":
1593
+ actions.setPanel({ type: name });
1594
+ return true;
1595
+ case "theme": {
1596
+ if (!args) {
1597
+ actions.setPanel({ type: "theme" });
1598
+ return true;
1599
+ }
1600
+ const preference = parseThemePreference(args);
1601
+ if (preference) {
1602
+ actions.setTheme(preference);
1603
+ actions.setPanel({ type: "notice", text: `\u5DF2\u5207\u6362\u5230 ${getThemeName(preference)}` });
1604
+ } else {
1605
+ actions.setPanel({ type: "notice", tone: "error", text: `\u672A\u77E5\u4E3B\u9898\uFF1A${args}\u3002\u53EF\u9009\uFF1A${themeOptions.map((option) => option.id).join("\u3001")}` });
1606
+ }
1607
+ return true;
1608
+ }
1609
+ case "clear":
1610
+ actions.clear();
1611
+ actions.setPanel(null);
1612
+ return true;
1613
+ default:
1614
+ return false;
1615
+ }
1616
+ }
1617
+
1538
1618
  // packages/cli/src/agent.ts
1539
1619
  import { useChatRuntime } from "@assistant-ui/ai-sdk";
1540
1620
  import { useEffect as useEffect7, useMemo as useMemo3 } from "react";
@@ -1853,32 +1933,16 @@ function useTerminalSession(client, initial) {
1853
1933
  const onCommand = async (name, args) => {
1854
1934
  setPreviewTheme(void 0);
1855
1935
  try {
1856
- if (pageCommands[name] && !(name === "tags" && args)) {
1857
- await navigate(pageCommands[name], `/${name}`);
1858
- return;
1859
- }
1860
- switch (name) {
1861
- case "help":
1862
- case "rss":
1863
- setPanel({ type: name });
1864
- break;
1865
- case "theme": {
1866
- const preference = parseThemePreference(args);
1867
- if (!args) setPanel({ type: "theme" });
1868
- else if (preference) onApplyTheme(preference);
1869
- else
1870
- notify(
1871
- `\u672A\u77E5\u4E3B\u9898\uFF1A${args}\u3002\u53EF\u9009\uFF1A${themeOptions.map((option) => option.id).join("\u3001")}`,
1872
- "error"
1873
- );
1874
- break;
1936
+ if (await runSharedCommand(name, args, {
1937
+ navigate: (path, prompt2) => navigate(name === "tags" && args ? resolveTagPath(args, index) : path, prompt2),
1938
+ setPanel,
1939
+ setTheme,
1940
+ clear: () => {
1941
+ stop();
1942
+ runtime.thread.reset();
1875
1943
  }
1876
- case "tags":
1877
- await navigate(resolveTagPath(args, index), `/tags ${args}`);
1878
- break;
1879
- case "search":
1880
- await navigate(getSearchPath(args), `/search ${args}`.trim());
1881
- break;
1944
+ })) return;
1945
+ switch (name) {
1882
1946
  case "read":
1883
1947
  await navigate(resolveReadPath(args, page), `/read ${args}`);
1884
1948
  break;
@@ -1888,11 +1952,6 @@ function useTerminalSession(client, initial) {
1888
1952
  case "open":
1889
1953
  await openBrowser(resolveOpenTarget(args, page), client.site);
1890
1954
  break;
1891
- case "clear":
1892
- stop();
1893
- runtime.thread.reset();
1894
- onClosePanel();
1895
- break;
1896
1955
  case "quit":
1897
1956
  stop();
1898
1957
  exit();
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.2.0");
19
+ console.log("0.3.0");
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-YAGEJAXF.js");
26
+ const { run } = await import("./app-VCJ3SOSQ.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.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "在终端浏览 miantiao.me,与面条的 AI 助手对话。",
5
5
  "type": "module",
6
6
  "bin": {