@agent-native/skills 0.1.0 → 0.2.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/dist/built-in-apps.d.ts +60 -0
- package/dist/built-in-apps.d.ts.map +1 -0
- package/dist/built-in-apps.js +105 -0
- package/dist/built-in-apps.js.map +1 -0
- package/dist/connect.d.ts +81 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +352 -0
- package/dist/connect.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -54
- package/dist/index.js.map +1 -1
- package/dist/mcp-config-writers.d.ts +104 -0
- package/dist/mcp-config-writers.d.ts.map +1 -0
- package/dist/mcp-config-writers.js +418 -0
- package/dist/mcp-config-writers.js.map +1 -0
- package/dist/telemetry.d.ts +13 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +115 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared MCP client-config writers — canonical home.
|
|
3
|
+
*
|
|
4
|
+
* This is the single source of truth for how every agent-native CLI writes
|
|
5
|
+
* MCP server entries into each supported client's on-disk config. Both the
|
|
6
|
+
* standalone `@agent-native/skills` installer AND `@agent-native/core`
|
|
7
|
+
* (`agent-native mcp install` / `connect` / `app-skill`) import from here so
|
|
8
|
+
* the on-disk formats never diverge.
|
|
9
|
+
*
|
|
10
|
+
* Supported clients and their config files:
|
|
11
|
+
* - claude-code / claude-code-cli → `.mcp.json` (project) or
|
|
12
|
+
* `~/.claude.json` (user). JSON `mcpServers[name] = entry`.
|
|
13
|
+
* - cowork → `~/.cowork/mcp.json`. Same JSON shape.
|
|
14
|
+
* - codex → `$CODEX_HOME/config.toml` when set,
|
|
15
|
+
* otherwise `~/.codex/config.toml`. `[mcp_servers.<name>]` block.
|
|
16
|
+
*
|
|
17
|
+
* Node-only. No npm deps — hand-rolled JSON merge + minimal TOML block merge.
|
|
18
|
+
*/
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import os from "node:os";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
export const CLIENTS = [
|
|
23
|
+
"claude-code",
|
|
24
|
+
"claude-code-cli",
|
|
25
|
+
"codex",
|
|
26
|
+
"cowork",
|
|
27
|
+
];
|
|
28
|
+
/** Build the HTTP MCP server entry for a deployed agent-native app. */
|
|
29
|
+
export function buildHttpMcpEntry(mcpUrl, token, headers) {
|
|
30
|
+
const mergedHeaders = {
|
|
31
|
+
...(headers ?? {}),
|
|
32
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
type: "http",
|
|
36
|
+
url: mcpUrl,
|
|
37
|
+
...(Object.keys(mergedHeaders).length ? { headers: mergedHeaders } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Config file locations.
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Cowork consumes MCP exactly like Claude Code (same JSON server-entry
|
|
45
|
+
* shape). Resolved lazily so `os.homedir()` reflects the current `$HOME`.
|
|
46
|
+
*/
|
|
47
|
+
export function coworkConfigPath() {
|
|
48
|
+
return path.join(os.homedir(), ".cowork", "mcp.json");
|
|
49
|
+
}
|
|
50
|
+
export function claudeCodeProjectConfig(baseDir) {
|
|
51
|
+
return path.join(baseDir, ".mcp.json");
|
|
52
|
+
}
|
|
53
|
+
export function claudeCodeUserConfig() {
|
|
54
|
+
return path.join(os.homedir(), ".claude.json");
|
|
55
|
+
}
|
|
56
|
+
export function codexConfigPath() {
|
|
57
|
+
const codexHome = process.env.CODEX_HOME?.trim();
|
|
58
|
+
if (codexHome)
|
|
59
|
+
return path.join(codexHome, "config.toml");
|
|
60
|
+
return path.join(os.homedir(), ".codex", "config.toml");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the on-disk config path for a client.
|
|
64
|
+
*
|
|
65
|
+
* `scope` only affects Claude Code / Claude Code CLI: `"user"` → the global
|
|
66
|
+
* `~/.claude.json`, anything else → the project-local `.mcp.json` rooted at
|
|
67
|
+
* `baseDir`.
|
|
68
|
+
*/
|
|
69
|
+
export function configPathFor(client, baseDir, scope) {
|
|
70
|
+
switch (client) {
|
|
71
|
+
case "claude-code":
|
|
72
|
+
case "claude-code-cli":
|
|
73
|
+
return scope === "user"
|
|
74
|
+
? claudeCodeUserConfig()
|
|
75
|
+
: claudeCodeProjectConfig(baseDir);
|
|
76
|
+
case "cowork":
|
|
77
|
+
return coworkConfigPath();
|
|
78
|
+
case "codex":
|
|
79
|
+
return codexConfigPath();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// JSON client configs (Claude Code, Claude Code CLI, Cowork)
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Read and parse a JSON config file.
|
|
87
|
+
*
|
|
88
|
+
* - Missing file → returns `{}` (fresh config).
|
|
89
|
+
* - Empty file → returns `{}` (treat as not-yet-initialised).
|
|
90
|
+
* - Non-empty file that fails to parse → throws a descriptive Error so the
|
|
91
|
+
* caller can surface it to the user instead of silently overwriting the
|
|
92
|
+
* file with only the new MCP entry (data-loss hazard).
|
|
93
|
+
*/
|
|
94
|
+
function readJsonFile(file) {
|
|
95
|
+
let raw;
|
|
96
|
+
try {
|
|
97
|
+
raw = fs.readFileSync(file, "utf-8");
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Missing (ENOENT) or unreadable file — treat as empty.
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
if (!raw.trim())
|
|
104
|
+
return {};
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(raw);
|
|
107
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new Error(`Cannot parse JSON config file: ${file}\n` +
|
|
111
|
+
`Fix or move the file and re-run. The file has not been modified.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Write `data` to `file` atomically: write a sibling temp file, then rename it
|
|
116
|
+
* over the target. `rename(2)` is atomic on the same filesystem, so a crash or
|
|
117
|
+
* `kill` mid-write can never leave a half-written/truncated file. This matters
|
|
118
|
+
* most for `~/.claude.json`, which is Claude Code's entire user state (projects,
|
|
119
|
+
* history, auth) — a torn write there would corrupt the user's whole config,
|
|
120
|
+
* not just our MCP entry. The temp file lives in the target's directory so the
|
|
121
|
+
* rename stays within one filesystem.
|
|
122
|
+
*/
|
|
123
|
+
export function writeFileAtomic(file, data) {
|
|
124
|
+
const dir = path.dirname(file);
|
|
125
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
126
|
+
// Preserve the target's existing permission bits. A fresh temp file would
|
|
127
|
+
// otherwise be created with the umask default (typically 0644), silently
|
|
128
|
+
// loosening a secret-bearing file the user locked down to 0600 (e.g. .env).
|
|
129
|
+
let mode;
|
|
130
|
+
try {
|
|
131
|
+
mode = fs.statSync(file).mode & 0o777;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Target doesn't exist yet — let the default creation mode apply.
|
|
135
|
+
}
|
|
136
|
+
const tmp = path.join(dir, `.${path.basename(file)}.tmp-${process.pid}`);
|
|
137
|
+
try {
|
|
138
|
+
fs.writeFileSync(tmp, data, "utf-8");
|
|
139
|
+
if (mode !== undefined)
|
|
140
|
+
fs.chmodSync(tmp, mode);
|
|
141
|
+
fs.renameSync(tmp, file);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
try {
|
|
145
|
+
fs.rmSync(tmp, { force: true });
|
|
146
|
+
}
|
|
147
|
+
catch { }
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Idempotently write `mcpServers[name] = entry` into a JSON config file.
|
|
153
|
+
* Pass `entry === null` to delete the named entry. Re-running with the same
|
|
154
|
+
* name replaces the existing entry in place — never duplicates.
|
|
155
|
+
*/
|
|
156
|
+
export function writeJsonMcpEntry(file, name, entry) {
|
|
157
|
+
const config = readJsonFile(file);
|
|
158
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
159
|
+
config.mcpServers = {};
|
|
160
|
+
}
|
|
161
|
+
if (entry === null) {
|
|
162
|
+
delete config.mcpServers[name];
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
config.mcpServers[name] = entry;
|
|
166
|
+
}
|
|
167
|
+
writeFileAtomic(file, JSON.stringify(config, null, 2) + "\n");
|
|
168
|
+
}
|
|
169
|
+
export function hasJsonMcpEntry(file, name) {
|
|
170
|
+
const config = readJsonFile(file);
|
|
171
|
+
return !!config?.mcpServers && name in config.mcpServers;
|
|
172
|
+
}
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Codex TOML (hand-rolled minimal block merge, no new dep)
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
function tomlQuote(s) {
|
|
177
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
178
|
+
}
|
|
179
|
+
function codexMcpHeader(name) {
|
|
180
|
+
return `[mcp_servers.${tomlQuote(name)}]`;
|
|
181
|
+
}
|
|
182
|
+
function legacyCodexMcpHeader(name) {
|
|
183
|
+
return /^[A-Za-z0-9_-]+$/.test(name) ? `[mcp_servers.${name}]` : null;
|
|
184
|
+
}
|
|
185
|
+
/** Build a `[mcp_servers.<name>]` block for an HTTP-type MCP server. */
|
|
186
|
+
export function buildCodexHttpBlock(name, mcpUrl, token, headers) {
|
|
187
|
+
const lines = [codexMcpHeader(name)];
|
|
188
|
+
lines.push(`url = ${tomlQuote(mcpUrl)}`);
|
|
189
|
+
const mergedHeaders = {
|
|
190
|
+
...(headers ?? {}),
|
|
191
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
192
|
+
};
|
|
193
|
+
const headerEntries = Object.entries(mergedHeaders);
|
|
194
|
+
if (headerEntries.length) {
|
|
195
|
+
lines.push(`http_headers = { ${headerEntries
|
|
196
|
+
.map(([key, value]) => `${tomlQuote(key)} = ${tomlQuote(value)}`)
|
|
197
|
+
.join(", ")} }`);
|
|
198
|
+
}
|
|
199
|
+
return lines.join("\n") + "\n";
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Replace (or append) the `[mcp_servers.<name>]` block in a TOML file
|
|
203
|
+
* without disturbing other content. A block is the header line plus every
|
|
204
|
+
* following line until the next top-level `[` table header or EOF. Pass
|
|
205
|
+
* `block === null` to remove the block.
|
|
206
|
+
*/
|
|
207
|
+
export function writeCodexBlock(file, name, block) {
|
|
208
|
+
let content = "";
|
|
209
|
+
try {
|
|
210
|
+
content = fs.readFileSync(file, "utf-8");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
content = "";
|
|
214
|
+
}
|
|
215
|
+
const headers = new Set([codexMcpHeader(name), legacyCodexMcpHeader(name)].filter(Boolean));
|
|
216
|
+
const lines = content.split(/\r?\n/);
|
|
217
|
+
const out = [];
|
|
218
|
+
let i = 0;
|
|
219
|
+
let removed = false;
|
|
220
|
+
while (i < lines.length) {
|
|
221
|
+
const line = lines[i];
|
|
222
|
+
if (headers.has(line.trim())) {
|
|
223
|
+
// Skip this block entirely (header + body until next table header).
|
|
224
|
+
removed = true;
|
|
225
|
+
i++;
|
|
226
|
+
while (i < lines.length && !/^\s*\[/.test(lines[i]))
|
|
227
|
+
i++;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
out.push(line);
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
let next = out
|
|
234
|
+
.join("\n")
|
|
235
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
236
|
+
.replace(/\n*$/, "\n");
|
|
237
|
+
if (block !== null) {
|
|
238
|
+
next = next.replace(/\n*$/, "\n");
|
|
239
|
+
if (next.trim().length)
|
|
240
|
+
next += "\n";
|
|
241
|
+
next += block;
|
|
242
|
+
}
|
|
243
|
+
if (block === null && !removed)
|
|
244
|
+
return; // nothing to do
|
|
245
|
+
writeFileAtomic(file, next);
|
|
246
|
+
}
|
|
247
|
+
export function codexHasBlock(file, name) {
|
|
248
|
+
try {
|
|
249
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
250
|
+
const headers = new Set([codexMcpHeader(name), legacyCodexMcpHeader(name)].filter(Boolean));
|
|
251
|
+
return content.split(/\r?\n/).some((line) => headers.has(line.trim()));
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Unified write helper
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
/**
|
|
261
|
+
* Idempotently write the HTTP MCP server entry for `serverName` into the
|
|
262
|
+
* given client's config file and return the file path that was written.
|
|
263
|
+
* Re-running replaces the same named entry — never duplicates.
|
|
264
|
+
*/
|
|
265
|
+
export function writeHttpEntryForClient(client, serverName, mcpUrl, token, baseDir, scope, headers) {
|
|
266
|
+
const file = configPathFor(client, baseDir, scope);
|
|
267
|
+
if (client === "codex") {
|
|
268
|
+
writeCodexBlock(file, serverName, buildCodexHttpBlock(serverName, mcpUrl, token, headers));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
writeJsonMcpEntry(file, serverName, buildHttpMcpEntry(mcpUrl, token, headers));
|
|
272
|
+
}
|
|
273
|
+
return file;
|
|
274
|
+
}
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Same-URL duplicate removal
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
/**
|
|
279
|
+
* Canonicalise a URL for comparison: strip hash, trailing slashes, and
|
|
280
|
+
* normalise the scheme+host+path. Returns `undefined` for invalid URLs.
|
|
281
|
+
*/
|
|
282
|
+
export function canonicalUrl(value) {
|
|
283
|
+
if (!value)
|
|
284
|
+
return undefined;
|
|
285
|
+
try {
|
|
286
|
+
const u = new URL(value);
|
|
287
|
+
u.hash = "";
|
|
288
|
+
u.search = "";
|
|
289
|
+
return u.toString().replace(/\/+$/, "");
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* After writing the canonical `serverName` entry into a JSON config file,
|
|
297
|
+
* remove any OTHER entries whose URL normalises to the same value as
|
|
298
|
+
* `mcpUrl`. This cleans up stale alias names, legacy default names, and
|
|
299
|
+
* leftover custom names that all pointed at the same server.
|
|
300
|
+
*
|
|
301
|
+
* Returns the list of entry names that were removed.
|
|
302
|
+
*/
|
|
303
|
+
export function removeJsonSameUrlDuplicates(file, mcpUrl, keepName) {
|
|
304
|
+
let config;
|
|
305
|
+
try {
|
|
306
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
307
|
+
if (!raw.trim())
|
|
308
|
+
return [];
|
|
309
|
+
config = JSON.parse(raw);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
const servers = config?.mcpServers;
|
|
315
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
const targetCanonical = canonicalUrl(mcpUrl);
|
|
319
|
+
if (!targetCanonical)
|
|
320
|
+
return [];
|
|
321
|
+
const toRemove = [];
|
|
322
|
+
for (const name of Object.keys(servers)) {
|
|
323
|
+
if (name === keepName)
|
|
324
|
+
continue;
|
|
325
|
+
const entry = servers[name];
|
|
326
|
+
if (!entry || typeof entry !== "object")
|
|
327
|
+
continue;
|
|
328
|
+
const entryUrl = typeof entry.url === "string" ? entry.url : undefined;
|
|
329
|
+
if (canonicalUrl(entryUrl) === targetCanonical) {
|
|
330
|
+
toRemove.push(name);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (toRemove.length === 0)
|
|
334
|
+
return [];
|
|
335
|
+
for (const name of toRemove) {
|
|
336
|
+
delete servers[name];
|
|
337
|
+
}
|
|
338
|
+
writeFileAtomic(file, JSON.stringify(config, null, 2) + "\n");
|
|
339
|
+
return toRemove;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* After writing the canonical `serverName` Codex block, remove any OTHER
|
|
343
|
+
* `[mcp_servers.*]` blocks in the same TOML file whose `url =` line
|
|
344
|
+
* normalises to the same value as `mcpUrl`. Returns removed entry names.
|
|
345
|
+
*/
|
|
346
|
+
export function removeCodexSameUrlDuplicates(file, mcpUrl, keepName) {
|
|
347
|
+
let content = "";
|
|
348
|
+
try {
|
|
349
|
+
content = fs.readFileSync(file, "utf-8");
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return [];
|
|
353
|
+
}
|
|
354
|
+
const targetCanonical = canonicalUrl(mcpUrl);
|
|
355
|
+
if (!targetCanonical)
|
|
356
|
+
return [];
|
|
357
|
+
const lines = content.split(/\r?\n/);
|
|
358
|
+
const out = [];
|
|
359
|
+
const removed = [];
|
|
360
|
+
let i = 0;
|
|
361
|
+
while (i < lines.length) {
|
|
362
|
+
const line = lines[i];
|
|
363
|
+
const trimmed = line.trim();
|
|
364
|
+
const quoted = trimmed.match(/^\[mcp_servers\."((?:\\.|[^"])*)"\]$/);
|
|
365
|
+
const bare = trimmed.match(/^\[mcp_servers\.([A-Za-z0-9_-]+)\]$/);
|
|
366
|
+
const serverName = quoted
|
|
367
|
+
? quoted[1].replace(/\\"/g, '"').replace(/\\\\/g, "\\")
|
|
368
|
+
: bare?.[1];
|
|
369
|
+
if (serverName !== undefined && serverName !== keepName) {
|
|
370
|
+
// Collect the block
|
|
371
|
+
const block = [line];
|
|
372
|
+
i++;
|
|
373
|
+
while (i < lines.length && !/^\s*\[/.test(lines[i])) {
|
|
374
|
+
block.push(lines[i]);
|
|
375
|
+
i++;
|
|
376
|
+
}
|
|
377
|
+
// Check url in block
|
|
378
|
+
const urlMatch = block
|
|
379
|
+
.join("\n")
|
|
380
|
+
.match(/^\s*url\s*=\s*"((?:\\.|[^"])*)"/m);
|
|
381
|
+
const blockUrl = urlMatch
|
|
382
|
+
? urlMatch[1].replace(/\\"/g, '"').replace(/\\\\/g, "\\")
|
|
383
|
+
: undefined;
|
|
384
|
+
if (canonicalUrl(blockUrl) === targetCanonical) {
|
|
385
|
+
removed.push(serverName);
|
|
386
|
+
// Skip this block (don't push to out)
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
// Not a duplicate — keep it
|
|
390
|
+
for (const l of block)
|
|
391
|
+
out.push(l);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
out.push(line);
|
|
395
|
+
i++;
|
|
396
|
+
}
|
|
397
|
+
if (removed.length === 0)
|
|
398
|
+
return [];
|
|
399
|
+
const next = out
|
|
400
|
+
.join("\n")
|
|
401
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
402
|
+
.replace(/\n*$/, "\n");
|
|
403
|
+
writeFileAtomic(file, next);
|
|
404
|
+
return removed;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Unified helper: after writing the canonical `serverName` entry for the
|
|
408
|
+
* given `client`, remove same-URL duplicates from its config file.
|
|
409
|
+
* Returns the list of removed names (empty if nothing was cleaned up).
|
|
410
|
+
*/
|
|
411
|
+
export function removeSameUrlDuplicatesForClient(client, serverName, mcpUrl, baseDir, scope) {
|
|
412
|
+
const file = configPathFor(client, baseDir, scope);
|
|
413
|
+
if (client === "codex") {
|
|
414
|
+
return removeCodexSameUrlDuplicates(file, mcpUrl, serverName);
|
|
415
|
+
}
|
|
416
|
+
return removeJsonSameUrlDuplicates(file, mcpUrl, serverName);
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=mcp-config-writers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-config-writers.js","sourceRoot":"","sources":["../src/mcp-config-writers.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared MCP client-config writers — canonical home.\n *\n * This is the single source of truth for how every agent-native CLI writes\n * MCP server entries into each supported client's on-disk config. Both the\n * standalone `@agent-native/skills` installer AND `@agent-native/core`\n * (`agent-native mcp install` / `connect` / `app-skill`) import from here so\n * the on-disk formats never diverge.\n *\n * Supported clients and their config files:\n * - claude-code / claude-code-cli → `.mcp.json` (project) or\n * `~/.claude.json` (user). JSON `mcpServers[name] = entry`.\n * - cowork → `~/.cowork/mcp.json`. Same JSON shape.\n * - codex → `$CODEX_HOME/config.toml` when set,\n * otherwise `~/.codex/config.toml`. `[mcp_servers.<name>]` block.\n *\n * Node-only. No npm deps — hand-rolled JSON merge + minimal TOML block merge.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type ClientId = \"claude-code\" | \"claude-code-cli\" | \"codex\" | \"cowork\";\n\nexport const CLIENTS: ClientId[] = [\n \"claude-code\",\n \"claude-code-cli\",\n \"codex\",\n \"cowork\",\n];\n\n/** The HTTP MCP server entry written into a JSON client config. */\nexport interface HttpMcpEntry {\n type: \"http\";\n url: string;\n headers?: Record<string, string>;\n}\n\n/** Build the HTTP MCP server entry for a deployed agent-native app. */\nexport function buildHttpMcpEntry(\n mcpUrl: string,\n token?: string,\n headers?: Record<string, string>,\n): HttpMcpEntry {\n const mergedHeaders = {\n ...(headers ?? {}),\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n };\n return {\n type: \"http\",\n url: mcpUrl,\n ...(Object.keys(mergedHeaders).length ? { headers: mergedHeaders } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// Config file locations.\n// ---------------------------------------------------------------------------\n\n/**\n * Cowork consumes MCP exactly like Claude Code (same JSON server-entry\n * shape). Resolved lazily so `os.homedir()` reflects the current `$HOME`.\n */\nexport function coworkConfigPath(): string {\n return path.join(os.homedir(), \".cowork\", \"mcp.json\");\n}\n\nexport function claudeCodeProjectConfig(baseDir: string): string {\n return path.join(baseDir, \".mcp.json\");\n}\n\nexport function claudeCodeUserConfig(): string {\n return path.join(os.homedir(), \".claude.json\");\n}\n\nexport function codexConfigPath(): string {\n const codexHome = process.env.CODEX_HOME?.trim();\n if (codexHome) return path.join(codexHome, \"config.toml\");\n return path.join(os.homedir(), \".codex\", \"config.toml\");\n}\n\n/**\n * Resolve the on-disk config path for a client.\n *\n * `scope` only affects Claude Code / Claude Code CLI: `\"user\"` → the global\n * `~/.claude.json`, anything else → the project-local `.mcp.json` rooted at\n * `baseDir`.\n */\nexport function configPathFor(\n client: ClientId,\n baseDir: string,\n scope: string | undefined,\n): string {\n switch (client) {\n case \"claude-code\":\n case \"claude-code-cli\":\n return scope === \"user\"\n ? claudeCodeUserConfig()\n : claudeCodeProjectConfig(baseDir);\n case \"cowork\":\n return coworkConfigPath();\n case \"codex\":\n return codexConfigPath();\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON client configs (Claude Code, Claude Code CLI, Cowork)\n// ---------------------------------------------------------------------------\n\n/**\n * Read and parse a JSON config file.\n *\n * - Missing file → returns `{}` (fresh config).\n * - Empty file → returns `{}` (treat as not-yet-initialised).\n * - Non-empty file that fails to parse → throws a descriptive Error so the\n * caller can surface it to the user instead of silently overwriting the\n * file with only the new MCP entry (data-loss hazard).\n */\nfunction readJsonFile(file: string): Record<string, any> {\n let raw: string;\n try {\n raw = fs.readFileSync(file, \"utf-8\");\n } catch {\n // Missing (ENOENT) or unreadable file — treat as empty.\n return {};\n }\n if (!raw.trim()) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? parsed : {};\n } catch {\n throw new Error(\n `Cannot parse JSON config file: ${file}\\n` +\n `Fix or move the file and re-run. The file has not been modified.`,\n );\n }\n}\n\n/**\n * Write `data` to `file` atomically: write a sibling temp file, then rename it\n * over the target. `rename(2)` is atomic on the same filesystem, so a crash or\n * `kill` mid-write can never leave a half-written/truncated file. This matters\n * most for `~/.claude.json`, which is Claude Code's entire user state (projects,\n * history, auth) — a torn write there would corrupt the user's whole config,\n * not just our MCP entry. The temp file lives in the target's directory so the\n * rename stays within one filesystem.\n */\nexport function writeFileAtomic(file: string, data: string): void {\n const dir = path.dirname(file);\n fs.mkdirSync(dir, { recursive: true });\n // Preserve the target's existing permission bits. A fresh temp file would\n // otherwise be created with the umask default (typically 0644), silently\n // loosening a secret-bearing file the user locked down to 0600 (e.g. .env).\n let mode: number | undefined;\n try {\n mode = fs.statSync(file).mode & 0o777;\n } catch {\n // Target doesn't exist yet — let the default creation mode apply.\n }\n const tmp = path.join(dir, `.${path.basename(file)}.tmp-${process.pid}`);\n try {\n fs.writeFileSync(tmp, data, \"utf-8\");\n if (mode !== undefined) fs.chmodSync(tmp, mode);\n fs.renameSync(tmp, file);\n } catch (err) {\n try {\n fs.rmSync(tmp, { force: true });\n } catch {}\n throw err;\n }\n}\n\n/**\n * Idempotently write `mcpServers[name] = entry` into a JSON config file.\n * Pass `entry === null` to delete the named entry. Re-running with the same\n * name replaces the existing entry in place — never duplicates.\n */\nexport function writeJsonMcpEntry(\n file: string,\n name: string,\n entry: Record<string, unknown> | null,\n): void {\n const config = readJsonFile(file);\n if (!config.mcpServers || typeof config.mcpServers !== \"object\") {\n config.mcpServers = {};\n }\n if (entry === null) {\n delete config.mcpServers[name];\n } else {\n config.mcpServers[name] = entry;\n }\n writeFileAtomic(file, JSON.stringify(config, null, 2) + \"\\n\");\n}\n\nexport function hasJsonMcpEntry(file: string, name: string): boolean {\n const config = readJsonFile(file);\n return !!config?.mcpServers && name in config.mcpServers;\n}\n\n// ---------------------------------------------------------------------------\n// Codex TOML (hand-rolled minimal block merge, no new dep)\n// ---------------------------------------------------------------------------\n\nfunction tomlQuote(s: string): string {\n return `\"${s.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n}\n\nfunction codexMcpHeader(name: string): string {\n return `[mcp_servers.${tomlQuote(name)}]`;\n}\n\nfunction legacyCodexMcpHeader(name: string): string | null {\n return /^[A-Za-z0-9_-]+$/.test(name) ? `[mcp_servers.${name}]` : null;\n}\n\n/** Build a `[mcp_servers.<name>]` block for an HTTP-type MCP server. */\nexport function buildCodexHttpBlock(\n name: string,\n mcpUrl: string,\n token?: string,\n headers?: Record<string, string>,\n): string {\n const lines: string[] = [codexMcpHeader(name)];\n lines.push(`url = ${tomlQuote(mcpUrl)}`);\n const mergedHeaders = {\n ...(headers ?? {}),\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n };\n const headerEntries = Object.entries(mergedHeaders);\n if (headerEntries.length) {\n lines.push(\n `http_headers = { ${headerEntries\n .map(([key, value]) => `${tomlQuote(key)} = ${tomlQuote(value)}`)\n .join(\", \")} }`,\n );\n }\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n/**\n * Replace (or append) the `[mcp_servers.<name>]` block in a TOML file\n * without disturbing other content. A block is the header line plus every\n * following line until the next top-level `[` table header or EOF. Pass\n * `block === null` to remove the block.\n */\nexport function writeCodexBlock(\n file: string,\n name: string,\n block: string | null,\n): void {\n let content = \"\";\n try {\n content = fs.readFileSync(file, \"utf-8\");\n } catch {\n content = \"\";\n }\n\n const headers = new Set(\n [codexMcpHeader(name), legacyCodexMcpHeader(name)].filter(\n Boolean,\n ) as string[],\n );\n const lines = content.split(/\\r?\\n/);\n const out: string[] = [];\n let i = 0;\n let removed = false;\n while (i < lines.length) {\n const line = lines[i];\n if (headers.has(line.trim())) {\n // Skip this block entirely (header + body until next table header).\n removed = true;\n i++;\n while (i < lines.length && !/^\\s*\\[/.test(lines[i])) i++;\n continue;\n }\n out.push(line);\n i++;\n }\n\n let next = out\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .replace(/\\n*$/, \"\\n\");\n if (block !== null) {\n next = next.replace(/\\n*$/, \"\\n\");\n if (next.trim().length) next += \"\\n\";\n next += block;\n }\n if (block === null && !removed) return; // nothing to do\n\n writeFileAtomic(file, next);\n}\n\nexport function codexHasBlock(file: string, name: string): boolean {\n try {\n const content = fs.readFileSync(file, \"utf-8\");\n const headers = new Set(\n [codexMcpHeader(name), legacyCodexMcpHeader(name)].filter(\n Boolean,\n ) as string[],\n );\n return content.split(/\\r?\\n/).some((line) => headers.has(line.trim()));\n } catch {\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified write helper\n// ---------------------------------------------------------------------------\n\n/**\n * Idempotently write the HTTP MCP server entry for `serverName` into the\n * given client's config file and return the file path that was written.\n * Re-running replaces the same named entry — never duplicates.\n */\nexport function writeHttpEntryForClient(\n client: ClientId,\n serverName: string,\n mcpUrl: string,\n token: string | undefined,\n baseDir: string,\n scope: string | undefined,\n headers?: Record<string, string>,\n): string {\n const file = configPathFor(client, baseDir, scope);\n if (client === \"codex\") {\n writeCodexBlock(\n file,\n serverName,\n buildCodexHttpBlock(serverName, mcpUrl, token, headers),\n );\n } else {\n writeJsonMcpEntry(\n file,\n serverName,\n buildHttpMcpEntry(mcpUrl, token, headers) as unknown as Record<\n string,\n unknown\n >,\n );\n }\n return file;\n}\n\n// ---------------------------------------------------------------------------\n// Same-URL duplicate removal\n// ---------------------------------------------------------------------------\n\n/**\n * Canonicalise a URL for comparison: strip hash, trailing slashes, and\n * normalise the scheme+host+path. Returns `undefined` for invalid URLs.\n */\nexport function canonicalUrl(value: string | undefined): string | undefined {\n if (!value) return undefined;\n try {\n const u = new URL(value);\n u.hash = \"\";\n u.search = \"\";\n return u.toString().replace(/\\/+$/, \"\");\n } catch {\n return undefined;\n }\n}\n\n/**\n * After writing the canonical `serverName` entry into a JSON config file,\n * remove any OTHER entries whose URL normalises to the same value as\n * `mcpUrl`. This cleans up stale alias names, legacy default names, and\n * leftover custom names that all pointed at the same server.\n *\n * Returns the list of entry names that were removed.\n */\nexport function removeJsonSameUrlDuplicates(\n file: string,\n mcpUrl: string,\n keepName: string,\n): string[] {\n let config: Record<string, any>;\n try {\n const raw = fs.readFileSync(file, \"utf-8\");\n if (!raw.trim()) return [];\n config = JSON.parse(raw);\n } catch {\n return [];\n }\n const servers = config?.mcpServers;\n if (!servers || typeof servers !== \"object\" || Array.isArray(servers)) {\n return [];\n }\n const targetCanonical = canonicalUrl(mcpUrl);\n if (!targetCanonical) return [];\n\n const toRemove: string[] = [];\n for (const name of Object.keys(servers)) {\n if (name === keepName) continue;\n const entry = servers[name];\n if (!entry || typeof entry !== \"object\") continue;\n const entryUrl = typeof entry.url === \"string\" ? entry.url : undefined;\n if (canonicalUrl(entryUrl) === targetCanonical) {\n toRemove.push(name);\n }\n }\n if (toRemove.length === 0) return [];\n for (const name of toRemove) {\n delete servers[name];\n }\n writeFileAtomic(file, JSON.stringify(config, null, 2) + \"\\n\");\n return toRemove;\n}\n\n/**\n * After writing the canonical `serverName` Codex block, remove any OTHER\n * `[mcp_servers.*]` blocks in the same TOML file whose `url =` line\n * normalises to the same value as `mcpUrl`. Returns removed entry names.\n */\nexport function removeCodexSameUrlDuplicates(\n file: string,\n mcpUrl: string,\n keepName: string,\n): string[] {\n let content = \"\";\n try {\n content = fs.readFileSync(file, \"utf-8\");\n } catch {\n return [];\n }\n const targetCanonical = canonicalUrl(mcpUrl);\n if (!targetCanonical) return [];\n\n const lines = content.split(/\\r?\\n/);\n const out: string[] = [];\n const removed: string[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i];\n const trimmed = line.trim();\n const quoted = trimmed.match(/^\\[mcp_servers\\.\"((?:\\\\.|[^\"])*)\"\\]$/);\n const bare = trimmed.match(/^\\[mcp_servers\\.([A-Za-z0-9_-]+)\\]$/);\n const serverName = quoted\n ? quoted[1].replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\")\n : bare?.[1];\n if (serverName !== undefined && serverName !== keepName) {\n // Collect the block\n const block: string[] = [line];\n i++;\n while (i < lines.length && !/^\\s*\\[/.test(lines[i])) {\n block.push(lines[i]);\n i++;\n }\n // Check url in block\n const urlMatch = block\n .join(\"\\n\")\n .match(/^\\s*url\\s*=\\s*\"((?:\\\\.|[^\"])*)\"/m);\n const blockUrl = urlMatch\n ? urlMatch[1].replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\")\n : undefined;\n if (canonicalUrl(blockUrl) === targetCanonical) {\n removed.push(serverName);\n // Skip this block (don't push to out)\n continue;\n }\n // Not a duplicate — keep it\n for (const l of block) out.push(l);\n continue;\n }\n out.push(line);\n i++;\n }\n\n if (removed.length === 0) return [];\n const next = out\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .replace(/\\n*$/, \"\\n\");\n writeFileAtomic(file, next);\n return removed;\n}\n\n/**\n * Unified helper: after writing the canonical `serverName` entry for the\n * given `client`, remove same-URL duplicates from its config file.\n * Returns the list of removed names (empty if nothing was cleaned up).\n */\nexport function removeSameUrlDuplicatesForClient(\n client: ClientId,\n serverName: string,\n mcpUrl: string,\n baseDir: string,\n scope: string | undefined,\n): string[] {\n const file = configPathFor(client, baseDir, scope);\n if (client === \"codex\") {\n return removeCodexSameUrlDuplicates(file, mcpUrl, serverName);\n }\n return removeJsonSameUrlDuplicates(file, mcpUrl, serverName);\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CliTelemetryOptions {
|
|
2
|
+
/** Stable identifier for the emitting CLI, e.g. "skills-installer". */
|
|
3
|
+
cli: string;
|
|
4
|
+
cliVersion: string;
|
|
5
|
+
command: string;
|
|
6
|
+
interactive: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface CliTelemetry {
|
|
9
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
10
|
+
flush(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createCliTelemetry(options: CliTelemetryOptions): CliTelemetry;
|
|
13
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AA2CD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAmD7E"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort install-funnel telemetry for the skills CLI.
|
|
3
|
+
*
|
|
4
|
+
* Events are POSTed to the first-party Agent Native Analytics endpoint
|
|
5
|
+
* (analytics.agent-native.com/track) using a PUBLIC, write-only key — the same
|
|
6
|
+
* mechanism every agent-native app uses to report client-side events. Nothing
|
|
7
|
+
* here ever blocks or throws into the install flow: sends are fire-and-forget
|
|
8
|
+
* and `flush()` awaits any in-flight requests with a short cap before exit.
|
|
9
|
+
*
|
|
10
|
+
* Privacy: we report skill NAMES, client ids, scope, counts, platform, and the
|
|
11
|
+
* CLI version — never file paths, repo names, cwd, skill sources, or anything
|
|
12
|
+
* user-identifying. A random per-machine install id (unique installs) and a
|
|
13
|
+
* per-invocation run id (step-by-step dropoff) are the only identifiers.
|
|
14
|
+
*
|
|
15
|
+
* Opt out with DO_NOT_TRACK=1 or AGENT_NATIVE_TELEMETRY_DISABLED=1.
|
|
16
|
+
*/
|
|
17
|
+
import crypto from "node:crypto";
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import os from "node:os";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
// Public, write-only analytics key. Safe to embed (revocable from the Analytics
|
|
22
|
+
// settings UI). Override with AGENT_NATIVE_ANALYTICS_PUBLIC_KEY for testing or
|
|
23
|
+
// to point telemetry at a different first-party analytics instance.
|
|
24
|
+
// guard:allow-public-key -- first-party analytics write key is public by design.
|
|
25
|
+
const EMBEDDED_PUBLIC_KEY = "anpk_dc523e34b99bc34d76e82d94c46593544e4a8509a4bfc93c";
|
|
26
|
+
const DEFAULT_ENDPOINT = "https://analytics.agent-native.com/track";
|
|
27
|
+
const FLUSH_TIMEOUT_MS = 1500;
|
|
28
|
+
function resolvePublicKey() {
|
|
29
|
+
const fromEnv = process.env.AGENT_NATIVE_ANALYTICS_PUBLIC_KEY?.trim();
|
|
30
|
+
return fromEnv || EMBEDDED_PUBLIC_KEY;
|
|
31
|
+
}
|
|
32
|
+
function resolveEndpoint() {
|
|
33
|
+
const fromEnv = process.env.AGENT_NATIVE_ANALYTICS_ENDPOINT?.trim();
|
|
34
|
+
return (fromEnv || DEFAULT_ENDPOINT).replace(/\/+$/, "");
|
|
35
|
+
}
|
|
36
|
+
function telemetryDisabled() {
|
|
37
|
+
return (process.env.DO_NOT_TRACK === "1" ||
|
|
38
|
+
process.env.AGENT_NATIVE_TELEMETRY_DISABLED === "1" ||
|
|
39
|
+
process.env.NODE_ENV === "test" ||
|
|
40
|
+
typeof fetch !== "function");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read (or lazily create) a stable per-machine install id, shared across both
|
|
44
|
+
* skills CLIs so one developer counts once. Best-effort: an unwritable home
|
|
45
|
+
* directory just yields an ephemeral id for this run.
|
|
46
|
+
*/
|
|
47
|
+
function resolveInstallId() {
|
|
48
|
+
try {
|
|
49
|
+
const dir = path.join(os.homedir(), ".agent-native");
|
|
50
|
+
const file = path.join(dir, "installation-id");
|
|
51
|
+
const existing = fs.existsSync(file)
|
|
52
|
+
? fs.readFileSync(file, "utf8").trim()
|
|
53
|
+
: "";
|
|
54
|
+
if (existing)
|
|
55
|
+
return existing;
|
|
56
|
+
const id = crypto.randomUUID();
|
|
57
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
58
|
+
fs.writeFileSync(file, `${id}\n`, "utf8");
|
|
59
|
+
return id;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return crypto.randomUUID();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function createCliTelemetry(options) {
|
|
66
|
+
const publicKey = resolvePublicKey();
|
|
67
|
+
const disabled = telemetryDisabled() || !publicKey;
|
|
68
|
+
const endpoint = resolveEndpoint();
|
|
69
|
+
const installId = disabled ? "" : resolveInstallId();
|
|
70
|
+
const runId = crypto.randomUUID();
|
|
71
|
+
const inFlight = new Set();
|
|
72
|
+
const base = {
|
|
73
|
+
cli: options.cli,
|
|
74
|
+
cliVersion: options.cliVersion,
|
|
75
|
+
command: options.command,
|
|
76
|
+
node: process.version,
|
|
77
|
+
platform: process.platform,
|
|
78
|
+
ci: process.env.CI === "true",
|
|
79
|
+
interactive: options.interactive,
|
|
80
|
+
runId,
|
|
81
|
+
installId,
|
|
82
|
+
};
|
|
83
|
+
function track(event, properties) {
|
|
84
|
+
if (disabled)
|
|
85
|
+
return;
|
|
86
|
+
const body = JSON.stringify({
|
|
87
|
+
publicKey,
|
|
88
|
+
event,
|
|
89
|
+
anonymousId: installId,
|
|
90
|
+
sessionId: runId,
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
properties: { ...base, ...properties },
|
|
93
|
+
});
|
|
94
|
+
const promise = fetch(endpoint, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body,
|
|
98
|
+
keepalive: true,
|
|
99
|
+
})
|
|
100
|
+
.then(() => undefined)
|
|
101
|
+
.catch(() => undefined);
|
|
102
|
+
inFlight.add(promise);
|
|
103
|
+
void promise.finally(() => inFlight.delete(promise));
|
|
104
|
+
}
|
|
105
|
+
async function flush() {
|
|
106
|
+
if (disabled || inFlight.size === 0)
|
|
107
|
+
return;
|
|
108
|
+
await Promise.race([
|
|
109
|
+
Promise.allSettled([...inFlight]),
|
|
110
|
+
new Promise((resolve) => setTimeout(resolve, FLUSH_TIMEOUT_MS)),
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
return { track, flush };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Best-effort install-funnel telemetry for the skills CLI.\n *\n * Events are POSTed to the first-party Agent Native Analytics endpoint\n * (analytics.agent-native.com/track) using a PUBLIC, write-only key — the same\n * mechanism every agent-native app uses to report client-side events. Nothing\n * here ever blocks or throws into the install flow: sends are fire-and-forget\n * and `flush()` awaits any in-flight requests with a short cap before exit.\n *\n * Privacy: we report skill NAMES, client ids, scope, counts, platform, and the\n * CLI version — never file paths, repo names, cwd, skill sources, or anything\n * user-identifying. A random per-machine install id (unique installs) and a\n * per-invocation run id (step-by-step dropoff) are the only identifiers.\n *\n * Opt out with DO_NOT_TRACK=1 or AGENT_NATIVE_TELEMETRY_DISABLED=1.\n */\nimport crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\n// Public, write-only analytics key. Safe to embed (revocable from the Analytics\n// settings UI). Override with AGENT_NATIVE_ANALYTICS_PUBLIC_KEY for testing or\n// to point telemetry at a different first-party analytics instance.\n// guard:allow-public-key -- first-party analytics write key is public by design.\nconst EMBEDDED_PUBLIC_KEY =\n \"anpk_dc523e34b99bc34d76e82d94c46593544e4a8509a4bfc93c\";\nconst DEFAULT_ENDPOINT = \"https://analytics.agent-native.com/track\";\nconst FLUSH_TIMEOUT_MS = 1500;\n\nexport interface CliTelemetryOptions {\n /** Stable identifier for the emitting CLI, e.g. \"skills-installer\". */\n cli: string;\n cliVersion: string;\n command: string;\n interactive: boolean;\n}\n\nexport interface CliTelemetry {\n track(event: string, properties?: Record<string, unknown>): void;\n flush(): Promise<void>;\n}\n\nfunction resolvePublicKey(): string {\n const fromEnv = process.env.AGENT_NATIVE_ANALYTICS_PUBLIC_KEY?.trim();\n return fromEnv || EMBEDDED_PUBLIC_KEY;\n}\n\nfunction resolveEndpoint(): string {\n const fromEnv = process.env.AGENT_NATIVE_ANALYTICS_ENDPOINT?.trim();\n return (fromEnv || DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n}\n\nfunction telemetryDisabled(): boolean {\n return (\n process.env.DO_NOT_TRACK === \"1\" ||\n process.env.AGENT_NATIVE_TELEMETRY_DISABLED === \"1\" ||\n process.env.NODE_ENV === \"test\" ||\n typeof fetch !== \"function\"\n );\n}\n\n/**\n * Read (or lazily create) a stable per-machine install id, shared across both\n * skills CLIs so one developer counts once. Best-effort: an unwritable home\n * directory just yields an ephemeral id for this run.\n */\nfunction resolveInstallId(): string {\n try {\n const dir = path.join(os.homedir(), \".agent-native\");\n const file = path.join(dir, \"installation-id\");\n const existing = fs.existsSync(file)\n ? fs.readFileSync(file, \"utf8\").trim()\n : \"\";\n if (existing) return existing;\n const id = crypto.randomUUID();\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(file, `${id}\\n`, \"utf8\");\n return id;\n } catch {\n return crypto.randomUUID();\n }\n}\n\nexport function createCliTelemetry(options: CliTelemetryOptions): CliTelemetry {\n const publicKey = resolvePublicKey();\n const disabled = telemetryDisabled() || !publicKey;\n const endpoint = resolveEndpoint();\n const installId = disabled ? \"\" : resolveInstallId();\n const runId = crypto.randomUUID();\n const inFlight = new Set<Promise<void>>();\n\n const base = {\n cli: options.cli,\n cliVersion: options.cliVersion,\n command: options.command,\n node: process.version,\n platform: process.platform,\n ci: process.env.CI === \"true\",\n interactive: options.interactive,\n runId,\n installId,\n };\n\n function track(event: string, properties?: Record<string, unknown>): void {\n if (disabled) return;\n const body = JSON.stringify({\n publicKey,\n event,\n anonymousId: installId,\n sessionId: runId,\n timestamp: new Date().toISOString(),\n properties: { ...base, ...properties },\n });\n const promise = fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n keepalive: true,\n })\n .then(() => undefined)\n .catch(() => undefined);\n inFlight.add(promise);\n void promise.finally(() => inFlight.delete(promise));\n }\n\n async function flush(): Promise<void> {\n if (disabled || inFlight.size === 0) return;\n await Promise.race([\n Promise.allSettled([...inFlight]),\n new Promise((resolve) => setTimeout(resolve, FLUSH_TIMEOUT_MS)),\n ]);\n }\n\n return { track, flush };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Install BuilderIO skills for Codex and Claude Code.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"test": "vitest --run src --passWithNoTests",
|
|
37
37
|
"prepublishOnly": "npm run build"
|
|
38
38
|
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@agent-native/core": ">=0.48.4"
|
|
41
|
+
},
|
|
39
42
|
"devDependencies": {
|
|
43
|
+
"@agent-native/core": "workspace:*",
|
|
40
44
|
"@types/node": "^25.9.2",
|
|
41
45
|
"typescript": "catalog:",
|
|
42
46
|
"vitest": "catalog:"
|