canary-agent 0.1.6 → 0.2.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.
package/README.md CHANGED
@@ -1,16 +1,43 @@
1
1
  # canary-agent
2
2
 
3
- API observability SDK for AI agents. Zero-dependency Node.js library that captures HTTP telemetry and structured feedback from AI agent sessions.
3
+ API observability SDK for AI agents. Captures HTTP telemetry and structured feedback from AI agent sessions.
4
4
 
5
- ## Install
5
+ ## Setup
6
+
7
+ ### Option A: Paste into Claude Code (easiest)
8
+
9
+ Copy this prompt and paste it into Claude Code:
10
+
11
+ ```
12
+ Install and configure canary-agent for me. Run these two commands:
13
+
14
+ 1. npm install canary-agent
15
+ 2. npx canary-setup --api-key PASTE_YOUR_KEY_HERE --local
16
+
17
+ Then verify that .mcp.json was created with the canary-journal MCP server entry,
18
+ and that ~/.claude/skills/canary-feedback/SKILL.md exists. Tell me when it's ready.
19
+ ```
20
+
21
+ Replace `PASTE_YOUR_KEY_HERE` with your actual API key (`cnry_sk_...`).
22
+
23
+ ### Option B: One command
24
+
25
+ ```bash
26
+ npm install canary-agent && npx canary-setup --api-key cnry_sk_YOUR_KEY --local
27
+ ```
28
+
29
+ This writes `.mcp.json` (MCP server config) and installs the Claude Code skill. Restart Claude Code and you're done.
30
+
31
+ ### Option C: Interactive setup
6
32
 
7
33
  ```bash
8
34
  npm install canary-agent
35
+ npx canary-setup
9
36
  ```
10
37
 
11
- ## Quick Start
38
+ Walks you through API key, endpoint, config location (Claude Code / Cursor / both), and skill installation.
12
39
 
13
- ### As MCP Server (recommended for Claude Code / Cursor)
40
+ ### Option D: Manual
14
41
 
15
42
  Add to your project's `.mcp.json`:
16
43
 
@@ -21,108 +48,79 @@ Add to your project's `.mcp.json`:
21
48
  "command": "npx",
22
49
  "args": ["-y", "canary-agent"],
23
50
  "env": {
24
- "CANARY_API_KEY": "your_api_key",
25
- "CANARY_ENDPOINT": "https://api.canary.dev"
51
+ "CANARY_API_KEY": "cnry_sk_YOUR_KEY"
26
52
  }
27
53
  }
28
54
  }
29
55
  }
30
56
  ```
31
57
 
32
- The MCP server provides two tools:
58
+ ## What gets installed
33
59
 
34
- - **`canary_annotate`** Record API observations during your work
35
- - **`canary_report`** — Submit structured feedback when your task is complete
60
+ | Component | Purpose | Location |
61
+ |-----------|---------|----------|
62
+ | **MCP server** | Provides `canary_annotate` + `canary_report` tools | `.mcp.json` or `~/.claude/mcp.json` |
63
+ | **Skill** | Tells Claude *when and how* to use the tools | `~/.claude/skills/canary-feedback/` |
36
64
 
37
- ### As SDK (programmatic)
65
+ Both are needed for Claude Code. The MCP server provides the tools; the skill provides the instructions.
38
66
 
39
- ```typescript
40
- import { init, shutdown, survey } from 'canary-agent';
41
-
42
- // Initialize — patches http/https and fetch automatically
43
- init({
44
- apiKey: process.env.CANARY_API_KEY,
45
- endpoint: 'https://api.canary.dev',
46
- });
47
-
48
- // All HTTP calls to tracked APIs are now captured automatically
49
- const res = await fetch('https://api.stripe.com/v1/charges');
67
+ ## Setup flags
50
68
 
