@coze-arch/cli 0.0.19-beta.1 → 0.0.20

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 (32) hide show
  1. package/lib/__templates__/nuxt-vue/app/pages/index.vue +6 -0
  2. package/lib/__templates__/nuxt-vue/nuxt.config.ts +2 -2
  3. package/lib/__templates__/pi-agent/AGENTS.md +7 -2
  4. package/lib/__templates__/pi-agent/README.md +2 -0
  5. package/lib/__templates__/pi-agent/docs/project-overview.md +9 -15
  6. package/lib/__templates__/pi-agent/docs/user/getting-started.md +3 -4
  7. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +4 -10
  8. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +6 -18
  9. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +9 -37
  10. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +17 -30
  11. package/lib/__templates__/pi-agent/src/config.ts +60 -19
  12. package/lib/__templates__/pi-agent/src/core.ts +1 -0
  13. package/lib/__templates__/pi-agent/src/dashboard/index.ts +39 -4
  14. package/lib/__templates__/pi-agent/src/dashboard/server.ts +0 -12
  15. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +1 -15
  16. package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +0 -6
  17. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +0 -11
  18. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +268 -72
  19. package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +0 -91
  20. package/lib/__templates__/pi-agent/tests/config.test.ts +63 -1
  21. package/lib/__templates__/templates.json +24 -24
  22. package/lib/cli.js +1 -1
  23. package/package.json +1 -1
  24. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/scripts/asr.mjs +0 -9
  25. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/scripts/gen.mjs +0 -9
  26. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/scripts/tts.mjs +0 -9
  27. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/scripts/gen.mjs +0 -9
  28. package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +0 -204
  29. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +0 -188
  30. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +0 -65
  31. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +0 -122
  32. package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +0 -125
@@ -8,7 +8,6 @@ import { useLocalStorageState } from "../hooks/use-local-storage-state";
8
8
  import { Streamdown } from "streamdown";
9
9
  import { code } from "@streamdown/code";
10
10
  import { cn } from "../utils";
11
- import { Link } from "react-router-dom";
12
11
 
