@analytix402/openclaw-skill 0.1.0 → 0.1.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/Skill/SKILL.md ADDED
@@ -0,0 +1,47 @@
1
+ # Analytix402
2
+
3
+ Monitor, control, and optimize your AI agent's API spend and LLM costs in real-time.
4
+
5
+ ## Description
6
+
7
+ Analytix402 gives your OpenClaw agent financial visibility and guardrails. Track every API call, LLM invocation, and x402 payment your agent makes. Set budget limits, detect duplicate purchases, and get alerts before costs spiral.
8
+
9
+ **What it does:**
10
+ - Tracks all outbound API calls and x402 payments automatically
11
+ - Monitors LLM token usage and costs across OpenAI, Anthropic, and other providers
12
+ - Enforces daily budget limits and per-call spend caps
13
+ - Detects duplicate API purchases to prevent waste
14
+ - Sends heartbeats so you know your agent is alive and healthy
15
+ - Provides a real-time dashboard at analytix402.com
16
+
17
+ ## Configuration
18
+
19
+ ```yaml
20
+ # Required
21
+ ANALYTIX402_API_KEY: ax_live_your_key_here
22
+
23
+ # Optional
24
+ ANALYTIX402_AGENT_ID: my-openclaw-agent
25
+ ANALYTIX402_BASE_URL: https://analytix402.com
26
+ ANALYTIX402_DAILY_BUDGET: 50.00
27
+ ANALYTIX402_PER_CALL_LIMIT: 5.00
28
+ ANALYTIX402_TRACK_LLM: true
29
+ ```
30
+
31
+ ## Tools
32
+
33
+ ### analytix402_spend_report
34
+ Get a summary of your agent's spend — total cost, breakdown by API and LLM provider, and efficiency score.
35
+
36
+ ### analytix402_set_budget
37
+ Set or update the daily budget limit for this agent session.
38
+
39
+ ### analytix402_check_budget
40
+ Check remaining budget before making an expensive API call.
41
+
42
+ ### analytix402_flag_purchase
43
+ Flag a potential duplicate or unnecessary purchase for review.
44
+
45
+ ## Tags
46
+
47
+ monitoring, analytics, budget, x402, payments, observability, cost-tracking
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -29,242 +30,7 @@ __export(index_exports, {
29
30
  trackLLMCall: () => trackLLMCall
30
31
  });
31
32
  module.exports = __toCommonJS(index_exports);