51
- // Optional: submit manual feedback
52
- survey({
53
- worked: true,
54
- context: 'Payment processed successfully',
55
- provider: 'stripe',
56
- });
57
-
58
- // At shutdown, auto-reports are generated
59
- await shutdown();
60
69
  ```
70
+ npx canary-setup [options]
61
71
 
62
- ## Configuration
63
-
64
- | Option | Env Var | Description |
65
- |--------|---------|-------------|
66
- | `apiKey` | `CANARY_API_KEY` | Your Canary API key (required, starts with `cnry_sk_`) |
67
- | `endpoint` | `CANARY_ENDPOINT` | Backend URL (default: `https://api.canary.dev`) |
68
- | `sessionId` | `CANARY_SESSION_ID` | Link to a specific session |
69
- | `agentName` | `CANARY_AGENT_NAME` | Identify this agent in multi-agent setups |
70
- | `autoReport` | — | Auto-generate feedback at shutdown (default: `true`) |
71
- | `autoFlush` | — | Start periodic flush timer (default: `true`) |
72
-
73
- ## Session Correlation
74
-
75
- Use `runWithSession()` to scope API calls to a session:
72
+ --api-key KEY Your Canary API key (enables non-interactive mode)
73
+ --local Write .mcp.json in current directory (default: ~/.claude/mcp.json)
74
+ --cursor Write config for Cursor (~/.cursor/mcp.json)
75
+ --endpoint URL Custom backend URL
76
+ --no-skill Skip skill installation
77
+ ```
76
78
 
77
- ```typescript
78
- import { runWithSession } from 'canary-agent';
79
+ ## How it works
79
80
 
80
- await runWithSession('skill-execution-42', async () => {
81
- // All API calls here are grouped under one session
82
- await fetch('https://api.stripe.com/v1/charges', { ... });
83
- });
81
+ ```
82
+ Your agent (Claude Code, Cursor, etc.)
83
+ ├── canary_annotate → saves notes locally (~/.canary/annotations/)
84
+ └── canary_report → submits feedback to Canary backend
85
+
86
+ Dashboard shows reports, quality scores, credits
84
87
  ```
85
88
 
86
- ## MCP Server CLI
89
+ ## SDK (programmatic)
87
90
 
88
- ```bash
89
- npx canary-agent --api-key=YOUR_KEY --endpoint=https://api.canary.dev
90
- ```
91
+ For embedding in your own Node.js app:
91
92
 
92
- Or with environment variables:
93
+ ```typescript
94
+ import { init, shutdown, survey } from 'canary-agent';
93
95
 
94
- ```bash
95
- CANARY_API_KEY=your_key npx canary-agent
96
- ```
96
+ init({ apiKey: process.env.CANARY_API_KEY });
97
97
 
98
- ## Multi-Agent Setup
98
+ // All HTTP calls are now captured automatically
99
+ const res = await fetch('https://api.stripe.com/v1/charges');
99
100
 
100
- Set `CANARY_AGENT_NAME` to identify each agent under a shared API key:
101
+ survey({ worked: true, context: 'Payment processed', provider: 'stripe' });
101
102
 
102
- ```json
103
- {
104
- "mcpServers": {
105
- "canary-journal": {
106
- "command": "npx",
107
- "args": ["-y", "canary-agent"],
108
- "env": {
109
- "CANARY_API_KEY": "shared_team_key",
110
- "CANARY_AGENT_NAME": "checkout-agent"
111
- }
112
- }
113
- }
114
- }
103
+ await shutdown();
115
104
  ```
116
105
 
117
106
  ## OpenClaw Integration
118
107
 
119
- For OpenClaw skills, import the auto-init module:
108
+ For OpenClaw agents, import the auto-init module:
120
109
 
121
110
  ```typescript
122
111
  import 'canary-agent/openclaw';
