@agentic-patterns/cli 0.1.4 → 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>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentic-patterns",
3
3
  "version": "0.1.0",
4
- "description": "Streams Claude Code lifecycle events into the @agentic-patterns/server dashboard for live observability. Pair with `ap playground` or `ap serve`.",
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
5
  "author": {
6
6
  "name": "pattern-stack",
7
7
  "url": "https://github.com/pattern-stack"
@@ -1,8 +1,15 @@
1
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.
2
9
  import { stdin } from "node:process";
3
10
 
4
11
  const eventName = process.argv[2] ?? "Unknown";
5
- const base = process.env.AP_DASHBOARD_URL ?? "http://localhost:3000";
12
+ const base = process.env.AP_DASHBOARD_URL ?? "http://localhost:3456";
6
13
 
7
14
  try {
8
15
  const chunks = [];
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,8 +333,10 @@ async function runInitCommand(opts) {
308
333
  process.stdout.write(` cd ${rootRel}
309
334
  `);
310
335
  }
311
- process.stdout.write(` bun 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(
@@ -328,8 +355,7 @@ 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(` bun install
332
- `);
358
+ process.stdout.write(" bun install\n");
333
359
  process.stdout.write(` bun run dev ${DIM3}# launch playground${RESET3}
334
360
 
335
361
  `);
@@ -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,6 +579,50 @@ 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() {
552
627
  const here = path3.dirname(fileURLToPath(import.meta.url));
553
628
  const bundledCandidates = [
@@ -608,7 +683,7 @@ import {
608
683
  import { createServer } from "@agentic-patterns/server";
609
684
  import { serve } from "@hono/node-server";
610
685
  async function runPlaygroundCommand(opts) {
611
- const port = opts.port ?? 3e3;
686
+ const port = opts.port ?? DEFAULT_DASHBOARD_PORT;
612
687
  const shouldOpen = opts.open !== false;
613
688
  const serveDashboard = opts.noDashboard !== true;
614
689
  const eventBus = new AgentEventBus();
@@ -1007,16 +1082,29 @@ function formatConfigRow(config) {
1007
1082
  return parts.join(" \xB7 ");
1008
1083
  }
1009
1084
  function detectRunnerFromEnv() {
1085
+ const pinned = process.env.AGENT_MODEL;
1010
1086
  if (process.env.ANTHROPIC_API_KEY) {
1011
- 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
+ };
1012
1091
  }
1013
1092
  if (process.env.OPENAI_API_KEY) {
1014
- 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
+ };
1015
1097
  }
1016
1098
  if (process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_API_KEY) {
1017
- 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
+ };
1018
1103
  }
1019
1104
  if (process.env.OLLAMA_HOST) {
1105
+ if (pinned) {
1106
+ return { provider: "ollama", detail: `env OLLAMA_HOST \u2192 ${pinned} (AGENT_MODEL)` };
1107
+ }
1020
1108
  const tier = process.env.AGENT_TIER ?? "sonnet";
1021
1109
  const model = tier === "opus" ? "qwen3:30b-a3b" : tier === "haiku" ? "qwen3:4b" : "qwen3:14b";
1022
1110
  return {
@@ -1072,7 +1160,8 @@ function resolveProjectConfig(from = process.cwd()) {
1072
1160
  }
1073
1161
  }
1074
1162
  const agents = normalizeGlobs(manifest.agentic?.agents) ?? DEFAULT_AGENT_GLOBS;
1075
- 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);
1076
1165
  return { root, agents, port, hasManifest };
1077
1166
  }
1078
1167
  function normalizeGlobs(value) {
@@ -1180,7 +1269,7 @@ Commands:
1180
1269
 
1181
1270
  Options:
1182
1271
  -h, --help show this help
1183
- --port <port> server port for playground (default 3000)
1272
+ --port <port> server port for playground (default 3456)
1184
1273
  --no-dashboard playground without dashboard (API only)
1185
1274
  --no-open don't auto-open the browser
1186
1275
  --agents <glob> override agent discovery glob