32
-
33
- // ../sdk/src/client.ts
34
- var SDK_NAME = "@analytix402/sdk";
35
- var SDK_VERSION = "0.1.1";
36
- var DEFAULT_CONFIG = {
37
- baseUrl: "https://analytix402.com",
38
- debug: false,
39
- batchSize: 100,
40
- flushInterval: 5e3,
41
- maxRetries: 3,
42
- maxQueueSize: 1e3,
43
- timeout: 1e4,
44
- excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"]
45
- };
46
- function createClient(config) {
47
- if (!config.apiKey) {
48
- throw new Error("Analytix402: apiKey is required");
49
- }
50
- if (!config.apiKey.startsWith("ax_live_") && !config.apiKey.startsWith("ax_test_")) {
51
- console.warn("Analytix402: API key should start with ax_live_ or ax_test_");
52
- }
53
- const cfg = {
54
- ...DEFAULT_CONFIG,
55
- ...config
56
- };
57
- const agentId = cfg.agentId;
58
- const queue = [];
59
- let flushTimer = null;
60
- let isFlushing = false;
61
- let isShutdown = false;
62
- const log = (...args) => {
63
- if (cfg.debug) {
64
- console.log("[Analytix402]", ...args);
65
- }
66
- };
67
- const warn = (...args) => {
68
- console.warn("[Analytix402]", ...args);
69
- };
70
- function enqueue(event) {
71
- if (isShutdown) {
72
- warn("Client is shutdown, event dropped");
73
- return;
74
- }
75
- if (queue.length >= cfg.maxQueueSize) {
76
- warn(`Queue full (${cfg.maxQueueSize}), dropping oldest event`);
77
- queue.shift();
78
- }
79
- queue.push({
80
- event,
81
- attempts: 0,
82
- addedAt: Date.now()
83
- });
84
- log(`Event queued (${queue.length} in queue)`);
85
- if (queue.length >= cfg.batchSize) {
86
- log("Batch size reached, flushing");
87
- flush();
88
- } else if (!flushTimer) {
89
- flushTimer = setTimeout(() => {
90
- flushTimer = null;
91
- flush();
92
- }, cfg.flushInterval);
93
- }
94
- }
95
- function track(event) {
96
- if (agentId && !event.agentId) {
97
- event.agentId = agentId;
98
- }
99
- enqueue(event);
100
- }
101
- function heartbeat(status = "healthy", metadata) {
102
- if (!agentId) {
103
- warn("heartbeat() requires agentId in config");
104
- return;
105
- }
106
- const event = {
107
- type: "heartbeat",
108
- agentId,
109
- status,
110
- metadata,
111
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
112
- };
113
- enqueue(event);
114
- }
115
- function reportOutcome(taskId, success, options) {
116
- const event = {
117
- type: "task_outcome",
118
- agentId,
119
- taskId,
120
- success,
121
- durationMs: options == null ? void 0 : options.durationMs,
122
- cost: options == null ? void 0 : options.cost,
123
- metadata: options == null ? void 0 : options.metadata,
124
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
125
- };
126
- enqueue(event);
127
- }
128
- function startTask(taskId) {
129
- const id = taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
130
- const startTime = Date.now();
131
- return {
132
- taskId: id,
133
- end(success, metadata) {
134
- const durationMs = Date.now() - startTime;
135
- reportOutcome(id, success, { durationMs, metadata });
136
- }
137
- };
138
- }
139
- function trackLLM(usage) {
140
- const event = {
141
- type: "llm_usage",
142
- agentId,
143
- taskId: usage.taskId,
144
- model: usage.model,
145
- provider: usage.provider,
146
- inputTokens: usage.inputTokens,
147
- outputTokens: usage.outputTokens,
148
- totalTokens: usage.inputTokens + usage.outputTokens,
149
- costUsd: usage.costUsd,
150
- durationMs: usage.durationMs,
151
- metadata: usage.metadata,
152
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
153
- };
154
- enqueue(event);
155
- }
156
- async function sendBatch(events) {
157
- if (events.length === 0) return true;
158
- const payload = {
159
- events,
160
- sdk: {
161
- name: SDK_NAME,
162
- version: SDK_VERSION
163
- },
164
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
165
- };
166
- try {
167
- const controller = new AbortController();
168
- const timeoutId = setTimeout(() => controller.abort(), cfg.timeout);
169
- const response = await fetch(`${cfg.baseUrl}/api/ingest/batch`, {
170
- method: "POST",
171
- headers: {
172
- "Content-Type": "application/json",
173
- "X-API-Key": cfg.apiKey,
174
- "User-Agent": `${SDK_NAME}/${SDK_VERSION}`
175
- },
176
- body: JSON.stringify(payload),
177
- signal: controller.signal
178
- });
179
- clearTimeout(timeoutId);
180
- if (!response.ok) {
181
- const text = await response.text().catch(() => "Unknown error");
182
- throw new Error(`HTTP ${response.status}: ${text}`);
183
- }
184
- log(`Sent ${events.length} events successfully`);
185
- return true;
186
- } catch (error) {
187
- if (error instanceof Error && error.name === "AbortError") {
188
- warn("Request timed out");
189
- } else {
190
- warn("Failed to send events:", error);
191
- }
192
- return false;
193
- }
194
- }
195
- async function flush() {
196
- if (isFlushing || queue.length === 0) {
197
- return;
198
- }
199
- isFlushing = true;
200
- if (flushTimer) {
201
- clearTimeout(flushTimer);
202
- flushTimer = null;
203
- }
204
- const batch = queue.splice(0, cfg.batchSize);
205
- const events = batch.map((q) => q.event);
206
- log(`Flushing ${events.length} events`);
207
- const success = await sendBatch(events);
208
- if (!success) {
209
- const retriable = batch.filter((q) => q.attempts < cfg.maxRetries);
210
- if (retriable.length > 0) {
211
- log(`Re-queuing ${retriable.length} events for retry`);
212
- for (const item of retriable.reverse()) {
213
- item.attempts++;
214
- queue.unshift(item);
215
- }
216
- const backoff = Math.min(1e3 * Math.pow(2, retriable[0].attempts), 3e4);
217
- log(`Retry scheduled in ${backoff}ms`);
218
- flushTimer = setTimeout(() => {
219
- flushTimer = null;
220
- flush();
221
- }, backoff);
222
- } else {
223
- warn(`Dropped ${batch.length} events after ${cfg.maxRetries} retries`);
224
- }
225
- }
226
- isFlushing = false;
227
- if (queue.length > 0 && !flushTimer) {
228
- flushTimer = setTimeout(() => {
229
- flushTimer = null;
230
- flush();
231
- }, cfg.flushInterval);
232
- }
233
- }
234
- async function shutdown2() {
235
- log("Shutting down...");
236
- isShutdown = true;
237
- if (flushTimer) {
238
- clearTimeout(flushTimer);
239
- flushTimer = null;
240
- }
241
- if (queue.length > 0) {
242
- log(`Flushing ${queue.length} remaining events`);
243
- isFlushing = false;
244
- await flush();
245
- }
246
- log("Shutdown complete");
247
- }
248
- if (typeof process !== "undefined") {
249
- const handleExit = () => {
250
- shutdown2().catch(console.error);
251
- };
252
- process.on("beforeExit", handleExit);
253
- process.on("SIGINT", handleExit);
254
- process.on("SIGTERM", handleExit);
255
- }
256
- return {
257
- track,
258
- flush,
259
- shutdown: shutdown2,
260
- heartbeat,
261
- reportOutcome,
262
- startTask,
263
- trackLLM
264
- };
265
- }
266
-
267
- // src/index.ts
33
+ var import_sdk = require("@analytix402/sdk");
268
34
  var API_KEY = process.env.ANALYTIX402_API_KEY || "";
