miantiao.me 0.2.0 → 0.4.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.
@@ -1,1998 +0,0 @@
1
- import {
2
- define_TERMINAL_PALETTES_default
3
- } from "./chunk-ZOMIOTOE.js";
4
-
5
- // packages/cli/src/app.tsx
6
- import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
7
- import { Box as Box14, render, Text as Text14, useApp as useApp2, useInput as useInput9, useWindowSize as useWindowSize4 } from "ink";
8
- import { useEffect as useEffect9, useState as useState11 } from "react";
9
-
10
- // src/terminal/schema.ts
11
- import { z } from "zod";
12
- var terminalApiPath = "/api/terminal/v1";
13
- var linkSchema = z.object({ title: z.string(), href: z.string() });
14
- var pageSchema = z.object({
15
- path: z.string(),
16
- title: z.string(),
17
- documentUrl: z.string()
18
- });
19
- var terminalIndexSchema = z.object({
20
- schemaVersion: z.literal(1),
21
- revision: z.string().min(1),
22
- site: z.object({
23
- name: z.string(),
24
- description: z.string(),
25
- url: z.string(),
26
- feedUrl: z.string(),
27
- author: z.object({ name: z.string(), roles: z.array(z.string()) }),
28
- nav: z.array(linkSchema),
29
- socials: z.array(linkSchema),
30
- friends: z.array(linkSchema)
31
- }),
32
- ai: z.object({ baseUrl: z.string(), model: z.string() }),
33
- pages: z.object({ home: pageSchema, about: pageSchema }),
34
- posts: z.array(z.object({
35
- slug: z.string(),
36
- title: z.string(),
37
- description: z.string(),
38
- date: z.string(),
39
- tags: z.array(z.string()),
40
- path: z.string(),
41
- url: z.string(),
42
- documentUrl: z.string()
43
- })),
44
- projects: z.array(z.object({
45
- id: z.string(),
46
- title: z.string(),
47
- description: z.string(),
48
- url: z.string(),
49
- repo: z.string(),
50
- stars: z.number()
51
- })),
52
- tags: z.array(z.object({ name: z.string(), path: z.string(), count: z.number().int().nonnegative() })),
53
- searchIndexUrl: z.string()
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
- ]);
64
- var terminalDocumentSchema = z.object({
65
- schemaVersion: z.literal(1),
66
- revision: z.string().min(1),
67
- path: z.string(),
68
- title: z.string(),
69
- markdown: z.string().min(1),
70
- sections: z.array(sectionSchema).optional()
71
- });
72
-
73
- // src/utils/search-core.ts
74
- import { Document } from "flexsearch";
75
- var SearchManager = class _SearchManager {
76
- index;
77
- dataById;
78
- constructor(searchData) {
79
- this.index = new Document({
80
- document: {
81
- id: "id",
82
- index: [
83
- { field: "searchTitle", tokenize: "forward", resolution: 9 },
84
- { field: "searchDescription", tokenize: "strict", resolution: 7 },
85
- {
86
- field: "searchContent",
87
- tokenize: "forward",
88
- resolution: 5,
89
- context: {
90
- depth: 1,
91
- resolution: 3,
92
- bidirectional: true
93
- }
94
- },
95
- { field: "searchTags", tokenize: "strict", resolution: 9 }
96
- ]
97
- },
98
- cache: 100,
99
- fastupdate: false
100
- });
101
- this.dataById = new Map(searchData.map((doc) => [doc.id, doc]));
102
- searchData.forEach((doc) => this.index.add(doc));
103
- }
104
- static fromData(data) {
105
- return new _SearchManager(data);
106
- }
107
- search(query, limit = 20) {
108
- const trimmedQuery = query.trim();
109
- if (!trimmedQuery || limit <= 0) return [];
110
- const phraseTerms = /[\u4E00-\u9FFF]{4,}/.test(trimmedQuery) ? trimmedQuery.toLowerCase().match(/[\u4E00-\u9FFF]+|[a-z0-9]+/g) : null;
111
- const matchesPhrase = (document) => {
112
- if (!phraseTerms) return true;
113
- const fields = [document.title, document.description, document.content, ...document.tags];
114
- const words = new Set(
115
- fields.flatMap((field) => field.toLowerCase().match(/[a-z0-9]+/g) ?? [])
116
- );
117
- return phraseTerms.every(
118
- (term) => /^[a-z0-9]+$/.test(term) ? words.has(term) : fields.some((field) => field.includes(term))
119
- );
120
- };
121
- const results = this.index.search(trimmedQuery, {
122
- limit,
123
- suggest: true
124
- });
125
- const seenIds = /* @__PURE__ */ new Set();
126
- const documents = [];
127
- for (const fieldResult of results) {
128
- for (const docId of fieldResult.result) {
129
- const id = String(docId);
130
- if (seenIds.has(id)) continue;
131
- const document = this.dataById.get(id);
132
- if (!document || !matchesPhrase(document)) continue;
133
- seenIds.add(id);
134
- documents.push(document);
135
- if (documents.length >= limit) return documents;
136
- }
137
- }
138
- if (phraseTerms) {
139
- for (const document of this.dataById.values()) {
140
- if (seenIds.has(document.id) || !matchesPhrase(document)) continue;
141
- seenIds.add(document.id);
142
- documents.push(document);
143
- if (documents.length >= limit) break;
144
- }
145
- }
146
- return documents;
147
- }
148
- };
149
-
150
- // src/utils/search-schema.ts
151
- import { z as z2 } from "zod";
152
- var searchIndexSchema = z2.array(
153
- z2.object({
154
- id: z2.string(),
155
- title: z2.string(),
156
- description: z2.string(),
157
- content: z2.string(),
158
- url: z2.string(),
159
- date: z2.string(),
160
- tags: z2.array(z2.string()),
161
- searchTitle: z2.string(),
162
- searchDescription: z2.string(),
163
- searchContent: z2.string(),
164
- searchTags: z2.string()
165
- })
166
- );
167
-
168
- // packages/cli/src/client.ts
169
- var ContentError = class extends Error {
170
- constructor(message, status) {
171
- super(message);
172
- this.status = status;
173
- }
174
- status;
175
- };
176
- var TerminalClient = class {
177
- constructor(site, fetcher = fetch) {
178
- this.site = site;
179
- this.fetcher = fetcher;
180
- }
181
- site;
182
- fetcher;
183
- cache = /* @__PURE__ */ new Map();
184
- pending = /* @__PURE__ */ new Map();
185
- revision;
186
- searchManager;
187
- warning = "";
188
- async request(path, schema, signal) {
189
- signal?.throwIfAborted();
190
- const url = new URL(path, this.site).href;
191
- const running = this.pending.get(url);
192
- if (running && running.signal === signal) return running.promise;
193
- const promise = this.fetchDocument(url, schema, signal);
194
- this.pending.set(url, { promise, signal });
195
- try {
196
- return await promise;
197
- } finally {
198
- if (this.pending.get(url)?.promise === promise) this.pending.delete(url);
199
- }
200
- }
201
- async fetchDocument(url, schema, signal) {
202
- const cached = this.cache.get(url);
203
- const headers = new Headers({ Accept: "application/json" });
204
- if (cached?.etag) headers.set("If-None-Match", cached.etag);
205
- else if (cached?.modified) headers.set("If-Modified-Since", cached.modified);
206
- try {
207
- const response = await this.fetcher(url, {
208
- headers,
209
- signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
210
- });
211
- if (response.status >= 500) {
212
- await response.body?.cancel();
213
- throw new Error(`HTTP ${response.status}`);
214
- }
215
- if (response.status === 304 && cached) return cached.value;
216
- if (!response.ok) {
217
- await response.body?.cancel();
218
- throw new ContentError(
219
- response.status === 404 ? "\u5B98\u7F51\u5C1A\u672A\u63D0\u4F9B\u8BE5\u5185\u5BB9\uFF0C\u6216\u5185\u5BB9\u5DF2\u66F4\u65B0\uFF1B\u8BF7\u4F7F\u7528 /refresh \u91CD\u8BD5\u3002" : `\u5B98\u7F51\u5185\u5BB9\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${response.status}\uFF09\u3002`,
220
- response.status
221
- );
222
- }
223
- signal?.throwIfAborted();
224
- const body = await response.json();
225
- signal?.throwIfAborted();
226
- const value = schema.safeParse(body);
227
- if (!value.success)
228
- throw new ContentError("\u5B98\u7F51\u5185\u5BB9\u683C\u5F0F\u4E0E\u5F53\u524D CLI \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u8FD0\u884C npx miantiao.me@latest\u3002");
229
- this.cache.set(url, {
230
- value: value.data,
231
- etag: response.headers.get("etag"),
232
- modified: response.headers.get("last-modified")
233
- });
234
- return value.data;
235
- } catch (error) {
236
- if (signal?.aborted || error instanceof ContentError) throw error;
237
- if (error instanceof SyntaxError)
238
- throw new ContentError("\u5B98\u7F51\u8FD4\u56DE\u4E86\u65E0\u6CD5\u8BFB\u53D6\u7684\u5185\u5BB9\uFF0C\u8BF7\u7A0D\u540E /refresh \u91CD\u8BD5\u3002");
239
- if (cached) {
240
- this.warning = "\u7F51\u7EDC\u4E0D\u53EF\u7528\uFF0C\u663E\u793A\u672C\u6B21\u4F1A\u8BDD\u5DF2\u8BFB\u53D6\u7684\u5185\u5BB9\uFF1B/refresh \u91CD\u8BD5\u3002";
241
- return cached.value;
242
- }
243
- throw new ContentError("\u65E0\u6CD5\u8FDE\u63A5\u5B98\u7F51\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u4F7F\u7528 /refresh \u91CD\u8BD5\u3002");
244
- }
245
- }
246
- async getIndex(signal) {
247
- this.warning = "";
248
- const index = await this.request(`${terminalApiPath}/index.json`, terminalIndexSchema, signal);
249
- if (this.revision && this.revision !== index.revision) {
250
- const indexUrl = new URL(`${terminalApiPath}/index.json`, this.site).href;
251
- for (const key of this.cache.keys()) {
252
- if (key !== indexUrl) this.cache.delete(key);
253
- }
254
- this.searchManager = void 0;
255
- }
256
- this.revision = index.revision;
257
- return index;
258
- }
259
- async getDocument(path, index, signal) {
260
- for (let attempt = 0; attempt < 2; attempt++) {
261
- const page = [...Object.values(index.pages), ...index.posts].find(
262
- (item) => item.path === path
263
- );
264
- if (!page) throw new ContentError("\u627E\u4E0D\u5230\u8FD9\u4E2A\u9875\u9762\uFF0C\u8BF7\u4F7F\u7528 /blog \u6216 /search \u67E5\u627E\u3002");
265
- try {
266
- const document = await this.request(page.documentUrl, terminalDocumentSchema, signal);
267
- if (document.revision === index.revision && document.path === path)
268
- return { index, document };
269
- } catch (error) {
270
- if (!(error instanceof ContentError) || error.status !== 404) throw error;
271
- }
272
- if (attempt === 0) {
273
- index = await this.getIndex(signal);
274
- this.cache.delete(new URL(page.documentUrl, this.site).href);
275
- }
276
- }
277
- throw new ContentError("\u5B98\u7F51\u6B63\u5728\u66F4\u65B0\uFF0C\u7D22\u5F15\u4E0E\u6B63\u6587\u6682\u65F6\u4E0D\u4E00\u81F4\uFF1B\u8BF7\u7A0D\u540E /refresh\u3002");
278
- }
279
- async search(query, index, signal) {
280
- if (!this.searchManager) {
281
- const data = await this.request(index.searchIndexUrl, searchIndexSchema, signal);
282
- this.searchManager = SearchManager.fromData(data);
283
- }
284
- return this.searchManager.search(query);
285
- }
286
- };
287
-
288
- // packages/cli/src/console.tsx
289
- import { ThreadPrimitive as ThreadPrimitive2 } from "@assistant-ui/react-ink";
290
- import { useRef as useRef8 } from "react";
291
-
292
- // packages/cli/src/chat.tsx
293
- import { ThreadPrimitive, useAuiState } from "@assistant-ui/react-ink";
294
- import { Box as Box4, Text as Text4 } from "ink";
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
- // src/theme-data.ts
304
- var themes = [
305
- { id: "groknight", name: "GrokNight", description: "\u4E2D\u6027\u6697\u8272\uFF0C\u9ED8\u8BA4\u4E3B\u9898", dark: true, color: "#141414" },
306
- { id: "grokday", name: "GrokDay", description: "\u660E\u4EAE\u7684\u6D45\u8272\u4E3B\u9898", dark: false, color: "#eeeeee" },
307
- { id: "tokyonight", name: "TokyoNight", description: "\u84DD\u8C03\u6697\u8272", dark: true, color: "#24283b" },
308
- { id: "rosepine", name: "RosePine Moon", description: "\u67D4\u548C\u7684\u7D2B\u7070\u6697\u8272", dark: true, color: "#232136" },
309
- { id: "oscura", name: "Oscura Midnight", description: "\u6DF1\u9ED1\u5E95\u4E0E\u7D2B\u8272\u70B9\u7F00", dark: true, color: "#030304" }
310
- ];
311
- var themeOptions = [
312
- ...themes,
313
- { id: "auto", name: "Auto", description: "\u8DDF\u968F\u7CFB\u7EDF\u4EAE\u6697" }
314
- ];
315
- var aliases = {
316
- "dark": "groknight",
317
- "night": "groknight",
318
- "grok-night": "groknight",
319
- "light": "grokday",
320
- "day": "grokday",
321
- "grok-day": "grokday",
322
- "tokyo": "tokyonight",
323
- "tokyo-night": "tokyonight",
324
- "rose-pine": "rosepine",
325
- "rosepine-moon": "rosepine",
326
- "rose-pine-moon": "rosepine",
327
- "oscura-midnight": "oscura",
328
- "system": "auto"
329
- };
330
- function parseThemePreference(input) {
331
- const name = input?.trim().toLowerCase();
332
- if (!name)
333
- return void 0;
334
- return themeOptions.find((option) => option.id === name)?.id ?? aliases[name];
335
- }
336
- function resolveTheme(preference, prefersDark2) {
337
- if (preference !== "auto")
338
- return preference;
339
- return prefersDark2 ? "groknight" : "grokday";
340
- }
341
- function getThemeName(id) {
342
- return themeOptions.find((option) => option.id === id)?.name ?? id;
343
- }
344
-
345
- // packages/cli/src/theme.tsx
346
- import { stripVTControlCharacters } from "node:util";
347
- import { jsx } from "react/jsx-runtime";
348
- function cleanText(text) {
349
- return stripVTControlCharacters(text).replace(
350
- /[\p{Cc}\p{Cf}]/gu,
351
- (character) => /[\n\t]/.test(character) ? character : ""
352
- );
353
- }
354
- function prefersDark() {
355
- const background = process.env.COLORFGBG?.split(";").at(-1);
356
- return background === void 0 || Number(background) < 8;
357
- }
358
- var syntaxThemes = {
359
- groknight: "github-dark",
360
- grokday: "github-light",
361
- rosepine: "rose-pine-moon",
362
- tokyonight: "tokyo-night",
363
- oscura: "github-dark"
364
- };
365
- var StyleContext = createContext(null);
366
- function TerminalTheme({
367
- preference,
368
- width,
369
- children
370
- }) {
371
- const id = resolveTheme(preference, prefersDark());
372
- const highlighter = useShikiHighlighter({ theme: syntaxThemes[id] });
373
- const style = useMemo(() => {
374
- const colors = define_TERMINAL_PALETTES_default[id];
375
- return {
376
- colors,
377
- width,
378
- highlighter,
379
- markdownTheme: {
380
- heading: { color: colors.h1, bold: true },
381
- strong: { color: colors.fg, bold: true },
382
- inlineCode: { color: colors.code },
383
- code: { color: colors.code },
384
- link: { color: colors.link, underline: true },
385
- quote: { color: colors.muted },
386
- hr: { color: colors.line },
387
- listMarker: { color: colors.accent },
388
- tableHeader: { color: colors.fg, bold: true }
389
- }
390
- };
391
- }, [id, width, highlighter]);
392
- return /* @__PURE__ */ jsx(StyleContext, { value: style, children });
393
- }
394
- function useTerminalStyle() {
395
- const style = useContext(StyleContext);
396
- if (!style) throw new Error("TerminalTheme is required");
397
- return style;
398
- }
399
- function Markdown({ text, width: availableWidth }) {
400
- const { width, markdownTheme, highlighter } = useTerminalStyle();
401
- return /* @__PURE__ */ jsx(
402
- MarkdownText,
403
- {
404
- text,
405
- width: Math.max(10, availableWidth ?? width - 3),
406
- theme: markdownTheme,
407
- highlighter,
408
- codeWrap: true,
409
- hyperlinks: true,
410
- tableTruncate: false
411
- }
412
- );
413
- }
414
-
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 { Box, Text, measureElement, useBoxMetrics, useFocus, useInput } from "ink";
425
- import {
426
- createContext as createContext2,
427
- useContext as useContext2,
428
- useImperativeHandle,
429
- useMemo as useMemo2,
430
- useRef,
431
- useState
432
- } from "react";
433
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
434
- var ScrollContext = createContext2(null);
435
- function useScrollArea() {
436
- const value = useContext2(ScrollContext);
437
- if (!value) throw new Error("ScrollArea is required");
438
- return value;
439
- }
440
- function ScrollArea({
441
- children,
442
- selection,
443
- active = true,
444
- ref
445
- }) {
446
- const { colors } = useTerminalStyle();
447
- const { isFocused } = useFocus({ id: "content", isActive: active });
448
- const viewport = useRef(null);
449
- const content = useRef(null);
450
- const { clientHeight: height } = useBoxMetrics(viewport);
451
- const { height: contentHeight } = useBoxMetrics(content);
452
- const [anchor, setAnchor] = useState(0);
453
- const maximum = Math.max(0, contentHeight - height);
454
- const offset = anchor === "end" ? maximum : Math.min(anchor, maximum);
455
- const context = useMemo2(() => ({ focused: isFocused, height }), [isFocused, height]);
456
- useImperativeHandle(
457
- ref,
458
- () => ({
459
- end: () => setAnchor("end"),
460
- reveal: (element) => {
461
- if (!viewport.current) return false;
462
- const item = measureElement(element);
463
- const top = item.y - measureElement(viewport.current).y;
464
- const visible = top >= offset && top < offset + height;
465
- if (top < offset || item.height > height) setAnchor(Math.max(0, top));
466
- else if (top + item.height > offset + height)
467
- setAnchor(Math.min(maximum, top + item.height - height));
468
- return visible;
469
- }
470
- }),
471
- [offset, height, maximum]
472
- );
473
- useInput(
474
- (_input, key) => {
475
- let next = offset;
476
- if (key.pageUp) next -= Math.max(1, height - 1);
477
- else if (key.pageDown) next += Math.max(1, height - 1);
478
- else if (isFocused && !selection && key.upArrow) next--;
479
- else if (isFocused && !selection && key.downArrow) next++;
480
- else if (isFocused && !selection && key.home) next = 0;
481
- else if (isFocused && !selection && key.end) next = maximum;
482
- else return;
483
- setAnchor(next >= maximum ? "end" : Math.max(0, next));
484
- },
485
- { isActive: active }
486
- );
487
- const track = Math.max(0, height);
488
- const thumbSize = Math.min(
489
- track,
490
- Math.max(1, Math.round(height * height / Math.max(1, contentHeight)))
491
- );
492
- const thumbTop = maximum > 0 ? Math.round(offset / maximum * (height - thumbSize)) : 0;
493
- const before = Array(thumbTop).fill("\u2502").join("\n");
494
- const thumb = Array(thumbSize).fill("\u2503").join("\n");
495
- const after = Array(Math.max(0, track - thumbTop - thumbSize)).fill("\u2502").join("\n");
496
- return /* @__PURE__ */ jsx2(ScrollContext, { value: context, children: /* @__PURE__ */ jsxs(Box, { flexGrow: 1, flexShrink: 1, minHeight: 1, marginX: 2, children: [
497
- /* @__PURE__ */ jsx2(
498
- Box,
499
- {
500
- ref: viewport,
501
- flexDirection: "column",
502
- flexGrow: 1,
503
- flexShrink: 1,
504
- overflow: "hidden",
505
- contentOffsetY: offset,
506
- children: /* @__PURE__ */ jsx2(Box, { ref: content, flexDirection: "column", flexShrink: 0, width: "100%", children })
507
- }
508
- ),
509
- /* @__PURE__ */ jsx2(Box, { width: 1, flexShrink: 0, "aria-hidden": true, children: /* @__PURE__ */ jsxs(Text, { color: colors.line, children: [
510
- before,
511
- before && "\n",
512
- /* @__PURE__ */ jsx2(Text, { color: isFocused ? colors.accent : colors.muted, children: thumb }),
513
- after && "\n",
514
- after
515
- ] }) })
516
- ] }) });
517
- }
518
-
519
- // packages/cli/src/page-list.tsx
520
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
521
- function PageList({ items, onSelect, scroll, compact = false }) {
522
- const { focused } = useScrollArea();
523
- const { colors } = useTerminalStyle();
524
- const [position, setSelected] = useState2(0);
525
- const selected = Math.max(0, Math.min(position, items.length - 1));
526
- const selectedItem = items[selected];
527
- const elements = useRef2(/* @__PURE__ */ new Map());
528
- const container = useRef2(null);
529
- const { hasMeasured } = useBoxMetrics2(container);
530
- useEffect(() => {
531
- const element = selectedItem && elements.current.get(selectedItem.path);
532
- if (focused && hasMeasured && element) scroll.current?.reveal(element);
533
- }, [focused, hasMeasured, selectedItem, scroll]);
534
- useInput2(
535
- (_input, key) => {
536
- let next = selected;
537
- if (key.upArrow) next--;
538
- else if (key.downArrow) next++;
539
- else if (key.home) next = 0;
540
- else if (key.end) next = items.length - 1;
541
- else if (key.return) {
542
- if (!selectedItem) return;
543
- const element = elements.current.get(selectedItem.path);
544
- if (element && scroll.current?.reveal(element) === false) return;
545
- onSelect(selectedItem.path, selectedItem.external);
546
- return;
547
- } else return;
548
- next = Math.max(0, Math.min(items.length - 1, next));
549
- setSelected(next);
550
- },
551
- { isActive: focused }
552
- );
553
- return /* @__PURE__ */ jsx3(Box2, { ref: container, flexDirection: "column", marginTop: 1, children: items.map((item, index) => {
554
- const active = focused && index === selected;
555
- return /* @__PURE__ */ jsxs2(
556
- Box2,
557
- {
558
- flexDirection: compact ? "row" : "column",
559
- justifyContent: compact ? "space-between" : void 0,
560
- marginBottom: compact ? 0 : 1,
561
- backgroundColor: active ? colors.band : void 0,
562
- ref: (element) => {
563
- if (element) elements.current.set(item.path, element);
564
- else elements.current.delete(item.path);
565
- },
566
- children: [
567
- /* @__PURE__ */ jsxs2(Text2, { color: active ? colors.accent : colors.fg, bold: active, children: [
568
- active ? "\u25B8" : compact ? " " : "\u2022",
569
- " ",
570
- compact ? "" : `${index + 1}. `,
571
- cleanText(item.title)
572
- ] }),
573
- item.description && /* @__PURE__ */ jsx3(Text2, { color: colors.muted, children: cleanText(item.description) })
574
- ]
575
- },
576
- item.path
577
- );
578
- }) });
579
- }
580
-
581
- // packages/cli/src/blocks.tsx
582
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
583
- function Prompt({ text, command = false }) {
584
- const { colors } = useTerminalStyle();
585
- return /* @__PURE__ */ jsxs3(Box3, { backgroundColor: colors.band, paddingX: 2, children: [
586
- /* @__PURE__ */ jsx4(Text3, { color: colors.prompt, children: "\u276F " }),
587
- /* @__PURE__ */ jsx4(Text3, { color: command ? colors.skill : colors.fg, children: cleanText(text) })
588
- ] });
589
- }
590
- function Welcome({ section, ...selection }) {
591
- const { colors, width } = useTerminalStyle();
592
- const content = useRef3(null);
593
- const { clientWidth } = useBoxMetrics3(content);
594
- return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "round", borderColor: colors.line, paddingX: 2, paddingY: 1, gap: 4, children: [
595
- section.logo && width >= 90 && /* @__PURE__ */ jsx4(Text3, { color: colors.skill, children: cleanText(section.logo) }),
596
- /* @__PURE__ */ jsxs3(Box3, { ref: content, flexDirection: "column", flexGrow: 1, flexShrink: 1, minWidth: 0, children: [
597
- /* @__PURE__ */ jsx4(Markdown, { text: section.markdown, width: clientWidth || width - 6 }),
598
- section.menu && selection.items.length > 0 && /* @__PURE__ */ jsx4(PageList, { ...selection, compact: true })
599
- ] })
600
- ] });
601
- }
602
- function HomeSections({ page, ...selection }) {
603
- 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: [
604
- /* @__PURE__ */ jsx4(Prompt, { text: section.prompt }),
605
- /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, children: /* @__PURE__ */ jsx4(Markdown, { text: section.markdown }) })
606
- ] }, section.prompt)) });
607
- }
608
-
609
- // packages/cli/src/chat.tsx
610
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
611
- var toolTitles = { set_theme: "Theme", open_page: "Open" };
612
- function MessageBody({ message }) {
613
- const { colors } = useTerminalStyle();
614
- if (message.role === "user") {
615
- return /* @__PURE__ */ jsx5(Box4, { marginY: 1, children: /* @__PURE__ */ jsx5(Prompt, { text: message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n") }) });
616
- }
617
- if (message.role !== "assistant") return null;
618
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
619
- message.content.map((part, index) => {
620
- if (part.type === "text")
621
- return /* @__PURE__ */ jsx5(Markdown, { text: part.text }, index);
622
- if (part.type === "reasoning") {
623
- const label = message.status.type === "running" ? "\u601D\u8003\u4E2D\u2026" : "\u601D\u8003";
624
- return (
625
- // eslint-disable-next-line react-doctor/no-array-index-as-key
626
- /* @__PURE__ */ jsxs4(Text4, { color: colors.muted, children: [
627
- "\u25C6 ",
628
- label
629
- ] }, index)
630
- );
631
- }
632
- if (part.type === "tool-call") {
633
- const title = toolTitles[part.toolName] ?? part.toolName;
634
- return /* @__PURE__ */ jsxs4(Text4, { color: colors.skill, children: [
635
- "\u25C6 ",
636
- cleanText(title),
637
- " ",
638
- cleanText(part.result === void 0 ? part.argsText : String(part.result))
639
- ] }, part.toolCallId);
640
- }
641
- return null;
642
- }),
643
- 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" })
644
- ] });
645
- }
646
- function Message() {
647
- const message = useAuiState((state) => state.message);
648
- return /* @__PURE__ */ jsx5(MessageBody, { message });
649
- }
650
- function ChatThread() {
651
- return /* @__PURE__ */ jsx5(ThreadPrimitive.Messages, { components: { Message } });
652
- }
653
-
654
- // packages/cli/src/composer.tsx
655
- import { useAui, useAuiState as useAuiState2 } from "@assistant-ui/react-ink";
656
- import {
657
- Box as Box10,
658
- Text as Text10,
659
- useBoxMetrics as useBoxMetrics6,
660
- useFocus as useFocus3,
661
- useFocusManager as useFocusManager2,
662
- useInput as useInput7,
663
- useWindowSize as useWindowSize2
664
- } from "ink";
665
- import { useEffect as useEffect5, useEffectEvent, useRef as useRef6 } from "react";
666
-
667
- // src/agent/appearance.ts
668
- var composerPlaceholder = "\u95EE\u70B9\u4EC0\u4E48\uFF0C\u6216\u8F93\u5165 / \u67E5\u770B\u547D\u4EE4";
669
- var composerBrand = { mark: "<MT/>", model: "V3.14 Pro" };
670
-
671
- // src/agent/commands.ts
672
- var commands = [
673
- { id: "help", description: "\u67E5\u770B\u53EF\u7528\u547D\u4EE4" },
674
- { id: "theme", args: "[\u4E3B\u9898]", description: "\u5207\u6362\u4E3B\u9898\uFF0C\u4E0D\u5E26\u53C2\u6570\u65F6\u6253\u5F00\u9009\u62E9\u5668" },
675
- { id: "clear", description: "\u6E05\u7A7A\u5F53\u524D\u5BF9\u8BDD" },
676
- { id: "home", description: "\u6253\u5F00\u9996\u9875" },
677
- { id: "blog", description: "\u6D4F\u89C8\u5168\u90E8\u6587\u7AE0" },
678
- { id: "projects", description: "\u67E5\u770B\u6298\u817E\u7684\u9879\u76EE" },
679
- { id: "tags", description: "\u6309\u6807\u7B7E\u6D4F\u89C8\u6587\u7AE0" },
680
- { id: "search", args: "[\u5173\u952E\u8BCD]", description: "\u641C\u7D22\u6587\u7AE0" },
681
- { id: "about", description: "\u5173\u4E8E\u672C\u7AD9" },
682
- { id: "rss", description: "\u8BA2\u9605 RSS" }
683
- ];
684
- var pageCommands = {
685
- home: "/",
686
- blog: "/blog/",
687
- projects: "/projects/",
688
- tags: "/tags/",
689
- about: "/about/"
690
- };
691
- function parseCommand(input) {
692
- const match = input.trim().match(/^\/(\S+)(?:\s+([\s\S]*))?$/);
693
- if (!match)
694
- return null;
695
- return { name: match[1].toLowerCase(), args: (match[2] ?? "").trim() };
696
- }
697
- function getSearchPath(query) {
698
- return query ? `/search/?q=${encodeURIComponent(query)}` : "/search/";
699
- }
700
-
701
- // packages/cli/src/command-menu.tsx
702
- import { Box as Box5, Text as Text5, useFocusManager, useInput as useInput3 } from "ink";
703
- import { useEffect as useEffect2, useImperativeHandle as useImperativeHandle2, useState as useState3 } from "react";
704
-
705
- // packages/cli/src/commands.ts
706
- var terminalCommands = [
707
- ...commands.map((command) => command.id === "tags" ? { ...command, args: "[\u6807\u7B7E]" } : command),
708
- { id: "read", args: "<\u7F16\u53F7\u3001slug \u6216\u8DEF\u5F84>", description: "\u9605\u8BFB\u6587\u7AE0\u5168\u6587" },
709
- { id: "open", args: "[\u7F16\u53F7\u6216 URL]", description: "\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u5F53\u524D\u9875\u9762\u6216\u94FE\u63A5" },
710
- { id: "refresh", description: "\u4ECE\u5B98\u7F51\u5237\u65B0\u5F53\u524D\u9875\u9762" },
711
- { id: "quit", description: "\u9000\u51FA" }
712
- ];
713
- function getCommandSuggestions(text) {
714
- const match = text.match(/^\/([^\s]*)$/);
715
- if (!match) return null;
716
- const query = match[1].toLowerCase();
717
- return terminalCommands.filter(
718
- (command) => command.id.includes(query) || command.description.toLowerCase().includes(query)
719
- );
720
- }
721
- function numberedItem(input, page) {
722
- if (!/^\d+$/.test(input)) return void 0;
723
- const item = page.items[Number(input) - 1];
724
- 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");
725
- return item;
726
- }
727
- function resolveReadPath(input, page) {
728
- if (!input) throw new Error("\u8F93\u5165 /read <\u7F16\u53F7\u3001slug \u6216\u7AD9\u5185\u8DEF\u5F84> \u9605\u8BFB\u6587\u7AE0\u3002");
729
- const item = numberedItem(input, page);
730
- if (item) {
731
- if (item.external || !item.path.startsWith("/posts/"))
732
- throw new Error("\u8FD9\u4E2A\u7F16\u53F7\u4E0D\u662F\u6587\u7AE0\uFF0C\u8BF7\u4F7F\u7528 /blog \u6216 /search \u67E5\u627E\u6587\u7AE0\u3002");
733
- return item.path;
734
- }
735
- return input.startsWith("/") ? input : `/posts/${input.replace(/\/$/, "")}/`;
736
- }
737
- function resolveOpenTarget(input, page) {
738
- return numberedItem(input, page)?.path ?? (input || page.path);
739
- }
740
- function resolveTagPath(input, index) {
741
- const tag = index.tags.find((tag2) => tag2.name === input);
742
- if (!tag) throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u6807\u7B7E\uFF0C\u8BF7\u4F7F\u7528 /tags \u67E5\u770B\u53EF\u7528\u6807\u7B7E\u3002");
743
- return tag.path;
744
- }
745
-
746
- // packages/cli/src/command-menu.tsx
747
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
748
- function CommandMenu({
749
- text,
750
- focused,
751
- availableHeight,
752
- onSelect,
753
- ref
754
- }) {
755
- const { colors } = useTerminalStyle();
756
- const { activeId, disableFocus, enableFocus, focus, focusPrevious } = useFocusManager();
757
- const [selected, setSelected] = useState3(0);
758
- const [dismissed, setDismissed] = useState3(false);
759
- const suggestions = getCommandSuggestions(text);
760
- const matches = suggestions ?? [];
761
- const open = focused && suggestions !== null && !dismissed;
762
- const visibleCount = Math.max(1, Math.min(8, availableHeight - 4));
763
- const start = Math.max(0, selected - visibleCount + 1);
764
- useEffect2(() => {
765
- if (!open || !activeId) return;
766
- disableFocus();
767
- focus(activeId);
768
- return enableFocus;
769
- }, [open, activeId, disableFocus, enableFocus, focus]);
770
- useImperativeHandle2(ref, () => ({
771
- isOpen: () => open,
772
- submit: (input) => {
773
- if (!open || input !== text) return false;
774
- if (matches.length) onSelect(matches[selected]);
775
- return true;
776
- },
777
- dismiss: () => {
778
- if (!open) return false;
779
- setDismissed(true);
780
- return true;
781
- }
782
- }));
783
- useInput3(
784
- (_input, key) => {
785
- if (key.tab) {
786
- if (key.shift) queueMicrotask(focusPrevious);
787
- else if (matches.length) onSelect(matches[selected]);
788
- return;
789
- }
790
- if (!matches.length) return;
791
- const direction = Number(key.downArrow) - Number(key.upArrow);
792
- if (direction) setSelected((index) => (index + direction + matches.length) % matches.length);
793
- },
794
- { isActive: open }
795
- );
796
- if (!open) return null;
797
- return /* @__PURE__ */ jsxs5(
798
- Box5,
799
- {
800
- flexDirection: "column",
801
- borderStyle: "round",
802
- borderColor: colors.line,
803
- backgroundColor: colors.bg,
804
- paddingX: 1,
805
- marginBottom: 1,
806
- children: [
807
- matches.length === 0 && /* @__PURE__ */ jsx6(Text5, { color: colors.muted, children: "\u6CA1\u6709\u5339\u914D\u7684\u547D\u4EE4" }),
808
- matches.slice(start, start + visibleCount).map((command, index) => {
809
- const active = start + index === selected;
810
- return /* @__PURE__ */ jsxs5(Box5, { height: 1, backgroundColor: active ? colors.band : void 0, children: [
811
- /* @__PURE__ */ jsx6(Box5, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsxs5(Text5, { color: colors.skill, children: [
812
- active ? "\u25B8" : " ",
813
- " /",
814
- command.id
815
- ] }) }),
816
- /* @__PURE__ */ jsx6(Box5, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx6(Text5, { color: colors.muted, wrap: "truncate-end", children: command.description }) })
817
- ] }, command.id);
818
- }),
819
- /* @__PURE__ */ jsx6(Text5, { color: colors.muted, wrap: "truncate-end", children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter/Tab \u786E\u8BA4 \xB7 Esc \u5173\u95ED" })
820
- ]
821
- }
822
- );
823
- }
824
-
825
- // packages/cli/src/composer-input.tsx
826
- import { Box as Box6, Text as Text6, useBoxMetrics as useBoxMetrics4, useInput as useInput4, useWindowSize } from "ink";
827
- import { useRef as useRef4, useState as useState4 } from "react";
828
-
829
- // packages/cli/src/input-editor.ts
830
- import stringWidth from "string-width";
831
- var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
832
- function editorLines(text, width) {
833
- const lines = [];
834
- let start = 0;
835
- let columns = 0;
836
- for (const { segment, index } of segmenter.segment(text)) {
837
- if (segment === "\n") {
838
- lines.push({ start, end: index, text: text.slice(start, index) });
839
- if (columns >= width) lines.push({ start: index, end: index, text: "" });
840
- start = index + 1;
841
- columns = 0;
842
- continue;
843
- }
844
- const size = stringWidth(segment);
845
- if (columns && columns + size > width) {
846
- lines.push({ start, end: index, text: text.slice(start, index) });
847
- start = index;
848
- columns = 0;
849
- }
850
- columns += size;
851
- }
852
- lines.push({ start, end: text.length, text: text.slice(start) });
853
- if (columns >= width) lines.push({ start: text.length, end: text.length, text: "" });
854
- return lines;
855
- }
856
- function cursorRow(lines, cursor) {
857
- return lines.findLastIndex((line) => line.start <= cursor);
858
- }
859
- function editText(state, start, end, inserted = "") {
860
- return {
861
- text: state.text.slice(0, start) + inserted + state.text.slice(end),
862
- cursor: start + inserted.length
863
- };
864
- }
865
- function moveCursor(state, direction) {
866
- const boundaries = [...segmenter.segment(state.text)].map((part) => part.index);
867
- boundaries.push(state.text.length);
868
- const cursor = direction < 0 ? boundaries.findLast((index) => index < state.cursor) ?? 0 : boundaries.find((index) => index > state.cursor) ?? state.text.length;
869
- return { text: state.text, cursor };
870
- }
871
- function moveVertical(state, width, direction) {
872
- const lines = editorLines(state.text, width);
873
- const row = cursorRow(lines, state.cursor);
874
- const column = state.column ?? stringWidth(state.text.slice(lines[row].start, state.cursor));
875
- const target = lines[Math.max(0, Math.min(lines.length - 1, row + direction))];
876
- let cursor = target.start;
877
- let cells = 0;
878
- for (const { segment, index } of segmenter.segment(target.text)) {
879
- cells += stringWidth(segment);
880
- if (cells > column) break;
881
- cursor = target.start + index + segment.length;
882
- }
883
- return { text: state.text, cursor, column };
884
- }
885
- function wordCursor(state, direction) {
886
- if (direction < 0) {
887
- return state.cursor - (state.text.slice(0, state.cursor).match(/\S+\s*$|\s+$/u)?.[0].length ?? 0);
888
- }
889
- return state.cursor + (state.text.slice(state.cursor).match(/^\s*\S*/u)?.[0].length ?? 0);
890
- }
891
- function updateEditor(state, input, key, width) {
892
- const { text, cursor } = state;
893
- if (key.escape || key.tab || key.pageUp || key.pageDown) return;
894
- if (key.upArrow || key.downArrow) return moveVertical(state, width, key.upArrow ? -1 : 1);
895
- if (key.leftArrow || key.rightArrow) {
896
- const direction = key.leftArrow ? -1 : 1;
897
- return key.ctrl || key.meta ? { text, cursor: wordCursor(state, direction) } : moveCursor(state, direction);
898
- }
899
- const start = cursor === 0 ? 0 : text.lastIndexOf("\n", cursor - 1) + 1;
900
- const nextBreak = text.indexOf("\n", cursor);
901
- const end = nextBreak < 0 ? text.length : nextBreak;
902
- if (key.home || key.ctrl && input === "a") return { text, cursor: start };
903
- if (key.end || key.ctrl && input === "e") return { text, cursor: end };
904
- if (key.return || key.ctrl && input === "j") return editText(state, cursor, cursor, "\n");
905
- if (key.ctrl) {
906
- if (input === "u") return editText(state, start, cursor);
907
- if (input === "k") return editText(state, cursor, end);
908
- if (input === "w") return editText(state, wordCursor(state, -1), cursor);
909
- return;
910
- }
911
- if (key.backspace) return editText(state, moveCursor(state, -1).cursor, cursor);
912
- if (key.delete) return editText(state, cursor, moveCursor(state, 1).cursor);
913
- if (key.meta) return;
914
- const inserted = input.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(new RegExp("\\p{Cc}", "gu"), (character) => character === "\n" ? character : "");
915
- if (inserted) return editText(state, cursor, cursor, inserted);
916
- }
917
-
918
- // packages/cli/src/composer-input.tsx
919
- import { Fragment, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
920
- function ComposerInput({
921
- value,
922
- focused,
923
- onChange,
924
- onSubmit,
925
- ignoreArrows
926
- }) {
927
- const { colors, width: availableWidth } = useTerminalStyle();
928
- const { rows } = useWindowSize();
929
- const container = useRef4(null);
930
- const { clientWidth } = useBoxMetrics4(container);
931
- const width = Math.max(1, clientWidth || availableWidth - 5);
932
- const maximum = Math.max(1, Math.min(10, rows - 8));
933
- const [editor, setEditor] = useState4({ text: value, cursor: value.length });
934
- const [offset, setOffset] = useState4(0);
935
- const state = editor.text === value ? editor : { text: value, cursor: value.length };
936
- if (state !== editor) setEditor(state);
937
- const lines = editorLines(value, width);
938
- const row = cursorRow(lines, state.cursor);
939
- const height = Math.min(maximum, lines.length);
940
- const start = Math.max(0, Math.min(offset, lines.length - height, row));
941
- const visibleStart = row >= start + height ? row - height + 1 : start;
942
- if (visibleStart !== offset) setOffset(visibleStart);
943
- useInput4(
944
- (input, key) => {
945
- if (key.return && !key.shift && !key.meta) {
946
- onSubmit(value);
947
- return;
948
- }
949
- if (ignoreArrows() && (key.upArrow || key.downArrow)) return;
950
- const next = updateEditor(state, input, key, width);
951
- if (!next) return;
952
- setEditor(next);
953
- if (next.text !== value) onChange(next.text);
954
- },
955
- { isActive: focused }
956
- );
957
- return /* @__PURE__ */ jsx7(Box6, { ref: container, flexGrow: 1, minWidth: 0, height, flexDirection: "column", children: lines.slice(visibleStart, visibleStart + height).map((line, index) => {
958
- const current = visibleStart + index === row;
959
- const cursor = current ? state.cursor - line.start : -1;
960
- const after = current ? moveCursor(state, 1).cursor - line.start : -1;
961
- return /* @__PURE__ */ jsx7(Text6, { color: colors.fg, wrap: "truncate-end", children: !value ? /* @__PURE__ */ jsxs6(Text6, { color: colors.muted, children: [
962
- /* @__PURE__ */ jsx7(Text6, { inverse: focused, children: composerPlaceholder[0] }),
963
- composerPlaceholder.slice(1)
964
- ] }) : current && focused ? /* @__PURE__ */ jsxs6(Fragment, { children: [
965
- line.text.slice(0, cursor),
966
- /* @__PURE__ */ jsx7(Text6, { inverse: true, children: line.text.slice(cursor, after) || " " }),
967
- line.text.slice(after)
968
- ] }) : line.text || " " }, line.start);
969
- }) });
970
- }
971
-
972
- // packages/cli/src/panels.tsx
973
- import { Box as Box8, Text as Text8, useBoxMetrics as useBoxMetrics5, useInput as useInput6 } from "ink";
974
- import { useRef as useRef5, useState as useState6 } from "react";
975
-
976
- // packages/cli/src/theme-picker.tsx
977
- import { Box as Box7, Text as Text7, useFocus as useFocus2, useInput as useInput5 } from "ink";
978
- import { useEffect as useEffect3, useState as useState5 } from "react";
979
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
980
- function ThemePicker({
981
- initial,
982
- visibleCount,
983
- onPreview,
984
- onApply
985
- }) {
986
- const { colors } = useTerminalStyle();
987
- const { isFocused, focus } = useFocus2({ id: "theme-picker" });
988
- const [selected, setSelected] = useState5(
989
- () => Math.max(
990
- 0,
991
- themeOptions.findIndex((option) => option.id === initial)
992
- )
993
- );
994
- useEffect3(() => {
995
- focus("theme-picker");
996
- return () => onPreview(void 0);
997
- }, [focus, onPreview]);
998
- useInput5(
999
- (input, key) => {
1000
- const direction = Number(key.downArrow || input === "j") - Number(key.upArrow || input === "k");
1001
- if (direction) {
1002
- const next = (selected + direction + themeOptions.length) % themeOptions.length;
1003
- setSelected(next);
1004
- onPreview(themeOptions[next].id);
1005
- } else if (key.return) {
1006
- onApply(themeOptions[selected].id);
1007
- }
1008
- },
1009
- { isActive: isFocused }
1010
- );
1011
- const start = Math.max(0, selected - visibleCount + 1);
1012
- return /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", children: themeOptions.slice(start, start + visibleCount).map((option, index) => /* @__PURE__ */ jsxs7(Box7, { backgroundColor: start + index === selected ? colors.band : void 0, children: [
1013
- /* @__PURE__ */ jsx8(Box7, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.fg, bold: true, children: [
1014
- start + index === selected ? "\u25B8" : " ",
1015
- " ",
1016
- option.name
1017
- ] }) }),
1018
- /* @__PURE__ */ jsx8(Box7, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: colors.muted, wrap: "truncate-end", children: [
1019
- option.description,
1020
- option.id === initial ? "\uFF08\u5F53\u524D\uFF09" : ""
1021
- ] }) })
1022
- ] }, option.id)) });
1023
- }
1024
-
1025
- // packages/cli/src/panels.tsx
1026
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1027
- function PanelBox({
1028
- title,
1029
- availableHeight,
1030
- children,
1031
- hints = "Esc:\u5173\u95ED"
1032
- }) {
1033
- const { colors } = useTerminalStyle();
1034
- const viewport = useRef5(null);
1035
- const content = useRef5(null);
1036
- const { clientHeight: height } = useBoxMetrics5(viewport);
1037
- const { height: contentHeight } = useBoxMetrics5(content);
1038
- const [offset, setOffset] = useState6(0);
1039
- const maximum = Math.max(0, contentHeight - height);
1040
- useInput6((_input, key) => {
1041
- const direction = Number(key.pageDown) - Number(key.pageUp);
1042
- if (direction)
1043
- setOffset(
1044
- (current) => Math.max(0, Math.min(maximum, current + direction * Math.max(1, height - 1)))
1045
- );
1046
- });
1047
- return /* @__PURE__ */ jsxs8(
1048
- Box8,
1049
- {
1050
- flexDirection: "column",
1051
- borderStyle: "round",
1052
- borderColor: colors.line,
1053
- backgroundColor: colors.bg,
1054
- paddingX: 2,
1055
- marginBottom: 1,
1056
- children: [
1057
- /* @__PURE__ */ jsx9(Text8, { color: colors.fg, bold: true, children: title }),
1058
- /* @__PURE__ */ jsx9(
1059
- Box8,
1060
- {
1061
- ref: viewport,
1062
- flexDirection: "column",
1063
- maxHeight: Math.max(1, availableHeight - 5),
1064
- overflow: "hidden",
1065
- contentOffsetY: Math.min(offset, maximum),
1066
- children: /* @__PURE__ */ jsx9(
1067
- Box8,
1068
- {
1069
- ref: content,
1070
- flexDirection: "column",
1071
- flexShrink: 0,
1072
- marginY: availableHeight >= 8 ? 1 : 0,
1073
- children
1074
- }
1075
- )
1076
- }
1077
- ),
1078
- /* @__PURE__ */ jsxs8(Text8, { color: colors.muted, wrap: "truncate-end", children: [
1079
- hints,
1080
- maximum > 0 ? " \u2502 PgUp/PgDn:\u6EDA\u52A8" : ""
1081
- ] })
1082
- ]
1083
- }
1084
- );
1085
- }
1086
- function TerminalPanels({
1087
- panel,
1088
- availableHeight,
1089
- savedTheme,
1090
- onPreviewTheme,
1091
- onApplyTheme,
1092
- site
1093
- }) {
1094
- const { colors } = useTerminalStyle();
1095
- if (!panel) return null;
1096
- if (panel.type === "theme") {
1097
- return /* @__PURE__ */ jsx9(
1098
- PanelBox,
1099
- {
1100
- title: "\u9009\u62E9\u4E3B\u9898",
1101
- availableHeight,
1102
- hints: "\u2191\u2193:\u9884\u89C8 \u2502 Enter:\u5E94\u7528 \u2502 Esc:\u53D6\u6D88",
1103
- children: /* @__PURE__ */ jsx9(
1104
- ThemePicker,
1105
- {
1106
- initial: savedTheme,
1107
- visibleCount: Math.max(1, availableHeight - (availableHeight >= 8 ? 7 : 5)),
1108
- onPreview: onPreviewTheme,
1109
- onApply: onApplyTheme
1110
- }
1111
- )
1112
- }
1113
- );
1114
- }
1115
- if (panel.type === "help") {
1116
- return /* @__PURE__ */ jsxs8(PanelBox, { title: "\u547D\u4EE4", availableHeight, children: [
1117
- terminalCommands.map((command) => /* @__PURE__ */ jsxs8(Box8, { children: [
1118
- /* @__PURE__ */ jsx9(Box8, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsxs8(Text8, { color: colors.skill, children: [
1119
- "/",
1120
- command.id,
1121
- /* @__PURE__ */ jsx9(Text8, { color: colors.muted, children: command.args ? ` ${command.args}` : "" })
1122
- ] }) }),
1123
- /* @__PURE__ */ jsx9(Box8, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx9(Text8, { color: colors.muted, wrap: "truncate-end", children: command.description }) })
1124
- ] }, command.id)),
1125
- /* @__PURE__ */ jsx9(Text8, { color: colors["fg-soft"], children: "\u76F4\u63A5\u8F93\u5165\u95EE\u9898\uFF0CAI \u4F1A\u68C0\u7D22\u5168\u7AD9\u6587\u7AE0\u540E\u56DE\u7B54\uFF0C\u4E5F\u80FD\u5E2E\u4F60\u5207\u6362\u4E3B\u9898\u3001\u6253\u5F00\u9875\u9762\u3002" })
1126
- ] });
1127
- }
1128
- if (panel.type === "rss") {
1129
- return /* @__PURE__ */ jsx9(PanelBox, { title: "\u8BA2\u9605", availableHeight, children: /* @__PURE__ */ jsx9(Markdown, { text: `RSS [${site.feedUrl}](${site.feedUrl})` }) });
1130
- }
1131
- return /* @__PURE__ */ jsxs8(
1132
- Text8,
1133
- {
1134
- color: panel.tone === "error" ? colors.err : colors["fg-soft"],
1135
- backgroundColor: colors.bg,
1136
- children: [
1137
- "\u25C6 ",
1138
- cleanText(panel.text)
1139
- ]
1140
- }
1141
- );
1142
- }
1143
-
1144
- // packages/cli/src/run-status.tsx
1145
- import { LoadingPrimitive } from "@assistant-ui/react-ink";
1146
- import { Box as Box9, Text as Text9 } from "ink";
1147
- 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();
1151
- const [seconds, setSeconds] = useState7(0);
1152
- useEffect4(() => {
1153
- if (!running) return;
1154
- const started = Date.now();
1155
- setSeconds(0);
1156
- const timer = setInterval(() => setSeconds(Math.floor((Date.now() - started) / 1e3)), 1e3);
1157
- return () => clearInterval(timer);
1158
- }, [running]);
1159
- if (!running && !loading) return null;
1160
- return /* @__PURE__ */ jsxs9(Box9, { paddingLeft: 2, height: 1, children: [
1161
- /* @__PURE__ */ jsx10(LoadingPrimitive.Spinner, {}),
1162
- /* @__PURE__ */ jsxs9(Text9, { color: colors.fg, children: [
1163
- " ",
1164
- loading ? "\u601D\u8003\u4E2D\u2026\u2026" : "\u56DE\u590D\u4E2D\u2026"
1165
- ] }),
1166
- running && /* @__PURE__ */ jsxs9(Text9, { color: colors.muted, children: [
1167
- " ",
1168
- seconds,
1169
- "s"
1170
- ] }),
1171
- /* @__PURE__ */ jsx10(Box9, { flexGrow: 1 }),
1172
- /* @__PURE__ */ jsx10(Text9, { color: colors.muted, children: "[Esc \u505C\u6B62]" })
1173
- ] });
1174
- }
1175
-
1176
- // packages/cli/src/composer.tsx
1177
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1178
- function Composer({
1179
- runtime,
1180
- loading,
1181
- panel,
1182
- onCommand,
1183
- onCancel,
1184
- focusKey,
1185
- onSubmit,
1186
- ...panels
1187
- }) {
1188
- const { colors } = useTerminalStyle();
1189
- const { rows } = useWindowSize2();
1190
- const aui = useAui();
1191
- const { activeId, focus } = useFocusManager2();
1192
- const running = useAuiState2((state) => state.thread.isRunning);
1193
- const text = useAuiState2((state) => state.composer.text);
1194
- const menu = useRef6(null);
1195
- const dock = useRef6(null);
1196
- const { height } = useBoxMetrics6(dock);
1197
- const { isFocused: focused } = useFocus3({ id: "composer", autoFocus: true });
1198
- const restoreComposerFocus = useEffectEvent(() => {
1199
- if (panel?.type !== "theme") focus("composer");
1200
- });
1201
- useEffect5(() => {
1202
- restoreComposerFocus();
1203
- }, [focusKey, panel?.type]);
1204
- useInput7((input, key) => {
1205
- if (key.escape) {
1206
- if (!menu.current?.dismiss()) onCancel();
1207
- focus("composer");
1208
- }
1209
- if (input === "/" && activeId === "content") {
1210
- focus("composer");
1211
- aui.composer.setText("/");
1212
- }
1213
- });
1214
- return /* @__PURE__ */ jsxs10(Box10, { ref: dock, flexDirection: "column", marginX: 2, flexShrink: 0, children: [
1215
- /* @__PURE__ */ jsx11(RunStatus, { loading, running }),
1216
- /* @__PURE__ */ jsxs10(
1217
- Box10,
1218
- {
1219
- borderStyle: "round",
1220
- borderColor: focused ? colors["line-strong"] : colors.line,
1221
- paddingX: 1,
1222
- alignItems: "flex-start",
1223
- children: [
1224
- /* @__PURE__ */ jsx11(Text10, { color: colors.prompt, children: "\u276F " }),
1225
- /* @__PURE__ */ jsx11(
1226
- ComposerInput,
1227
- {
1228
- value: text,
1229
- focused,
1230
- onChange: (value) => aui.composer.setText(value),
1231
- ignoreArrows: () => menu.current?.isOpen() ?? false,
1232
- onSubmit: (text2) => {
1233
- const input = text2.trim();
1234
- if (!input) return;
1235
- if (menu.current?.submit(text2)) return;
1236
- if (input.startsWith("/")) {
1237
- const command = parseCommand(input);
1238
- aui.composer.setText("");
1239
- void onCommand(command?.name ?? "help", command?.args ?? "");
1240
- } else if (!running) {
1241
- onSubmit();
1242
- runtime.thread.composer.send();
1243
- }
1244
- }
1245
- }
1246
- ),
1247
- /* @__PURE__ */ jsx11(Box10, { position: "absolute", bottom: -1, right: 1, children: /* @__PURE__ */ jsxs10(Text10, { color: colors.muted, backgroundColor: colors.bg, children: [
1248
- " ",
1249
- composerBrand.mark,
1250
- " \xB7 ",
1251
- composerBrand.model,
1252
- " "
1253
- ] }) })
1254
- ]
1255
- }
1256
- ),
1257
- /* @__PURE__ */ jsxs10(Text10, { color: colors.muted, wrap: "truncate-end", children: [
1258
- /* @__PURE__ */ jsx11(Text10, { bold: true, children: "/" }),
1259
- ":\u547D\u4EE4 \u2502 ",
1260
- /* @__PURE__ */ jsx11(Text10, { bold: true, children: "Enter" }),
1261
- ":\u53D1\u9001 \u2502 ",
1262
- /* @__PURE__ */ jsx11(Text10, { bold: true, children: "Shift+Enter" }),
1263
- ":\u6362\u884C \u2502 ",
1264
- /* @__PURE__ */ jsx11(Text10, { bold: true, children: "Esc" }),
1265
- ":\u505C\u6B62"
1266
- ] }),
1267
- /* @__PURE__ */ jsx11(Box10, { position: "absolute", bottom: height, width: "100%", flexDirection: "column", children: /* @__PURE__ */ jsx11(
1268
- TerminalPanels,
1269
- {
1270
- panel,
1271
- availableHeight: Math.max(6, rows - height - 1),
1272
- ...panels
1273
- }
1274
- ) }),
1275
- /* @__PURE__ */ jsx11(Box10, { position: "absolute", bottom: height, width: "100%", flexDirection: "column", children: /* @__PURE__ */ jsx11(
1276
- CommandMenu,
1277
- {
1278
- ref: menu,
1279
- text,
1280
- focused,
1281
- availableHeight: Math.max(6, rows - height - 1),
1282
- onSelect: (command) => {
1283
- if (command.id === "read") aui.composer.setText("/read ");
1284
- else {
1285
- aui.composer.setText("");
1286
- void onCommand(command.id, "");
1287
- }
1288
- }
1289
- },
1290
- text
1291
- ) })
1292
- ] });
1293
- }
1294
-
1295
- // packages/cli/src/page-content.tsx
1296
- import { Box as Box12, Text as Text12 } from "ink";
1297
- import { memo } from "react";
1298
-
1299
- // packages/cli/src/search-input.tsx
1300
- import { TextInput } from "@assistant-ui/react-ink";
1301
- import { Box as Box11, Text as Text11 } from "ink";
1302
- import { useEffect as useEffect6, useRef as useRef7, useState as useState8 } from "react";
1303
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1304
- function SearchInput({
1305
- path,
1306
- onSearch
1307
- }) {
1308
- const { colors } = useTerminalStyle();
1309
- const [query, setQuery] = useState8(
1310
- () => new URL(path, "https://miantiao.me").searchParams.get("q") ?? ""
1311
- );
1312
- const timer = useRef7(void 0);
1313
- useEffect6(() => () => clearTimeout(timer.current), []);
1314
- return /* @__PURE__ */ jsxs11(Box11, { borderStyle: "round", borderColor: colors.line, paddingX: 1, marginY: 1, children: [
1315
- /* @__PURE__ */ jsx12(Text11, { color: colors.prompt, children: "\u276F " }),
1316
- /* @__PURE__ */ jsx12(
1317
- TextInput,
1318
- {
1319
- value: query,
1320
- autoFocus: false,
1321
- flexGrow: 1,
1322
- placeholder: "\u641C\u7D22\u6807\u9898\u3001\u63CF\u8FF0\u3001\u6B63\u6587\u4E0E\u6807\u7B7E",
1323
- onChange: (value) => {
1324
- setQuery(value);
1325
- clearTimeout(timer.current);
1326
- timer.current = setTimeout(() => onSearch(value.trim()), 300);
1327
- }
1328
- }
1329
- )
1330
- ] });
1331
- }
1332
-
1333
- // packages/cli/src/page-content.tsx
1334
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1335
- var PageContent = memo(function PageContent2({
1336
- page,
1337
- prompt,
1338
- onSelect,
1339
- scroll,
1340
- onSearch
1341
- }) {
1342
- const { colors } = useTerminalStyle();
1343
- if (page.sections) return /* @__PURE__ */ jsx13(HomeSections, { page, onSelect, scroll });
1344
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginBottom: 1, children: [
1345
- page.path !== "/" && /* @__PURE__ */ jsx13(Prompt, { text: prompt, command: true }),
1346
- /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 2, paddingRight: 1, paddingTop: 1, children: [
1347
- page.path !== "/" && /* @__PURE__ */ jsxs12(Text12, { children: [
1348
- /* @__PURE__ */ jsx13(Text12, { color: colors.skill, children: "\u25C6 " }),
1349
- /* @__PURE__ */ jsx13(Text12, { color: colors.fg, bold: true, children: page.verb }),
1350
- /* @__PURE__ */ jsxs12(Text12, { color: colors.path, children: [
1351
- " ",
1352
- cleanText(page.title)
1353
- ] })
1354
- ] }),
1355
- page.path.startsWith("/search/") && /* @__PURE__ */ jsx13(SearchInput, { path: page.path, onSearch }),
1356
- page.markdown && /* @__PURE__ */ jsx13(Markdown, { text: page.markdown }),
1357
- page.items.length > 0 && /* @__PURE__ */ jsx13(PageList, { items: page.items, onSelect, scroll })
1358
- ] })
1359
- ] });
1360
- });
1361
-
1362
- // packages/cli/src/terminal-layout.tsx
1363
- import { Box as Box13, Text as Text13, useWindowSize as useWindowSize3 } from "ink";
1364
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
1365
- function TerminalLayout({
1366
- path,
1367
- nav = [],
1368
- children
1369
- }) {
1370
- const { columns, rows } = useWindowSize3();
1371
- const { colors } = useTerminalStyle();
1372
- const directory = `~/miantiao.me${path === "/" ? "" : path.replace(/\/$/, "")}`;
1373
- return /* @__PURE__ */ jsxs13(
1374
- Box13,
1375
- {
1376
- width: columns,
1377
- height: rows,
1378
- flexDirection: "column",
1379
- backgroundColor: colors.bg,
1380
- overflow: "hidden",
1381
- children: [
1382
- /* @__PURE__ */ jsxs13(Box13, { paddingX: 2, flexShrink: 0, children: [
1383
- /* @__PURE__ */ jsx14(Box13, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx14(Text13, { color: colors.muted, wrap: "truncate-middle", children: cleanText(directory) }) }),
1384
- columns >= 70 && nav.map((link) => /* @__PURE__ */ jsx14(Box13, { marginLeft: 2, flexShrink: 0, children: /* @__PURE__ */ jsx14(Text13, { color: path.startsWith(link.href) ? colors.fg : colors.muted, children: cleanText(link.title) }) }, link.href))
1385
- ] }),
1386
- children
1387
- ]
1388
- }
1389
- );
1390
- }
1391
-
1392
- // packages/cli/src/console.tsx
1393
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1394
- function Console({
1395
- runtime,
1396
- page,
1397
- prompt,
1398
- revision,
1399
- locationKey,
1400
- site,
1401
- loading,
1402
- panel,
1403
- onCommand,
1404
- onCancel,
1405
- onSelect,
1406
- onSearch,
1407
- onClosePanel,
1408
- ...themePicker
1409
- }) {
1410
- const scroll = useRef8(null);
1411
- const focusKey = `${locationKey}:${revision}`;
1412
- return /* @__PURE__ */ jsx15(TerminalLayout, { path: page.path, nav: site.nav, children: /* @__PURE__ */ jsxs14(ThreadPrimitive2.Root, { flexGrow: 1, flexShrink: 1, minHeight: 0, children: [
1413
- /* @__PURE__ */ jsxs14(
1414
- ScrollArea,
1415
- {
1416
- ref: scroll,
1417
- selection: page.items.length > 0,
1418
- active: !panel || panel.type === "notice",
1419
- children: [
1420
- /* @__PURE__ */ jsx15(
1421
- PageContent,
1422
- {
1423
- page,
1424
- prompt,
1425
- onSelect,
1426
- scroll,
1427
- onSearch
1428
- }
1429
- ),
1430
- /* @__PURE__ */ jsx15(ChatThread, {})
1431
- ]
1432
- },
1433
- focusKey
1434
- ),
1435
- /* @__PURE__ */ jsx15(
1436
- Composer,
1437
- {
1438
- runtime,
1439
- focusKey,
1440
- loading,
1441
- panel,
1442
- site,
1443
- onCommand,
1444
- onCancel,
1445
- ...themePicker,
1446
- onSubmit: () => {
1447
- onClosePanel();
1448
- scroll.current?.end();
1449
- }
1450
- }
1451
- )
1452
- ] }) });
1453
- }
1454
-
1455
- // packages/cli/src/pages.ts
1456
- async function loadPage(client, path, signal) {
1457
- let index = await client.getIndex(signal);
1458
- const url = new URL(path, client.site);
1459
- const pathname = url.pathname.endsWith("/") ? url.pathname : `${url.pathname}/`;
1460
- const page = { path: pathname + url.search, title: "", verb: "List", items: [] };
1461
- const documentPage = [...Object.values(index.pages), ...index.posts].find(
1462
- (item) => new URL(item.path, client.site).pathname === pathname
1463
- );
1464
- if (documentPage) {
1465
- const result = await client.getDocument(documentPage.path, index, signal);
1466
- index = result.index;
1467
- page.path = result.document.path + url.search;
1468
- page.title = result.document.title;
1469
- page.markdown = result.document.markdown;
1470
- page.sections = result.document.sections;
1471
- page.verb = "Read";
1472
- if (page.path === index.pages.home.path) {
1473
- const welcome = page.sections?.find((section) => section.kind === "welcome");
1474
- const menu = welcome?.menu ?? index.site.nav.map((link) => ({ ...link, command: void 0 }));
1475
- const siteOrigins = new Set(
1476
- [client.site, index.site.url].map((site) => new URL(site).origin)
1477
- );
1478
- page.items = menu.map((link) => {
1479
- const destination = new URL(link.href, client.site);
1480
- const external = !siteOrigins.has(destination.origin);
1481
- return {
1482
- title: link.title,
1483
- description: link.command,
1484
- path: external ? destination.href : destination.pathname + destination.search,
1485
- external
1486
- };
1487
- });
1488
- }
1489
- } else if (pathname === "/blog/") {
1490
- page.title = "\u5168\u90E8\u6587\u7AE0";
1491
- page.items = index.posts.map((post) => ({
1492
- title: post.title,
1493
- description: `${post.date.slice(0, 10)} \xB7 ${post.description}`,
1494
- path: post.path
1495
- }));
1496
- } else if (pathname === "/projects/") {
1497
- page.title = "\u6298\u817E\u7684\u9879\u76EE";
1498
- page.items = index.projects.map((project) => ({
1499
- title: `${project.title} \xB7 \u2605 ${project.stars}`,
1500
- description: project.description,
1501
- path: project.url,
1502
- external: true
1503
- }));
1504
- } else if (pathname === "/tags/") {
1505
- page.title = "\u6807\u7B7E";
1506
- page.items = index.tags.map((tag) => ({ title: `${tag.name} (${tag.count})`, path: tag.path }));
1507
- } else if (pathname.startsWith("/tags/")) {
1508
- const tag = index.tags.find((tag2) => tag2.path === pathname);
1509
- if (!tag) throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u6807\u7B7E\uFF0C\u8BF7\u4F7F\u7528 /tags \u67E5\u627E\u3002");
1510
- page.title = tag.name;
1511
- page.items = index.posts.filter((post) => post.tags.includes(tag.name)).map((post) => ({ title: post.title, description: post.description, path: post.path }));
1512
- } else if (pathname === "/search/") {
1513
- const query = url.searchParams.get("q") ?? "";
1514
- page.title = query ? `\u641C\u7D22\uFF1A${query}` : "\u641C\u7D22\u6587\u7AE0";
1515
- page.verb = "Search";
1516
- if (query) {
1517
- const results = await client.search(query, index, signal);
1518
- page.items = results.map((post) => ({
1519
- title: post.title,
1520
- description: post.description,
1521
- path: post.url
1522
- }));
1523
- }
1524
- if (query && !page.items.length) page.markdown = `\u6CA1\u6709\u627E\u5230\u4E0E\u300C${query}\u300D\u76F8\u5173\u7684\u6587\u7AE0`;
1525
- } else if (pathname === "/rss/") {
1526
- page.title = "\u8BA2\u9605 RSS";
1527
- page.items = [{ title: index.site.feedUrl, path: index.site.feedUrl, external: true }];
1528
- } else {
1529
- throw new Error("\u627E\u4E0D\u5230\u8FD9\u4E2A\u9875\u9762\uFF0C\u8BF7\u4F7F\u7528 /help \u67E5\u770B\u547D\u4EE4\u3002");
1530
- }
1531
- return { index, page };
1532
- }
1533
-
1534
- // packages/cli/src/session.ts
1535
- import { useApp, useInput as useInput8 } from "ink";
1536
- import { useCallback as useCallback2, useState as useState10 } from "react";
1537
-
1538
- // packages/cli/src/agent.ts
1539
- import { useChatRuntime } from "@assistant-ui/ai-sdk";
1540
- import { useEffect as useEffect7, useMemo as useMemo3 } from "react";
1541
-
1542
- // src/agent/transport.ts
1543
- import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
1544
- import { DirectChatTransport, isStepCount, tool, ToolLoopAgent } from "ai";
1545
- import { z as z3 } from "zod";
1546
-
1547
- // src/agent/context-core.ts
1548
- import { escapeRegExp } from "es-toolkit/string";
1549
- function formatTime(now, timeZone) {
1550
- const parts = Object.fromEntries(
1551
- new Intl.DateTimeFormat("en-CA", {
1552
- timeZone,
1553
- year: "numeric",
1554
- month: "2-digit",
1555
- day: "2-digit",
1556
- hour: "2-digit",
1557
- minute: "2-digit",
1558
- second: "2-digit",
1559
- hourCycle: "h23",
1560
- timeZoneName: "longOffset"
1561
- }).formatToParts(now).map((part) => [part.type, part.value])
1562
- );
1563
- const offset = parts.timeZoneName === "GMT" ? "UTC+00:00" : parts.timeZoneName.replace("GMT", "UTC");
1564
- return `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second} (${timeZone}, ${offset})`;
1565
- }
1566
- function describeDevice(userAgent, viewportWidth) {
1567
- const os = [
1568
- ["iOS", /iPhone|iPad|iPod/],
1569
- ["Android", /Android/],
1570
- ["Windows", /Windows/],
1571
- ["macOS", /Mac OS X|Macintosh/],
1572
- ["Linux", /Linux/]
1573
- ].find(([, pattern]) => pattern.test(userAgent))?.[0] ?? "Unknown OS";
1574
- const browser = [
1575
- ["Edge", /Edg\//],
1576
- ["Firefox", /Firefox\//],
1577
- ["Chrome", /Chrome\//],
1578
- ["Safari", /Safari\//]
1579
- ].find(([, pattern]) => pattern.test(userAgent))?.[0] ?? "Unknown browser";
1580
- return `${os} \xB7 ${browser} \xB7 ${viewportWidth < 768 ? "mobile" : "desktop"}`;
1581
- }
1582
- function buildContextMessage(context, siteName) {
1583
- const title = context.title.replace(new RegExp(`\\s*[|-]\\s*${escapeRegExp(siteName)}.*$`), "") || siteName;
1584
- const lines = [
1585
- `page: ${title} (${context.path})`,
1586
- `theme: ${context.theme}`,
1587
- `language: ${context.languages.slice(0, 3).join(", ")}`,
1588
- `time: ${formatTime(context.now, context.timeZone)}`,
1589
- `device: ${context.terminal ? `terminal \xB7 ${context.terminal.platform} \xB7 ${context.terminal.columns}x${context.terminal.rows}` : describeDevice(context.userAgent, context.viewportWidth)}`
1590
- ];
1591
- if (context.referrer && !context.referrer.startsWith(context.origin))
1592
- lines.push(`referrer: ${context.referrer}`);
1593
- return `<context>
1594
- ${lines.join("\n")}
1595
- </context>`;
1596
- }
1597
- function retryContextMessage(message) {
1598
- return message.replace("\n</context>", "\nretry: 1\n</context>");
1599
- }
1600
-
1601
- // src/agent/fetch.ts
1602
- function stripSseEvent(body, eventName) {
1603
- const pattern = new RegExp(`^event:\\s*${eventName}\\s*$`, "m");
1604
- let buffer = "";
1605
- return body.pipeThrough(new TextDecoderStream()).pipeThrough(new TransformStream({
1606
- transform(text, controller) {
1607
- buffer += text;
1608
- const events = buffer.split(/\r?\n\r?\n/);
1609
- buffer = events.pop() ?? "";
1610
- for (const event of events) {
1611
- if (!pattern.test(event))
1612
- controller.enqueue(`${event}
1613
-
1614
- `);
1615
- }
1616
- },
1617
- flush(controller) {
1618
- if (buffer && !pattern.test(buffer))
1619
- controller.enqueue(buffer);
1620
- }
1621
- })).pipeThrough(new TextEncoderStream());
1622
- }
1623
- var aiSearchFetch = async (input, init) => {
1624
- let response = await fetch(input, init);
1625
- if (response.status >= 500 && typeof init?.body === "string") {
1626
- const body = JSON.parse(init.body);
1627
- const context = body.messages[0];
1628
- if (context?.role === "user" && typeof context.content === "string") {
1629
- context.content = retryContextMessage(context.content);
1630
- await response.body?.cancel();
1631
- response = await fetch(input, { ...init, body: JSON.stringify(body) });
1632
- }
1633
- }
1634
- if (!response.ok || !response.body || !response.headers.get("content-type")?.includes("text/event-stream"))
1635
- return response;
1636
- return new Response(stripSseEvent(response.body, "chunks"), response);
1637
- };
1638
-
1639
- // src/agent/transport.ts
1640
- var themeIds = themeOptions.map((option) => option.id);
1641
- function createAgentTransport(handlers, options) {
1642
- const provider = createOpenAICompatible({
1643
- name: "miantiao",
1644
- baseURL: options.baseUrl,
1645
- fetch: aiSearchFetch
1646
- });
1647
- const agent = new ToolLoopAgent({
1648
- model: provider(options.model),
1649
- maxRetries: 0,
1650
- stopWhen: isStepCount(2),
1651
- // 工具执行后只生成答复,避免模型反复执行同一导航。
1652
- prepareStep: ({ stepNumber }) => stepNumber > 0 ? { toolChoice: "none" } : void 0,
1653
- tools: {
1654
- set_theme: tool({
1655
- description: "\u5207\u6362\u5F53\u524D\u754C\u9762\u4E3B\u9898",
1656
- inputSchema: z3.object({ theme: z3.enum(themeIds) }),
1657
- execute: async ({ theme }) => {
1658
- handlers.setTheme(theme);
1659
- return `\u5DF2\u5207\u6362\u5230 ${getThemeName(theme)}`;
1660
- }
1661
- }),
1662
- open_page: tool({
1663
- description: "\u6253\u5F00\u7AD9\u5185\u9875\u9762\uFF0C\u53C2\u6570\u4E3A\u4EE5 / \u5F00\u5934\u7684\u7AD9\u5185\u8DEF\u5F84",
1664
- inputSchema: z3.object({ path: z3.string().regex(/^\/(?!\/)[^\s\\]*$/) }),
1665
- execute: async ({ path }) => {
1666
- await handlers.openPage(path);
1667
- return `\u5DF2\u6253\u5F00 ${path}`;
1668
- }
1669
- })
1670
- },
1671
- // DirectChatTransport 以 `prompt` 传入完整的消息历史
1672
- prepareCall: ({ prompt, messages, ...rest }) => ({
1673
- ...rest,
1674
- messages: [
1675
- { role: "user", content: buildContextMessage(handlers.getContext(), options.siteName) },
1676
- { role: "assistant", content: "\u5DF2\u8BFB\u53D6\u4E0A\u4E0B\u6587\u3002" },
1677
- ...messages ?? (Array.isArray(prompt) ? prompt : [])
1678
- ]
1679
- })
1680
- });
1681
- return new DirectChatTransport({ agent, onError: () => "AI \u6682\u65F6\u65E0\u6CD5\u56DE\u590D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002" });
1682
- }
1683
-
1684
- // packages/cli/src/agent.ts
1685
- function useTerminalAgent({ site, index, page, theme, setTheme, navigate }) {
1686
- const transport = useMemo3(
1687
- () => createAgentTransport(
1688
- {
1689
- getContext: () => {
1690
- const locale = Intl.DateTimeFormat().resolvedOptions();
1691
- return {
1692
- title: page.title,
1693
- path: page.path,
1694
- theme,
1695
- languages: [locale.locale],
1696
- timeZone: locale.timeZone,
1697
- now: /* @__PURE__ */ new Date(),
1698
- userAgent: "",
1699
- viewportWidth: process.stdout.columns || 80,
1700
- referrer: "",
1701
- origin: site,
1702
- terminal: {
1703
- platform: process.platform,
1704
- columns: process.stdout.columns || 80,
1705
- rows: process.stdout.rows || 24
1706
- }
1707
- };
1708
- },
1709
- setTheme,
1710
- openPage: async (path) => {
1711
- if (!await navigate(path)) throw new Error("\u65E0\u6CD5\u6253\u5F00\u8FD9\u4E2A\u7AD9\u5185\u9875\u9762\u3002");
1712
- }
1713
- },
1714
- { baseUrl: index.ai.baseUrl, model: index.ai.model, siteName: index.site.name }
1715
- ),
1716
- [
1717
- site,
1718
- page.title,
1719
- page.path,
1720
- theme,
1721
- setTheme,
1722
- navigate,
1723
- index.ai.baseUrl,
1724
- index.ai.model,
1725
- index.site.name
1726
- ]
1727
- );
1728
- const runtime = useChatRuntime({ transport });
1729
- useEffect7(() => () => runtime.thread.cancelRun(), [runtime]);
1730
- return runtime;
1731
- }
1732
-
1733
- // packages/cli/src/browser.ts
1734
- import { spawn } from "node:child_process";
1735
- async function openBrowser(input, site) {
1736
- const url = new URL(input, site);
1737
- if (!["http:", "https:"].includes(url.protocol))
1738
- throw new Error("\u53EA\u80FD\u5728\u6D4F\u89C8\u5668\u6253\u5F00 HTTP \u6216 HTTPS \u94FE\u63A5\u3002");
1739
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer.exe" : "xdg-open";
1740
- await new Promise((resolve, reject) => {
1741
- const child = spawn(command, [url.href], { stdio: "ignore" });
1742
- child.once("error", () => reject(new Error(`\u65E0\u6CD5\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE ${url.href}`)));
1743
- child.once(
1744
- "exit",
1745
- (code) => code === 0 ? resolve() : reject(new Error(`\u65E0\u6CD5\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE ${url.href}`))
1746
- );
1747
- });
1748
- }
1749
-
1750
- // packages/cli/src/navigation.ts
1751
- import { useCallback, useEffect as useEffect8, useRef as useRef9, useState as useState9 } from "react";
1752
- function useNavigation(client, initial) {
1753
- const [state, setState] = useState9({
1754
- ...initial,
1755
- status: "idle",
1756
- panel: client.warning ? { type: "notice", text: client.warning } : null,
1757
- prompt: "/home",
1758
- locationKey: 0
1759
- });
1760
- const active = useRef9(void 0);
1761
- useEffect8(
1762
- () => () => {
1763
- active.current?.controller.abort();
1764
- active.current = void 0;
1765
- },
1766
- []
1767
- );
1768
- const cancel = useCallback(() => {
1769
- active.current?.controller.abort();
1770
- active.current = void 0;
1771
- setState((current) => current.status === "idle" ? current : { ...current, status: "idle" });
1772
- }, []);
1773
- const setPanel = useCallback((panel) => {
1774
- setState((current) => ({ ...current, panel }));
1775
- }, []);
1776
- const notify = useCallback(
1777
- (text, tone) => {
1778
- setPanel({ type: "notice", text, tone });
1779
- },
1780
- [setPanel]
1781
- );
1782
- const navigate = useCallback(
1783
- async (path, prompt = path, replace = false) => {
1784
- if (replace && active.current && !active.current.replace) return false;
1785
- active.current?.controller.abort();
1786
- const request = { controller: new AbortController(), replace };
1787
- active.current = request;
1788
- setState((current) => ({ ...current, status: "loading", panel: null }));
1789
- let next;
1790
- try {
1791
- next = await loadPage(client, path, request.controller.signal);
1792
- } catch (error) {
1793
- if (active.current === request) {
1794
- active.current = void 0;
1795
- setState((current) => ({
1796
- ...current,
1797
- status: "idle",
1798
- panel: {
1799
- type: "notice",
1800
- tone: "error",
1801
- text: error instanceof Error ? error.message : "\u65E0\u6CD5\u8BFB\u53D6\u5B98\u7F51\u5185\u5BB9\u3002"
1802
- }
1803
- }));
1804
- }
1805
- return false;
1806
- }
1807
- if (active.current !== request) return false;
1808
- active.current = void 0;
1809
- setState((current) => ({
1810
- ...next,
1811
- status: "idle",
1812
- panel: client.warning ? { type: "notice", text: client.warning } : null,
1813
- prompt,
1814
- locationKey: current.locationKey + Number(!replace)
1815
- }));
1816
- return true;
1817
- },
1818
- [client]
1819
- );
1820
- return { ...state, navigate, setPanel, cancel, notify };
1821
- }
1822
-
1823
- // packages/cli/src/session.ts
1824
- function useTerminalSession(client, initial) {
1825
- const { exit } = useApp();
1826
- const [theme, setTheme] = useState10("auto");
1827
- const [previewTheme, setPreviewTheme] = useState10();
1828
- const { index, page, prompt, locationKey, status, panel, navigate, setPanel, cancel, notify } = useNavigation(client, initial);
1829
- const runtime = useTerminalAgent({ site: client.site, index, page, theme, setTheme, navigate });
1830
- const stop = () => {
1831
- cancel();
1832
- runtime.thread.cancelRun();
1833
- };
1834
- useInput8((input, key) => {
1835
- if (key.ctrl && input === "c") {
1836
- stop();
1837
- exit();
1838
- }
1839
- });
1840
- const onClosePanel = () => {
1841
- setPreviewTheme(void 0);
1842
- setPanel(null);
1843
- };
1844
- const onCancel = () => {
1845
- if (panel) onClosePanel();
1846
- else stop();
1847
- };
1848
- const onApplyTheme = (preference) => {
1849
- setPreviewTheme(void 0);
1850
- setTheme(preference);
1851
- notify(`\u5DF2\u5207\u6362\u5230 ${getThemeName(preference)}`);
1852
- };
1853
- const onCommand = async (name, args) => {
1854
- setPreviewTheme(void 0);
1855
- 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;
1875
- }
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;
1882
- case "read":
1883
- await navigate(resolveReadPath(args, page), `/read ${args}`);
1884
- break;
1885
- case "refresh":
1886
- await navigate(page.path, "/refresh");
1887
- break;
1888
- case "open":
1889
- await openBrowser(resolveOpenTarget(args, page), client.site);
1890
- break;
1891
- case "clear":
1892
- stop();
1893
- runtime.thread.reset();
1894
- onClosePanel();
1895
- break;
1896
- case "quit":
1897
- stop();
1898
- exit();
1899
- break;
1900
- default:
1901
- notify(`\u672A\u77E5\u547D\u4EE4\uFF1A/${name}\uFF0C\u8F93\u5165 /help \u67E5\u770B\u53EF\u7528\u547D\u4EE4`, "error");
1902
- }
1903
- } catch (error) {
1904
- notify(error instanceof Error ? error.message : "\u65E0\u6CD5\u6267\u884C\u547D\u4EE4\u3002", "error");
1905
- }
1906
- };
1907
- const onSelect = (path, external = false) => {
1908
- if (external) void onCommand("open", path);
1909
- else void navigate(path);
1910
- };
1911
- const onSearch = useCallback2(
1912
- (query) => {
1913
- void navigate(getSearchPath(query), `/search ${query}`.trim(), true);
1914
- },
1915
- [navigate]
1916
- );
1917
- return {
1918
- runtime,
1919
- page,
1920
- revision: index.revision,
1921
- locationKey,
1922
- prompt,
1923
- site: index.site,
1924
- loading: status === "loading",
1925
- panel,
1926
- onCommand,
1927
- onCancel,
1928
- onClosePanel,
1929
- onSelect,
1930
- onSearch,
1931
- theme: previewTheme ?? theme,
1932
- savedTheme: theme,
1933
- onPreviewTheme: setPreviewTheme,
1934
- onApplyTheme
1935
- };
1936
- }
1937
-
1938
- // packages/cli/src/app.tsx
1939
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
1940
- function Session({
1941
- client,
1942
- initial,
1943
- width
1944
- }) {
1945
- const { theme, ...session } = useTerminalSession(client, initial);
1946
- return /* @__PURE__ */ jsx16(TerminalTheme, { preference: theme, width, children: /* @__PURE__ */ jsx16(AssistantRuntimeProvider, { runtime: session.runtime, children: /* @__PURE__ */ jsx16(Console, { ...session }) }) });
1947
- }
1948
- function App({ site }) {
1949
- const { exit } = useApp2();
1950
- const { columns } = useWindowSize4();
1951
- const [client] = useState11(() => new TerminalClient(site));
1952
- const [state, setState] = useState11({ status: "loading" });
1953
- const [attempt, setAttempt] = useState11(0);
1954
- const width = Math.max(10, columns - 5);
1955
- useEffect9(() => {
1956
- const request = new AbortController();
1957
- loadPage(client, "/", request.signal).then((content) => {
1958
- if (!request.signal.aborted) setState({ status: "ready", content });
1959
- }).catch((error) => {
1960
- if (!request.signal.aborted)
1961
- setState({
1962
- status: "error",
1963
- message: error instanceof Error ? error.message : "\u65E0\u6CD5\u8BFB\u53D6\u5B98\u7F51\u5185\u5BB9\u3002"
1964
- });
1965
- });
1966
- return () => request.abort();
1967
- }, [client, attempt]);
1968
- useInput9(
1969
- (input, key) => {
1970
- if (key.ctrl && input === "c" || input === "q") exit();
1971
- else if (state.status === "error" && (key.return || input === "r" || input === "/refresh")) {
1972
- setState({ status: "loading" });
1973
- setAttempt((value) => value + 1);
1974
- }
1975
- },
1976
- { isActive: state.status !== "ready" }
1977
- );
1978
- if (state.status === "ready")
1979
- return /* @__PURE__ */ jsx16(Session, { client, initial: state.content, width });
1980
- return /* @__PURE__ */ jsx16(TerminalTheme, { preference: "auto", width, children: /* @__PURE__ */ jsx16(TerminalLayout, { path: "/", children: /* @__PURE__ */ jsxs15(Box14, { flexDirection: "column", flexGrow: 1, padding: 2, children: [
1981
- /* @__PURE__ */ jsxs15(Text14, { children: [
1982
- "\u25C6 ",
1983
- state.status === "error" ? state.message : "\u601D\u8003\u4E2D\u2026\u2026"
1984
- ] }),
1985
- /* @__PURE__ */ jsx16(Text14, { children: state.status === "error" ? "Enter \u91CD\u8BD5 \xB7 q \u9000\u51FA" : "Ctrl+C \u9000\u51FA" })
1986
- ] }) }) });
1987
- }
1988
- async function run(site) {
1989
- const app = render(/* @__PURE__ */ jsx16(App, { site }), {
1990
- exitOnCtrlC: false,
1991
- alternateScreen: true,
1992
- incrementalRendering: true
1993
- });
1994
- await app.waitUntilExit();
1995
- }
1996
- export {
1997
- run
1998
- };