@agentic-patterns/cli 0.1.3 → 0.1.5

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Agentic Patterns Dashboard</title>
7
- <script type="module" crossorigin src="/assets/index-C2JvJdBt.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-x4ivBrgq.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-whvaenaU.css">
9
9
  </head>
10
10
  <body>
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "agentic-patterns",
3
+ "version": "0.1.0",
4
+ "description": "Streams all 26 Claude Code lifecycle events (tool calls, permissions, subagents, compaction, sessions) into the @agentic-patterns/server dashboard for live observability. Pair with `ap playground`. See https://github.com/pattern-stack/agentic-patterns-ts/blob/main/docs/CLAUDE-CODE-PLUGIN-ACTIVATION.md for activation + troubleshooting.",
5
+ "author": {
6
+ "name": "pattern-stack",
7
+ "url": "https://github.com/pattern-stack"
8
+ },
9
+ "homepage": "https://github.com/pattern-stack/agentic-patterns-ts",
10
+ "repository": "https://github.com/pattern-stack/agentic-patterns-ts",
11
+ "license": "MIT",
12
+ "keywords": ["agents", "observability", "llm", "ai"]
13
+ }
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // Zero-dependency hook shim. Keep this file tiny and stable — it's copied
3
+ // verbatim into every user project via `ap init --with-plugin` and will not
4
+ // retroactively update. All evolving behavior belongs server-side at
5
+ // /hooks/:eventType. See docs/CLAUDE-CODE-PLUGIN-ACTIVATION.md.
6
+ //
7
+ // Fallback port 3456 mirrors DEFAULT_DASHBOARD_PORT in
8
+ // packages/agent-cli/src/constants.ts. Keep in sync.
9
+ import { stdin } from "node:process";
10
+
11
+ const eventName = process.argv[2] ?? "Unknown";
12
+ const base = process.env.AP_DASHBOARD_URL ?? "http://localhost:3456";
13
+
14
+ try {
15
+ const chunks = [];
16
+ for await (const c of stdin) chunks.push(c);
17
+ const body = Buffer.concat(chunks).toString("utf8") || "{}";
18
+
19
+ const controller = new AbortController();
20
+ const timer = setTimeout(() => controller.abort(), 500);
21
+
22
+ const headers = { "content-type": "application/json" };
23
+ const correlationId = process.env.AP_RUNNER_CORRELATION_ID;
24
+ if (correlationId) {
25
+ headers["x-ap-runner-correlation-id"] = correlationId;
26
+ }
27
+
28
+ await fetch(`${base}/hooks/${eventName}`, {
29
+ method: "POST",
30
+ headers,
31
+ body,
32
+ signal: controller.signal,
33
+ }).catch((err) => {
34
+ process.stderr.write(`[ap-hook] ${eventName}: ${err.message ?? err}\n`);
35
+ });
36
+
37
+ clearTimeout(timer);
38
+ } catch (err) {
39
+ process.stderr.write(`[ap-hook] ${eventName}: ${err?.message ?? err}\n`);
40
+ }
41
+
42
+ process.exit(0);
@@ -0,0 +1,342 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs SessionStart",
10
+ "async": true,
11
+ "timeout": 10
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "InstructionsLoaded": [
17
+ {
18
+ "matcher": "",
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs InstructionsLoaded",
23
+ "async": true,
24
+ "timeout": 10
25
+ }
26
+ ]
27
+ }
28
+ ],
29
+ "UserPromptSubmit": [
30
+ {
31
+ "matcher": "",
32
+ "hooks": [
33
+ {
34
+ "type": "command",
35
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs UserPromptSubmit",
36
+ "async": true,
37
+ "timeout": 10
38
+ }
39
+ ]
40
+ }
41
+ ],
42
+ "PreToolUse": [
43
+ {
44
+ "matcher": "",
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PreToolUse",
49
+ "async": true,
50
+ "timeout": 10
51
+ }
52
+ ]
53
+ }
54
+ ],
55
+ "PermissionRequest": [
56
+ {
57
+ "matcher": "",
58
+ "hooks": [
59
+ {
60
+ "type": "command",
61
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PermissionRequest",
62
+ "async": true,
63
+ "timeout": 10
64
+ }
65
+ ]
66
+ }
67
+ ],
68
+ "PermissionDenied": [
69
+ {
70
+ "matcher": "",
71
+ "hooks": [
72
+ {
73
+ "type": "command",
74
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PermissionDenied",
75
+ "async": true,
76
+ "timeout": 10
77
+ }
78
+ ]
79
+ }
80
+ ],
81
+ "PostToolUse": [
82
+ {
83
+ "matcher": "",
84
+ "hooks": [
85
+ {
86
+ "type": "command",
87
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PostToolUse",
88
+ "async": true,
89
+ "timeout": 10
90
+ }
91
+ ]
92
+ }
93
+ ],
94
+ "PostToolUseFailure": [
95
+ {
96
+ "matcher": "",
97
+ "hooks": [
98
+ {
99
+ "type": "command",
100
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PostToolUseFailure",
101
+ "async": true,
102
+ "timeout": 10
103
+ }
104
+ ]
105
+ }
106
+ ],
107
+ "Notification": [
108
+ {
109
+ "matcher": "",
110
+ "hooks": [
111
+ {
112
+ "type": "command",
113
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs Notification",
114
+ "async": true,
115
+ "timeout": 10
116
+ }
117
+ ]
118
+ }
119
+ ],
120
+ "SubagentStart": [
121
+ {
122
+ "matcher": "",
123
+ "hooks": [
124
+ {
125
+ "type": "command",
126
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs SubagentStart",
127
+ "async": true,
128
+ "timeout": 10
129
+ }
130
+ ]
131
+ }
132
+ ],
133
+ "SubagentStop": [
134
+ {
135
+ "matcher": "",
136
+ "hooks": [
137
+ {
138
+ "type": "command",
139
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs SubagentStop",
140
+ "async": true,
141
+ "timeout": 10
142
+ }
143
+ ]
144
+ }
145
+ ],
146
+ "TaskCreated": [
147
+ {
148
+ "matcher": "",
149
+ "hooks": [
150
+ {
151
+ "type": "command",
152
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs TaskCreated",
153
+ "async": true,
154
+ "timeout": 10
155
+ }
156
+ ]
157
+ }
158
+ ],
159
+ "TaskCompleted": [
160
+ {
161
+ "matcher": "",
162
+ "hooks": [
163
+ {
164
+ "type": "command",
165
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs TaskCompleted",
166
+ "async": true,
167
+ "timeout": 10
168
+ }
169
+ ]
170
+ }
171
+ ],
172
+ "Stop": [
173
+ {
174
+ "matcher": "",
175
+ "hooks": [
176
+ {
177
+ "type": "command",
178
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs Stop",
179
+ "async": true,
180
+ "timeout": 10
181
+ }
182
+ ]
183
+ }
184
+ ],
185
+ "StopFailure": [
186
+ {
187
+ "matcher": "",
188
+ "hooks": [
189
+ {
190
+ "type": "command",
191
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs StopFailure",
192
+ "async": true,
193
+ "timeout": 10
194
+ }
195
+ ]
196
+ }
197
+ ],
198
+ "TeammateIdle": [
199
+ {
200
+ "matcher": "",
201
+ "hooks": [
202
+ {
203
+ "type": "command",
204
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs TeammateIdle",
205
+ "async": true,
206
+ "timeout": 10
207
+ }
208
+ ]
209
+ }
210
+ ],
211
+ "ConfigChange": [
212
+ {
213
+ "matcher": "",
214
+ "hooks": [
215
+ {
216
+ "type": "command",
217
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs ConfigChange",
218
+ "async": true,
219
+ "timeout": 10
220
+ }
221
+ ]
222
+ }
223
+ ],
224
+ "CwdChanged": [
225
+ {
226
+ "matcher": "",
227
+ "hooks": [
228
+ {
229
+ "type": "command",
230
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs CwdChanged",
231
+ "async": true,
232
+ "timeout": 10
233
+ }
234
+ ]
235
+ }
236
+ ],
237
+ "FileChanged": [
238
+ {
239
+ "matcher": "",
240
+ "hooks": [
241
+ {
242
+ "type": "command",
243
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs FileChanged",
244
+ "async": true,
245
+ "timeout": 10
246
+ }
247
+ ]
248
+ }
249
+ ],
250
+ "WorktreeCreate": [
251
+ {
252
+ "matcher": "",
253
+ "hooks": [
254
+ {
255
+ "type": "command",
256
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs WorktreeCreate",
257
+ "async": true,
258
+ "timeout": 10
259
+ }
260
+ ]
261
+ }
262
+ ],
263
+ "WorktreeRemove": [
264
+ {
265
+ "matcher": "",
266
+ "hooks": [
267
+ {
268
+ "type": "command",
269
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs WorktreeRemove",
270
+ "async": true,
271
+ "timeout": 10
272
+ }
273
+ ]
274
+ }
275
+ ],
276
+ "PreCompact": [
277
+ {
278
+ "matcher": "",
279
+ "hooks": [
280
+ {
281
+ "type": "command",
282
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PreCompact",
283
+ "async": true,
284
+ "timeout": 10
285
+ }
286
+ ]
287
+ }
288
+ ],
289
+ "PostCompact": [
290
+ {
291
+ "matcher": "",
292
+ "hooks": [
293
+ {
294
+ "type": "command",
295
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs PostCompact",
296
+ "async": true,
297
+ "timeout": 10
298
+ }
299
+ ]
300
+ }
301
+ ],
302
+ "Elicitation": [
303
+ {
304
+ "matcher": "",
305
+ "hooks": [
306
+ {
307
+ "type": "command",
308
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs Elicitation",
309
+ "async": true,
310
+ "timeout": 10
311
+ }
312
+ ]
313
+ }
314
+ ],
315
+ "ElicitationResult": [
316
+ {
317
+ "matcher": "",
318
+ "hooks": [
319
+ {
320
+ "type": "command",
321
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs ElicitationResult",
322
+ "async": true,
323
+ "timeout": 10
324
+ }
325
+ ]
326
+ }
327
+ ],
328
+ "SessionEnd": [
329
+ {
330
+ "matcher": "",
331
+ "hooks": [
332
+ {
333
+ "type": "command",
334
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/emit.mjs SessionEnd",
335
+ "async": true,
336
+ "timeout": 10
337
+ }
338
+ ]
339
+ }
340
+ ]
341
+ }
342
+ }
package/dist/cli.js CHANGED
@@ -65,7 +65,8 @@ var TRACKED_ENV = [
65
65
  { key: "DEEPSEEK_API_KEY", label: "DeepSeek API key", secret: true },
66
66
  { key: "OPENROUTER_API_KEY", label: "OpenRouter API key", secret: true },
67
67
  { key: "OLLAMA_HOST", label: "Ollama host URL", secret: false },
68
- { key: "AGENT_TIER", label: "Default tier (opus | sonnet | haiku)", secret: false }
68
+ { key: "AGENT_TIER", label: "Default tier (opus | sonnet | haiku)", secret: false },
69
+ { key: "AGENT_MODEL", label: "Pinned model id (overrides tier)", secret: false }
69
70
  ];