13
12
  type ChatUiMessage = {
14
13
  role: "user" | "assistant";
@@ -424,16 +423,6 @@ export function ChatPage() {
424
423
  </Button>
425
424
  </div>
426
425
  </div>
427
- <div className="px-1 text-xs text-muted-foreground">
428
- 如需接入飞书机器人,见
429
- <Link
430
- to="/docs"
431
- className="ml-1 underline decoration-border underline-offset-4 transition-colors hover:text-primary"
432
- >
433
- 飞书接入文档
434
- </Link>
435
-
436
- </div>
437
426
  </div>
438
427
  </section>
439
428
  );
@@ -1,8 +1,12 @@
1
1
  import React from "react";
2
2
  import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
3
- import { Badge } from "../components/ui/badge";
3
+ import { Button } from "../components/ui/button";
4
4
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card";
5
+ import { Input } from "../components/ui/input";
6
+ import { Label } from "../components/ui/label";
7
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select";
5
8
  import { useFetch } from "../hooks/use-fetch";
9
+ import { toast } from "sonner";
6
10
 
7
11
  type Overview = {
8
12
  appName: string;
@@ -14,6 +18,41 @@ type Overview = {
14
18
  enabledChannels: Array<{ id: string; enabled: boolean }>;
15
19
  };
16
20
 
21
+ type Channels = {
22
+ routing: {
23
+ feishuGroupRequireMention: boolean;
24
+ wechatGroupRequireMention: boolean;
25
+ };
26
+ feishu: {
27
+ enabled: boolean;
28
+ requireMention?: boolean;
29
+ appId?: string;
30
+ domain?: string;
31
+ encryptKey?: string;
32
+ verificationToken?: string;
33
+ appSecret?: string;
34
+ thinkingReaction?: {
35
+ enabled?: boolean;
36
+ emojiType?: string;
37
+ };
38
+ };
39
+ wechat: {
40
+ enabled: boolean;
41
+ requireMention?: boolean;
42
+ implementation?: string;
43
+ };
44
+ };
45
+
46
+ type ModelOption = {
47
+ value: string;
48
+ label: string;
49
+ };
50
+
51
+ type ModelsConfig = {
52
+ defaultModel: string;
53
+ options: ModelOption[];
54
+ };
55
+
17
56
  export function OverviewPage() {
18
57
  const { data, error, loading } = useFetch<Overview>("/api/overview");
19
58
  const enabledChannels = data?.enabledChannels ?? [];
@@ -28,7 +67,7 @@ export function OverviewPage() {
28
67
  <CardTitle className="text-base">系统摘要</CardTitle>
29
68
  <CardDescription>当前 bot、渠道与工作区的核心状态。</CardDescription>
30
69
  </CardHeader>
31
- <CardContent className="grid gap-3 sm:grid-cols-3">
70
+ <CardContent className="grid gap-3 sm:grid-cols-2">
32
71
  <SummaryStat
33
72
  label="运行状态"
34
73
  value={botStatusText}
@@ -39,79 +78,199 @@ export function OverviewPage() {
39
78
  value={String(activeChannelCount)}
40
79
  hint={enabledChannels.length ? `${enabledChannels.length} 个渠道已接入` : "暂无渠道信息"}
41
80
  />
42
- <SummaryStat
43
- label="Agent 模式"
44
- value={data?.agentMode ?? "--"}
45
- hint="当前启动模式"
46
- />
47
81
  </CardContent>
48
82
  </Card>
49
83
 
50
- <Card>
51
- <CardHeader className="pb-3">
52
- <CardTitle className="text-base">渠道状态</CardTitle>
53
- <CardDescription>快速查看各渠道是否已启用。</CardDescription>
54
- </CardHeader>
55
- <CardContent className="flex flex-wrap gap-2">
56
- {enabledChannels.length === 0 ? (
57
- <Badge variant="outline">暂无数据</Badge>
58
- ) : (
59
- enabledChannels.map((channel) => (
60
- <Badge
61
- key={channel.id}
62
- variant={channel.enabled ? "default" : "outline"}
63
- className="rounded-full px-3 py-1"
64
- >
65
- {channel.id} · {channel.enabled ? "已开启" : "已关闭"}
66
- </Badge>
67
- ))
68
- )}
69
- </CardContent>
70
- </Card>
84
+ <DefaultModelSection />
71
85
  </div>
72
86
 
73
- <Card className="overflow-hidden">
74
- <CardHeader>
75
- <CardTitle>概览</CardTitle>
76
- <CardDescription>关键运行信息</CardDescription>
77
- </CardHeader>
78
- <CardContent>
79
- {loading ? (
80
- <Alert>
81
- <AlertTitle>加载中</AlertTitle>
82
- <AlertDescription>正在获取 Dashboard 运行信息。</AlertDescription>
83
- </Alert>
84
- ) : error ? (
85
- <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
86
- <AlertTitle>请求失败</AlertTitle>
87
- <AlertDescription>错误:{error}</AlertDescription>
88
- </Alert>
89
- ) : (
90
- <div className="grid gap-3">
91
- <InfoRow label="面板地址">
92
- <a
93
- className="underline decoration-border underline-offset-4 transition-colors hover:text-primary"
94
- href={data?.dashboardUrl}
95
- target="_blank"
96
- rel="noreferrer"
97
- >
98
- {data?.dashboardUrl}
99
- </a>
100
- </InfoRow>
101
- <InfoRow label="工作区目录" mono>
102
- {data?.workspaceDir}
103
- </InfoRow>
104
- <InfoRow label="Agent 目录" mono>
105
- {data?.agentDir}
106
- </InfoRow>
107
- </div>
108
- )}
109
- </CardContent>
110
- </Card>
87
+ <ChannelsSection />
111
88
  </section>
112
89
  );
113
90
  }
114
91
 
92
+ function DefaultModelSection() {
93
+ const { data, error, loading } = useFetch<ModelsConfig>("/api/models");
94
+ const [saving, setSaving] = React.useState(false);
95
+ const [form, setForm] = React.useState<ModelsConfig | null>(null);
96
+
97
+ React.useEffect(() => {
98
+ if (!data) return;
99
+ setForm({
100
+ defaultModel: typeof data.defaultModel === "string" ? data.defaultModel : "",
101
+ options: Array.isArray((data as unknown as { options?: unknown }).options)
102
+ ? (data as unknown as { options: ModelOption[] }).options
103
+ : [],
104
+ });
105
+ }, [data]);
106
+
107
+ const saveDefaultModel = async (defaultModel: string) => {
108
+ setSaving(true);
109
+ try {
110
+ const res = await fetch("/api/models", {
111
+ method: "POST",
112
+ headers: { "Content-Type": "application/json" },
113
+ body: JSON.stringify({ models: { defaultModel } }),
114
+ });
115
+ const json = (await res.json()) as { ok?: boolean; error?: string };
116
+ if (!res.ok || json.ok === false) {
117
+ throw new Error(json.error || `${res.status} ${res.statusText}`);
118
+ }
119
+ toast.success("已保存(需要重启进程生效)");
120
+ } catch (e) {
121
+ toast.error(`保存失败:${String(e)}`, { duration: 4500 });
122
+ throw e;
123
+ } finally {
124
+ setSaving(false);
125
+ }
126
+ };
127
+
128
+ const options = form?.options ?? [];
129
+ const onDefaultModelChange = async (nextValue: string) => {
130
+ if (!form || nextValue === form.defaultModel) return;
131
+ const previousValue = form.defaultModel;
132
+ setForm({ ...form, defaultModel: nextValue });
133
+ try {
134
+ await saveDefaultModel(nextValue);
135
+ } catch {
136
+ setForm((current) => (current ? { ...current, defaultModel: previousValue } : current));
137
+ }
138
+ };
139
+
140
+ return (
141
+ <Card className="border-border/60 bg-background/70 shadow-none">
142
+ <CardHeader className="pb-3">
143
+ <CardTitle className="text-base">默认模型</CardTitle>
144
+ <CardDescription>切换当前使用的默认模型。</CardDescription>
145
+ </CardHeader>
146
+ <CardContent>
147
+ {loading ? (
148
+ <Alert>
149
+ <AlertTitle>加载中</AlertTitle>
150
+ <AlertDescription>正在读取模型配置。</AlertDescription>
151
+ </Alert>
152
+ ) : error ? (
153
+ <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
154
+ <AlertTitle>读取失败</AlertTitle>
155
+ <AlertDescription>错误:{error}</AlertDescription>
156
+ </Alert>
157
+ ) : form && options.length > 0 ? (
158
+ <Select
159
+ value={form.defaultModel}
160
+ onValueChange={onDefaultModelChange}
161
+ disabled={saving}
162
+ >
163
+ <SelectTrigger aria-label="选择默认模型">
164
+ <SelectValue placeholder="选择默认模型" />
165
+ </SelectTrigger>
166
+ <SelectContent>
167
+ {options.map((option) => (
168
+ <SelectItem key={option.value} value={option.value}>
169
+ {option.label}
170
+ </SelectItem>
171
+ ))}
172
+ </SelectContent>
173
+ </Select>
174
+ ) : form ? (
175
+ <Alert>
176
+ <AlertTitle>没有可选模型</AlertTitle>
177
+ <AlertDescription>当前还没有可用模型,请先补充模型定义。</AlertDescription>
178
+ </Alert>
179
+ ) : null}
180
+ </CardContent>
181
+ </Card>
182
+ );
183
+ }
184
+
185
+ function ChannelsSection() {
186
+ const { data, error, loading } = useFetch<Channels>("/api/channels");
187
+ const [saving, setSaving] = React.useState(false);
188
+ const [form, setForm] = React.useState<Channels | null>(null);
189
+
190
+ React.useEffect(() => {
191
+ if (data) setForm(data);
192
+ }, [data]);
193
+
194
+ const patch = (fn: (draft: Channels) => void) => {
195
+ if (!form) return;
196
+ const draft = { ...form, feishu: { ...form.feishu }, wechat: { ...form.wechat } };
197
+ fn(draft);
198
+ setForm(draft);
199
+ };
200
+
201
+ const onSave = async () => {
202
+ if (!form) return;
203
+ setSaving(true);
204
+ try {
205
+ const res = await fetch("/api/channels", {
206
+ method: "POST",
207
+ headers: { "Content-Type": "application/json" },
208
+ body: JSON.stringify({ channels: form }),
209
+ });
210
+ const json = (await res.json()) as { ok?: boolean; error?: string };
211
+ if (!res.ok || json.ok === false) {
212
+ throw new Error(json.error || `${res.status} ${res.statusText}`);
213
+ }
214
+ toast.success("已保存(需要重启进程生效)");
215
+ } catch (e) {
216
+ toast.error(`保存失败:${String(e)}`, { duration: 4500 });
217
+ } finally {
218
+ setSaving(false);
219
+ }
220
+ };
221
+
222
+ return (
223
+ <Card className="border-border/60 bg-background/70 shadow-none">
224
+ <CardHeader>
225
+ <CardTitle className="text-base">飞书配置</CardTitle>
226
+ <CardDescription>管理飞书渠道的启用状态与接入凭证。</CardDescription>
227
+ </CardHeader>
228
+ <CardContent>
229
+ {loading ? (
230
+ <Alert>
231
+ <AlertTitle>加载中</AlertTitle>
232
+ <AlertDescription>正在读取渠道配置。</AlertDescription>
233
+ </Alert>
234
+ ) : error ? (
235
+ <Alert className="border-destructive/20 bg-destructive/5 text-destructive">
236
+ <AlertTitle>读取失败</AlertTitle>
237
+ <AlertDescription>错误:{error}</AlertDescription>
238
+ </Alert>
239
+ ) : form ? (
240
+ <div className="space-y-4">
241
+ <div className="grid gap-4">
242
+ <CheckboxField
243
+ label="启用状态"
244
+ checked={!!form.feishu.enabled}
245
+ onChange={(checked) => patch((d) => (d.feishu.enabled = checked))}
246
+ />
247
+ <TextField
248
+ label="App ID"
249
+ value={form.feishu.appId ?? ""}
250
+ onChange={(value) => patch((d) => (d.feishu.appId = value))}
251
+ />
252
+ <TextField
253
+ label="App Secret"
254
+ value={form.feishu.appSecret ?? ""}
255
+ onChange={(value) => patch((d) => (d.feishu.appSecret = value))}
256
+ />
257
+ </div>
258
+ <div className="flex justify-start">
259
+ <Button
260
+ onClick={onSave}
261
+ disabled={!form || saving || loading || Boolean(error)}
262
+ className="shrink-0"
263
+ >
264
+ {saving ? "保存中…" : "保存"}
265
+ </Button>
266
+ </div>
267
+ </div>
268
+ ) : null}
269
+ </CardContent>
270
+ </Card>
271
+ );
272
+ }
273
+
115
274
  function SummaryStat(props: { label: string; value: string; hint: string }) {
116
275
  return (
117
276
  <div className="rounded-2xl border border-border/70 bg-muted/30 p-4">
@@ -122,13 +281,50 @@ function SummaryStat(props: { label: string; value: string; hint: string }) {
122
281
  );
123
282
  }
124
283
 
125
- function InfoRow(props: { label: string; children: React.ReactNode; mono?: boolean }) {
284
+ function FieldShell(props: { label: string; children: React.ReactNode; description?: string }) {
126
285
  return (
127
- <div className="grid gap-2 rounded-xl border border-border/60 bg-muted/30 px-4 py-3 md:grid-cols-[220px_minmax(0,1fr)] md:items-start">
128
- <div className="text-sm font-medium text-muted-foreground">{props.label}</div>
129
- <div className={props.mono ? "[overflow-wrap:anywhere] font-mono text-xs sm:text-sm" : "[overflow-wrap:anywhere] text-sm"}>
130
- {props.children}
131
- </div>
286
+ <div className="space-y-2">
287
+ <Label className="text-xs uppercase tracking-[0.16em] text-muted-foreground">{props.label}</Label>
288
+ {props.children}
289
+ {props.description ? <p className="text-xs text-muted-foreground">{props.description}</p> : null}
132
290
  </div>
133
291
  );
134
292
  }
293
+
294
+ function TextField(props: {
295
+ label: string;
296
+ value: string;
297
+ placeholder?: string;
298
+ onChange: (value: string) => void;
299
+ }) {
300
+ return (
301
+ <FieldShell label={props.label}>
302
+ <Input
303
+ className="font-mono text-xs sm:text-sm"
304
+ value={props.value}
305
+ placeholder={props.placeholder}
306
+ onChange={(e) => props.onChange(e.target.value)}
307
+ />
308
+ </FieldShell>
309
+ );
310
+ }
311
+
312
+ function CheckboxField(props: {
313
+ label: string;
314
+ checked: boolean;
315
+ onChange: (checked: boolean) => void;
316
+ }) {
317
+ return (
318
+ <FieldShell label={props.label}>
319
+ <label className="flex h-10 items-center gap-3 rounded-md border border-input bg-background/80 px-3 text-sm">
320
+ <input
321
+ type="checkbox"
322
+ className="h-4 w-4 rounded border-border accent-primary"
323
+ checked={props.checked}
324
+ onChange={(e) => props.onChange(e.target.checked)}
325
+ />
326
+ <span className="text-sm text-foreground">{props.checked ? "已启用" : "未启用"}</span>
327
+ </label>
328
+ </FieldShell>
329
+ );
330
+ }
@@ -201,94 +201,3 @@
201
201
  max-width: 100%;
202
202
  height: auto;
203
203
  }
204
-
205
- .docs-content [data-streamdown] {
206
- overflow-wrap: anywhere;
207
- }
208
-
209
- .docs-content h1,
210
- .docs-content h2,
211
- .docs-content h3,
212
- .docs-content h4 {
213
- margin: 1.5rem 0 0.75rem;
214
- font-weight: 600;
215
- letter-spacing: -0.02em;
216
- }
217
-
218
- .docs-content h1:first-child,
219
- .docs-content h2:first-child,
220
- .docs-content h3:first-child {
221
- margin-top: 0;
222
- }
223
-
224
- .docs-content h1 {
225
- font-size: 1.5rem;
226
- }
227
-
228
- .docs-content h2 {
229
- font-size: 1.2rem;
230
- }
231
-
232
- .docs-content h3 {
233
- font-size: 1rem;
234
- }
235
-
236
- .docs-content p,
237
- .docs-content ul,
238
- .docs-content ol,
239
- .docs-content table,
240
- .docs-content blockquote {
241
- margin: 0.75rem 0;
242
- }
243
-
244
- .docs-content ul,
245
- .docs-content ol {
246
- padding-left: 1.25rem;
247
- }
248
-
249
- .docs-content li + li {
250
- margin-top: 0.35rem;
251
- }
252
-
253
- .docs-content a {
254
- color: var(--primary);
255
- text-decoration: underline;
256
- text-underline-offset: 0.2em;
257
- }
258
-
259
- .docs-content hr {
260
- margin: 1.25rem 0;
261
- border: 0;
262
- border-top: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
263
- }
264
-
265
- .docs-content blockquote {
266
- border-left: 3px solid color-mix(in oklab, var(--border) 100%, transparent);
267
- padding-left: 1rem;
268
- color: var(--muted-foreground);
269
- }
270
-
271
- .docs-content table {
272
- width: 100%;
273
- border-collapse: collapse;
274
- overflow: hidden;
275
- border-radius: 0.875rem;
276
- border: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
277
- }
278
-
279
- .docs-content th,
280
- .docs-content td {
281
- border-bottom: 1px solid color-mix(in oklab, var(--border) 100%, transparent);
282
- padding: 0.75rem;
283
- text-align: left;
284
- vertical-align: top;
285
- }
286
-
287
- .docs-content th {
288
- background: color-mix(in oklab, var(--muted) 80%, var(--background));
289
- font-weight: 600;
290
- }
291
-
292
- .docs-content tbody tr:last-child td {
293
- border-bottom: 0;
294
- }
@@ -300,16 +300,78 @@ test("loadBotAppConfig builds the app config from config.json plus env-backed ch
300
300
 
301
301
  rmSync(tempRoot, { recursive: true, force: true });
302
302
 
303
- assert.equal(getDefaultConfigPath(), resolve(process.cwd(), "../workspace/config.json"));
303
+ assert.equal(getDefaultConfigPath(), resolve(process.cwd(), "<%= workspaceDir %>/config.json"));
304
304
  assert.equal(loaded.agent.mode, "pi");
305
305
  assert.equal(loaded.agent.provider, "coze");
306
306
  assert.equal(loaded.agent.model, "auto");
307
307
  assert.equal(loaded.agent.configPath, configPath);
308
308
  assert.equal(loaded.agent.thinkingLevel, "high");
309
309
  assert.equal(loaded.agent.cwd, dirname(configPath));
310
+ assert.equal(
311
+ (((loaded.configRoot?.agents as { defaults?: { model?: { primary?: string } } })?.defaults?.model?.primary) ?? ""),
312
+ "coze/auto"
313
+ );
310
314
  assert.equal(loaded.channels.feishu?.appId, "app-id");
311
315
  assert.equal(loaded.channels.feishu?.verificationToken, "verify-token");
312
316
  assert.equal(loaded.routing.feishuGroupRequireMention, false);
313
317
  assert.equal(loaded.channels.feishu?.thinkingReaction?.enabled, false);
314
318
  assert.equal(loaded.channels.feishu?.thinkingReaction?.emojiType, "OneSecond");
315
319
  });
320
+
321
+ test("loadConfig falls back to a minimal mock config when config.json is missing", () => {
322
+ const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-missing-config-"));
323
+ const configPath = join(tempDir, "workspace", "config.json");
324
+
325
+ const loaded = loadConfig(configPath);
326
+
327
+ rmSync(tempDir, { recursive: true, force: true });
328
+
329
+ assert.equal(loaded.source, "fallback-mock");
330
+ assert.equal(loaded.path, configPath);
331
+ assert.equal(loaded.workspaceDir, dirname(configPath));
332
+ assert.deepEqual(loaded.defaultModel, {
333
+ provider: "openai",
334
+ modelId: "gpt-5-mini"
335
+ });
336
+ assert.equal(loaded.config.channels?.feishu?.enabled, false);
337
+ assert.equal(loaded.config.channels?.wechat?.enabled, false);
338
+ });
339
+
340
+ test("loadBotAppConfig falls back to mock mode and skips file config path when config.json is missing", () => {
341
+ const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-missing-config-"));
342
+ const configPath = join(tempDir, "workspace", "config.json");
343
+
344
+ const loaded = loadBotAppConfig({
345
+ configPath,
346
+ env: {}
347
+ });
348
+
349
+ rmSync(tempDir, { recursive: true, force: true });
350
+
351
+ assert.equal(loaded.agent.mode, "mock");
352
+ assert.equal(loaded.agent.provider, "openai");
353
+ assert.equal(loaded.agent.model, "gpt-5-mini");
354
+ assert.equal(loaded.agent.configPath, undefined);
355
+ assert.equal(loaded.agent.cwd, dirname(configPath));
356
+ assert.equal(
357
+ (((loaded.configRoot?.agents as { defaults?: { model?: { primary?: string } } })?.defaults?.model?.primary) ?? ""),
358
+ "openai/gpt-5-mini"
359
+ );
360
+ });
361
+
362
+ test("loadBotAppConfig still allows env to override mode when config.json is missing", () => {
363
+ const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-missing-config-"));
364
+ const configPath = join(tempDir, "workspace", "config.json");
365
+
366
+ const loaded = loadBotAppConfig({
367
+ configPath,
368
+ env: {
369
+ PI_BOT_AGENT_MODE: "pi"
370
+ }
371
+ });
372
+
373
+ rmSync(tempDir, { recursive: true, force: true });
374
+
375
+ assert.equal(loaded.agent.mode, "pi");
376
+ assert.equal(loaded.agent.configPath, undefined);
377
+ });
@@ -94,30 +94,6 @@
94
94
  "additionalProperties": false
95
95
  }
96
96
  },
97
- {
98
- "name": "pi-agent",
99
- "description": "Pi Agent:`coze init ${COZE_WORKSPACE_PATH} --template pi-agent`\n- 适用:基于 pi-agent-core 的 AI Agent 应用\n- 支持飞书、微信等多渠道接入\n- 内置 Dashboard 管理面板\n- 使用 TypeScript + Express + Vite",
100
- "location": "./pi-agent",
101
- "paramsSchema": {
102
- "type": "object",
103
- "properties": {
104
- "port": {
105
- "type": "number",
106
- "default": 5000,
107
- "minimum": 1024,
108
- "maximum": 65535,
109
- "description": "Dashboard server port"
110
- },
111
- "workspaceDir": {
112
- "type": "string",
113
- "default": "/workspace/workspace",
114
- "description": "Workspace directory path"
115
- }
116
- },
117
- "required": [],
118
- "additionalProperties": false
119
- }
120
- },
121
97
  {
122
98
  "name": "taro",
123
99
  "description": "Taro(小程序 + H5):`coze init ${COZE_WORKSPACE_PATH} --template taro`\n- 适用:微信小程序、H5 跨端应用\n- 前后端分离架构:Taro 4 + NestJS\n- 支持微信小程序和 H5 双端构建\n- 使用 TailwindCSS + weapp-tailwindcss 实现跨端样式",
@@ -181,6 +157,30 @@
181
157
  "required": [],
182
158
  "additionalProperties": false
183
159
  }
160
+ },
161
+ {
162
+ "name": "pi-agent",
163
+ "description": "Pi Agent:`coze init ${COZE_WORKSPACE_PATH} --template pi-agent`\n- 适用:基于 pi-agent-core 的 AI Agent 应用\n- 支持飞书、微信等多渠道接入\n- 内置 Dashboard 管理面板\n- 使用 TypeScript + Express + Vite",
164
+ "location": "./pi-agent",
165
+ "paramsSchema": {
166
+ "type": "object",
167
+ "properties": {
168
+ "port": {
169
+ "type": "number",
170
+ "default": 5000,
171
+ "minimum": 1024,
172
+ "maximum": 65535,
173
+ "description": "Dashboard server port"
174
+ },
175
+ "workspaceDir": {
176
+ "type": "string",
177
+ "default": "/workspace/workspace",
178
+ "description": "Workspace directory path"
179
+ }
180
+ },
181
+ "required": [],
182
+ "additionalProperties": false
183
+ }
184
184
  }