269
35
  var BASE_URL = (process.env.ANALYTIX402_BASE_URL || "https://analytix402.com").replace(/\/$/, "");
270
36
  var AGENT_ID = process.env.ANALYTIX402_AGENT_ID || `openclaw-${Date.now()}`;
@@ -284,7 +50,7 @@ function getClient() {
284
50
  "ANALYTIX402_API_KEY is not set. Add it to your OpenClaw skill config."
285
51
  );
286
52
  }
287
- client = createClient({
53
+ client = (0, import_sdk.createClient)({
288
54
  apiKey: API_KEY,
289
55
  baseUrl: BASE_URL,
290
56
  agentId: AGENT_ID,
package/dist/index.mjs CHANGED
@@ -1,238 +1,5 @@
1
- // ../sdk/src/client.ts
2
- var SDK_NAME = "@analytix402/sdk";
3
- var SDK_VERSION = "0.1.1";
4
- var DEFAULT_CONFIG = {
5
- baseUrl: "https://analytix402.com",
6
- debug: false,
7
- batchSize: 100,
8
- flushInterval: 5e3,
9
- maxRetries: 3,
10
- maxQueueSize: 1e3,
11
- timeout: 1e4,
12
- excludePaths: ["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"]
13
- };
14
- function createClient(config) {
15
- if (!config.apiKey) {
16
- throw new Error("Analytix402: apiKey is required");
17
- }
18
- if (!config.apiKey.startsWith("ax_live_") && !config.apiKey.startsWith("ax_test_")) {
19
- console.warn("Analytix402: API key should start with ax_live_ or ax_test_");
20
- }
21
- const cfg = {
22
- ...DEFAULT_CONFIG,
23
- ...config
24
- };
25
- const agentId = cfg.agentId;
26
- const queue = [];
27
- let flushTimer = null;
28
- let isFlushing = false;
29
- let isShutdown = false;
30
- const log = (...args) => {
31
- if (cfg.debug) {
32
- console.log("[Analytix402]", ...args);
33
- }
34
- };
35
- const warn = (...args) => {
36
- console.warn("[Analytix402]", ...args);
37
- };
38
- function enqueue(event) {
39
- if (isShutdown) {
40
- warn("Client is shutdown, event dropped");
41
- return;
42
- }
43
- if (queue.length >= cfg.maxQueueSize) {
44
- warn(`Queue full (${cfg.maxQueueSize}), dropping oldest event`);
45
- queue.shift();
46
- }
47
- queue.push({
48
- event,
49
- attempts: 0,
50
- addedAt: Date.now()
51
- });
52
- log(`Event queued (${queue.length} in queue)`);
53
- if (queue.length >= cfg.batchSize) {
54
- log("Batch size reached, flushing");
55
- flush();
56
- } else if (!flushTimer) {
57
- flushTimer = setTimeout(() => {
58
- flushTimer = null;
59
- flush();
60
- }, cfg.flushInterval);
61
- }
62
- }
63
- function track(event) {
64
- if (agentId && !event.agentId) {
65
- event.agentId = agentId;
66
- }
67
- enqueue(event);
68
- }
69
- function heartbeat(status = "healthy", metadata) {
70
- if (!agentId) {
71
- warn("heartbeat() requires agentId in config");
72
- return;
73
- }
74
- const event = {
75
- type: "heartbeat",
76
- agentId,
77
- status,
78
- metadata,
79
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
80
- };
81
- enqueue(event);
82
- }
83
- function reportOutcome(taskId, success, options) {
84
- const event = {
85
- type: "task_outcome",
86
- agentId,
87
- taskId,
88
- success,
89
- durationMs: options == null ? void 0 : options.durationMs,
90
- cost: options == null ? void 0 : options.cost,
91
- metadata: options == null ? void 0 : options.metadata,
92
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
93
- };
94
- enqueue(event);
95
- }
96
- function startTask(taskId) {
97
- const id = taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
98
- const startTime = Date.now();
99
- return {
100
- taskId: id,
101
- end(success, metadata) {
102
- const durationMs = Date.now() - startTime;
103
- reportOutcome(id, success, { durationMs, metadata });
104
- }
105
- };
106
- }
107
- function trackLLM(usage) {
108
- const event = {
109
- type: "llm_usage",
110
- agentId,
111
- taskId: usage.taskId,
112
- model: usage.model,
113
- provider: usage.provider,
114
- inputTokens: usage.inputTokens,
115
- outputTokens: usage.outputTokens,
116
- totalTokens: usage.inputTokens + usage.outputTokens,
117
- costUsd: usage.costUsd,
118
- durationMs: usage.durationMs,
119
- metadata: usage.metadata,
120
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
121
- };
122
- enqueue(event);
123
- }
124
- async function sendBatch(events) {
125
- if (events.length === 0) return true;
126
- const payload = {
127
- events,
128
- sdk: {
129
- name: SDK_NAME,
130
- version: SDK_VERSION
131
- },
132
- sentAt: (/* @__PURE__ */ new Date()).toISOString()
133
- };
134
- try {
135
- const controller = new AbortController();
136
- const timeoutId = setTimeout(() => controller.abort(), cfg.timeout);
137
- const response = await fetch(`${cfg.baseUrl}/api/ingest/batch`, {
138
- method: "POST",
139
- headers: {
140
- "Content-Type": "application/json",
141
- "X-API-Key": cfg.apiKey,
142
- "User-Agent": `${SDK_NAME}/${SDK_VERSION}`
143
- },
144
- body: JSON.stringify(payload),
145
- signal: controller.signal
146
- });
147
- clearTimeout(timeoutId);
148
- if (!response.ok) {
149
- const text = await response.text().catch(() => "Unknown error");
150
- throw new Error(`HTTP ${response.status}: ${text}`);
151
- }
152
- log(`Sent ${events.length} events successfully`);
153
- return true;
154
- } catch (error) {
155
- if (error instanceof Error && error.name === "AbortError") {
156
- warn("Request timed out");
157
- } else {
158
- warn("Failed to send events:", error);
159
- }
160
- return false;
161
- }
162
- }
163
- async function flush() {
164
- if (isFlushing || queue.length === 0) {
165
- return;
166
- }
167
- isFlushing = true;
168
- if (flushTimer) {
169
- clearTimeout(flushTimer);
170
- flushTimer = null;
171
- }
172
- const batch = queue.splice(0, cfg.batchSize);
173
- const events = batch.map((q) => q.event);
174
- log(`Flushing ${events.length} events`);
175
- const success = await sendBatch(events);
176
- if (!success) {
177
- const retriable = batch.filter((q) => q.attempts < cfg.maxRetries);
178
- if (retriable.length > 0) {
179
- log(`Re-queuing ${retriable.length} events for retry`);
180
- for (const item of retriable.reverse()) {
181
- item.attempts++;
182
- queue.unshift(item);
183
- }
184
- const backoff = Math.min(1e3 * Math.pow(2, retriable[0].attempts), 3e4);
185
- log(`Retry scheduled in ${backoff}ms`);
186
- flushTimer = setTimeout(() => {
187
- flushTimer = null;
188
- flush();
189
- }, backoff);
190
- } else {
191
- warn(`Dropped ${batch.length} events after ${cfg.maxRetries} retries`);
192
- }
193
- }
194
- isFlushing = false;
195
- if (queue.length > 0 && !flushTimer) {
196
- flushTimer = setTimeout(() => {
197
- flushTimer = null;
198
- flush();
199
- }, cfg.flushInterval);
200
- }
201
- }
202
- async function shutdown2() {
203
- log("Shutting down...");
204
- isShutdown = true;
205
- if (flushTimer) {
206
- clearTimeout(flushTimer);
207
- flushTimer = null;
208
- }
209
- if (queue.length > 0) {
210
- log(`Flushing ${queue.length} remaining events`);
211
- isFlushing = false;
212
- await flush();
213
- }
214
- log("Shutdown complete");
215
- }
216
- if (typeof process !== "undefined") {
217
- const handleExit = () => {
218
- shutdown2().catch(console.error);
219
- };
220
- process.on("beforeExit", handleExit);
221
- process.on("SIGINT", handleExit);
222
- process.on("SIGTERM", handleExit);
223
- }
224
- return {
225
- track,
226
- flush,
227
- shutdown: shutdown2,
228
- heartbeat,
229
- reportOutcome,
230
- startTask,
231
- trackLLM
232
- };
233
- }
234
-
235
1
  // src/index.ts
2
+ import { createClient } from "@analytix402/sdk";
236
3
  var API_KEY = process.env.ANALYTIX402_API_KEY || "";
237
4
  var BASE_URL = (process.env.ANALYTIX402_BASE_URL || "https://analytix402.com").replace(/\/$/, "");
238
5
  var AGENT_ID = process.env.ANALYTIX402_AGENT_ID || `openclaw-${Date.now()}`;
package/package.json CHANGED
@@ -1,16 +1,80 @@
1
1
  {
2
2
  "name": "@analytix402/openclaw-skill",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Analytix402 skill for OpenClaw — monitor, control, and optimize your AI agent's spend",
5
5
  "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
6
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
7
15
  "scripts": {
8
16
  "build": "tsup src/index.ts --format cjs,esm --dts",
9
17
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
10
- "typecheck": "tsc --noEmit"
18
+ "typecheck": "tsc --noEmit",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "openclaw": {
22
+ "tools": {
23
+ "analytix402_spend_report": {
24
+ "description": "Get a summary of your agent's spend — total cost, breakdown by API and LLM provider, and efficiency score.",
25
+ "parameters": {}
26
+ },
27
+ "analytix402_set_budget": {
28
+ "description": "Set or update the daily budget limit for this agent session.",
29
+ "parameters": {
30
+ "daily_limit": {
31
+ "type": "number",
32
+ "description": "Maximum daily spend in USD (0 = unlimited)"
33
+ },
34
+ "per_call_limit": {
35
+ "type": "number",
36
+ "description": "Maximum spend per single API call in USD (0 = unlimited)"
37
+ }
38
+ }
39
+ },
40
+ "analytix402_check_budget": {
41
+ "description": "Check remaining budget before making an expensive API call.",
42
+ "parameters": {
43
+ "estimated_cost": {
44
+ "type": "number",
45
+ "description": "The estimated cost in USD for the upcoming API call"
46
+ }
47
+ }
48
+ },
49
+ "analytix402_flag_purchase": {
50
+ "description": "Flag a potential duplicate or unnecessary purchase for review.",
51
+ "parameters": {
52
+ "url": {
53
+ "type": "string",
54
+ "description": "The URL of the API call to flag",
55
+ "required": true
56
+ },
57
+ "reason": {
58
+ "type": "string",
59
+ "description": "Why this purchase is being flagged"
60
+ },
61
+ "estimated_cost": {
62
+ "type": "number",
63
+ "description": "Estimated cost of the flagged purchase in USD"
64
+ }
65
+ }
66
+ }
67
+ },
68
+ "hooks": [
69
+ "trackLLMCall",
70
+ "trackAPICall",
71
+ "sendHeartbeat",
72
+ "shutdown"
73
+ ]
11
74
  },
12
75
  "keywords": [
13
76
  "openclaw",
77
+ "openclaw-skill",
14
78
  "analytix402",
15
79
  "x402",
16
80
  "ai-agent",
package/src/index.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  * real-time visibility into what their agents are spending.
9
9
  */
10
10
 
11
- import { createClient } from '../../sdk/src/client';
12
- import type { Analytix402Client } from '../../sdk/src/types';
11
+ import { createClient } from '@analytix402/sdk';
12
+ import type { Analytix402Client } from '@analytix402/sdk';
13
13
 
14
14
  // ============================================================
15
15
  // Configuration
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "nodenext",
6
+ "lib": ["ES2020", "DOM"],
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist"]
21
+ }