123
112
  ```
124
113
 
125
- This automatically initializes Canary when `OPENCLAW_SESSION_ID` and `CANARY_API_KEY` are set.
114
+ This automatically initializes when `OPENCLAW_SESSION_ID` and `CANARY_API_KEY` are set. HTTP interception works automatically in the OpenClaw process.
115
+
116
+ ## Configuration
117
+
118
+ | Option | Env Var | Description |
119
+ |--------|---------|-------------|
120
+ | `apiKey` | `CANARY_API_KEY` | Your API key (required, starts with `cnry_sk_`) |
121
+ | `endpoint` | `CANARY_ENDPOINT` | Backend URL (default: production) |
122
+ | `sessionId` | `CANARY_SESSION_ID` | Link to a specific session |
123
+ | `agentName` | `CANARY_AGENT_NAME` | Identify this agent in multi-agent setups |
126
124
 
127
125
  ## Tracked Providers
128
126
 
package/dist/openclaw.cjs CHANGED
@@ -11,7 +11,7 @@ var sessionId = process.env.OPENCLAW_SESSION_ID;
11
11
  if (apiKey && sessionId) {
12
12
  _chunkDRKEGQ7Ucjs.init.call(void 0, {
13
13
  apiKey,
14
- endpoint: process.env.CANARY_ENDPOINT || "https://api.canary.dev"
14
+ endpoint: process.env.CANARY_ENDPOINT || "https://canary-production-89d8.up.railway.app"
15
15
  });
16
16
  _chunkDRKEGQ7Ucjs.setFrameworkSession.call(void 0, sessionId);
17
17
  const cleanup = () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/stalapaneni/conductor/workspaces/canary/brasilia-v1/canary/dist/openclaw.cjs","../src/openclaw.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;ACWA,IAAM,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,cAAA;AAC3B,IAAM,UAAA,EAAY,OAAA,CAAQ,GAAA,CAAI,mBAAA;AAE9B,GAAA,CAAI,OAAA,GAAU,SAAA,EAAW;AACvB,EAAA,oCAAA;AAAK,IACH,MAAA;AAAA,IACA,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,gBAAA,GAAmB;AAAA,EAC3C,CAAC,CAAA;AACD,EAAA,mDAAA,SAA6B,CAAA;AAE7B,EAAA,MAAM,QAAA,EAAU,CAAA,EAAA,GAAM;AACpB,IAAA,qDAAA,CAAsB;AACtB,IAAA,wCAAA,CAAS,CAAE,KAAA,CAAM,CAAA,EAAA,GAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EAC3B,CAAA;AAEA,EAAA,OAAA,CAAQ,EAAA,CAAG,YAAA,EAAc,OAAO,CAAA;AAChC,EAAA,OAAA,CAAQ,EAAA,CAAG,SAAA,EAAW,OAAO,CAAA;AAC7B,EAAA,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,OAAO,CAAA;AAC9B;ADXA;AACE;AACA;AACA;AACF,0JAAC","file":"/Users/stalapaneni/conductor/workspaces/canary/brasilia-v1/canary/dist/openclaw.cjs","sourcesContent":[null,"/**\n * OpenClaw auto-init entry point.\n * Import this module in OpenClaw skills to automatically initialize Canary.\n *\n * Environment variables:\n * CANARY_API_KEY — required\n * CANARY_ENDPOINT — backend URL (default: https://api.canary.dev)\n * OPENCLAW_SESSION_ID — used as framework_session_id\n * CANARY_AGENT_NAME — agent identifier\n */\n\nimport {\n clearFrameworkSession,\n init,\n shutdown,\n setFrameworkSession,\n} from \"./index.js\";\n\nconst apiKey = process.env.CANARY_API_KEY;\nconst sessionId = process.env.OPENCLAW_SESSION_ID;\n\nif (apiKey && sessionId) {\n init({\n apiKey,\n endpoint: process.env.CANARY_ENDPOINT || \"https://api.canary.dev\",\n });\n setFrameworkSession(sessionId);\n\n const cleanup = () => {\n clearFrameworkSession();\n shutdown().catch(() => {});\n };\n\n process.on(\"beforeExit\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n process.on(\"SIGINT\", cleanup);\n}\n\nexport { init, shutdown, setFrameworkSession } from \"./index.js\";\n"]}
1
+ {"version":3,"sources":["/Users/stalapaneni/conductor/workspaces/canary/brasilia-v1/canary/dist/openclaw.cjs","../src/openclaw.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;ACWA,IAAM,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,cAAA;AAC3B,IAAM,UAAA,EAAY,OAAA,CAAQ,GAAA,CAAI,mBAAA;AAE9B,GAAA,CAAI,OAAA,GAAU,SAAA,EAAW;AACvB,EAAA,oCAAA;AAAK,IACH,MAAA;AAAA,IACA,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,gBAAA,GAAmB;AAAA,EAC3C,CAAC,CAAA;AACD,EAAA,mDAAA,SAA6B,CAAA;AAE7B,EAAA,MAAM,QAAA,EAAU,CAAA,EAAA,GAAM;AACpB,IAAA,qDAAA,CAAsB;AACtB,IAAA,wCAAA,CAAS,CAAE,KAAA,CAAM,CAAA,EAAA,GAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EAC3B,CAAA;AAEA,EAAA,OAAA,CAAQ,EAAA,CAAG,YAAA,EAAc,OAAO,CAAA;AAChC,EAAA,OAAA,CAAQ,EAAA,CAAG,SAAA,EAAW,OAAO,CAAA;AAC7B,EAAA,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,OAAO,CAAA;AAC9B;ADXA;AACE;AACA;AACA;AACF,0JAAC","file":"/Users/stalapaneni/conductor/workspaces/canary/brasilia-v1/canary/dist/openclaw.cjs","sourcesContent":[null,"/**\n * OpenClaw auto-init entry point.\n * Import this module in OpenClaw skills to automatically initialize Canary.\n *\n * Environment variables:\n * CANARY_API_KEY — required\n * CANARY_ENDPOINT — backend URL (default: https://api.canary.dev)\n * OPENCLAW_SESSION_ID — used as framework_session_id\n * CANARY_AGENT_NAME — agent identifier\n */\n\nimport {\n clearFrameworkSession,\n init,\n shutdown,\n setFrameworkSession,\n} from \"./index.js\";\n\nconst apiKey = process.env.CANARY_API_KEY;\nconst sessionId = process.env.OPENCLAW_SESSION_ID;\n\nif (apiKey && sessionId) {\n init({\n apiKey,\n endpoint: process.env.CANARY_ENDPOINT || \"https://canary-production-89d8.up.railway.app\",\n });\n setFrameworkSession(sessionId);\n\n const cleanup = () => {\n clearFrameworkSession();\n shutdown().catch(() => {});\n };\n\n process.on(\"beforeExit\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n process.on(\"SIGINT\", cleanup);\n}\n\nexport { init, shutdown, setFrameworkSession } from \"./index.js\";\n"]}
package/dist/openclaw.js CHANGED
@@ -11,7 +11,7 @@ var sessionId = process.env.OPENCLAW_SESSION_ID;
11
11
  if (apiKey && sessionId) {
12
12
  init({
13
13
  apiKey,
14
- endpoint: process.env.CANARY_ENDPOINT || "https://api.canary.dev"
14
+ endpoint: process.env.CANARY_ENDPOINT || "https://canary-production-89d8.up.railway.app"
15
15
  });
16
16
  setFrameworkSession(sessionId);
17
17
  const cleanup = () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/openclaw.ts"],"sourcesContent":["/**\n * OpenClaw auto-init entry point.\n * Import this module in OpenClaw skills to automatically initialize Canary.\n *\n * Environment variables:\n * CANARY_API_KEY — required\n * CANARY_ENDPOINT — backend URL (default: https://api.canary.dev)\n * OPENCLAW_SESSION_ID — used as framework_session_id\n * CANARY_AGENT_NAME — agent identifier\n */\n\nimport {\n clearFrameworkSession,\n init,\n shutdown,\n setFrameworkSession,\n} from \"./index.js\";\n\nconst apiKey = process.env.CANARY_API_KEY;\nconst sessionId = process.env.OPENCLAW_SESSION_ID;\n\nif (apiKey && sessionId) {\n init({\n apiKey,\n endpoint: process.env.CANARY_ENDPOINT || \"https://api.canary.dev\",\n });\n setFrameworkSession(sessionId);\n\n const cleanup = () => {\n clearFrameworkSession();\n shutdown().catch(() => {});\n };\n\n process.on(\"beforeExit\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n process.on(\"SIGINT\", cleanup);\n}\n\nexport { init, shutdown, setFrameworkSession } from \"./index.js\";\n"],"mappings":";;;;;;;;AAkBA,IAAM,SAAS,QAAQ,IAAI;AAC3B,IAAM,YAAY,QAAQ,IAAI;AAE9B,IAAI,UAAU,WAAW;AACvB,OAAK;AAAA,IACH;AAAA,IACA,UAAU,QAAQ,IAAI,mBAAmB;AAAA,EAC3C,CAAC;AACD,sBAAoB,SAAS;AAE7B,QAAM,UAAU,MAAM;AACpB,0BAAsB;AACtB,aAAS,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC3B;AAEA,UAAQ,GAAG,cAAc,OAAO;AAChC,UAAQ,GAAG,WAAW,OAAO;AAC7B,UAAQ,GAAG,UAAU,OAAO;AAC9B;","names":[]}
1
+ {"version":3,"sources":["../src/openclaw.ts"],"sourcesContent":["/**\n * OpenClaw auto-init entry point.\n * Import this module in OpenClaw skills to automatically initialize Canary.\n *\n * Environment variables:\n * CANARY_API_KEY — required\n * CANARY_ENDPOINT — backend URL (default: https://api.canary.dev)\n * OPENCLAW_SESSION_ID — used as framework_session_id\n * CANARY_AGENT_NAME — agent identifier\n */\n\nimport {\n clearFrameworkSession,\n init,\n shutdown,\n setFrameworkSession,\n} from \"./index.js\";\n\nconst apiKey = process.env.CANARY_API_KEY;\nconst sessionId = process.env.OPENCLAW_SESSION_ID;\n\nif (apiKey && sessionId) {\n init({\n apiKey,\n endpoint: process.env.CANARY_ENDPOINT || \"https://canary-production-89d8.up.railway.app\",\n });\n setFrameworkSession(sessionId);\n\n const cleanup = () => {\n clearFrameworkSession();\n shutdown().catch(() => {});\n };\n\n process.on(\"beforeExit\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n process.on(\"SIGINT\", cleanup);\n}\n\nexport { init, shutdown, setFrameworkSession } from \"./index.js\";\n"],"mappings":";;;;;;;;AAkBA,IAAM,SAAS,QAAQ,IAAI;AAC3B,IAAM,YAAY,QAAQ,IAAI;AAE9B,IAAI,UAAU,WAAW;AACvB,OAAK;AAAA,IACH;AAAA,IACA,UAAU,QAAQ,IAAI,mBAAmB;AAAA,EAC3C,CAAC;AACD,sBAAoB,SAAS;AAE7B,QAAM,UAAU,MAAM;AACpB,0BAAsB;AACtB,aAAS,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC3B;AAEA,UAAQ,GAAG,cAAc,OAAO;AAChC,UAAQ,GAAG,WAAW,OAAO;AAC7B,UAAQ,GAAG,UAAU,OAAO;AAC9B;","names":[]}
package/dist/setup.cjs CHANGED
@@ -8,10 +8,95 @@ var import_node_os = require("os");
8
8
  var import_node_readline = require("readline");
9
9
  var import_meta = {};
10
10
  var DEFAULT_ENDPOINT = "https://canary-production-89d8.up.railway.app";
11
+ function parseCliArgs() {
12
+ const args = process.argv.slice(2);
13
+ let apiKey = "";
14
+ let endpoint = "";
15
+ let local = false;
16
+ let cursor = false;
17
+ let skipSkill = false;
18
+ for (let i = 0; i < args.length; i++) {
19
+ if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
20
+ else if (args[i].startsWith("--api-key=")) apiKey = args[i].split("=").slice(1).join("=");
21
+ else if (args[i] === "--endpoint" && args[i + 1]) endpoint = args[++i];
22
+ else if (args[i].startsWith("--endpoint=")) endpoint = args[i].split("=").slice(1).join("=");
23
+ else if (args[i] === "--local") local = true;
24
+ else if (args[i] === "--cursor") cursor = true;
25
+ else if (args[i] === "--no-skill") skipSkill = true;
26
+ }
27
+ return { apiKey, endpoint, local, cursor, skipSkill, isNonInteractive: !!apiKey };
28
+ }
11
29
  function ask(rl, question) {
12
30
  return new Promise((resolve) => rl.question(question, resolve));
13
31
  }
14
- async function main() {
32
+ function buildMcpEntry(apiKey, endpoint) {
33
+ return {
34
+ command: "npx",
35
+ args: ["-y", "canary-agent"],
36
+ env: {
37
+ CANARY_API_KEY: apiKey || "YOUR_API_KEY_HERE",
38
+ ...endpoint !== DEFAULT_ENDPOINT ? { CANARY_ENDPOINT: endpoint } : {}
39
+ }
40
+ };
41
+ }
42
+ function mergeJsonFile(path, key, serverName, entry) {
43
+ let existing = {};
44
+ if ((0, import_node_fs.existsSync)(path)) {
45
+ try {
46
+ existing = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
47
+ } catch {
48
+ }
49
+ }
50
+ const merged = {
51
+ ...existing,
52
+ [key]: {
53
+ ...existing[key] || {},
54
+ [serverName]: entry
55
+ }
56
+ };
57
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(merged, null, 2) + "\n");
58
+ }
59
+ function writeClaudeGlobalMcp(apiKey, endpoint) {
60
+ const claudeDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude");
61
+ (0, import_node_fs.mkdirSync)(claudeDir, { recursive: true });
62
+ const configPath = (0, import_node_path.join)(claudeDir, "mcp.json");
63
+ mergeJsonFile(configPath, "mcpServers", "canary-journal", buildMcpEntry(apiKey, endpoint));
64
+ return configPath;
65
+ }
66
+ function writeProjectMcp(apiKey, endpoint) {
67
+ const configPath = (0, import_node_path.join)(process.cwd(), ".mcp.json");
68
+ mergeJsonFile(configPath, "mcpServers", "canary-journal", buildMcpEntry(apiKey, endpoint));
69
+ return configPath;
70
+ }
71
+ function writeCursorMcp(apiKey, endpoint) {
72
+ const cursorDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".cursor");
73
+ (0, import_node_fs.mkdirSync)(cursorDir, { recursive: true });
74
+ const configPath = (0, import_node_path.join)(cursorDir, "mcp.json");
75
+ mergeJsonFile(configPath, "mcpServers", "canary-journal", buildMcpEntry(apiKey, endpoint));
76
+ return configPath;
77
+ }
78
+ function installSkill() {
79
+ try {
80
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : new URL(".", import_meta.url).pathname;
81
+ const pkgRoot = (0, import_node_path.join)(thisDir, "..");
82
+ const skillSrc = (0, import_node_path.join)(pkgRoot, "skills", "canary-feedback");
83
+ const claudeDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude");
84
+ const skillDest = (0, import_node_path.join)(claudeDir, "skills", "canary-feedback");
85
+ if ((0, import_node_fs.existsSync)((0, import_node_path.join)(skillSrc, "SKILL.md"))) {
86
+ (0, import_node_fs.mkdirSync)(skillDest, { recursive: true });
87
+ for (const file of ["SKILL.md", "manifest.yaml", "mcp-config.json"]) {
88
+ const src = (0, import_node_path.join)(skillSrc, file);
89
+ if ((0, import_node_fs.existsSync)(src)) {
90
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(skillDest, file), (0, import_node_fs.readFileSync)(src, "utf8"));
91
+ }
92
+ }
93
+ return skillDest;
94
+ }
95
+ } catch {
96
+ }
97
+ return null;
98
+ }
99
+ async function runInteractive() {
15
100
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
16
101
  console.log("");
17
102
  console.log(" Canary Agent Setup");
@@ -19,93 +104,89 @@ async function main() {
19
104
  console.log("");
20
105
  const apiKey = await ask(rl, " Enter your Canary API key (cnry_sk_...): ");
21
106
  if (!apiKey.trim()) {
22
- console.log("\n No API key provided. You can set CANARY_API_KEY env var later.");
107
+ console.log(" No API key provided. You can set CANARY_API_KEY env var later.\n");
23
108
  }
24
- const customEndpoint = await ask(
25
- rl,
26
- ` Backend endpoint [${DEFAULT_ENDPOINT}]: `
27
- );
109
+ const customEndpoint = await ask(rl, ` Backend endpoint [${DEFAULT_ENDPOINT}]: `);
28
110
  const endpoint = customEndpoint.trim() || DEFAULT_ENDPOINT;
29
111
  console.log("");
30
- console.log(" Where should the MCP config be written?");
31
- console.log(" 1) ~/.claude/mcp.json (global \u2014 works for all projects)");
32
- console.log(" 2) .mcp.json (project-local \u2014 current directory only)");
112
+ console.log(" Where are you using Canary?");
113
+ console.log(" 1) Claude Code (global ~/.claude/mcp.json)");
114
+ console.log(" 2) Claude Code (project .mcp.json)");
115
+ console.log(" 3) Cursor");
116
+ console.log(" 4) All of the above");
33
117
  console.log("");
34
- const configChoice = await ask(rl, " Choice [1]: ");
35
- const mcpConfig = {
36
- mcpServers: {
37
- "canary-journal": {
38
- command: "npx",
39
- args: ["-y", "canary-agent"],
40
- env: {
41
- CANARY_API_KEY: apiKey.trim() || "YOUR_API_KEY_HERE",
42
- ...endpoint !== DEFAULT_ENDPOINT ? { CANARY_ENDPOINT: endpoint } : {}
43
- }
44
- }
45
- }
46
- };
47
- let configPath;
48
- if (configChoice.trim() === "2") {
49
- configPath = (0, import_node_path.join)(process.cwd(), ".mcp.json");
118
+ const choice = await ask(rl, " Choice [1]: ");
119
+ const paths = [];
120
+ const c = choice.trim();
121
+ if (c === "2") {
122
+ paths.push(writeProjectMcp(apiKey.trim(), endpoint));
123
+ } else if (c === "3") {
124
+ paths.push(writeCursorMcp(apiKey.trim(), endpoint));
125
+ } else if (c === "4") {
126
+ paths.push(writeClaudeGlobalMcp(apiKey.trim(), endpoint));
127
+ paths.push(writeProjectMcp(apiKey.trim(), endpoint));
128
+ paths.push(writeCursorMcp(apiKey.trim(), endpoint));
50
129
  } else {
51
- const claudeDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude");
52
- (0, import_node_fs.mkdirSync)(claudeDir, { recursive: true });
53
- configPath = (0, import_node_path.join)(claudeDir, "mcp.json");
130
+ paths.push(writeClaudeGlobalMcp(apiKey.trim(), endpoint));
54
131
  }
55
- let existing = {};
56
- if ((0, import_node_fs.existsSync)(configPath)) {
57
- try {
58
- existing = JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf8"));
59
- } catch {
60
- }
132
+ for (const p of paths) {
133
+ console.log(` Wrote MCP config: ${p}`);
61
134
  }
62
- const merged = {
63
- ...existing,
64
- mcpServers: {
65
- ...existing.mcpServers || {},
66
- ...mcpConfig.mcpServers
67
- }
68
- };
69
- (0, import_node_fs.writeFileSync)(configPath, JSON.stringify(merged, null, 2) + "\n");
70
- console.log(`
71
- Wrote MCP config to: ${configPath}`);
72
135
  console.log("");
73
- const installSkill = await ask(
74
- rl,
75
- " Install the canary-feedback skill for Claude Code? (Y/n): "
76
- );
77
- if (installSkill.trim().toLowerCase() !== "n") {
78
- try {
79
- const thisDir = typeof __dirname !== "undefined" ? __dirname : new URL(".", import_meta.url).pathname;
80
- const pkgRoot = (0, import_node_path.join)(thisDir, "..");
81
- const skillSrc = (0, import_node_path.join)(pkgRoot, "skills", "canary-feedback");
82
- const claudeDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude");
83
- const skillDest = (0, import_node_path.join)(claudeDir, "skills", "canary-feedback");
84
- if ((0, import_node_fs.existsSync)((0, import_node_path.join)(skillSrc, "SKILL.md"))) {
85
- (0, import_node_fs.mkdirSync)(skillDest, { recursive: true });
86
- for (const file of ["SKILL.md", "manifest.yaml", "mcp-config.json"]) {
87
- const src = (0, import_node_path.join)(skillSrc, file);
88
- if ((0, import_node_fs.existsSync)(src)) {
89
- (0, import_node_fs.writeFileSync)((0, import_node_path.join)(skillDest, file), (0, import_node_fs.readFileSync)(src, "utf8"));
90
- }
91
- }
92
- console.log(` Installed skill to: ${skillDest}`);
93
- } else {
94
- console.log(" Skill files not found in package. Skipping.");
95
- }
96
- } catch (err) {
97
- console.log(` Could not install skill: ${err.message}`);
98
- }
136
+ const doSkill = await ask(rl, " Install the canary-feedback skill for Claude Code? (Y/n): ");
137
+ if (doSkill.trim().toLowerCase() !== "n") {
138
+ const dest = installSkill();
139
+ if (dest) console.log(` Installed skill: ${dest}`);
140
+ else console.log(" Skill files not found in package. Skipping.");
141
+ }
142
+ rl.close();
143
+ printDone(paths);
144
+ }
145
+ function runNonInteractive(cli2) {
146
+ console.log("");
147
+ console.log(" Canary Agent Setup");
148
+ console.log(" ==================");
149
+ console.log("");
150
+ const endpoint = cli2.endpoint || DEFAULT_ENDPOINT;
151
+ const paths = [];
152
+ if (cli2.cursor) {
153
+ paths.push(writeCursorMcp(cli2.apiKey, endpoint));
154
+ }
155
+ if (cli2.local) {
156
+ paths.push(writeProjectMcp(cli2.apiKey, endpoint));
157
+ }
158
+ if (!cli2.local && !cli2.cursor) {
159
+ paths.push(writeClaudeGlobalMcp(cli2.apiKey, endpoint));
99
160
  }
161
+ for (const p of paths) {
162
+ console.log(` Wrote MCP config: ${p}`);
163
+ }
164
+ if (!cli2.skipSkill) {
165
+ const dest = installSkill();
166
+ if (dest) console.log(` Installed skill: ${dest}`);
167
+ }
168
+ printDone(paths);
169
+ }
170
+ function printDone(paths) {
171
+ console.log("");
172
+ console.log(" Setup complete!");
100
173
  console.log("");
101
- console.log(" Setup complete! Next steps:");
102
- console.log(" 1. Start Claude Code in any project");
174
+ console.log(" What was installed:");
175
+ console.log(" - MCP server config (canary_annotate + canary_report tools)");
176
+ console.log(" - canary-feedback skill (tells Claude when/how to use the tools)");
177
+ console.log("");
178
+ console.log(" Next steps:");
179
+ console.log(" 1. Restart Claude Code / Cursor");
103
180
  console.log(" 2. The canary-journal MCP server will auto-start");
104
- console.log(" 3. Claude will use canary_annotate and canary_report tools automatically");
181
+ console.log(" 3. Claude will annotate API calls and submit feedback automatically");
105
182
  console.log("");
106
- rl.close();
107
183
  }
108
- main().catch((err) => {
109
- console.error("Setup failed:", err.message);
110
- process.exit(1);
111
- });
184
+ var cli = parseCliArgs();
185
+ if (cli.isNonInteractive) {
186
+ runNonInteractive(cli);
187
+ } else {
188
+ runInteractive().catch((err) => {
189
+ console.error("Setup failed:", err.message);
190
+ process.exit(1);
191
+ });
192
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "canary-agent",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "Canary API observability SDK for Node.js — zero-dependency HTTP telemetry and structured feedback for AI agents",
5
5
  "license": "MIT",
6
6
  "engines": {