185
185
  ]
186
186
  }
package/lib/cli.js CHANGED
@@ -2107,7 +2107,7 @@ const EventBuilder = {
2107
2107
  };
2108
2108
 
2109
2109
  var name = "@coze-arch/cli";
2110
- var version = "0.0.19-beta.1";
2110
+ var version = "0.0.20";
2111
2111
  var description = "coze coding devtools cli";
2112
2112
  var license = "MIT";
2113
2113
  var author = "fanwenjie.fe@bytedance.com";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coze-arch/cli",
3
- "version": "0.0.19-beta.1",
3
+ "version": "0.0.20",
4
4
  "private": false,
5
5
  "description": "coze coding devtools cli",
6
6
  "license": "MIT",
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import createJiti from "jiti";
4
-
5
- const jiti = createJiti(import.meta.url);
6
- const { runAsrCli } = await jiti.import("../../../src/skill-cli.ts");
7
-
8
- const code = await runAsrCli(process.argv.slice(2), process.env);
9
- process.exit(code);
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import createJiti from "jiti";
4
-
5
- const jiti = createJiti(import.meta.url);
6
- const { runImageCli } = await jiti.import("../../../src/skill-cli.ts");
7
-
8
- const code = await runImageCli(process.argv.slice(2), process.env);
9
- process.exit(code);