70
71
  function runConfigStatusCommand(input) {
71
72
  const { config } = input;
@@ -172,6 +173,12 @@ import fs2 from "fs";
172
173
  import path3 from "path";
173
174
  import { fileURLToPath } from "url";
174
175
  import { isCancel as isCancel2, select as select2, text as text2 } from "@clack/prompts";
176
+
177
+ // src/constants.ts
178
+ var DEFAULT_DASHBOARD_PORT = 3456;
179
+ var DEFAULT_DASHBOARD_URL = `http://localhost:${DEFAULT_DASHBOARD_PORT}`;
180
+
181
+ // src/commands/init.ts
175
182
  var DIM3 = "\x1B[2m";
176
183
  var BOLD3 = "\x1B[1m";
177
184
  var GREEN2 = "\x1B[32m";
@@ -186,8 +193,7 @@ async function runInitCommand(opts) {
186
193
  monorepoRoot = resolveMonorepoRoot();
187
194
  if (!monorepoRoot) {
188
195
  process.stderr.write(
189
- `error: --link requires the CLI to be run from the agentic-patterns-ts source tree
190
- `
196
+ "error: --link requires the CLI to be run from the agentic-patterns-ts source tree\n"
191
197
  );
192
198
  process.exit(1);
193
199
  }
@@ -279,6 +285,25 @@ async function runInitCommand(opts) {
279
285
  copyDir(pluginSrc.pluginDir, path3.join(targetDir, ".claude-plugin"));
280
286
  copyDir(pluginSrc.hooksDir, path3.join(targetDir, "hooks"));
281
287
  created.push(".claude-plugin/", "hooks/");
288
+ const settingsDir = path3.join(targetDir, ".claude");
289
+ const settingsPath = path3.join(settingsDir, "settings.json");
290
+ const hooksSource = fs2.readFileSync(path3.join(pluginSrc.hooksDir, "hooks.json"), "utf8");
291
+ const ourHooks = JSON.parse(
292
+ hooksSource.replaceAll("${CLAUDE_PLUGIN_ROOT}", "${CLAUDE_PROJECT_DIR}")
293
+ );
294
+ const mergeOutcome = mergeHookSettings(settingsPath, ourHooks);
295
+ fs2.mkdirSync(settingsDir, { recursive: true });
296
+ fs2.writeFileSync(settingsPath, `${JSON.stringify(mergeOutcome.merged, null, 2)}
297
+ `);
298
+ if (mergeOutcome.kind === "created") {
299
+ created.push(".claude/settings.json");
300
+ } else if (mergeOutcome.kind === "merged") {
301
+ created.push(
302
+ `.claude/settings.json ${DIM3}(merged ${mergeOutcome.added} hook entries)${RESET3}`
303
+ );
304
+ } else {
305
+ created.push(`.claude/settings.json ${DIM3}(already up to date)${RESET3}`);
306
+ }
282
307
  } else {
283
308
  pluginNote = `${YELLOW2}warning${RESET3}: --with-plugin requested but plugin source not found.
284
309
  ${DIM3}Run from the agentic-patterns-ts source tree, or wait for plugin packaging (Phase 2).${RESET3}`;
@@ -308,15 +333,17 @@ async function runInitCommand(opts) {
308
333
  process.stdout.write(` cd ${rootRel}
309
334
  `);
310
335
  }
311
- process.stdout.write(` pnpm install ${DIM3}# picks up the new example${RESET3}
312
- `);
336
+ process.stdout.write(
337
+ ` bun install ${DIM3}# picks up the new example${RESET3}
338
+ `
339
+ );
313
340
  process.stdout.write(` cd ${projRel}
314
341
  `);
315
342
  process.stdout.write(
316
343
  ` cp .env.example .env ${DIM3}# fill in your ${envKeyFor(provider)}${RESET3}
317
344
  `
318
345
  );
319
- process.stdout.write(` pnpm dev ${DIM3}# launch playground${RESET3}
346
+ process.stdout.write(` bun run dev ${DIM3}# launch playground${RESET3}
320
347
 
321
348
  `);
322
349
  } else {
@@ -328,9 +355,8 @@ async function runInitCommand(opts) {
328
355
  ` cp .env.example .env ${DIM3}# fill in your ${envKeyFor(provider)}${RESET3}
329
356
  `
330
357
  );
331
- process.stdout.write(` pnpm install
332
- `);
333
- process.stdout.write(` pnpm dev ${DIM3}# launch playground${RESET3}
358
+ process.stdout.write(" bun install\n");
359
+ process.stdout.write(` bun run dev ${DIM3}# launch playground${RESET3}
334
360
 
335
361
  `);
336
362
  }
@@ -376,10 +402,15 @@ function renderPackageJson(name, provider, link) {
376
402
  function renderEnvExample(provider) {
377
403
  const lines = [
378
404
  "# Dashboard URL \u2014 used by the Claude Code plugin to ship lifecycle events",
379
- "AP_DASHBOARD_URL=http://localhost:3000",
405
+ `AP_DASHBOARD_URL=${DEFAULT_DASHBOARD_URL}`,
380
406
  "",
381
407
  "# Default model tier \u2014 opus | sonnet | haiku (used by the agent runner)",
382
408
  "AGENT_TIER=sonnet",
409
+ "",
410
+ "# Optional: pin an exact model id; wins over AGENT_TIER. Useful when",
411
+ "# your provider has a model the framework's tier map doesn't list",
412
+ "# (e.g. AGENT_MODEL=qwen3.6:27b for an Ollama box).",
413
+ "# AGENT_MODEL=",
383
414
  ""
384
415
  ];
385
416
  if (provider === "anthropic") {
@@ -428,7 +459,7 @@ function renderAgent(provider) {
428
459
  * file (via \`agents/**\\/agent.ts\`), builds a runner from your environment
429
460
  * (using ${provider}), and wires it into the playground dashboard.
430
461
  *
431
- * pnpm dev # launch the dashboard at http://localhost:3000
462
+ * bun run dev # launch the dashboard at http://localhost:3456
432
463
  * ap run demo # chat with this agent in the terminal
433
464
  */
434
465
 
@@ -548,18 +579,83 @@ function writeFile(root, rel, contents, log) {
548
579
  function copyDir(src, dest) {
549
580
  fs2.cpSync(src, dest, { recursive: true });
550
581
  }
582
+ function mergeHookSettings(settingsPath, ours) {
583
+ if (!fs2.existsSync(settingsPath)) {
584
+ return { kind: "created", merged: ours };
585
+ }
586
+ let existing;
587
+ try {
588
+ existing = JSON.parse(fs2.readFileSync(settingsPath, "utf8"));
589
+ } catch {
590
+ const backup = `${settingsPath}.ap-backup-${Date.now()}`;
591
+ fs2.renameSync(settingsPath, backup);
592
+ process.stdout.write(
593
+ `${YELLOW2}warning${RESET3}: existing .claude/settings.json was malformed; moved to ${path3.basename(backup)}
594
+ `
595
+ );
596
+ return { kind: "created", merged: ours };
597
+ }
598
+ const merged = { ...existing, hooks: { ...existing.hooks ?? {} } };
599
+ let added = 0;
600
+ for (const event of Object.keys(ours.hooks)) {
601
+ const ourMatchers = ours.hooks[event] ?? [];
602
+ const theirMatchers = merged.hooks[event] ?? [];
603
+ const result = [...theirMatchers];
604
+ for (const ourMatcher of ourMatchers) {
605
+ const theirMatcher = result.find((m) => m.matcher === ourMatcher.matcher);
606
+ if (!theirMatcher) {
607
+ result.push(ourMatcher);
608
+ added += ourMatcher.hooks.length;
609
+ continue;
610
+ }
611
+ for (const ourHook of ourMatcher.hooks) {
612
+ const duplicate = theirMatcher.hooks.some(
613
+ (h) => h.type === ourHook.type && h.command === ourHook.command
614
+ );
615
+ if (!duplicate) {
616
+ theirMatcher.hooks.push(ourHook);
617
+ added += 1;
618
+ }
619
+ }
620
+ }
621
+ merged.hooks[event] = result;
622
+ }
623
+ if (added === 0) return { kind: "unchanged", merged };
624
+ return { kind: "merged", merged, added };
625
+ }
551
626
  function resolvePluginSource() {
627
+ const here = path3.dirname(fileURLToPath(import.meta.url));
628
+ const bundledCandidates = [
629
+ path3.resolve(here, "../assets/plugin-template"),
630
+ path3.resolve(here, "../../assets/plugin-template")
631
+ ];
632
+ for (const base of bundledCandidates) {
633
+ const pluginDir = path3.join(base, ".claude-plugin");
634
+ const hooksDir = path3.join(base, "hooks");
635
+ if (fs2.existsSync(pluginDir) && fs2.existsSync(hooksDir)) {
636
+ return { pluginDir, hooksDir };
637
+ }
638
+ }
552
639
  const root = resolveMonorepoRoot();
553
- if (!root) return null;
554
- return { pluginDir: path3.join(root, ".claude-plugin"), hooksDir: path3.join(root, "hooks") };
640
+ if (root) {
641
+ return { pluginDir: path3.join(root, ".claude-plugin"), hooksDir: path3.join(root, "hooks") };
642
+ }
643
+ return null;
555
644
  }
556
645
  function resolveMonorepoRoot() {
557
646
  try {
558
647
  const here = path3.dirname(fileURLToPath(import.meta.url));
559
648
  let cur = here;
560
649
  for (let i = 0; i < 8; i++) {
561
- if (fs2.existsSync(path3.join(cur, "pnpm-workspace.yaml")) && fs2.existsSync(path3.join(cur, "packages", "agent-core"))) {
562
- return cur;
650
+ const rootPkgPath = path3.join(cur, "package.json");
651
+ if (fs2.existsSync(rootPkgPath) && fs2.existsSync(path3.join(cur, "packages", "agent-core"))) {
652
+ try {
653
+ const rootPkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf8"));
654
+ if (rootPkg.workspaces && rootPkg.workspaces.length > 0) {
655
+ return cur;
656
+ }
657
+ } catch {
658
+ }
563
659
  }
564
660
  const parent = path3.dirname(cur);
565
661
  if (parent === cur) break;
@@ -587,7 +683,7 @@ import {
587
683
  import { createServer } from "@agentic-patterns/server";
588
684
  import { serve } from "@hono/node-server";
589
685
  async function runPlaygroundCommand(opts) {
590
- const port = opts.port ?? 3e3;
686
+ const port = opts.port ?? DEFAULT_DASHBOARD_PORT;
591
687
  const shouldOpen = opts.open !== false;
592
688
  const serveDashboard = opts.noDashboard !== true;
593
689
  const eventBus = new AgentEventBus();
@@ -986,16 +1082,29 @@ function formatConfigRow(config) {
986
1082
  return parts.join(" \xB7 ");
987
1083
  }
988
1084
  function detectRunnerFromEnv() {
1085
+ const pinned = process.env.AGENT_MODEL;
989
1086
  if (process.env.ANTHROPIC_API_KEY) {
990
- return { provider: "anthropic", detail: "env ANTHROPIC_API_KEY \u2192 claude-sonnet-4-5" };
1087
+ return {
1088
+ provider: "anthropic",
1089
+ detail: `env ANTHROPIC_API_KEY \u2192 ${pinned ?? "claude-sonnet-4-5"}${pinned ? " (AGENT_MODEL)" : ""}`
1090
+ };
991
1091
  }
992
1092
  if (process.env.OPENAI_API_KEY) {
993
- return { provider: "openai", detail: "env OPENAI_API_KEY \u2192 gpt-4o" };
1093
+ return {
1094
+ provider: "openai",
1095
+ detail: `env OPENAI_API_KEY \u2192 ${pinned ?? "gpt-4o"}${pinned ? " (AGENT_MODEL)" : ""}`
1096
+ };
994
1097
  }
995
1098
  if (process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_API_KEY) {
996
- return { provider: "google", detail: "env GOOGLE_*_API_KEY \u2192 gemini-2.5-flash" };
1099
+ return {
1100
+ provider: "google",
1101
+ detail: `env GOOGLE_*_API_KEY \u2192 ${pinned ?? "gemini-2.5-flash"}${pinned ? " (AGENT_MODEL)" : ""}`
1102
+ };
997
1103
  }
998
1104
  if (process.env.OLLAMA_HOST) {
1105
+ if (pinned) {
1106
+ return { provider: "ollama", detail: `env OLLAMA_HOST \u2192 ${pinned} (AGENT_MODEL)` };
1107
+ }
999
1108
  const tier = process.env.AGENT_TIER ?? "sonnet";
1000
1109
  const model = tier === "opus" ? "qwen3:30b-a3b" : tier === "haiku" ? "qwen3:4b" : "qwen3:14b";
1001
1110
  return {
@@ -1051,7 +1160,8 @@ function resolveProjectConfig(from = process.cwd()) {
1051
1160
  }
1052
1161
  }
1053
1162
  const agents = normalizeGlobs(manifest.agentic?.agents) ?? DEFAULT_AGENT_GLOBS;
1054
- const port = manifest.agentic?.port ?? Number.parseInt(process.env.PORT ?? "3000", 10);
1163
+ const envPort = process.env.PORT;
1164
+ const port = manifest.agentic?.port ?? (envPort !== void 0 ? Number.parseInt(envPort, 10) : DEFAULT_DASHBOARD_PORT);
1055
1165
  return { root, agents, port, hasManifest };
1056
1166
  }
1057
1167
  function normalizeGlobs(value) {
@@ -1159,7 +1269,7 @@ Commands:
1159
1269
 
1160
1270
  Options:
1161
1271
  -h, --help show this help
1162
- --port <port> server port for playground (default 3000)
1272
+ --port <port> server port for playground (default 3456)
1163
1273
  --no-dashboard playground without dashboard (API only)
1164
1274
  --no-open don't auto-open the browser
1165
1275
  --agents <glob> override agent discovery glob