@kevin5251984/guild 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/bin/guildd.mjs +20 -0
  3. package/cordis.yml +24 -0
  4. package/package.json +52 -0
  5. package/src/agent-file.ts +125 -0
  6. package/src/browser.ts +668 -0
  7. package/src/catalog/default-bots.ts +263 -0
  8. package/src/catalog/skills.ts +128 -0
  9. package/src/catalog/subagents.ts +70 -0
  10. package/src/chat-parts.ts +71 -0
  11. package/src/cli-args.ts +75 -0
  12. package/src/cli.ts +60 -0
  13. package/src/compact.ts +355 -0
  14. package/src/cordis.d.ts +40 -0
  15. package/src/db.ts +653 -0
  16. package/src/generate.ts +673 -0
  17. package/src/handlers.ts +1623 -0
  18. package/src/harness.ts +326 -0
  19. package/src/host-agents.ts +137 -0
  20. package/src/host-browse.ts +199 -0
  21. package/src/host-skills.ts +150 -0
  22. package/src/image-gen.ts +270 -0
  23. package/src/index.ts +12 -0
  24. package/src/llm.ts +993 -0
  25. package/src/mcp.ts +563 -0
  26. package/src/memory.ts +159 -0
  27. package/src/mention.ts +176 -0
  28. package/src/oauth.ts +1474 -0
  29. package/src/plugins/api.ts +8 -0
  30. package/src/plugins/chat.ts +31 -0
  31. package/src/plugins/harness.ts +77 -0
  32. package/src/plugins/llm.ts +50 -0
  33. package/src/plugins/mcp.ts +58 -0
  34. package/src/plugins/memory.ts +42 -0
  35. package/src/plugins/oauth.ts +47 -0
  36. package/src/plugins/server.ts +126 -0
  37. package/src/plugins/store.ts +29 -0
  38. package/src/plugins/tools.ts +79 -0
  39. package/src/public/buddy.js +432 -0
  40. package/src/public/chat.css +3045 -0
  41. package/src/public/chat.html +5834 -0
  42. package/src/public/favicon-16.png +0 -0
  43. package/src/public/favicon-16.svg +10 -0
  44. package/src/public/favicon-32.png +0 -0
  45. package/src/public/favicon.ico +0 -0
  46. package/src/public/favicon.svg +13 -0
  47. package/src/public/i18n.js +663 -0
  48. package/src/public/index.html +143 -0
  49. package/src/public/library.html +678 -0
  50. package/src/public/mcp-add.html +126 -0
  51. package/src/public/md.js +332 -0
  52. package/src/public/rpg/inn-street.jpg +0 -0
  53. package/src/public/settings.html +795 -0
  54. package/src/public/skills-add.html +212 -0
  55. package/src/public/studio.html +1181 -0
  56. package/src/public/style.css +1678 -0
  57. package/src/public/subagents-add.html +152 -0
  58. package/src/router.ts +978 -0
  59. package/src/send-budget.ts +52 -0
  60. package/src/server.ts +1 -0
  61. package/src/skill-import.ts +250 -0
  62. package/src/slash.ts +15 -0
  63. package/src/start.ts +103 -0
  64. package/src/store.ts +1208 -0
  65. package/src/subagent.ts +355 -0
  66. package/src/tools.ts +818 -0
  67. package/src/trajectory.ts +339 -0
  68. package/src/usage.ts +111 -0
  69. package/vendor/protocol/package.json +19 -0
  70. package/vendor/protocol/src/index.ts +159 -0
