squidgems 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/README.md +62 -0
- package/bin/squidgems +2 -0
- package/dist/index.js +447 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# squidgems
|
|
2
|
+
|
|
3
|
+
Track your [Claude Code](https://docs.anthropic.com/en/docs/claude-code) costs, tokens, and sessions in real time.
|
|
4
|
+
|
|
5
|
+
SquidGems connects to Claude Code via OpenTelemetry and gives you a live dashboard showing per-session cost breakdowns, token usage, and tool invocations.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g squidgems
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
**1. Register your machine**
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
squidgems
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Generates a stable local identity and registers it with the SquidGems API.
|
|
22
|
+
|
|
23
|
+
**2. Connect Claude Code**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
squidgems init --claude
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Configures Claude Code to emit OpenTelemetry data to SquidGems. Opens your dashboard automatically.
|
|
30
|
+
|
|
31
|
+
**3. Use Claude Code as normal**
|
|
32
|
+
|
|
33
|
+
Sessions appear in real time at your dashboard on [squidgems.ink](https://squidgems.ink).
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
| Command | Description |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| `squidgems` | Register your machine (run once) |
|
|
40
|
+
| `squidgems init --claude` | Connect Claude Code to SquidGems |
|
|
41
|
+
| `squidgems disable --claude` | Disconnect Claude Code from SquidGems |
|
|
42
|
+
| `squidgems uninstall` | Remove all SquidGems config from your machine |
|
|
43
|
+
|
|
44
|
+
## What gets tracked
|
|
45
|
+
|
|
46
|
+
- Session start/end times
|
|
47
|
+
- Token usage (input, output, cache)
|
|
48
|
+
- Cost per session (calculated from token counts)
|
|
49
|
+
- Tool invocations and durations
|
|
50
|
+
- Errors and retries
|
|
51
|
+
|
|
52
|
+
All data is tied to an anonymous machine identity. No source code or prompt content is collected.
|
|
53
|
+
|
|
54
|
+
## Requirements
|
|
55
|
+
|
|
56
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed
|
|
57
|
+
- Node.js 18+
|
|
58
|
+
|
|
59
|
+
## Links
|
|
60
|
+
|
|
61
|
+
- [Dashboard](https://squidgems.ink)
|
|
62
|
+
- [Report issues](mailto:hello@squidgems.ink)
|
package/bin/squidgems
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
19
|
+
var __toCommonJS = (from) => {
|
|
20
|
+
var entry = __moduleCache.get(from), desc;
|
|
21
|
+
if (entry)
|
|
22
|
+
return entry;
|
|
23
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
25
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
26
|
+
get: () => from[key],
|
|
27
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
28
|
+
}));
|
|
29
|
+
__moduleCache.set(from, entry);
|
|
30
|
+
return entry;
|
|
31
|
+
};
|
|
32
|
+
var __export = (target, all) => {
|
|
33
|
+
for (var name in all)
|
|
34
|
+
__defProp(target, name, {
|
|
35
|
+
get: all[name],
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
set: (newValue) => all[name] = () => newValue
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/index.ts
|
|
43
|
+
var exports_src = {};
|
|
44
|
+
__export(exports_src, {
|
|
45
|
+
__mainPromise: () => __mainPromise
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(exports_src);
|
|
48
|
+
|
|
49
|
+
// src/settings-merge.ts
|
|
50
|
+
var import_fs = __toESM(require("fs"));
|
|
51
|
+
var import_os = __toESM(require("os"));
|
|
52
|
+
var import_path = __toESM(require("path"));
|
|
53
|
+
function claudeDirExists() {
|
|
54
|
+
return import_fs.default.existsSync(import_path.default.join(import_os.default.homedir(), ".claude"));
|
|
55
|
+
}
|
|
56
|
+
function getSettingsPath() {
|
|
57
|
+
return import_path.default.join(import_os.default.homedir(), ".claude", "settings.json");
|
|
58
|
+
}
|
|
59
|
+
function readSettings() {
|
|
60
|
+
const settingsPath = getSettingsPath();
|
|
61
|
+
if (!import_fs.default.existsSync(settingsPath)) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
const raw = import_fs.default.readFileSync(settingsPath, "utf8");
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(raw);
|
|
67
|
+
} catch {
|
|
68
|
+
console.warn("Warning: settings.json is malformed. Creating backup and starting fresh.");
|
|
69
|
+
import_fs.default.writeFileSync(`${settingsPath}.bak`, raw);
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function buildOtelEnv(userId) {
|
|
74
|
+
return {
|
|
75
|
+
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
|
|
76
|
+
OTEL_LOGS_EXPORTER: "otlp",
|
|
77
|
+
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf",
|
|
78
|
+
OTEL_EXPORTER_OTLP_ENDPOINT: process.env.SQUIDGEMS_OTEL_ENDPOINT ?? "https://claude.otel.squidgems.ink",
|
|
79
|
+
OTEL_LOG_TOOL_DETAILS: "1",
|
|
80
|
+
OTEL_RESOURCE_ATTRIBUTES: `X_Squid_User_Id=${userId}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function mergeOtelEnv(userId) {
|
|
84
|
+
const settingsPath = getSettingsPath();
|
|
85
|
+
const existing = readSettings();
|
|
86
|
+
const otelEnv = buildOtelEnv(userId);
|
|
87
|
+
const {
|
|
88
|
+
OTEL_EXPORTER_OTLP_HEADERS: _h,
|
|
89
|
+
OTEL_RESOURCE_ATTRIBUTES: _r,
|
|
90
|
+
...restEnv
|
|
91
|
+
} = existing.env ?? {};
|
|
92
|
+
const updated = {
|
|
93
|
+
...existing,
|
|
94
|
+
otelHeadersHelper: "squidgems token",
|
|
95
|
+
env: {
|
|
96
|
+
...restEnv,
|
|
97
|
+
...otelEnv
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
import_fs.default.mkdirSync(import_path.default.dirname(settingsPath), { recursive: true });
|
|
101
|
+
import_fs.default.writeFileSync(settingsPath, JSON.stringify(updated, null, 2), "utf8");
|
|
102
|
+
}
|
|
103
|
+
function disableOtel() {
|
|
104
|
+
const settingsPath = getSettingsPath();
|
|
105
|
+
const existing = readSettings();
|
|
106
|
+
const { otelHeadersHelper: _, ...rest } = existing;
|
|
107
|
+
const updated = {
|
|
108
|
+
...rest,
|
|
109
|
+
env: {
|
|
110
|
+
...existing.env,
|
|
111
|
+
CLAUDE_CODE_ENABLE_TELEMETRY: "0"
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
import_fs.default.mkdirSync(import_path.default.dirname(settingsPath), { recursive: true });
|
|
115
|
+
import_fs.default.writeFileSync(settingsPath, JSON.stringify(updated, null, 2), "utf8");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/disable-claude.ts
|
|
119
|
+
function runDisableClaude() {
|
|
120
|
+
if (!claudeDirExists()) {
|
|
121
|
+
console.log("Claude Code is not installed — nothing to disable.");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
disableOtel();
|
|
125
|
+
console.log("✓ Telemetry disabled in ~/.claude/settings.json");
|
|
126
|
+
console.log("Run `squidgems init --claude` to re-enable.");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/api-client.ts
|
|
130
|
+
var API_BASE = process.env.SQUIDGEMS_API_URL ?? "https://api.squidgems.ink";
|
|
131
|
+
async function lookupMachineId(machineId) {
|
|
132
|
+
const url = `${API_BASE}/api/squid/install?machine_id=${encodeURIComponent(machineId)}`;
|
|
133
|
+
const res = await fetch(url);
|
|
134
|
+
if (res.status === 200) {
|
|
135
|
+
const data = await res.json();
|
|
136
|
+
return data.user_id_squid;
|
|
137
|
+
}
|
|
138
|
+
if (res.status === 204)
|
|
139
|
+
return null;
|
|
140
|
+
throw new Error(`Unexpected status: ${res.status}`);
|
|
141
|
+
}
|
|
142
|
+
async function registerInstall(userId, machineId) {
|
|
143
|
+
const res = await fetch(`${API_BASE}/api/squid/install`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: { "Content-Type": "application/json" },
|
|
146
|
+
body: JSON.stringify({ user_id_squid: userId, machine_id: machineId })
|
|
147
|
+
});
|
|
148
|
+
if (!res.ok)
|
|
149
|
+
throw new Error(`Registration failed: ${res.status}`);
|
|
150
|
+
}
|
|
151
|
+
async function requestToken(squidId) {
|
|
152
|
+
const res = await fetch(`${API_BASE}/api/squid/token`, {
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: { "Content-Type": "application/json" },
|
|
155
|
+
body: JSON.stringify({ squid_id: squidId })
|
|
156
|
+
});
|
|
157
|
+
if (res.status === 403 || res.status === 404)
|
|
158
|
+
return null;
|
|
159
|
+
if (!res.ok)
|
|
160
|
+
throw new Error(`Token request failed: ${res.status}`);
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
return data.token;
|
|
163
|
+
}
|
|
164
|
+
async function refreshToken(squidId, token) {
|
|
165
|
+
const res = await fetch(`${API_BASE}/api/squid/token/refresh`, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "Content-Type": "application/json" },
|
|
168
|
+
body: JSON.stringify({ token })
|
|
169
|
+
});
|
|
170
|
+
if (res.status === 401 || res.status === 403)
|
|
171
|
+
return null;
|
|
172
|
+
if (!res.ok)
|
|
173
|
+
throw new Error(`Token refresh failed: ${res.status}`);
|
|
174
|
+
const data = await res.json();
|
|
175
|
+
return data.token;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/config-file.ts
|
|
179
|
+
var import_crypto = __toESM(require("crypto"));
|
|
180
|
+
var import_fs2 = __toESM(require("fs"));
|
|
181
|
+
var import_os2 = __toESM(require("os"));
|
|
182
|
+
var import_path2 = __toESM(require("path"));
|
|
183
|
+
function generateUserId() {
|
|
184
|
+
return `squid_${import_crypto.default.randomBytes(16).toString("hex")}`;
|
|
185
|
+
}
|
|
186
|
+
function getConfigPath() {
|
|
187
|
+
return import_path2.default.join(import_os2.default.homedir(), ".squidgems", "config.json");
|
|
188
|
+
}
|
|
189
|
+
function readConfig() {
|
|
190
|
+
const configPath = getConfigPath();
|
|
191
|
+
try {
|
|
192
|
+
const raw = import_fs2.default.readFileSync(configPath, "utf8");
|
|
193
|
+
const parsed = JSON.parse(raw);
|
|
194
|
+
if (typeof parsed.userId === "string") {
|
|
195
|
+
return parsed;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function writeConfig(config) {
|
|
203
|
+
const configPath = getConfigPath();
|
|
204
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(configPath), { recursive: true });
|
|
205
|
+
import_fs2.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
206
|
+
}
|
|
207
|
+
function updateConfigToken(token) {
|
|
208
|
+
const config = readConfig();
|
|
209
|
+
if (!config)
|
|
210
|
+
return;
|
|
211
|
+
if (token) {
|
|
212
|
+
writeConfig({ ...config, token });
|
|
213
|
+
} else {
|
|
214
|
+
const { token: _, ...rest } = config;
|
|
215
|
+
writeConfig(rest);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
async function getOrCreateUserId() {
|
|
219
|
+
const cached = readConfig();
|
|
220
|
+
if (cached)
|
|
221
|
+
return cached.userId;
|
|
222
|
+
const userId = generateUserId();
|
|
223
|
+
writeConfig({ userId });
|
|
224
|
+
return userId;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/open-browser.ts
|
|
228
|
+
var import_child_process = require("child_process");
|
|
229
|
+
function openBrowser(url) {
|
|
230
|
+
let cmd;
|
|
231
|
+
let args;
|
|
232
|
+
switch (process.platform) {
|
|
233
|
+
case "darwin":
|
|
234
|
+
cmd = "open";
|
|
235
|
+
args = [url];
|
|
236
|
+
break;
|
|
237
|
+
case "win32":
|
|
238
|
+
cmd = "cmd";
|
|
239
|
+
args = ["/c", "start", url];
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
cmd = "xdg-open";
|
|
243
|
+
args = [url];
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const child = import_child_process.spawn(cmd, args, {
|
|
247
|
+
detached: true,
|
|
248
|
+
stdio: "ignore"
|
|
249
|
+
});
|
|
250
|
+
child.unref();
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/init-claude.ts
|
|
255
|
+
async function runInitClaude() {
|
|
256
|
+
console.log("\uD83D\uDD0D Checking for Claude Code...");
|
|
257
|
+
if (!claudeDirExists()) {
|
|
258
|
+
console.error("Claude Code is not installed. Install it from: https://claude.ai/code");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
const userId = await getOrCreateUserId();
|
|
262
|
+
console.log("\uD83D\uDD27 Configuring OpenTelemetry...");
|
|
263
|
+
mergeOtelEnv(userId);
|
|
264
|
+
console.log("✓ Modified ~/.claude/settings.json");
|
|
265
|
+
try {
|
|
266
|
+
const token = await requestToken(userId);
|
|
267
|
+
if (token) {
|
|
268
|
+
updateConfigToken(token);
|
|
269
|
+
console.log("✓ Token cached");
|
|
270
|
+
} else {
|
|
271
|
+
console.warn("Warning: Could not obtain token. Run `squidgems` first to register.");
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
console.warn("Warning: Token request failed. Will retry on next session.");
|
|
275
|
+
}
|
|
276
|
+
console.log("\uD83C\uDD94 Generating your tracking ID...");
|
|
277
|
+
console.log(`✓ User ID: ${userId}`);
|
|
278
|
+
const baseUrl = process.env.SQUIDGEMS_WEB_URL ?? "https://squidgems.ink";
|
|
279
|
+
let dashboardUrl = `${baseUrl}/sessions/${userId}?provider=claude`;
|
|
280
|
+
const config = readConfig();
|
|
281
|
+
if (config?.token) {
|
|
282
|
+
dashboardUrl += `&token=${encodeURIComponent(config.token)}`;
|
|
283
|
+
}
|
|
284
|
+
console.log("\uD83D\uDE80 Opening dashboard...");
|
|
285
|
+
openBrowser(dashboardUrl);
|
|
286
|
+
console.log(`✓ Browser opened at: ${dashboardUrl}`);
|
|
287
|
+
console.log("\uD83C\uDF89 You're all set!");
|
|
288
|
+
console.log("Claude Code is now configured to send telemetry to your SquidGems dashboard.");
|
|
289
|
+
console.log(`View your sessions at: ${dashboardUrl}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/machine-id.ts
|
|
293
|
+
var import_child_process2 = require("child_process");
|
|
294
|
+
var import_crypto2 = __toESM(require("crypto"));
|
|
295
|
+
var import_fs3 = __toESM(require("fs"));
|
|
296
|
+
function getRawSources() {
|
|
297
|
+
switch (process.platform) {
|
|
298
|
+
case "darwin": {
|
|
299
|
+
const out = import_child_process2.execSync("ioreg -rd1 -c IOPlatformExpertDevice", { encoding: "utf8" });
|
|
300
|
+
const uuidMatch = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
301
|
+
if (!uuidMatch)
|
|
302
|
+
throw new Error("IOPlatformUUID not found in ioreg output");
|
|
303
|
+
const serialMatch = out.match(/"IOPlatformSerialNumber"\s*=\s*"([^"]+)"/);
|
|
304
|
+
return [uuidMatch[1], serialMatch ? serialMatch[1] : ""];
|
|
305
|
+
}
|
|
306
|
+
case "linux": {
|
|
307
|
+
const machineId = import_fs3.default.readFileSync("/etc/machine-id", "utf8").trim();
|
|
308
|
+
let productUuid = "";
|
|
309
|
+
try {
|
|
310
|
+
productUuid = import_fs3.default.readFileSync("/sys/class/dmi/id/product_uuid", "utf8").trim();
|
|
311
|
+
} catch {}
|
|
312
|
+
return [machineId, productUuid];
|
|
313
|
+
}
|
|
314
|
+
case "win32": {
|
|
315
|
+
const regOut = import_child_process2.execSync("reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid", {
|
|
316
|
+
encoding: "utf8"
|
|
317
|
+
});
|
|
318
|
+
const guidMatch = regOut.match(/MachineGuid\s+REG_SZ\s+(\S+)/);
|
|
319
|
+
if (!guidMatch)
|
|
320
|
+
throw new Error("MachineGuid not found in registry output");
|
|
321
|
+
const biosOut = import_child_process2.execSync("wmic bios get serialnumber", { encoding: "utf8" });
|
|
322
|
+
const biosMatch = biosOut.match(/SerialNumber\s+(\S+)/i);
|
|
323
|
+
return [guidMatch[1], biosMatch ? biosMatch[1] : ""];
|
|
324
|
+
}
|
|
325
|
+
default:
|
|
326
|
+
throw new Error(`Unsupported platform: ${process.platform}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function getMachineId() {
|
|
330
|
+
try {
|
|
331
|
+
const [src1, src2] = getRawSources();
|
|
332
|
+
return import_crypto2.default.createHash("sha256").update(`${src1}:${src2}`).digest("hex");
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/installer.ts
|
|
339
|
+
async function runInstaller() {
|
|
340
|
+
try {} catch {
|
|
341
|
+
console.error("Failed to install squidgems globally.");
|
|
342
|
+
console.error("Try running with sudo: sudo npm install -g squidgems");
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
const userId = await getOrCreateUserId();
|
|
346
|
+
const registeredId = await syncInstall(userId);
|
|
347
|
+
console.log(`Your autogenerated id: ${registeredId}`);
|
|
348
|
+
console.log("Installation is done \uD83E\uDD91");
|
|
349
|
+
console.log("Enable cost tracking: `squidgems init --claude`");
|
|
350
|
+
}
|
|
351
|
+
async function syncInstall(localId) {
|
|
352
|
+
const machineId = getMachineId();
|
|
353
|
+
if (!machineId)
|
|
354
|
+
return localId;
|
|
355
|
+
try {
|
|
356
|
+
const existing = await lookupMachineId(machineId);
|
|
357
|
+
if (existing) {
|
|
358
|
+
if (existing !== localId)
|
|
359
|
+
writeConfig({ userId: existing });
|
|
360
|
+
return existing;
|
|
361
|
+
}
|
|
362
|
+
await registerInstall(localId, machineId);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error(`[squidgems] sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
365
|
+
}
|
|
366
|
+
return localId;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/token.ts
|
|
370
|
+
function output(headers) {
|
|
371
|
+
process.stdout.write(JSON.stringify(headers));
|
|
372
|
+
}
|
|
373
|
+
async function runToken() {
|
|
374
|
+
const config = readConfig();
|
|
375
|
+
if (!config) {
|
|
376
|
+
output({});
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const { userId, token: cachedToken } = config;
|
|
380
|
+
if (cachedToken) {
|
|
381
|
+
try {
|
|
382
|
+
const newToken = await refreshToken(userId, cachedToken);
|
|
383
|
+
if (newToken) {
|
|
384
|
+
updateConfigToken(newToken);
|
|
385
|
+
output({ X_Squid_Token: newToken, X_Squid_User_Id: userId });
|
|
386
|
+
} else {
|
|
387
|
+
updateConfigToken(null);
|
|
388
|
+
output({});
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
output({ X_Squid_Token: cachedToken, X_Squid_User_Id: userId });
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const token = await requestToken(userId);
|
|
397
|
+
if (token) {
|
|
398
|
+
updateConfigToken(token);
|
|
399
|
+
output({ X_Squid_Token: token, X_Squid_User_Id: userId });
|
|
400
|
+
} else {
|
|
401
|
+
output({});
|
|
402
|
+
}
|
|
403
|
+
} catch {
|
|
404
|
+
output({});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/uninstall.ts
|
|
409
|
+
var import_fs4 = __toESM(require("fs"));
|
|
410
|
+
var import_os3 = __toESM(require("os"));
|
|
411
|
+
var import_path3 = __toESM(require("path"));
|
|
412
|
+
function runUninstall() {
|
|
413
|
+
if (!claudeDirExists()) {
|
|
414
|
+
console.log("Claude Code is not installed — skipping telemetry disable.");
|
|
415
|
+
} else {
|
|
416
|
+
disableOtel();
|
|
417
|
+
console.log("✓ Telemetry disabled in ~/.claude/settings.json");
|
|
418
|
+
}
|
|
419
|
+
const squidDir = import_path3.default.join(import_os3.default.homedir(), ".squidgems");
|
|
420
|
+
if (import_fs4.default.existsSync(squidDir)) {
|
|
421
|
+
import_fs4.default.rmSync(squidDir, { recursive: true, force: true });
|
|
422
|
+
console.log("✓ Removed ~/.squidgems");
|
|
423
|
+
} else {
|
|
424
|
+
console.log("~/.squidgems not found — nothing to remove.");
|
|
425
|
+
}
|
|
426
|
+
console.log("Done. To remove the CLI: npm uninstall -g squidgems");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/index.ts
|
|
430
|
+
var args = process.argv.slice(2);
|
|
431
|
+
var __mainPromise = (async () => {
|
|
432
|
+
if (args[0] === "init" && args[1] === "--claude") {
|
|
433
|
+
await runInitClaude();
|
|
434
|
+
} else if (args[0] === "disable" && args[1] === "--claude") {
|
|
435
|
+
runDisableClaude();
|
|
436
|
+
} else if (args[0] === "token") {
|
|
437
|
+
await runToken();
|
|
438
|
+
} else if (args[0] === "uninstall") {
|
|
439
|
+
runUninstall();
|
|
440
|
+
} else if (args.length === 0) {
|
|
441
|
+
await runInstaller();
|
|
442
|
+
} else {
|
|
443
|
+
console.error(`Unknown command: ${args.join(" ")}`);
|
|
444
|
+
console.error("Usage: squidgems [init --claude | disable --claude | uninstall]");
|
|
445
|
+
process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "squidgems",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Track Claude Code costs, tokens, and sessions in real time",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"author": "SquidGems",
|
|
9
|
+
"homepage": "https://squidgems.ink",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"claude",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"opentelemetry",
|
|
14
|
+
"telemetry",
|
|
15
|
+
"cost-tracking",
|
|
16
|
+
"ai",
|
|
17
|
+
"anthropic"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"squidgems": "bin/squidgems"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin/squidgems",
|
|
24
|
+
"dist/index.js",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "node dist/index.js",
|
|
29
|
+
"build": "bun build src/index.ts --outdir dist --target node --format cjs",
|
|
30
|
+
"start": "node dist/index.js",
|
|
31
|
+
"lint": "eslint",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "vitest",
|
|
34
|
+
"test:run": "vitest run",
|
|
35
|
+
"test:coverage": "vitest run --coverage",
|
|
36
|
+
"clean": "rm -rf node_modules dist"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20",
|
|
40
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
41
|
+
"typescript": "^5",
|
|
42
|
+
"vitest": "^3.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|