@analytix402/openclaw-skill 0.1.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/SKILL.md +47 -0
- package/dist/index.d.mts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +480 -0
- package/dist/index.mjs +447 -0
- package/package.json +35 -0
- package/src/index.ts +335 -0
package/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.d.mts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytix402 OpenClaw Skill
|
|
3
|
+
*
|
|
4
|
+
* Provides AI agent spend tracking, budget enforcement,
|
|
5
|
+
* and observability for OpenClaw agents.
|
|
6
|
+
*
|
|
7
|
+
* Integrates with the Analytix402 platform to give operators
|
|
8
|
+
* real-time visibility into what their agents are spending.
|
|
9
|
+
*/
|
|
10
|
+
declare function analytix402_spend_report(): Promise<string>;
|
|
11
|
+
interface SetBudgetParams {
|
|
12
|
+
daily_limit?: number;
|
|
13
|
+
per_call_limit?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function analytix402_set_budget(params: SetBudgetParams): string;
|
|
16
|
+
interface CheckBudgetParams {
|
|
17
|
+
estimated_cost?: number;
|
|
18
|
+
}
|
|
19
|
+
declare function analytix402_check_budget(params: CheckBudgetParams): string;
|
|
20
|
+
interface FlagPurchaseParams {
|
|
21
|
+
url: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
estimated_cost?: number;
|
|
24
|
+
}
|
|
25
|
+
declare function analytix402_flag_purchase(params: FlagPurchaseParams): string;
|
|
26
|
+
/**
|
|
27
|
+
* Call this after any LLM invocation to track usage.
|
|
28
|
+
* OpenClaw skills can hook into the agent lifecycle.
|
|
29
|
+
*/
|
|
30
|
+
declare function trackLLMCall(params: {
|
|
31
|
+
model: string;
|
|
32
|
+
provider: string;
|
|
33
|
+
inputTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
costUsd?: number;
|
|
36
|
+
durationMs?: number;
|
|
37
|
+
taskId?: string;
|
|
38
|
+
}): void;
|
|
39
|
+
/**
|
|
40
|
+
* Call this after any API call to track spend.
|
|
41
|
+
*/
|
|
42
|
+
declare function trackAPICall(params: {
|
|
43
|
+
method: string;
|
|
44
|
+
url: string;
|
|
45
|
+
statusCode: number;
|
|
46
|
+
responseTimeMs: number;
|
|
47
|
+
paymentAmount?: number;
|
|
48
|
+
paymentCurrency?: string;
|
|
49
|
+
txHash?: string;
|
|
50
|
+
}): void;
|
|
51
|
+
/**
|
|
52
|
+
* Send a heartbeat — call periodically to show agent is alive.
|
|
53
|
+
*/
|
|
54
|
+
declare function sendHeartbeat(status?: 'healthy' | 'degraded' | 'error'): void;
|
|
55
|
+
/**
|
|
56
|
+
* Gracefully shutdown — flush all pending events.
|
|
57
|
+
*/
|
|
58
|
+
declare function shutdown(): Promise<void>;
|
|
59
|
+
|
|
60
|
+
export { type CheckBudgetParams, type FlagPurchaseParams, type SetBudgetParams, analytix402_check_budget, analytix402_flag_purchase, analytix402_set_budget, analytix402_spend_report, sendHeartbeat, shutdown, trackAPICall, trackLLMCall };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytix402 OpenClaw Skill
|
|
3
|
+
*
|
|
4
|
+
* Provides AI agent spend tracking, budget enforcement,
|
|
5
|
+
* and observability for OpenClaw agents.
|
|
6
|
+
*
|
|
7
|
+
* Integrates with the Analytix402 platform to give operators
|
|
8
|
+
* real-time visibility into what their agents are spending.
|
|
9
|
+
*/
|
|
10
|
+
declare function analytix402_spend_report(): Promise<string>;
|
|
11
|
+
interface SetBudgetParams {
|
|
12
|
+
daily_limit?: number;
|
|
13
|
+
per_call_limit?: number;
|
|
14
|
+
}
|
|
15
|
+
declare function analytix402_set_budget(params: SetBudgetParams): string;
|
|
16
|
+
interface CheckBudgetParams {
|
|
17
|
+
estimated_cost?: number;
|
|
18
|
+
}
|
|
19
|
+
declare function analytix402_check_budget(params: CheckBudgetParams): string;
|
|
20
|
+
interface FlagPurchaseParams {
|
|
21
|
+
url: string;
|
|
22
|
+
reason?: string;
|
|
23
|
+
estimated_cost?: number;
|
|
24
|
+
}
|
|
25
|
+
declare function analytix402_flag_purchase(params: FlagPurchaseParams): string;
|
|
26
|
+
/**
|
|
27
|
+
* Call this after any LLM invocation to track usage.
|
|
28
|
+
* OpenClaw skills can hook into the agent lifecycle.
|
|
29
|
+
*/
|
|
30
|
+
declare function trackLLMCall(params: {
|
|
31
|
+
model: string;
|
|
32
|
+
provider: string;
|
|
33
|
+
inputTokens: number;
|
|
34
|
+
outputTokens: number;
|
|
35
|
+
costUsd?: number;
|
|
36
|
+
durationMs?: number;
|
|
37
|
+
taskId?: string;
|
|
38
|
+
}): void;
|
|
39
|
+
/**
|
|
40
|
+
* Call this after any API call to track spend.
|
|
41
|
+
*/
|
|
42
|
+
declare function trackAPICall(params: {
|
|
43
|
+
method: string;
|
|
44
|
+
url: string;
|
|
45
|
+
statusCode: number;
|
|
46
|
+
responseTimeMs: number;
|
|
47
|
+
paymentAmount?: number;
|
|
48
|
+
paymentCurrency?: string;
|
|
49
|
+
txHash?: string;
|
|
50
|
+
}): void;
|
|
51
|
+
/**
|
|
52
|
+
* Send a heartbeat — call periodically to show agent is alive.
|
|
53
|
+
*/
|
|
54
|
+
declare function sendHeartbeat(status?: 'healthy' | 'degraded' | 'error'): void;
|
|
55
|
+
/**
|
|
56
|
+
* Gracefully shutdown — flush all pending events.
|
|
57
|
+
*/
|
|
58
|
+
declare function shutdown(): Promise<void>;
|
|
59
|
+
|
|
60
|
+
export { type CheckBudgetParams, type FlagPurchaseParams, type SetBudgetParams, analytix402_check_budget, analytix402_flag_purchase, analytix402_set_budget, analytix402_spend_report, sendHeartbeat, shutdown, trackAPICall, trackLLMCall };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/index.ts
|
|
20
|
+
var index_exports = {};
|
|
21
|
+
__export(index_exports, {
|
|
22
|
+
analytix402_check_budget: () => analytix402_check_budget,
|
|
23
|
+
analytix402_flag_purchase: () => analytix402_flag_purchase,
|
|
24
|
+
analytix402_set_budget: () => analytix402_set_budget,
|
|
25
|
+
analytix402_spend_report: () => analytix402_spend_report,
|
|
26
|
+
sendHeartbeat: () => sendHeartbeat,
|
|
27
|
+
shutdown: () => shutdown,
|
|
28
|
+
trackAPICall: () => trackAPICall,
|
|
29
|
+
trackLLMCall: () => trackLLMCall
|
|
30
|
+
});
|
|
31
|
+
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
|
|
268
|
+
var API_KEY = process.env.ANALYTIX402_API_KEY || "";
|
|
269
|
+
var BASE_URL = (process.env.ANALYTIX402_BASE_URL || "https://analytix402.com").replace(/\/$/, "");
|
|
270
|
+
var AGENT_ID = process.env.ANALYTIX402_AGENT_ID || `openclaw-${Date.now()}`;
|
|
271
|
+
var AGENT_NAME = process.env.ANALYTIX402_AGENT_NAME || "OpenClaw Agent";
|
|
272
|
+
var DAILY_BUDGET = parseFloat(process.env.ANALYTIX402_DAILY_BUDGET || "0") || 0;
|
|
273
|
+
var PER_CALL_LIMIT = parseFloat(process.env.ANALYTIX402_PER_CALL_LIMIT || "0") || 0;
|
|
274
|
+
var TRACK_LLM = process.env.ANALYTIX402_TRACK_LLM !== "false";
|
|
275
|
+
var client = null;
|
|
276
|
+
var sessionSpend = 0;
|
|
277
|
+
var sessionCalls = 0;
|
|
278
|
+
var dailySpend = 0;
|
|
279
|
+
var dailySpendDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
280
|
+
function getClient() {
|
|
281
|
+
if (!client) {
|
|
282
|
+
if (!API_KEY) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
"ANALYTIX402_API_KEY is not set. Add it to your OpenClaw skill config."
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
client = createClient({
|
|
288
|
+
apiKey: API_KEY,
|
|
289
|
+
baseUrl: BASE_URL,
|
|
290
|
+
agentId: AGENT_ID,
|
|
291
|
+
agentName: AGENT_NAME
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return client;
|
|
295
|
+
}
|
|
296
|
+
async function apiRequest(method, path, body) {
|
|
297
|
+
const url = `${BASE_URL}/api${path}`;
|
|
298
|
+
const headers = {
|
|
299
|
+
"Content-Type": "application/json",
|
|
300
|
+
"Authorization": `Bearer ${API_KEY}`,
|
|
301
|
+
"X-API-Key": API_KEY
|
|
302
|
+
};
|
|
303
|
+
const opts = { method, headers };
|
|
304
|
+
if (body && method !== "GET") {
|
|
305
|
+
opts.body = JSON.stringify(body);
|
|
306
|
+
}
|
|
307
|
+
const res = await fetch(url, opts);
|
|
308
|
+
const text = await res.text();
|
|
309
|
+
let data;
|
|
310
|
+
try {
|
|
311
|
+
data = JSON.parse(text);
|
|
312
|
+
} catch {
|
|
313
|
+
data = { raw: text };
|
|
314
|
+
}
|
|
315
|
+
if (!res.ok) {
|
|
316
|
+
throw new Error(`Analytix402 API ${res.status}: ${data.error || text}`);
|
|
317
|
+
}
|
|
318
|
+
return data;
|
|
319
|
+
}
|
|
320
|
+
async function analytix402_spend_report() {
|
|
321
|
+
const c = getClient();
|
|
322
|
+
c.heartbeat("healthy", { sessionCalls, sessionSpend });
|
|
323
|
+
try {
|
|
324
|
+
const overview = await apiRequest("GET", "/agents/overview");
|
|
325
|
+
const insights = await apiRequest("GET", "/agents/insights");
|
|
326
|
+
return JSON.stringify({
|
|
327
|
+
session: {
|
|
328
|
+
totalCalls: sessionCalls,
|
|
329
|
+
totalSpend: `$${sessionSpend.toFixed(4)}`,
|
|
330
|
+
dailySpend: `$${dailySpend.toFixed(4)}`,
|
|
331
|
+
dailyBudget: DAILY_BUDGET > 0 ? `$${DAILY_BUDGET.toFixed(2)}` : "unlimited",
|
|
332
|
+
budgetRemaining: DAILY_BUDGET > 0 ? `$${(DAILY_BUDGET - dailySpend).toFixed(4)}` : "unlimited"
|
|
333
|
+
},
|
|
334
|
+
platform: overview,
|
|
335
|
+
insights
|
|
336
|
+
}, null, 2);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return JSON.stringify({
|
|
339
|
+
session: {
|
|
340
|
+
totalCalls: sessionCalls,
|
|
341
|
+
totalSpend: `$${sessionSpend.toFixed(4)}`,
|
|
342
|
+
dailySpend: `$${dailySpend.toFixed(4)}`,
|
|
343
|
+
dailyBudget: DAILY_BUDGET > 0 ? `$${DAILY_BUDGET.toFixed(2)}` : "unlimited"
|
|
344
|
+
},
|
|
345
|
+
error: error instanceof Error ? error.message : String(error)
|
|
346
|
+
}, null, 2);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function analytix402_set_budget(params) {
|
|
350
|
+
const updates = [];
|
|
351
|
+
if (params.daily_limit !== void 0 && params.daily_limit >= 0) {
|
|
352
|
+
Object.defineProperty(globalThis, "__ax402_daily_budget", {
|
|
353
|
+
value: params.daily_limit,
|
|
354
|
+
writable: true,
|
|
355
|
+
configurable: true
|
|
356
|
+
});
|
|
357
|
+
updates.push(`Daily budget set to $${params.daily_limit.toFixed(2)}`);
|
|
358
|
+
}
|
|
359
|
+
if (params.per_call_limit !== void 0 && params.per_call_limit >= 0) {
|
|
360
|
+
Object.defineProperty(globalThis, "__ax402_per_call_limit", {
|
|
361
|
+
value: params.per_call_limit,
|
|
362
|
+
writable: true,
|
|
363
|
+
configurable: true
|
|
364
|
+
});
|
|
365
|
+
updates.push(`Per-call limit set to $${params.per_call_limit.toFixed(2)}`);
|
|
366
|
+
}
|
|
367
|
+
if (updates.length === 0) {
|
|
368
|
+
return "No budget parameters provided. Use daily_limit and/or per_call_limit.";
|
|
369
|
+
}
|
|
370
|
+
return updates.join(". ") + ".";
|
|
371
|
+
}
|
|
372
|
+
function analytix402_check_budget(params) {
|
|
373
|
+
const budget = DAILY_BUDGET || globalThis.__ax402_daily_budget || 0;
|
|
374
|
+
const callLimit = PER_CALL_LIMIT || globalThis.__ax402_per_call_limit || 0;
|
|
375
|
+
const estimated = params.estimated_cost || 0;
|
|
376
|
+
const remaining = budget > 0 ? budget - dailySpend : Infinity;
|
|
377
|
+
const allowed = (budget === 0 || remaining >= estimated) && (callLimit === 0 || estimated <= callLimit);
|
|
378
|
+
return JSON.stringify({
|
|
379
|
+
allowed,
|
|
380
|
+
dailyBudget: budget > 0 ? `$${budget.toFixed(2)}` : "unlimited",
|
|
381
|
+
dailySpent: `$${dailySpend.toFixed(4)}`,
|
|
382
|
+
remaining: budget > 0 ? `$${remaining.toFixed(4)}` : "unlimited",
|
|
383
|
+
estimatedCost: `$${estimated.toFixed(4)}`,
|
|
384
|
+
perCallLimit: callLimit > 0 ? `$${callLimit.toFixed(2)}` : "unlimited",
|
|
385
|
+
withinPerCallLimit: callLimit === 0 || estimated <= callLimit,
|
|
386
|
+
withinDailyBudget: budget === 0 || remaining >= estimated
|
|
387
|
+
}, null, 2);
|
|
388
|
+
}
|
|
389
|
+
function analytix402_flag_purchase(params) {
|
|
390
|
+
const c = getClient();
|
|
391
|
+
c.track({
|
|
392
|
+
type: "request",
|
|
393
|
+
method: "FLAG",
|
|
394
|
+
path: params.url,
|
|
395
|
+
endpoint: params.url,
|
|
396
|
+
statusCode: 0,
|
|
397
|
+
responseTimeMs: 0,
|
|
398
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
399
|
+
agentId: AGENT_ID,
|
|
400
|
+
metadata: {
|
|
401
|
+
flagged: true,
|
|
402
|
+
reason: params.reason || "Potential duplicate or unnecessary purchase",
|
|
403
|
+
estimatedCost: params.estimated_cost
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
return `Flagged: ${params.url} \u2014 ${params.reason || "potential duplicate purchase"}. This will appear in your Analytix402 dashboard for review.`;
|
|
407
|
+
}
|
|
408
|
+
function trackLLMCall(params) {
|
|
409
|
+
if (!TRACK_LLM) return;
|
|
410
|
+
const c = getClient();
|
|
411
|
+
const cost = params.costUsd || 0;
|
|
412
|
+
c.trackLLM({
|
|
413
|
+
model: params.model,
|
|
414
|
+
provider: params.provider,
|
|
415
|
+
inputTokens: params.inputTokens,
|
|
416
|
+
outputTokens: params.outputTokens,
|
|
417
|
+
costUsd: params.costUsd,
|
|
418
|
+
durationMs: params.durationMs,
|
|
419
|
+
taskId: params.taskId
|
|
420
|
+
});
|
|
421
|
+
sessionSpend += cost;
|
|
422
|
+
sessionCalls += 1;
|
|
423
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
424
|
+
if (today !== dailySpendDate) {
|
|
425
|
+
dailySpend = 0;
|
|
426
|
+
}
|
|
427
|
+
dailySpend += cost;
|
|
428
|
+
}
|
|
429
|
+
function trackAPICall(params) {
|
|
430
|
+
const c = getClient();
|
|
431
|
+
const payment = params.paymentAmount ? {
|
|
432
|
+
amount: String(params.paymentAmount),
|
|
433
|
+
currency: params.paymentCurrency || "USDC",
|
|
434
|
+
wallet: "",
|
|
435
|
+
status: "success",
|
|
436
|
+
txHash: params.txHash
|
|
437
|
+
} : void 0;
|
|
438
|
+
c.track({
|
|
439
|
+
type: "request",
|
|
440
|
+
method: params.method,
|
|
441
|
+
path: params.url,
|
|
442
|
+
endpoint: params.url,
|
|
443
|
+
statusCode: params.statusCode,
|
|
444
|
+
responseTimeMs: params.responseTimeMs,
|
|
445
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
446
|
+
agentId: AGENT_ID,
|
|
447
|
+
payment
|
|
448
|
+
});
|
|
449
|
+
if (params.paymentAmount) {
|
|
450
|
+
sessionSpend += params.paymentAmount;
|
|
451
|
+
dailySpend += params.paymentAmount;
|
|
452
|
+
}
|
|
453
|
+
sessionCalls += 1;
|
|
454
|
+
}
|
|
455
|
+
function sendHeartbeat(status = "healthy") {
|
|
456
|
+
const c = getClient();
|
|
457
|
+
c.heartbeat(status, {
|
|
458
|
+
sessionCalls,
|
|
459
|
+
sessionSpend,
|
|
460
|
+
dailySpend,
|
|
461
|
+
uptime: process.uptime()
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
async function shutdown() {
|
|
465
|
+
if (client) {
|
|
466
|
+
await client.shutdown();
|
|
467
|
+
client = null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
471
|
+
0 && (module.exports = {
|
|
472
|
+
analytix402_check_budget,
|
|
473
|
+
analytix402_flag_purchase,
|
|
474
|
+
analytix402_set_budget,
|
|
475
|
+
analytix402_spend_report,
|
|
476
|
+
sendHeartbeat,
|
|
477
|
+
shutdown,
|
|
478
|
+
trackAPICall,
|
|
479
|
+
trackLLMCall
|
|
480
|
+
});
|