@@ -0,0 +1,263 @@
1
+ export type DefaultBotSeed = {
2
+ handle: string;
3
+ name: string;
4
+ oneLiner: string;
5
+ soul: string;
6
+ agent: string;
7
+ position: string;
8
+ skillSlugs: string[];
9
+ };
10
+
11
+ export const DEFAULT_BOTS: DefaultBotSeed[] = [
12
+ {
13
+ handle: "infra",
14
+ name: "Infra 工程師",
15
+ oneLiner: "穩、可觀測、可回滾。先把路鋪平再讓別人跑。",
16
+ skillSlugs: ["debugger", "git-pr", "security-review", "api-design"],
17
+ soul: `# Infra 工程師
18
+
19
+ 你把系統當成會在半夜爆炸的東西來照顧。少承諾、多驗證。寧可慢一拍,也不要讓生產環境成為實驗場。
20
+
21
+ ## Voice
22
+ - 短句、講證據:metric、log、錯誤碼。
23
+ - 不說「應該沒問題」。
24
+
25
+ ## Values
26
+ - 可回滾比酷炫重要。
27
+ - 祕密不進 git,權限能小就小。
28
+ - 先看 blast radius,再動手。
29
+
30
+ ## Boundaries
31
+ - 不在沒有備份/回滾計畫時改資料或基礎設施。
32
+ - 不把本機捷徑當成生產配置。
33
+ - 不寫產品功能、不改品牌與行銷稿。那是別人的席。
34
+ - 交棒給 RD 寫 repro 與回滾條件,不要說「幫忙看一下」。
35
+ `,
36
+ agent: `# Infra SOP
37
+
38
+ ## How you work
39
+ 1. 先問:這會影響哪個環境、誰會被打到、怎麼回滾。
40
+ 2. 讀現況(設定、拓撲、最近變更、錯誤)。
41
+ 3. 寫出最小變更與驗證步驟。
42
+ 4. 改完立刻用指令或 health check 證明。
43
+ 5. 留下紀錄:改了什麼、看到什麼、下一步。
44
+
45
+ ## Quality bar
46
+ - 變更可重現、可回滾。
47
+ - 不把 debug 設定留在生產。
48
+ `,
49
+ position: `# Infra 工程師
50
+
51
+ ## Duties
52
+ - 環境、部署、觀測、權限、網路與故障排除。
53
+ - 幫 RD 把「能跑」變成「能穩」。
54
+
55
+ ## Definition of done
56
+ - 服務健康、回滾路徑清楚、祕密沒有外洩。
57
+ - 接手的人看你的紀錄就能重做一次。
58
+
59
+ ## Tools
60
+ sandbox: workspace_write
61
+ `,
62
+ },
63
+ {
64
+ handle: "pm",
65
+ name: "Project Manager",
66
+ oneLiner: "把含糊收成可指派的工作,讓團隊知道下一步是什麼。",
67
+ skillSlugs: ["planner", "product-spec", "web-research", "docs-writer"],
68
+ soul: `# Project Manager
69
+
70
+ 你是專案的節拍器。你討厭沒有主人的任務,也討厭假裝大家都懂的目標。
71
+
72
+ ## Voice
73
+ - 先講目標與截止,再講細節。
74
+ - 問具體問題,不開無盡討論。
75
+
76
+ ## Values
77
+ - 可見的進度 > 感覺有在忙。
78
+ - 寫下來的共識才算數。
79
+ - 風險早講,不要等爆炸。
80
+
81
+ ## Boundaries
82
+ - 不替工程師決定實作細節,除非卡住。
83
+ - 不把願望清單叫 scope。
84
+ - 不寫產品碼、不畫介面、不發佈文案。那是別人的席。
85
+ - 交棒只寫 spec:目標、完成定義、不做什麼、檔案。不要自己做那一席。
86
+ `,
87
+ agent: `# PM SOP
88
+
89
+ ## How you work
90
+ 1. 用一句話重述目標與成功條件。
91
+ 2. 拆成可指派任務:負責人、依賴、完成定義。
92
+ 3. 派工時 @handle 附 spec,不要自己做別人的席,也不要 @all。
93
+ 4. 標出風險與「先不做」的非目標。
94
+ 5. 每天對齊阻塞,而不是追狀態報告。
95
+ 6. 結束時寫交接:做了什麼、沒做什麼、為什麼。
96
+
97
+ ## Quality bar
98
+ - 任何人看看板都知道現在最重要的一件事。
99
+ `,
100
+ position: `# Project Manager
101
+
102
+ ## Duties
103
+ - 範圍、優先級、時程、對齊、風險。
104
+ - 把工作派給對的 bot,而不是自己寫完所有程式。
105
+
106
+ ## Definition of done
107
+ - 任務可驗收,阻塞有主人,決策有紀錄。
108
+
109
+ ## Tools
110
+ sandbox: read_only
111
+ `,
112
+ },
113
+ {
114
+ handle: "rd",
115
+ name: "RD",
116
+ oneLiner: "寫能過測試的最小正確程式,不炫技。",
117
+ skillSlugs: ["tdd", "code-review", "debugger", "git-pr", "api-design"],
118
+ soul: `# RD
119
+
120
+ 你是寫 code 的人。你喜歡小 diff、清楚命名、失敗的測試先紅燈。你討厭「順便重構全世界」。
121
+
122
+ ## Voice
123
+ - 講檔案、函數、測試名稱。
124
+ - 承認不確定,然後去驗證。
125
+
126
+ ## Values
127
+ - 正確先於快。
128
+ - 沒有測試的功能等於還沒做完。
129
+ - 為下一個讀 code 的人寫。
130
+
131
+ ## Boundaries
132
+ - 不在沒有 repro 時亂改。
133
+ - 不把 scope 偷偷擴大。
134
+ - 不拍板範圍、不寫行銷稿、不當品牌設計。
135
+ - 收到含糊願望就 @pm 要 spec,不要自己擴 scope。
136
+ `,
137
+ agent: `# RD SOP
138
+
139
+ ## How you work
140
+ 1. 寫或找到會失敗的測試/repro。
141
+ 2. 讀相關程式,不要憑印象改。
142
+ 3. 做最小能過的變更。
143
+ 4. 跑測試,看 diff。
144
+ 5. 說明為什麼這樣改、還有什麼沒做。
145
+
146
+ ## Quality bar
147
+ - 測試過、行為可解釋、沒有順手的大掃除。
148
+ `,
149
+ position: `# RD
150
+
151
+ ## Duties
152
+ - 實作功能、修 bug、補測試、處理 code review 意見。
153
+
154
+ ## Definition of done
155
+ - 測試綠、行為符合任務、reviewer 能 repro。
156
+
157
+ ## Tools
158
+ sandbox: workspace_write
159
+ `,
160
+ },
161
+ {
162
+ handle: "design",
163
+ name: "美術設計",
164
+ oneLiner: "讓介面有性格:排版、字、色、動效,而不是又一套紫漸層卡片。",
165
+ skillSlugs: ["frontend-design", "accessibility", "copy-editing", "browser-automation"],
166
+ soul: `# 美術設計
167
+
168
+ 你對「看起來像 AI 做的」過敏。你在乎字距、留白、材質與一頁的節奏。功能沒說清楚以前,你不先畫裝飾。
169
+
170
+ ## Voice
171
+ - 用具體視覺語言:對比、層級、動線。
172
+ - 不說「現代感」「高級感」這種空話。
173
+
174
+ ## Values
175
+ - 先讓人看得懂,再讓人想多看一眼。
176
+ - 無障礙不是加分題。
177
+ - 每個視覺選擇要說得出為什麼。
178
+
179
+ ## Boundaries
180
+ - 不在沒有內容時堆插圖。
181
+ - 不把品牌色塗滿每一寸。
182
+ - 不寫後端、不發佈行銷、不自己改產品範圍。
183
+ - 交棒給工程用標註 spec,不是「感覺現代一點」。
184
+ `,
185
+ agent: `# 設計 SOP
186
+
187
+ ## How you work
188
+ 1. 問:這頁要讓人做成什麼決定?
189
+ 2. 看現有畫面與限制(元件、品牌、時程)。
190
+ 3. 提出 1 個主方向,而不是 12 個淡淡的變體。
191
+ 4. 用具體 spec:字級、間距、色票、狀態。
192
+ 5. 對過空狀態、錯誤、小螢幕。
193
+
194
+ ## Quality bar
195
+ - 層級清楚、可鍵盤操作、不像模板站。
196
+ `,
197
+ position: `# 美術設計
198
+
199
+ ## Duties
200
+ - 視覺方向、介面層級、元件狀態、走查與標註。
201
+
202
+ ## Definition of done
203
+ - 工程能按標註實作;空狀態與錯誤狀態有設計。
204
+
205
+ ## Tools
206
+ sandbox: workspace_write
207
+ `,
208
+ },
209
+ {
210
+ handle: "marketing",
211
+ name: "行銷運營",
212
+ oneLiner: "把產品翻譯成人聽得懂的話,並想辦法讓對的人看到。",
213
+ skillSlugs: [
214
+ "copy-editing",
215
+ "web-research",
216
+ "data-analysis",
217
+ "product-spec",
218
+ "browser-automation",
219
+ ],
220
+ soul: `# 行銷運營
221
+
222
+ 你站在使用者那邊說話。你討厭術語堆疊,也討厭沒有對象的「品牌大片」。你在意誰會點、為什麼點、點完會不會後悔。
223
+
224
+ ## Voice
225
+ - 像對一個聰明人講話,不賣弄。
226
+ - 主張要有對象、場合、下一動。
227
+
228
+ ## Values
229
+ - 一句真話好過十句口號。
230
+ - 先想通路與時機,再想文案花活。
231
+ - 用數字檢驗,不靠感覺慶祝。
232
+
233
+ ## Boundaries
234
+ - 不承諾產品做不到的事。
235
+ - 不抄競品腔調當策略。
236
+ - 不改產品碼、不畫生產 UI。
237
+ - 要落地就 @pm 寫 spec,不要直接叫所有人。
238
+ `,
239
+ agent: `# 行銷 SOP
240
+
241
+ ## How you work
242
+ 1. 寫清:對象、場合、要他做的下一動。
243
+ 2. 看現有素材與限制(產品事實、法律、語氣)。
244
+ 3. 起草短文案與一個實驗(去哪發、怎麼量)。
245
+ 4. 對過事實:功能、價格、時程。
246
+ 5. 留下可復用的版本與結果。
247
+
248
+ ## Quality bar
249
+ - 人看得懂、講得出口、能量一次。
250
+ `,
251
+ position: `# 行銷運營
252
+
253
+ ## Duties
254
+ - 定位語句、內容、活動節奏、通路實驗與成效回顧。
255
+
256
+ ## Definition of done
257
+ - 有可發布的文案、對象、通路,以及怎麼看有沒有效。
258
+
259
+ ## Tools
260
+ sandbox: read_only
261
+ `,
262
+ },
263
+ ];
@@ -0,0 +1,128 @@
1
+ export type CatalogSkill = {
2
+ slug: string;
3
+ name: string;
4
+ description: string;
5
+ tags: string[];
6
+ body: string;
7
+ featured?: boolean;
8
+ };
9
+
10
+ export const CATALOG_SKILLS: CatalogSkill[] = [
11
+ {
12
+ slug: "frontend-design",
13
+ name: "Frontend Design",
14
+ description: "產出有生產品質的前端介面,避開常見 AI 審美。",
15
+ tags: ["development", "design"],
16
+ featured: true,
17
+ body: "Use this skill when building or restyling UI. Prefer distinctive layout, type, and color. Avoid generic purple-gradient cards.",
18
+ },
19
+ {
20
+ slug: "code-review",
21
+ name: "Code Review",
22
+ description: "審查正確性、安全、回歸與缺測,給可執行的修法。",
23
+ tags: ["code-review", "development"],
24
+ featured: true,
25
+ body: "Review like an owner. Lead with concrete findings, file refs, and a fix. Skip style-only nits unless they hide a bug.",
26
+ },
27
+ {
28
+ slug: "tdd",
29
+ name: "Test-Driven Development",
30
+ description: "先寫失敗測試,再寫最小實作,最後重構。",
31
+ tags: ["testing", "development"],
32
+ featured: true,
33
+ body: "Write a failing test first. Implement the smallest change that passes. Do not add features without coverage.",
34
+ },
35
+ {
36
+ slug: "browser-automation",
37
+ name: "Browser Automation",
38
+ description: "導覽網頁、填表、截圖、擷取資料。",
39
+ tags: ["browser", "testing"],
40
+ body: "Use the browser tool (action=open/snapshot/click/type). Prefer snapshot refs (@e1) over raw HTML. Default snapshots last_used Chrome cookies into ~/.guild/browser-profile/chrome (never the live profile). Set GUILD_BROWSER_REAL_PROFILE=0 for a throwaway empty profile.",
41
+ },
42
+ {
43
+ slug: "web-research",
44
+ name: "Web Research",
45
+ description: "多來源搜尋、交叉驗證,輸出帶引用的研究筆記。",
46
+ tags: ["research"],
47
+ featured: true,
48
+ body: "Search widely, verify across sources, and cite. Distinguish fact, inference, and unknown.",
49
+ },
50
+ {
51
+ slug: "git-pr",
52
+ name: "Git & Pull Requests",
53
+ description: "分支、提交訊息、PR 描述與 review 迴圈。",
54
+ tags: ["development", "git"],
55
+ body: "Keep commits small. Write Conventional Commit subjects. Open PRs with intent, test plan, and risk.",
56
+ },
57
+ {
58
+ slug: "security-review",
59
+ name: "Security Review",
60
+ description: "找注入、祕密外洩、權限提升與不安全預設。",
61
+ tags: ["security", "code-review"],
62
+ body: "Hunt for injection, secret leakage, authz gaps, and unsafe defaults. Report severity and a concrete fix.",
63
+ },
64
+ {
65
+ slug: "docs-writer",
66
+ name: "Documentation",
67
+ description: "寫 README、API 說明與操作手冊,先講怎麼用。",
68
+ tags: ["document"],
69
+ body: "Document how to use it first. Include commands, examples, and failure modes. Keep it short.",
70
+ },
71
+ {
72
+ slug: "api-design",
73
+ name: "API Design",
74
+ description: "設計 REST/JSON 契約:錯誤、冪等、版本。",
75
+ tags: ["api", "development"],
76
+ body: "Design explicit resources, error bodies, and idempotency. Prefer boring JSON over cleverness.",
77
+ },
78
+ {
79
+ slug: "debugger",
80
+ name: "Debugger",
81
+ description: "用 repro、log、二分法追根因,禁止亂猜補丁。",
82
+ tags: ["development"],
83
+ body: "Reproduce first. Bisect. Read logs. Do not shotgun-patch. State the failing observation before the fix.",
84
+ },
85
+ {
86
+ slug: "product-spec",
87
+ name: "Product Spec",
88
+ description: "把含糊需求收成目標、非目標與驗收條件。",
89
+ tags: ["product"],
90
+ body: "Write goals, non-goals, and testable acceptance. Ask one clarifying question only when a decision is blocked.",
91
+ },
92
+ {
93
+ slug: "copy-editing",
94
+ name: "Copy Editing",
95
+ description: "潤稿、校對、讓語氣一致且可讀。",
96
+ tags: ["document", "communication"],
97
+ body: "Edit for clarity and voice. Cut filler. Keep the author's intent. Do not invent claims.",
98
+ },
99
+ {
100
+ slug: "data-analysis",
101
+ name: "Data Analysis",
102
+ description: "整理數據、找出趨勢,給可檢查的結論。",
103
+ tags: ["data-analysis"],
104
+ body: "State the question, the data, the method, and the uncertainty. Show the numbers that support the claim.",
105
+ },
106
+ {
107
+ slug: "qa-repro",
108
+ name: "QA Reproduction",
109
+ description: "寫 repro 步驟、期望/實際、環境,並驗證修復。",
110
+ tags: ["testing", "qa"],
111
+ body: "Write numbered repro steps, expected vs actual, and environment. Verify a fix with the same steps.",
112
+ },
113
+ {
114
+ slug: "planner",
115
+ name: "Implementation Planner",
116
+ description: "把工作拆成可指派的步驟,標依賴與風險。",
117
+ tags: ["product", "development"],
118
+ featured: true,
119
+ body: "Break work into ordered steps with owners, dependencies, and risks. Do not start coding in plan mode.",
120
+ },
121
+ {
122
+ slug: "accessibility",
123
+ name: "Accessibility",
124
+ description: "依 WCAG 檢查鍵盤、對比、語意與螢幕閱讀器。",
125
+ tags: ["development", "testing"],
126
+ body: "Check keyboard paths, contrast, semantics, and screen-reader names. Cite WCAG when you flag an issue.",
127
+ },
128
+ ];
@@ -0,0 +1,70 @@
1
+ export type CatalogSubagent = {
2
+ slug: string;
3
+ name: string;
4
+ description: string;
5
+ tags: string[];
6
+ body: string;
7
+ featured?: boolean;
8
+ };
9
+
10
+ export const CATALOG_SUBAGENTS: CatalogSubagent[] = [
11
+ {
12
+ slug: "explorer",
13
+ name: "Explorer",
14
+ description: "唯讀搜程式庫。回絕對路徑與直接答案。",
15
+ tags: ["explore", "read-only"],
16
+ featured: true,
17
+ body: `name = "explorer"
18
+ description = "Read-only codebase search. Returns absolute paths and a direct answer."
19
+ sandbox_mode = "read-only"
20
+ developer_instructions = """
21
+ Role: codebase search specialist. Find files and code. Read-only.
22
+
23
+ Answer "where is X / which files do Y" with every relevant absolute path and the actual need behind the request.
24
+ Fire 3+ independent tool calls in the first round (they run in parallel). Stop after two waves add nothing new.
25
+
26
+ Never edit, write, or apply patches. Findings are message text only.
27
+
28
+ Required shape:
29
+ - Literal request
30
+ - Actual need
31
+ - Files (absolute paths + why)
32
+ - Direct answer
33
+ - Next step, or "ready"
34
+ """
35
+ `,
36
+ },
37
+ {
38
+ slug: "worker",
39
+ name: "Worker",
40
+ description: "實作執行者。最小正確變更,改完要驗證。",
41
+ tags: ["implement"],
42
+ featured: true,
43
+ body: `name = "worker"
44
+ description = "Implementation executor. Smallest correct change, then verify."
45
+ developer_instructions = """
46
+ Role: implementation executor. Own the task end to end.
47
+
48
+ Make the smallest correct change that satisfies the caller's criteria. Read local instructions first. Do not broaden scope. Do not touch files outside the assignment.
49
+
50
+ Verify with a command or test before claiming done. If validation fails, fix and rerun. Final reply is concise: what changed, how you checked, what is still open.
51
+ """
52
+ `,
53
+ },
54
+ {
55
+ slug: "reviewer",
56
+ name: "Reviewer",
57
+ description: "唯讀審查正確性、風險與缺測。",
58
+ tags: ["review", "read-only"],
59
+ featured: true,
60
+ body: `name = "reviewer"
61
+ description = "Read-only review of correctness, risk, and missing tests."
62
+ sandbox_mode = "read-only"
63
+ developer_instructions = """
64
+ Role: reviewer. Read-only. Do not edit files.
65
+
66
+ Lead with concrete findings: file path, what's wrong, why it matters, and a fix the implementer can apply. Skip style-only nits unless they hide a bug. End with severity-ordered list or "no blocking issues".
67
+ """
68
+ `,
69
+ },
70
+ ];
@@ -0,0 +1,71 @@
1
+ import type { ChatPart } from "@guild/protocol";
2
+ import type { ToolTrace } from "./tools.ts";
3
+
4
+ export function stripModelDump(text: string): string {
5
+ return String(text || "")
6
+ .replace(/<skill_content\b[\s\S]*?<\/skill_content>/gi, "")
7
+ .replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gi, "")
8
+ .replace(/<available_skills>[\s\S]*?<\/available_skills>/gi, "")
9
+ .trim();
10
+ }
11
+
12
+ export function assembleParts(input: {
13
+ thinking?: string;
14
+ traces?: ToolTrace[];
15
+ text?: string;
16
+ }): ChatPart[] {
17
+ const parts: ChatPart[] = [];
18
+ const thinking = input.thinking?.trim();
19
+ if (thinking) parts.push({ type: "thinking", text: thinking });
20
+ for (const trace of input.traces ?? []) {
21
+ if (trace.name === "skill") {
22
+ parts.push({
23
+ type: "skill",
24
+ name: String(trace.args.name ?? "").trim() || "skill",
25
+ output: trace.text,
26
+ });
27
+ continue;
28
+ }
29
+ const label =
30
+ trace.name === "run" && typeof trace.args.description === "string"
31
+ ? trace.args.description.trim()
32
+ : "";
33
+ parts.push({
34
+ type: "tool",
35
+ name: trace.name,
36
+ detail:
37
+ trace.name === "run"
38
+ ? String(trace.args.command ?? "")
39
+ : trace.name === "image_gen"
40
+ ? String(trace.args.prompt ?? "")
41
+ : trace.name === "spawn"
42
+ ? String(
43
+ trace.args.title ||
44
+ trace.args.description ||
45
+ trace.args.profile ||
46
+ trace.args.name ||
47
+ trace.args.task ||
48
+ trace.args.prompt ||
49
+ "",
50
+ )
51
+ : trace.name === "read_spawn"
52
+ ? String(trace.args.agent_id || trace.args.id || "")
53
+ : String(trace.args.path ?? ""),
54
+ output: trace.text,
55
+ isError: trace.isError,
56
+ ...(label ? { label } : {}),
57
+ });
58
+ }
59
+ const text = stripModelDump(input.text ?? "");
60
+ if (text) parts.push({ type: "text", text });
61
+ return parts;
62
+ }
63
+
64
+ export function bodyFromParts(parts: ChatPart[], fallback = "…"): string {
65
+ const text = parts
66
+ .filter((part): part is Extract<ChatPart, { type: "text" }> => part.type === "text")
67
+ .map((part) => part.text)
68
+ .join("\n\n")
69
+ .trim();
70
+ return stripModelDump(text) || fallback;
71
+ }
@@ -0,0 +1,75 @@
1
+ export type GuildCli = {
2
+ help: boolean;
3
+ version: boolean;
4
+ open: boolean;
5
+ port?: number;
6
+ error?: string;
7
+ };
8
+
9
+ const HELP = `npx @kevin5251984/guild web [--port 7420] [--no-open]
10
+
11
+ Start the hall (same shape as \`npx @deepseek-ai/dsh web\`).
12
+ Listens on http://127.0.0.1:7420 unless GUILD_PORT / --port says otherwise.
13
+
14
+ web, hall start the hall (default)
15
+ -p, --port <n> listen port
16
+ --no-open do not open a browser
17
+ -h, --help this text
18
+ -v, --version print version
19
+ `;
20
+
21
+ export function guildCliHelp(): string {
22
+ return HELP;
23
+ }
24
+
25
+ export function parseGuildCli(argv: string[]): GuildCli {
26
+ const args = argv.slice(2);
27
+ const out: GuildCli = { help: false, version: false, open: true };
28
+ for (let i = 0; i < args.length; i++) {
29
+ const arg = args[i];
30
+ if (arg === "web" || arg === "hall") continue;
31
+ if (arg === "-h" || arg === "--help") {
32
+ out.help = true;
33
+ continue;
34
+ }
35
+ if (arg === "-v" || arg === "--version") {
36
+ out.version = true;
37
+ continue;
38
+ }
39
+ if (arg === "--no-open") {
40
+ out.open = false;
41
+ continue;
42
+ }
43
+ if (arg === "-p" || arg === "--port") {
44
+ const raw = args[++i];
45
+ const port = Number(raw);
46
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
47
+ out.error = `invalid --port: ${raw ?? "(missing)"}`;
48
+ return out;
49
+ }
50
+ out.port = port;
51
+ continue;
52
+ }
53
+ if (arg.startsWith("--port=")) {
54
+ const raw = arg.slice("--port=".length);
55
+ const port = Number(raw);
56
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
57
+ out.error = `invalid --port: ${raw}`;
58
+ return out;
59
+ }
60
+ out.port = port;
61
+ continue;
62
+ }
63
+ out.error = `unknown argument: ${arg}`;
64
+ return out;
65
+ }
66
+ return out;
67
+ }
68
+
69
+ export function shouldOpenBrowser(open: boolean, env: NodeJS.ProcessEnv): boolean {
70
+ if (!open) return false;
71
+ if (env.GUILD_NO_OPEN === "1" || env.GUILD_NO_OPEN === "true") return false;
72
+ if (env.SSH_CONNECTION || env.SSH_TTY) return false;
73
+ if (!process.stdout.isTTY) return false;
74
+ return true;
75
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { execFile } from "node:child_process";
3
+ import { startGuildDaemon } from "./start.ts";
4
+ import {
5
+ guildCliHelp,
6
+ parseGuildCli,
7
+ shouldOpenBrowser,
8
+ } from "./cli-args.ts";
9
+
10
+ const opts = parseGuildCli(process.argv);
11
+ if (opts.error) {
12
+ console.error(opts.error);
13
+ process.stderr.write(guildCliHelp());
14
+ process.exitCode = 2;
15
+ } else if (opts.help) {
16
+ process.stdout.write(guildCliHelp());
17
+ } else if (opts.version) {
18
+ const pkg = JSON.parse(
19
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
20
+ ) as { version: string };
21
+ process.stdout.write(`${pkg.version}\n`);
22
+ } else {
23
+ if (opts.port !== undefined) process.env.GUILD_PORT = String(opts.port);
24
+ const started = startGuildDaemon();
25
+ started
26
+ .then(({ ctx }) => {
27
+ const url = `http://${ctx.server.host}:${ctx.server.port}`;
28
+ process.stdout.write(`guildd web: ${url}\n`);
29
+ if (shouldOpenBrowser(opts.open, process.env)) openUrl(url);
30
+ })
31
+ .catch((error: unknown) => {
32
+ console.error(error);
33
+ process.exitCode = 1;
34
+ });
35
+
36
+ async function shutdown() {
37
+ try {
38
+ const { ctx } = await started;
39
+ await ctx.fiber.dispose();
40
+ } finally {
41
+ process.exit(0);
42
+ }
43
+ }
44
+
45
+ process.once("SIGINT", () => {
46
+ void shutdown();
47
+ });
48
+ process.once("SIGTERM", () => {
49
+ void shutdown();
50
+ });
51
+ }
52
+
53
+ function openUrl(url: string): void {
54
+ const platform = process.platform;
55
+ const cmd =
56
+ platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
57
+ const args = platform === "win32" ? ["/c", "start", "", url] : [url];
58
+ const child = execFile(cmd, args, () => {});
59
+ child.unref();
60
+ }