ai-cost-cli 1.0.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 +228 -0
- package/dist/analytics.d.ts +8 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +128 -0
- package/dist/analytics.js.map +1 -0
- package/dist/api.d.ts +11 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +152 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/analyze.d.ts +8 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +210 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/connect.d.ts +7 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +67 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +100 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/models.d.ts +6 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/models.js +74 -0
- package/dist/commands/models.js.map +1 -0
- package/dist/commands/optimize.d.ts +6 -0
- package/dist/commands/optimize.d.ts.map +1 -0
- package/dist/commands/optimize.js +129 -0
- package/dist/commands/optimize.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +31 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +71 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# ai-cost-cli
|
|
2
|
+
|
|
3
|
+
> 🛡️ **AI Cost Guard CLI** — Analyze and optimize your AI/LLM API costs from the terminal.
|
|
4
|
+
> Monitor OpenAI, Anthropic, Gemini, and Cohere spending with real-time dashboards, cost breakdowns, and optimization recommendations across 50+ models.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/ai-cost-cli)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g ai-cost-cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
After installation, two aliases are available:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
ai-cost-cli --help
|
|
22
|
+
ai-cost --help # shorthand
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 1. Log in and select a project (recommended)
|
|
31
|
+
ai-cost-cli login
|
|
32
|
+
|
|
33
|
+
# 2. — OR — connect directly with an API key
|
|
34
|
+
ai-cost-cli connect --key YOUR_API_KEY
|
|
35
|
+
|
|
36
|
+
# 3. Analyze the last 30 days of AI spending
|
|
37
|
+
ai-cost-cli analyze
|
|
38
|
+
|
|
39
|
+
# 4. Get model-switch recommendations to cut costs
|
|
40
|
+
ai-cost-cli optimize
|
|
41
|
+
|
|
42
|
+
# 5. Browse all supported models and pricing
|
|
43
|
+
ai-cost-cli models
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Commands
|
|
49
|
+
|
|
50
|
+
### `login`
|
|
51
|
+
|
|
52
|
+
Authenticate with your AI Cost Guard account. Automatically fetches your projects and lets you select one to connect.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ai-cost-cli login
|
|
56
|
+
ai-cost-cli login --email you@example.com
|
|
57
|
+
ai-cost-cli login --email you@example.com --url https://api.aicostguard.com
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Option | Description |
|
|
61
|
+
|--------|-------------|
|
|
62
|
+
| `-e, --email <email>` | Pre-fill your email address |
|
|
63
|
+
| `-u, --url <url>` | API base URL (default: `http://localhost:4000`) |
|
|
64
|
+
|
|
65
|
+
After login, your JWT token and selected project's API key are saved to `~/.ai-cost-guard/config.json`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### `connect`
|
|
70
|
+
|
|
71
|
+
Connect to a project directly using a project API key (no account required).
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ai-cost-cli connect
|
|
75
|
+
ai-cost-cli connect --key acg_live_xxxxxxxxxxxx
|
|
76
|
+
ai-cost-cli connect --key acg_live_xxxxxxxxxxxx --url https://api.aicostguard.com
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Option | Description |
|
|
80
|
+
|--------|-------------|
|
|
81
|
+
| `-k, --key <apiKey>` | Your project API key |
|
|
82
|
+
| `-u, --url <url>` | API base URL (default: `http://localhost:4000`) |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### `status`
|
|
87
|
+
|
|
88
|
+
Display the current connection status, project info, and recent activity.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
ai-cost-cli status
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Shows: logged-in email, project name, API key (masked), server URL, and events seen today.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### `analyze`
|
|
99
|
+
|
|
100
|
+
Pull a full cost report for your connected project.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ai-cost-cli analyze
|
|
104
|
+
ai-cost-cli analyze --days 7
|
|
105
|
+
ai-cost-cli analyze --days 90 --model gpt-4o
|
|
106
|
+
ai-cost-cli analyze --provider anthropic
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Option | Description |
|
|
110
|
+
|--------|-------------|
|
|
111
|
+
| `-d, --days <n>` | Analysis window in days (default: `30`) |
|
|
112
|
+
| `-m, --model <model>` | Filter results to a specific model |
|
|
113
|
+
| `-p, --provider <provider>` | Filter by provider: `openai`, `anthropic`, `google` |
|
|
114
|
+
|
|
115
|
+
**Output includes:**
|
|
116
|
+
- Total cost, total events, avg cost/event
|
|
117
|
+
- Input / output token totals
|
|
118
|
+
- Cost breakdown by model
|
|
119
|
+
- Cost breakdown by day (time series)
|
|
120
|
+
- Latency percentiles (p50 / p95 / p99) when available
|
|
121
|
+
|
|
122
|
+
When no JWT token is present, falls back to a pricing-only estimate based on the public `/pricing/models` endpoint.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### `optimize`
|
|
127
|
+
|
|
128
|
+
Get model-switch recommendations that can reduce your bill.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
ai-cost-cli optimize
|
|
132
|
+
ai-cost-cli optimize --days 7
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Option | Description |
|
|
136
|
+
|--------|-------------|
|
|
137
|
+
| `-d, --days <n>` | Analysis period for usage context (default: `30`) |
|
|
138
|
+
|
|
139
|
+
Compares models within each provider and surfaces alternatives that cost **30%+ less** per token, with estimated savings percentages.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `models`
|
|
144
|
+
|
|
145
|
+
Browse all supported models and their per-token pricing.
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
ai-cost-cli models
|
|
149
|
+
ai-cost-cli models --provider openai
|
|
150
|
+
ai-cost-cli models --provider anthropic
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Option | Description |
|
|
154
|
+
|--------|-------------|
|
|
155
|
+
| `-p, --provider <provider>` | Filter by provider name (case-insensitive) |
|
|
156
|
+
|
|
157
|
+
Displays a grouped, color-coded table with input price / output price per 1M tokens and a cost tier label.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Configuration
|
|
162
|
+
|
|
163
|
+
Credentials and project info are stored locally in:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
~/.ai-cost-guard/config.json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Field | Set by | Description |
|
|
170
|
+
|-------|--------|-------------|
|
|
171
|
+
| `token` | `login` | JWT access token |
|
|
172
|
+
| `email` | `login` | Logged-in user's email |
|
|
173
|
+
| `apiKey` | `login` / `connect` | Project API key (used as `x-api-key` header) |
|
|
174
|
+
| `projectId` | `login` | Selected project UUID |
|
|
175
|
+
| `projectName` | `login` | Selected project display name |
|
|
176
|
+
| `baseUrl` | `login` / `connect` | Backend API base URL |
|
|
177
|
+
|
|
178
|
+
To reset, simply delete the file:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
rm ~/.ai-cost-guard/config.json
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Self-Hosting
|
|
187
|
+
|
|
188
|
+
By default the CLI points to `http://localhost:4000`. To connect to a self-hosted or cloud instance, pass `--url` to any authentication command:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
ai-cost-cli login --url https://api.your-instance.com
|
|
192
|
+
ai-cost-cli connect --key YOUR_KEY --url https://api.your-instance.com
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Supported Providers
|
|
198
|
+
|
|
199
|
+
| Provider | Example Models |
|
|
200
|
+
|----------|---------------|
|
|
201
|
+
| **OpenAI** | gpt-4o, gpt-4o-mini, gpt-4-turbo, o1, o3-mini |
|
|
202
|
+
| **Anthropic** | claude-3-5-sonnet, claude-3-5-haiku, claude-3-opus |
|
|
203
|
+
| **Google** | gemini-1.5-pro, gemini-1.5-flash, gemini-2.0-flash |
|
|
204
|
+
| **Cohere** | command-r-plus, command-r |
|
|
205
|
+
|
|
206
|
+
Run `ai-cost-cli models` for the full, live list sourced directly from your backend.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Requirements
|
|
211
|
+
|
|
212
|
+
- **Node.js** ≥ 16.0.0
|
|
213
|
+
- A running [AI Cost Guard backend](https://github.com/crediblearena/ai-cost-guard) **or** a cloud account at [aicostguard.com](https://aicostguard.com)
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Links
|
|
218
|
+
|
|
219
|
+
- 🌐 [aicostguard.com](https://aicostguard.com)
|
|
220
|
+
- 📦 [npm: ai-cost-cli](https://www.npmjs.com/package/ai-cost-cli)
|
|
221
|
+
- 🐛 [Issue tracker](https://github.com/crediblearena/ai-cost-guard/issues)
|
|
222
|
+
- 📖 [Full documentation](https://aicostguard.com/docs)
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
MIT © [AI Cost Guard](https://aicostguard.com)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track cli_used_daily — fires at most once per calendar day.
|
|
3
|
+
* Fire-and-forget, never blocks CLI execution.
|
|
4
|
+
*/
|
|
5
|
+
export declare function trackCliUsage(command: string): Promise<void>;
|
|
6
|
+
/** Allow users to opt out of analytics */
|
|
7
|
+
export declare function setAnalyticsOptOut(optOut: boolean): void;
|
|
8
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAoDA;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuClE;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAIxD"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.trackCliUsage = trackCliUsage;
|
|
37
|
+
exports.setAnalyticsOptOut = setAnalyticsOptOut;
|
|
38
|
+
const posthog_node_1 = require("posthog-node");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const POSTHOG_API_KEY = 'phc_bX4Ab8QZ0WZL8nsEQfaTw2E6xtijwVjb6jIvbhZ436f';
|
|
43
|
+
const POSTHOG_HOST = 'https://us.i.posthog.com';
|
|
44
|
+
const CONFIG_DIR = path.join(os.homedir(), '.ai-cost-guard');
|
|
45
|
+
const ANALYTICS_FILE = path.join(CONFIG_DIR, 'analytics.json');
|
|
46
|
+
function getState() {
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(ANALYTICS_FILE)) {
|
|
49
|
+
return JSON.parse(fs.readFileSync(ANALYTICS_FILE, 'utf-8'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
function saveState(state) {
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
60
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(ANALYTICS_FILE, JSON.stringify(state, null, 2));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore — analytics should never break CLI
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Get user ID from CLI config (set during login) */
|
|
69
|
+
function getUserId() {
|
|
70
|
+
try {
|
|
71
|
+
const configPath = path.join(CONFIG_DIR, 'config.json');
|
|
72
|
+
if (fs.existsSync(configPath)) {
|
|
73
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
74
|
+
return config.userId || config.email || null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// ignore
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Track cli_used_daily — fires at most once per calendar day.
|
|
84
|
+
* Fire-and-forget, never blocks CLI execution.
|
|
85
|
+
*/
|
|
86
|
+
async function trackCliUsage(command) {
|
|
87
|
+
try {
|
|
88
|
+
const state = getState();
|
|
89
|
+
// Respect opt-out
|
|
90
|
+
if (state.optOut)
|
|
91
|
+
return;
|
|
92
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
93
|
+
// Daily dedup — only one event per day
|
|
94
|
+
if (state.lastCliDay === today)
|
|
95
|
+
return;
|
|
96
|
+
const userId = getUserId();
|
|
97
|
+
if (!userId)
|
|
98
|
+
return; // Only track logged-in users
|
|
99
|
+
const client = new posthog_node_1.PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST });
|
|
100
|
+
client.capture({
|
|
101
|
+
distinctId: userId,
|
|
102
|
+
event: 'cli_used_daily',
|
|
103
|
+
properties: {
|
|
104
|
+
command,
|
|
105
|
+
platform: process.platform,
|
|
106
|
+
node_version: process.version,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
// Update last usage day
|
|
110
|
+
state.lastCliDay = today;
|
|
111
|
+
saveState(state);
|
|
112
|
+
// Flush and close — with a timeout so we don't block CLI exit
|
|
113
|
+
await Promise.race([
|
|
114
|
+
client.shutdown(),
|
|
115
|
+
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Analytics should NEVER break the CLI
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Allow users to opt out of analytics */
|
|
123
|
+
function setAnalyticsOptOut(optOut) {
|
|
124
|
+
const state = getState();
|
|
125
|
+
state.optOut = optOut;
|
|
126
|
+
saveState(state);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface RequestOptions {
|
|
2
|
+
path: string;
|
|
3
|
+
method?: string;
|
|
4
|
+
body?: any;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function apiRequest<T>(options: RequestOptions): Promise<T>;
|
|
9
|
+
export declare function apiRequestAuth<T>(path: string, token: string, baseUrl?: string): Promise<T>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAIA,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,CAoEvE;AAED,wBAAsB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAmDjG"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.apiRequest = apiRequest;
|
|
37
|
+
exports.apiRequestAuth = apiRequestAuth;
|
|
38
|
+
const http = __importStar(require("http"));
|
|
39
|
+
const https = __importStar(require("https"));
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
async function apiRequest(options) {
|
|
42
|
+
const config = (0, config_1.getConfig)();
|
|
43
|
+
const baseUrl = options.baseUrl || config?.baseUrl || 'http://localhost:4000';
|
|
44
|
+
const apiKey = options.apiKey || config?.apiKey;
|
|
45
|
+
const fullUrl = `${baseUrl}/api/v1${options.path}`;
|
|
46
|
+
const url = new URL(fullUrl);
|
|
47
|
+
const isHttps = url.protocol === 'https:';
|
|
48
|
+
const lib = isHttps ? https : http;
|
|
49
|
+
const headers = {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
};
|
|
52
|
+
if (apiKey) {
|
|
53
|
+
headers['x-api-key'] = apiKey;
|
|
54
|
+
}
|
|
55
|
+
const body = options.body ? JSON.stringify(options.body) : undefined;
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const req = lib.request({
|
|
58
|
+
hostname: url.hostname,
|
|
59
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
60
|
+
path: url.pathname + url.search,
|
|
61
|
+
method: options.method || 'GET',
|
|
62
|
+
headers,
|
|
63
|
+
}, (res) => {
|
|
64
|
+
let data = '';
|
|
65
|
+
res.on('data', (chunk) => (data += chunk));
|
|
66
|
+
res.on('end', () => {
|
|
67
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
68
|
+
try {
|
|
69
|
+
const json = JSON.parse(data);
|
|
70
|
+
const message = json.message || json.error || `HTTP ${res.statusCode}`;
|
|
71
|
+
reject(new Error(message));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`));
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const json = JSON.parse(data);
|
|
80
|
+
// Handle TransformInterceptor wrapper
|
|
81
|
+
if (json.data !== undefined) {
|
|
82
|
+
resolve(json.data);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
resolve(json);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
reject(new Error(`Invalid JSON response: ${data.substring(0, 200)}`));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
req.on('error', (err) => {
|
|
94
|
+
reject(new Error(`Connection failed: ${err.message}\nMake sure the AI Cost Guard server is running.`));
|
|
95
|
+
});
|
|
96
|
+
if (body) {
|
|
97
|
+
req.write(body);
|
|
98
|
+
}
|
|
99
|
+
req.end();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async function apiRequestAuth(path, token, baseUrl) {
|
|
103
|
+
const config = (0, config_1.getConfig)();
|
|
104
|
+
const base = baseUrl || config?.baseUrl || 'http://localhost:4000';
|
|
105
|
+
const fullUrl = `${base}/api/v1${path}`;
|
|
106
|
+
const url = new URL(fullUrl);
|
|
107
|
+
const isHttps = url.protocol === 'https:';
|
|
108
|
+
const lib = isHttps ? https : http;
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const req = lib.request({
|
|
111
|
+
hostname: url.hostname,
|
|
112
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
113
|
+
path: url.pathname + url.search,
|
|
114
|
+
method: 'GET',
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': 'application/json',
|
|
117
|
+
Authorization: `Bearer ${token}`,
|
|
118
|
+
},
|
|
119
|
+
}, (res) => {
|
|
120
|
+
let data = '';
|
|
121
|
+
res.on('data', (chunk) => (data += chunk));
|
|
122
|
+
res.on('end', () => {
|
|
123
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
124
|
+
try {
|
|
125
|
+
const json = JSON.parse(data);
|
|
126
|
+
const message = json.message || json.error || `HTTP ${res.statusCode}`;
|
|
127
|
+
reject(new Error(message));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`));
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const json = JSON.parse(data);
|
|
136
|
+
if (json.data !== undefined) {
|
|
137
|
+
resolve(json.data);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
resolve(json);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
reject(new Error(`Invalid JSON response`));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
req.on('error', (err) => reject(new Error(`Connection failed: ${err.message}`)));
|
|
149
|
+
req.end();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,iBA2B3D"}
|