ardea 0.2.1 → 0.3.1

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Ardia
3
+ Copyright (c) 2026 Canary
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,35 +1,10 @@
1
- # ardea — MCP for API feedback
1
+ # ardea
2
2
 
3
- MCP server for AI agents to report API experience feedback and earn credits.
3
+ MCP tools for AI agents to report API experiences. Record observations during tasks and submit structured feedback that gets scored and surfaced in the [Ardea dashboard](https://ardea-production.up.railway.app).
4
4
 
5
- ## Quick Install
5
+ ## Quick Start
6
6
 
7
- **One command (Claude Code):**
8
-
9
- ```bash
10
- claude mcp add ardea -- npx -y ardea
11
- ```
12
-
13
- **Or paste this into Claude Code:**
14
-
15
- ```
16
- Add the Ardea API feedback MCP to my project.
17
- Merge this into .mcp.json (create the file if it doesn't exist):
18
-
19
- {
20
- "mcpServers": {
21
- "ardea": {
22
- "command": "npx",
23
- "args": ["-y", "ardea"]
24
- }
25
- }
26
- }
27
-
28
- Don't overwrite any existing servers in the file.
29
- After editing, tell me to restart Claude Code so the MCP can start.
30
- ```
31
-
32
- **Or manually** add to `.mcp.json`:
7
+ Add to your MCP config (`.mcp.json`, Claude Desktop, etc.):
33
8
 
34
9
  ```json
35
10
  {
@@ -42,64 +17,41 @@ After editing, tell me to restart Claude Code so the MCP can start.
42
17
  }
43
18
  ```
44
19
 
45
- ## Authentication
20
+ On first run, a browser window opens for signup. Your API key is saved to `~/.ardea/config.json` automatically.
46
21
 
47
- No API key needed at install time. Here's what happens:
48
-
49
- 1. **Install** — add the MCP config above, restart Claude Code
50
- 2. **First tool call** — Ardea checks for an API key:
51
- - `ARDEA_API_KEY` environment variable
52
- - `~/.ardea/config.json` (saved from previous signup)
53
- 3. **No key found?** — Your browser opens automatically to the signup page
54
- 4. **Sign up** — create your account in the browser
55
- 5. **Done** — API key is saved to `~/.ardea/config.json`. All future starts use it automatically.
22
+ ## Tools
56
23
 
57
- **Manual override** (CI, shared machines, or explicit setup):
24
+ ### `ardea_annotate`
58
25
 
59
- ```json
60
- {
61
- "mcpServers": {
62
- "ardea": {
63
- "command": "npx",
64
- "args": ["-y", "ardea"],
65
- "env": {
66
- "ARDEA_API_KEY": "ardea_sk_your_key_here"
67
- }
68
- }
69
- }
70
- }
71
- ```
26
+ Record API observations during a task. Stores notes locally in `~/.ardea/annotations/`.
72
27
 
73
- API keys start with `ardea_sk_`.
28
+ - `id` annotation key, e.g. `"stripe/charges"`
29
+ - `note` — what happened: result, errors, latency
30
+ - `list: true` — list all saved annotations
31
+ - `clear: true` — delete an annotation
74
32
 
75
- ## Tools
33
+ ### `ardea_report`
76
34
 
77
- | Tool | Purpose |
78
- |------|---------|
79
- | `ardea_annotate` | Record API observations as you work — endpoint, status code, errors, latency, surprises |
80
- | `ardea_report` | Submit a structured feedback summary when your task is complete |
81
- | `ardea_wallet` | Redeem vouchers, check credit balances, pay for API calls |
35
+ Submit a structured API experience report for scoring.
82
36
 
83
- **Workflow:** Annotate after each API call Report once when the task is done → Earn $0.10–$0.20 per quality report.
37
+ - `task_goal` (required) what you were trying to do
38
+ - `outcome` (required) — `"success"`, `"partial"`, `"failure"`, or `"blocked"`
39
+ - `apis_used` (required) — APIs and endpoints used
40
+ - `what_worked` — what went well
41
+ - `what_failed` — what broke, with status codes and errors
42
+ - `friction` — biggest pain point
43
+ - `suggestion` — improvement recommendation
84
44
 
85
45
  ## Environment Variables
86
46
 
87
- | Variable | Default | Description |
88
- |----------|---------|-------------|
89
- | `ARDEA_API_KEY` | (browser auth) | API key starting with `ardea_sk_` |
90
- | `ARDEA_ENDPOINT` | production | Backend URL (override for local dev) |
91
- | `ARDEA_SESSION_ID` | (auto) | Default session ID for reports |
92
- | `ARDEA_AGENT_NAME` | (unnamed) | Agent name for multi-agent setups |
93
-
94
- ## CLI Flags
95
-
96
- ```bash
97
- npx ardea --api-key=ardea_sk_... --endpoint=https://... --session-id=my-session --agent-name=my-agent
98
- ```
99
-
100
- ## Verify Installation
47
+ All optional browser signup handles configuration automatically.
101
48
 
102
- Ask your AI agent to run: `ardea_annotate` with `list: true`. You should get back an empty annotations list.
49
+ | Variable | Purpose |
50
+ |----------|---------|
51
+ | `ARDEA_API_KEY` | Skip browser signup |
52
+ | `ARDEA_ENDPOINT` | Custom backend URL (default: production) |
53
+ | `ARDEA_SESSION_ID` | Correlate reports to a session |
54
+ | `ARDEA_AGENT_NAME` | Label for this agent |
103
55
 
104
56
  ## License
105
57
 
@@ -0,0 +1,7 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/constants.ts
2
+ var DEFAULT_ENDPOINT = "https://ardea-production.up.railway.app";
3
+
4
+
5
+
6
+ exports.DEFAULT_ENDPOINT = DEFAULT_ENDPOINT;
7
+ //# sourceMappingURL=chunk-OREJ23CV.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/chunk-OREJ23CV.cjs","../src/constants.ts"],"names":[],"mappings":"AAAA;ACAO,IAAM,iBAAA,EACX,yCAAA;ADCF;AACA;AACE;AACF,4CAAC","file":"/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/chunk-OREJ23CV.cjs","sourcesContent":[null,"export const DEFAULT_ENDPOINT =\n \"https://ardea-production.up.railway.app\";\n"]}
@@ -0,0 +1,7 @@
1
+ // src/constants.ts
2
+ var DEFAULT_ENDPOINT = "https://ardea-production.up.railway.app";
3
+
4
+ export {
5
+ DEFAULT_ENDPOINT
6
+ };
7
+ //# sourceMappingURL=chunk-UL6WUTYP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["export const DEFAULT_ENDPOINT =\n \"https://ardea-production.up.railway.app\";\n"],"mappings":";AAAO,IAAM,mBACX;","names":[]}
package/dist/index.cjs ADDED
@@ -0,0 +1,396 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;
2
+
3
+ var _chunkOREJ23CVcjs = require('./chunk-OREJ23CV.cjs');
4
+
5
+ // src/buffer.ts
6
+ var _zlib = require('zlib');
7
+ var _https = require('https'); var _https2 = _interopRequireDefault(_https);
8
+ var _http = require('http'); var _http2 = _interopRequireDefault(_http);
9
+ var MAX_LEN = 1e4;
10
+ var BATCH_SIZE = 100;
11
+ var FLUSH_INTERVAL_MS = 1e4;
12
+ var MAX_EVENTS_PER_SESSION = 500;
13
+ var MAX_SESSION_CACHE_EVENTS = 5e3;
14
+ var RETRY_ATTEMPTS = 3;
15
+ var RETRY_BACKOFF_MS = [1e3, 2e3, 4e3];
16
+ var EventBuffer = (_class = class {
17
+ __init() {this._buffer = []}
18
+ __init2() {this._sessionCache = /* @__PURE__ */ new Map()}
19
+ __init3() {this._sessionCacheEventCount = 0}
20
+
21
+
22
+ __init4() {this._timer = null}
23
+ __init5() {this._stopped = false}
24
+ /** Reference to the original (unpatched) https.request for anti-recursion. */
25
+
26
+ /** Reference to the original (unpatched) http.request for anti-recursion. */
27
+
28
+ constructor(apiKey, endpoint = _chunkOREJ23CVcjs.DEFAULT_ENDPOINT, autoFlush = true, originalHttpsRequest, originalHttpRequest) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);
29
+ this._apiKey = apiKey;
30
+ this._endpoint = endpoint.replace(/\/+$/, "");
31
+ this._originalHttpsRequest = _nullishCoalesce(originalHttpsRequest, () => ( _https2.default.request));
32
+ this._originalHttpRequest = _nullishCoalesce(originalHttpRequest, () => ( _http2.default.request));
33
+ if (autoFlush) {
34
+ this._timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
35
+ this._timer.unref();
36
+ }
37
+ }
38
+ /** Add an event to the ring buffer. */
39
+ push(event) {
40
+ if (this._buffer.length >= MAX_LEN) {
41
+ this._buffer.shift();
42
+ }
43
+ this._buffer.push(event);
44
+ const sessionId = event.framework_session_id;
45
+ if (typeof sessionId === "string" && sessionId) {
46
+ if (this._sessionCacheEventCount < MAX_SESSION_CACHE_EVENTS) {
47
+ let sessionEvents = this._sessionCache.get(sessionId);
48
+ if (!sessionEvents) {
49
+ sessionEvents = [];
50
+ this._sessionCache.set(sessionId, sessionEvents);
51
+ }
52
+ if (sessionEvents.length < MAX_EVENTS_PER_SESSION) {
53
+ sessionEvents.push(event);
54
+ this._sessionCacheEventCount++;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ /** Return a copy of events for the given session from the cache. */
60
+ getSessionEvents(sessionId) {
61
+ return [..._nullishCoalesce(this._sessionCache.get(sessionId), () => ( []))];
62
+ }
63
+ /** Remove session events from the cache. */
64
+ clearSession(sessionId) {
65
+ const events = this._sessionCache.get(sessionId);
66
+ if (events) {
67
+ this._sessionCacheEventCount = Math.max(
68
+ 0,
69
+ this._sessionCacheEventCount - events.length
70
+ );
71
+ this._sessionCache.delete(sessionId);
72
+ }
73
+ }
74
+ /** Remove and return up to `count` events from the buffer. */
75
+ drain(count) {
76
+ const n = Math.min(count, this._buffer.length);
77
+ return this._buffer.splice(0, n);
78
+ }
79
+ /** Get all buffered events (without draining). For reporter access. */
80
+ peek() {
81
+ return [...this._buffer];
82
+ }
83
+ /** Number of buffered events. */
84
+ get length() {
85
+ return this._buffer.length;
86
+ }
87
+ /** Drain a batch and send to backend. */
88
+ async flush() {
89
+ const events = this.drain(BATCH_SIZE);
90
+ if (events.length === 0) return;
91
+ for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
92
+ try {
93
+ await this._send(events);
94
+ return;
95
+ } catch (e) {
96
+ if (attempt < RETRY_ATTEMPTS - 1) {
97
+ await sleep(RETRY_BACKOFF_MS[attempt]);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ /** Stop flush timer and drain all remaining events. */
103
+ async shutdown() {
104
+ this._stopped = true;
105
+ if (this._timer) {
106
+ clearInterval(this._timer);
107
+ this._timer = null;
108
+ }
109
+ while (this._buffer.length > 0) {
110
+ const events = this.drain(BATCH_SIZE);
111
+ if (events.length === 0) break;
112
+ for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
113
+ try {
114
+ await this._send(events);
115
+ break;
116
+ } catch (e2) {
117
+ if (attempt === RETRY_ATTEMPTS - 1) {
118
+ } else {
119
+ await sleep(RETRY_BACKOFF_MS[attempt]);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ /** POST events to the backend with gzip compression. */
126
+ _send(events) {
127
+ const payload = JSON.stringify({
128
+ events,
129
+ sdk_version: "0.1.0"
130
+ });
131
+ const compressed = _zlib.gzipSync.call(void 0, Buffer.from(payload, "utf-8"));
132
+ const url = new URL(`${this._endpoint}/v1/events`);
133
+ const isHttps = url.protocol === "https:";
134
+ const requestFn = isHttps ? this._originalHttpsRequest : this._originalHttpRequest;
135
+ return new Promise((resolve, reject) => {
136
+ const req = requestFn(
137
+ {
138
+ hostname: url.hostname,
139
+ port: url.port || (isHttps ? 443 : 80),
140
+ path: url.pathname,
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ "Content-Encoding": "gzip",
145
+ Authorization: `Bearer ${this._apiKey}`,
146
+ "Content-Length": compressed.length
147
+ },
148
+ timeout: 5e3
149
+ },
150
+ (res) => {
151
+ res.resume();
152
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
153
+ resolve();
154
+ } else {
155
+ reject(new Error(`HTTP ${res.statusCode}`));
156
+ }
157
+ }
158
+ );
159
+ req.on("error", reject);
160
+ req.on("timeout", () => {
161
+ req.destroy();
162
+ reject(new Error("Request timeout"));
163
+ });
164
+ req.write(compressed);
165
+ req.end();
166
+ });
167
+ }
168
+ }, _class);
169
+ function sleep(ms) {
170
+ return new Promise((resolve) => setTimeout(resolve, ms));
171
+ }
172
+
173
+ // src/context.ts
174
+ var _async_hooks = require('async_hooks');
175
+ var _crypto = require('crypto');
176
+
177
+ // src/agent-detect.ts
178
+ var AGENT_ENV_MAP = [
179
+ { envKey: "ARDEA_AGENT_NAME", name: "", useValue: true },
180
+ { envKey: "CANARY_AGENT_NAME", name: "", useValue: true },
181
+ { envKey: "CLAUDE_CODE", name: "claude_code" },
182
+ { envKey: "CURSOR_TRACE_ID", name: "cursor" },
183
+ { envKey: "WINDSURF_SESSION_ID", name: "windsurf" },
184
+ { envKey: "CLINE_TASK_ID", name: "cline" },
185
+ { envKey: "AIDER_VERSION", name: "aider", versionKey: "AIDER_VERSION" },
186
+ { envKey: "DEVIN_SESSION_ID", name: "devin" },
187
+ { envKey: "CODEX_CLI", name: "codex" },
188
+ { envKey: "GITHUB_COPILOT", name: "github_copilot" },
189
+ { envKey: "CODY_SESSION_ID", name: "sourcegraph_cody" }
190
+ ];
191
+ var cachedInfo = null;
192
+ function detectAgent() {
193
+ if (cachedInfo) return cachedInfo;
194
+ for (const entry of AGENT_ENV_MAP) {
195
+ const val = process.env[entry.envKey];
196
+ if (val !== void 0) {
197
+ cachedInfo = {
198
+ agent_name: entry.useValue ? val || null : entry.name,
199
+ agent_version: entry.versionKey ? _nullishCoalesce(process.env[entry.versionKey], () => ( null)) : null
200
+ };
201
+ if (cachedInfo.agent_name) return cachedInfo;
202
+ }
203
+ }
204
+ cachedInfo = { agent_name: null, agent_version: null };
205
+ return cachedInfo;
206
+ }
207
+ function resetAgentCache() {
208
+ cachedInfo = null;
209
+ }
210
+
211
+ // src/context.ts
212
+ var sessionStorage = new (0, _async_hooks.AsyncLocalStorage)();
213
+ var sdkInstanceId = _crypto.randomUUID.call(void 0, ).replace(/-/g, "");
214
+ var callSequence = 0;
215
+ var defaultContext = {
216
+ frameworkSessionId: void 0
217
+ };
218
+ function getMutableContext() {
219
+ return _nullishCoalesce(sessionStorage.getStore(), () => ( defaultContext));
220
+ }
221
+ function nextCallSequence() {
222
+ callSequence += 1;
223
+ return callSequence;
224
+ }
225
+ function getBaseSignals() {
226
+ const ctx = getMutableContext();
227
+ const configuredParentProcessId = Number.parseInt(
228
+ process.env.ARDEA_PARENT_PROCESS_ID || process.env.CANARY_PARENT_PROCESS_ID || "",
229
+ 10
230
+ );
231
+ const configuredProcessKind = process.env.ARDEA_PROCESS_KIND || process.env.CANARY_PROCESS_KIND || "agent";
232
+ const configuredProcessLabel = process.env.ARDEA_PROCESS_LABEL || process.env.CANARY_PROCESS_LABEL || void 0;
233
+ const signals = {
234
+ sdk_instance_id: sdkInstanceId,
235
+ process_id: process.pid,
236
+ thread_id: 0
237
+ };
238
+ if (Number.isFinite(configuredParentProcessId)) {
239
+ signals.parent_process_id = configuredParentProcessId;
240
+ }
241
+ if (configuredProcessKind) {
242
+ signals.process_kind = configuredProcessKind;
243
+ }
244
+ if (configuredProcessLabel) {
245
+ signals.process_label = configuredProcessLabel;
246
+ }
247
+ if (ctx.frameworkSessionId) {
248
+ signals.framework_session_id = ctx.frameworkSessionId;
249
+ }
250
+ const agentInfo = detectAgent();
251
+ if (agentInfo.agent_name) {
252
+ signals.agent_name = agentInfo.agent_name;
253
+ }
254
+ if (agentInfo.agent_version) {
255
+ signals.agent_version = agentInfo.agent_version;
256
+ }
257
+ return signals;
258
+ }
259
+ function getSessionSignals() {
260
+ return {
261
+ ...getBaseSignals(),
262
+ call_sequence: nextCallSequence()
263
+ };
264
+ }
265
+ function setFrameworkSession(sessionId) {
266
+ getMutableContext().frameworkSessionId = sessionId;
267
+ }
268
+ function clearFrameworkSession() {
269
+ getMutableContext().frameworkSessionId = void 0;
270
+ }
271
+ function runWithSession(sessionId, fn) {
272
+ return sessionStorage.run(
273
+ {
274
+ frameworkSessionId: sessionId
275
+ },
276
+ fn
277
+ );
278
+ }
279
+
280
+ // src/voucher.ts
281
+ var _fs = require('fs');
282
+ var _path = require('path');
283
+ var _os = require('os');
284
+ function getRedeemedPath() {
285
+ const dir = _path.join.call(void 0, _os.homedir.call(void 0, ), ".ardea");
286
+ _fs.mkdirSync.call(void 0, dir, { recursive: true });
287
+ return _path.join.call(void 0, dir, "redeemed-vouchers.json");
288
+ }
289
+ function getRedeemedSet() {
290
+ try {
291
+ const data = JSON.parse(_fs.readFileSync.call(void 0, getRedeemedPath(), "utf8"));
292
+ return new Set(Array.isArray(data) ? data : []);
293
+ } catch (e3) {
294
+ return /* @__PURE__ */ new Set();
295
+ }
296
+ }
297
+ function markRedeemed(code) {
298
+ const set = getRedeemedSet();
299
+ set.add(code);
300
+ _fs.writeFileSync.call(void 0, getRedeemedPath(), JSON.stringify([...set]));
301
+ }
302
+ function autoRedeemVoucher(apiKey, endpoint, code) {
303
+ if (!code.startsWith("CVR-")) return;
304
+ const redeemed = getRedeemedSet();
305
+ if (redeemed.has(code)) return;
306
+ fetch(`${endpoint}/v1/vouchers/redeem`, {
307
+ method: "POST",
308
+ headers: {
309
+ "Content-Type": "application/json",
310
+ Authorization: `Bearer ${apiKey}`
311
+ },
312
+ body: JSON.stringify({ code })
313
+ }).then(async (res) => {
314
+ if (res.ok) {
315
+ const data = await res.json();
316
+ markRedeemed(code);
317
+ console.error(
318
+ `[ardea] Voucher redeemed: +${_nullishCoalesce(data.credits, () => ( 0))} ${_nullishCoalesce(data.vendor, () => ( "unknown"))} credits`
319
+ );
320
+ } else if (res.status === 410) {
321
+ markRedeemed(code);
322
+ }
323
+ }).catch(() => {
324
+ });
325
+ }
326
+
327
+ // src/index.ts
328
+ var _initialized = false;
329
+ var _buffer = null;
330
+ function init(config) {
331
+ if (_initialized) return;
332
+ if (!config.apiKey.startsWith("cnry_sk_")) {
333
+ throw new Error(
334
+ "Invalid API key: must start with 'cnry_sk_'. Get your key at https://app.canary.dev"
335
+ );
336
+ }
337
+ const endpoint = _nullishCoalesce(config.endpoint, () => ( _chunkOREJ23CVcjs.DEFAULT_ENDPOINT));
338
+ const autoFlush = _nullishCoalesce(config.autoFlush, () => ( true));
339
+ _buffer = new EventBuffer(config.apiKey, endpoint, autoFlush);
340
+ _initialized = true;
341
+ const voucherCode = process.env.ARDEA_VOUCHER || process.env.CANARY_VOUCHER;
342
+ if (voucherCode) {
343
+ autoRedeemVoucher(config.apiKey, endpoint, voucherCode);
344
+ }
345
+ }
346
+ async function shutdown() {
347
+ if (!_initialized || !_buffer) return;
348
+ await _buffer.shutdown();
349
+ _buffer = null;
350
+ _initialized = false;
351
+ }
352
+ function survey(options) {
353
+ if (!_buffer) {
354
+ throw new Error("Canary SDK not initialized. Call init() first.");
355
+ }
356
+ const signals = getSessionSignals();
357
+ const event = {
358
+ event_type: "feedback",
359
+ source_plane: "declared",
360
+ capture_mode: "declared",
361
+ source: "manual",
362
+ worked: _nullishCoalesce(options.worked, () => ( true)),
363
+ context: _nullishCoalesce(options.context, () => ( "")),
364
+ ts: Date.now() / 1e3,
365
+ agent_name: _nullishCoalesce(signals.agent_name, () => ( void 0)),
366
+ agent_version: _nullishCoalesce(signals.agent_version, () => ( void 0)),
367
+ framework_session_id: _nullishCoalesce(_nullishCoalesce(options.sessionId, () => ( signals.framework_session_id)), () => ( void 0))
368
+ };
369
+ if (options.frictionPoints && options.frictionPoints.length > 0) {
370
+ event.friction_points = options.frictionPoints;
371
+ }
372
+ if (options.provider) {
373
+ event.provider = options.provider;
374
+ }
375
+ _buffer.push(event);
376
+ }
377
+ function getBuffer() {
378
+ return _buffer;
379
+ }
380
+ var Ardea = { init, shutdown, survey, getBuffer };
381
+ var Canary = Ardea;
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+ exports.Ardea = Ardea; exports.Canary = Canary; exports.clearFrameworkSession = clearFrameworkSession; exports.detectAgent = detectAgent; exports.getBuffer = getBuffer; exports.getSessionSignals = getSessionSignals; exports.init = init; exports.resetAgentCache = resetAgentCache; exports.runWithSession = runWithSession; exports.setFrameworkSession = setFrameworkSession; exports.shutdown = shutdown; exports.survey = survey;
396
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/index.cjs","../src/buffer.ts","../src/context.ts","../src/agent-detect.ts","../src/voucher.ts","../src/index.ts"],"names":[],"mappings":"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","file":"/Users/stalapaneni/conductor/workspaces/canary/bucharest/canary/dist/index.cjs","sourcesContent":[null,"/**\n * Event buffer with ring buffer, gzip flush, retry, and session cache.\n *\n * Mirrors sdk/src/ardea/buffer.py.\n */\n\nimport { gzipSync } from \"node:zlib\";\nimport https from \"node:https\";\nimport http from \"node:http\";\nimport type { CanaryEvent } from \"./types.js\";\nimport { DEFAULT_ENDPOINT } from \"./constants.js\";\n\nconst MAX_LEN = 10_000;\nconst BATCH_SIZE = 100;\nconst FLUSH_INTERVAL_MS = 10_000;\nconst MAX_EVENTS_PER_SESSION = 500;\nconst MAX_SESSION_CACHE_EVENTS = 5_000;\nconst RETRY_ATTEMPTS = 3;\nconst RETRY_BACKOFF_MS = [1000, 2000, 4000];\n\nexport class EventBuffer {\n private _buffer: CanaryEvent[] = [];\n private _sessionCache: Map<string, CanaryEvent[]> = new Map();\n private _sessionCacheEventCount = 0;\n private _apiKey: string;\n private _endpoint: string;\n private _timer: ReturnType<typeof setInterval> | null = null;\n private _stopped = false;\n /** Reference to the original (unpatched) https.request for anti-recursion. */\n private _originalHttpsRequest: typeof https.request;\n /** Reference to the original (unpatched) http.request for anti-recursion. */\n private _originalHttpRequest: typeof http.request;\n\n constructor(\n apiKey: string,\n endpoint = DEFAULT_ENDPOINT,\n autoFlush = true,\n originalHttpsRequest?: typeof https.request,\n originalHttpRequest?: typeof http.request\n ) {\n this._apiKey = apiKey;\n this._endpoint = endpoint.replace(/\\/+$/, \"\");\n this._originalHttpsRequest = originalHttpsRequest ?? https.request;\n this._originalHttpRequest = originalHttpRequest ?? http.request;\n\n if (autoFlush) {\n this._timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);\n this._timer.unref();\n }\n }\n\n /** Add an event to the ring buffer. */\n push(event: CanaryEvent): void {\n if (this._buffer.length >= MAX_LEN) {\n this._buffer.shift(); // drop oldest\n }\n this._buffer.push(event);\n\n // Retain in session cache\n const sessionId = (event as unknown as Record<string, unknown>).framework_session_id;\n if (typeof sessionId === \"string\" && sessionId) {\n if (this._sessionCacheEventCount < MAX_SESSION_CACHE_EVENTS) {\n let sessionEvents = this._sessionCache.get(sessionId);\n if (!sessionEvents) {\n sessionEvents = [];\n this._sessionCache.set(sessionId, sessionEvents);\n }\n if (sessionEvents.length < MAX_EVENTS_PER_SESSION) {\n sessionEvents.push(event);\n this._sessionCacheEventCount++;\n }\n }\n }\n }\n\n /** Return a copy of events for the given session from the cache. */\n getSessionEvents(sessionId: string): CanaryEvent[] {\n return [...(this._sessionCache.get(sessionId) ?? [])];\n }\n\n /** Remove session events from the cache. */\n clearSession(sessionId: string): void {\n const events = this._sessionCache.get(sessionId);\n if (events) {\n this._sessionCacheEventCount = Math.max(\n 0,\n this._sessionCacheEventCount - events.length\n );\n this._sessionCache.delete(sessionId);\n }\n }\n\n /** Remove and return up to `count` events from the buffer. */\n drain(count: number): CanaryEvent[] {\n const n = Math.min(count, this._buffer.length);\n return this._buffer.splice(0, n);\n }\n\n /** Get all buffered events (without draining). For reporter access. */\n peek(): CanaryEvent[] {\n return [...this._buffer];\n }\n\n /** Number of buffered events. */\n get length(): number {\n return this._buffer.length;\n }\n\n /** Drain a batch and send to backend. */\n async flush(): Promise<void> {\n const events = this.drain(BATCH_SIZE);\n if (events.length === 0) return;\n\n for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {\n try {\n await this._send(events);\n return;\n } catch {\n if (attempt < RETRY_ATTEMPTS - 1) {\n await sleep(RETRY_BACKOFF_MS[attempt]);\n }\n }\n }\n // All retries failed — events are lost (no disk persistence in Node SDK v1)\n }\n\n /** Stop flush timer and drain all remaining events. */\n async shutdown(): Promise<void> {\n this._stopped = true;\n if (this._timer) {\n clearInterval(this._timer);\n this._timer = null;\n }\n\n while (this._buffer.length > 0) {\n const events = this.drain(BATCH_SIZE);\n if (events.length === 0) break;\n\n for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {\n try {\n await this._send(events);\n break;\n } catch {\n if (attempt === RETRY_ATTEMPTS - 1) {\n // Final failure — events lost\n } else {\n await sleep(RETRY_BACKOFF_MS[attempt]);\n }\n }\n }\n }\n }\n\n /** POST events to the backend with gzip compression. */\n private _send(events: CanaryEvent[]): Promise<void> {\n const payload = JSON.stringify({\n events,\n sdk_version: \"0.1.0\",\n });\n const compressed = gzipSync(Buffer.from(payload, \"utf-8\"));\n\n const url = new URL(`${this._endpoint}/v1/events`);\n const isHttps = url.protocol === \"https:\";\n const requestFn = isHttps ? this._originalHttpsRequest : this._originalHttpRequest;\n\n return new Promise<void>((resolve, reject) => {\n const req = requestFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Encoding\": \"gzip\",\n Authorization: `Bearer ${this._apiKey}`,\n \"Content-Length\": compressed.length,\n },\n timeout: 5000,\n },\n (res) => {\n // Consume the response\n res.resume();\n if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {\n resolve();\n } else {\n reject(new Error(`HTTP ${res.statusCode}`));\n }\n }\n );\n\n req.on(\"error\", reject);\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request timeout\"));\n });\n req.write(compressed);\n req.end();\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Session correlation via AsyncLocalStorage.\n *\n * Node.js equivalent of sdk/src/canary/context.py using\n * AsyncLocalStorage instead of ContextVar.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomUUID } from \"node:crypto\";\nimport { detectAgent } from \"./agent-detect.js\";\n\ninterface SessionContext {\n frameworkSessionId?: string;\n}\n\nconst sessionStorage = new AsyncLocalStorage<SessionContext>();\nconst sdkInstanceId = randomUUID().replace(/-/g, \"\");\nlet callSequence = 0;\nconst defaultContext: SessionContext = {\n frameworkSessionId: undefined,\n};\n\nfunction getMutableContext(): SessionContext {\n return sessionStorage.getStore() ?? defaultContext;\n}\n\nfunction nextCallSequence(): number {\n callSequence += 1;\n return callSequence;\n}\n\nexport interface SessionSignals {\n sdk_instance_id: string;\n process_id: number;\n parent_process_id?: number;\n process_kind?: string;\n process_label?: string;\n thread_id: number;\n call_sequence: number;\n framework_session_id?: string;\n agent_name?: string;\n agent_version?: string;\n}\n\nfunction getBaseSignals(): Omit<SessionSignals, \"call_sequence\"> {\n const ctx = getMutableContext();\n const configuredParentProcessId = Number.parseInt(\n process.env.ARDEA_PARENT_PROCESS_ID || process.env.CANARY_PARENT_PROCESS_ID || \"\",\n 10\n );\n const configuredProcessKind = process.env.ARDEA_PROCESS_KIND || process.env.CANARY_PROCESS_KIND || \"agent\";\n const configuredProcessLabel = process.env.ARDEA_PROCESS_LABEL || process.env.CANARY_PROCESS_LABEL || undefined;\n const signals: Omit<SessionSignals, \"call_sequence\"> = {\n sdk_instance_id: sdkInstanceId,\n process_id: process.pid,\n thread_id: 0,\n };\n\n if (Number.isFinite(configuredParentProcessId)) {\n signals.parent_process_id = configuredParentProcessId;\n }\n if (configuredProcessKind) {\n signals.process_kind = configuredProcessKind;\n }\n if (configuredProcessLabel) {\n signals.process_label = configuredProcessLabel;\n }\n\n if (ctx.frameworkSessionId) {\n signals.framework_session_id = ctx.frameworkSessionId;\n }\n\n const agentInfo = detectAgent();\n if (agentInfo.agent_name) {\n signals.agent_name = agentInfo.agent_name;\n }\n if (agentInfo.agent_version) {\n signals.agent_version = agentInfo.agent_version;\n }\n\n return signals;\n}\n\n/**\n * Return session correlation signals for the current execution context.\n */\nexport function getSessionSignals(): SessionSignals {\n return {\n ...getBaseSignals(),\n call_sequence: nextCallSequence(),\n };\n}\n\n/**\n * Set the framework session ID for the current async context.\n *\n * If no async context is active, this becomes the default session ID used\n * by subsequent feedback/reporting calls on this process.\n */\nexport function setFrameworkSession(sessionId: string): void {\n getMutableContext().frameworkSessionId = sessionId;\n}\n\n/**\n * Clear the framework session ID for the current async context.\n */\nexport function clearFrameworkSession(): void {\n getMutableContext().frameworkSessionId = undefined;\n}\n\n/**\n * Run a function with a scoped framework session ID.\n *\n * The session ID propagates through async call chains automatically.\n */\nexport function runWithSession<T>(sessionId: string, fn: () => T): T {\n return sessionStorage.run(\n {\n frameworkSessionId: sessionId,\n },\n fn\n );\n}\n\n/**\n * Reset all context state. For testing only.\n */\nexport function resetContext(): void {\n callSequence = 0;\n defaultContext.frameworkSessionId = undefined;\n}\n","/**\n * Detect which AI agent framework is running via environment variables.\n */\n\nexport interface AgentInfo {\n agent_name: string | null;\n agent_version: string | null;\n}\n\nexport const AGENT_ENV_MAP: ReadonlyArray<{ envKey: string; name: string; versionKey?: string; useValue?: boolean }> = [\n { envKey: \"ARDEA_AGENT_NAME\", name: \"\", useValue: true },\n { envKey: \"CANARY_AGENT_NAME\", name: \"\", useValue: true },\n { envKey: \"CLAUDE_CODE\", name: \"claude_code\" },\n { envKey: \"CURSOR_TRACE_ID\", name: \"cursor\" },\n { envKey: \"WINDSURF_SESSION_ID\", name: \"windsurf\" },\n { envKey: \"CLINE_TASK_ID\", name: \"cline\" },\n { envKey: \"AIDER_VERSION\", name: \"aider\", versionKey: \"AIDER_VERSION\" },\n { envKey: \"DEVIN_SESSION_ID\", name: \"devin\" },\n { envKey: \"CODEX_CLI\", name: \"codex\" },\n { envKey: \"GITHUB_COPILOT\", name: \"github_copilot\" },\n { envKey: \"CODY_SESSION_ID\", name: \"sourcegraph_cody\" },\n];\n\nlet cachedInfo: AgentInfo | null = null;\n\nexport function detectAgent(): AgentInfo {\n if (cachedInfo) return cachedInfo;\n\n for (const entry of AGENT_ENV_MAP) {\n const val = process.env[entry.envKey];\n // Check !== undefined so empty strings (presence flags) still detect\n if (val !== undefined) {\n cachedInfo = {\n agent_name: entry.useValue ? (val || null) : entry.name,\n agent_version: entry.versionKey ? (process.env[entry.versionKey] ?? null) : null,\n };\n if (cachedInfo.agent_name) return cachedInfo;\n }\n }\n\n cachedInfo = { agent_name: null, agent_version: null };\n return cachedInfo;\n}\n\nexport function resetAgentCache(): void {\n cachedInfo = null;\n}\n","/**\n * Auto-redeem a voucher code on SDK init.\n * Non-blocking — fires in the background, never delays SDK startup.\n * Tracks redeemed codes in ~/.ardea/redeemed-vouchers.json.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\ntype VoucherRedeemResponse = {\n credits?: number;\n vendor?: string;\n};\n\nfunction getRedeemedPath(): string {\n const dir = join(homedir(), \".ardea\");\n mkdirSync(dir, { recursive: true });\n return join(dir, \"redeemed-vouchers.json\");\n}\n\nfunction getRedeemedSet(): Set<string> {\n try {\n const data = JSON.parse(readFileSync(getRedeemedPath(), \"utf8\"));\n return new Set(Array.isArray(data) ? data : []);\n } catch {\n return new Set();\n }\n}\n\nfunction markRedeemed(code: string): void {\n const set = getRedeemedSet();\n set.add(code);\n writeFileSync(getRedeemedPath(), JSON.stringify([...set]));\n}\n\nexport function autoRedeemVoucher(\n apiKey: string,\n endpoint: string,\n code: string,\n): void {\n if (!code.startsWith(\"CVR-\")) return;\n\n const redeemed = getRedeemedSet();\n if (redeemed.has(code)) return;\n\n // Fire-and-forget — don't block SDK init\n fetch(`${endpoint}/v1/vouchers/redeem`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ code }),\n })\n .then(async (res) => {\n if (res.ok) {\n const data = (await res.json()) as VoucherRedeemResponse;\n markRedeemed(code);\n // Log to stderr to avoid corrupting any MCP stdio\n console.error(\n `[ardea] Voucher redeemed: +${data.credits ?? 0} ${data.vendor ?? \"unknown\"} credits`,\n );\n } else if (res.status === 410) {\n // Already redeemed, expired, or revoked — mark locally to avoid retries\n markRedeemed(code);\n }\n })\n .catch(() => {\n // Silently fail — SDK init must not be affected\n });\n}\n","/**\n * ardea — MCP tools for AI agents to report API experiences.\n */\n\nimport { EventBuffer } from \"./buffer.js\";\nimport {\n runWithSession,\n setFrameworkSession,\n clearFrameworkSession,\n getSessionSignals,\n} from \"./context.js\";\nimport type { CanaryConfig, CanaryEvent, FeedbackEvent } from \"./types.js\";\nimport { DEFAULT_ENDPOINT } from \"./constants.js\";\nimport { autoRedeemVoucher } from \"./voucher.js\";\n\nlet _initialized = false;\nlet _buffer: EventBuffer | null = null;\n\n/**\n * Initialize Canary SDK for manual feedback submission.\n */\nexport function init(config: CanaryConfig): void {\n if (_initialized) return;\n\n if (!config.apiKey.startsWith(\"cnry_sk_\")) {\n throw new Error(\n \"Invalid API key: must start with 'cnry_sk_'. \" +\n \"Get your key at https://app.canary.dev\"\n );\n }\n\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n const autoFlush = config.autoFlush ?? true;\n\n _buffer = new EventBuffer(config.apiKey, endpoint, autoFlush);\n _initialized = true;\n\n // Auto-redeem voucher if ARDEA_VOUCHER or CANARY_VOUCHER env var is set (non-blocking)\n const voucherCode = process.env.ARDEA_VOUCHER || process.env.CANARY_VOUCHER;\n if (voucherCode) {\n autoRedeemVoucher(config.apiKey, endpoint, voucherCode);\n }\n}\n\n/**\n * Shut down the SDK and flush any queued feedback events.\n */\nexport async function shutdown(): Promise<void> {\n if (!_initialized || !_buffer) return;\n\n await _buffer.shutdown();\n _buffer = null;\n _initialized = false;\n}\n\n/**\n * Submit manual feedback.\n */\nexport function survey(options: {\n worked?: boolean;\n context?: string;\n frictionPoints?: string[];\n provider?: string;\n sessionId?: string;\n}): void {\n if (!_buffer) {\n throw new Error(\"Canary SDK not initialized. Call init() first.\");\n }\n\n // Pick up session context from AsyncLocalStorage (set by runWithSession)\n const signals = getSessionSignals();\n const event: FeedbackEvent = {\n event_type: \"feedback\",\n source_plane: \"declared\",\n capture_mode: \"declared\",\n source: \"manual\",\n worked: options.worked ?? true,\n context: options.context ?? \"\",\n ts: Date.now() / 1000,\n agent_name: signals.agent_name ?? undefined,\n agent_version: signals.agent_version ?? undefined,\n framework_session_id: options.sessionId ?? signals.framework_session_id ?? undefined,\n };\n\n if (options.frictionPoints && options.frictionPoints.length > 0) {\n event.friction_points = options.frictionPoints;\n }\n if (options.provider) {\n event.provider = options.provider;\n }\n\n _buffer.push(event);\n}\n\n/**\n * Get the global EventBuffer (for advanced usage / adapters).\n */\nexport function getBuffer(): EventBuffer | null {\n return _buffer;\n}\n\n// Re-export session helpers\nexport {\n runWithSession,\n setFrameworkSession,\n clearFrameworkSession,\n getSessionSignals,\n};\n\n// Re-export types\nexport type { ArdeaConfig, CanaryConfig, CanaryEvent, FeedbackEvent } from \"./types.js\";\nexport { detectAgent, resetAgentCache } from \"./agent-detect.js\";\n\n/** Alias for init — Ardea rebrand entry point. */\nexport const Ardea = { init, shutdown, survey, getBuffer };\n\n/** @deprecated Use Ardea instead. */\nexport const Canary = Ardea;\n"]}