@truealter/sdk 0.2.4 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,8 +1,16 @@
1
1
  'use strict';
2
2
 
3
+ var p256 = require('@noble/curves/p256');
4
+ var sha256 = require('@noble/hashes/sha256');
5
+ var utils = require('@noble/hashes/utils');
3
6
  var ed25519 = require('@noble/ed25519');
4
7
  var sha512 = require('@noble/hashes/sha512');
5
- var utils = require('@noble/hashes/utils');
8
+ var child_process = require('child_process');
9
+ var os = require('os');
10
+ var path = require('path');
11
+ var process$1 = require('process');
12
+ var fs = require('fs');
13
+ var crypto$1 = require('crypto');
6
14
 
7
15
  function _interopNamespace(e) {
8
16
  if (e && e.__esModule) return e;
@@ -24,7 +32,166 @@ function _interopNamespace(e) {
24
32
 
25
33
  var ed25519__namespace = /*#__PURE__*/_interopNamespace(ed25519);
26
34
 
35
+ var __defProp = Object.defineProperty;
36
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
37
+ var __getOwnPropNames = Object.getOwnPropertyNames;
38
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
39
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
40
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
41
+ }) : x)(function(x) {
42
+ if (typeof require !== "undefined") return require.apply(this, arguments);
43
+ throw Error('Dynamic require of "' + x + '" is not supported');
44
+ });
45
+ var __esm = (fn, res) => function __init() {
46
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
47
+ };
48
+ var __export = (target, all) => {
49
+ for (var name in all)
50
+ __defProp(target, name, { get: all[name], enumerable: true });
51
+ };
52
+ var __copyProps = (to, from, except, desc) => {
53
+ if (from && typeof from === "object" || typeof from === "function") {
54
+ for (let key of __getOwnPropNames(from))
55
+ if (!__hasOwnProp.call(to, key) && key !== except)
56
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
57
+ }
58
+ return to;
59
+ };
60
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
61
+
62
+ // node_modules/tsup/assets/cjs_shims.js
63
+ var init_cjs_shims = __esm({
64
+ "node_modules/tsup/assets/cjs_shims.js"() {
65
+ }
66
+ });
67
+
68
+ // src/signing.ts
69
+ var signing_exports = {};
70
+ __export(signing_exports, {
71
+ canonicalArgsSha256: () => canonicalArgsSha256,
72
+ canonicalStringify: () => canonicalStringify,
73
+ loadPrivateKey: () => loadPrivateKey,
74
+ signInvocation: () => signInvocation
75
+ });
76
+ function canonicalStringify(value) {
77
+ return stringifyInner(value);
78
+ }
79
+ function stringifyInner(value) {
80
+ if (value === null) return "null";
81
+ if (value === void 0) {
82
+ throw new TypeError("canonicalStringify: undefined is not representable in JSON");
83
+ }
84
+ if (typeof value === "boolean") return value ? "true" : "false";
85
+ if (typeof value === "number") {
86
+ if (!Number.isFinite(value)) {
87
+ throw new TypeError("canonicalStringify: non-finite numbers are not representable");
88
+ }
89
+ return JSON.stringify(value);
90
+ }
91
+ if (typeof value === "string") return encodeString(value);
92
+ if (Array.isArray(value)) {
93
+ return "[" + value.map((v) => stringifyInner(v)).join(",") + "]";
94
+ }
95
+ if (typeof value === "object") {
96
+ const obj = value;
97
+ const keys = Object.keys(obj).sort();
98
+ return "{" + keys.map((k) => encodeString(k) + ":" + stringifyInner(obj[k])).join(",") + "}";
99
+ }
100
+ throw new TypeError(`canonicalStringify: unsupported type ${typeof value}`);
101
+ }
102
+ function encodeString(s) {
103
+ return JSON.stringify(s);
104
+ }
105
+ function canonicalArgsSha256(toolArgs) {
106
+ const canonical = canonicalStringify(toolArgs ?? {});
107
+ const bytes = new TextEncoder().encode(canonical);
108
+ const digest = sha256.sha256(bytes);
109
+ return bytesToHex(digest);
110
+ }
111
+ function bytesToHex(bytes) {
112
+ let out = "";
113
+ for (let i = 0; i < bytes.length; i++) {
114
+ out += bytes[i].toString(16).padStart(2, "0");
115
+ }
116
+ return out;
117
+ }
118
+ function base64urlEncode(bytes) {
119
+ const raw = typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes;
120
+ if (typeof Buffer !== "undefined") {
121
+ return Buffer.from(raw).toString("base64url");
122
+ }
123
+ let binary = "";
124
+ for (let i = 0; i < raw.length; i++) binary += String.fromCharCode(raw[i]);
125
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
126
+ }
127
+ function loadPrivateKey(key) {
128
+ if (key instanceof Uint8Array) {
129
+ if (key.length !== 32) {
130
+ throw new TypeError("ES256 raw private key must be 32 bytes.");
131
+ }
132
+ return key;
133
+ }
134
+ if (typeof key === "string" && key.includes("-----BEGIN")) {
135
+ const nodeCrypto = __require("crypto");
136
+ const keyObj = nodeCrypto.createPrivateKey({ key, format: "pem" });
137
+ const jwk = keyObj.export({ format: "jwk" });
138
+ if (jwk.crv !== "P-256" || !jwk.d) {
139
+ throw new TypeError("PEM is not a P-256 private key.");
140
+ }
141
+ return base64urlDecodeToBytes(jwk.d);
142
+ }
143
+ throw new TypeError("loadPrivateKey: expected Uint8Array(32) or PEM string.");
144
+ }
145
+ function base64urlDecodeToBytes(s) {
146
+ const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
147
+ const b64 = (s + pad).replace(/-/g, "+").replace(/_/g, "/");
148
+ if (typeof Buffer !== "undefined") {
149
+ return new Uint8Array(Buffer.from(b64, "base64"));
150
+ }
151
+ const binary = atob(b64);
152
+ const out = new Uint8Array(binary.length);
153
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
154
+ return out;
155
+ }
156
+ function signInvocation(toolName, toolArgs, options) {
157
+ const { kid, privateKey, handle } = options;
158
+ const nonce = options.nonce ?? base64urlEncode(utils.randomBytes(24));
159
+ const iat = options.iatSeconds ?? Math.floor(Date.now() / 1e3);
160
+ const claims = {
161
+ tool: toolName,
162
+ args_sha256: canonicalArgsSha256(toolArgs ?? {}),
163
+ nonce,
164
+ iat,
165
+ iss: handle
166
+ };
167
+ const headerB64 = base64urlEncode(JSON.stringify({ alg: "ES256", kid }));
168
+ const payloadB64 = base64urlEncode(JSON.stringify(claims));
169
+ const signingInput = `${headerB64}.${payloadB64}`;
170
+ const signingBytes = new TextEncoder().encode(signingInput);
171
+ const dBytes = loadPrivateKey(privateKey);
172
+ const digest = sha256.sha256(signingBytes);
173
+ const sig = p256.p256.sign(digest, dBytes, { prehash: false });
174
+ const sigBytes = sig.toCompactRawBytes();
175
+ const sigB64 = base64urlEncode(sigBytes);
176
+ return `${signingInput}.${sigB64}`;
177
+ }
178
+ var init_signing = __esm({
179
+ "src/signing.ts"() {
180
+ init_cjs_shims();
181
+ }
182
+ });
183
+
184
+ // src/index.ts
185
+ init_cjs_shims();
186
+
187
+ // src/client.ts
188
+ init_cjs_shims();
189
+
190
+ // src/discovery.ts
191
+ init_cjs_shims();
192
+
27
193
  // src/errors.ts
194
+ init_cjs_shims();
28
195
  var AlterError = class extends Error {
29
196
  code;
30
197
  cause;
@@ -123,7 +290,12 @@ async function discover(domain, opts = {}) {
123
290
  try {
124
291
  const dnsHit = await tryDns(host);
125
292
  if (dnsHit) {
126
- const result = { url: dnsHit, transport: "streamable-http", source: "dns" };
293
+ const parsed = validateDiscoveredUrl(dnsHit, "dns");
294
+ const result = {
295
+ url: parsed.toString().replace(/\/$/, ""),
296
+ transport: "streamable-http",
297
+ source: "dns"
298
+ };
127
299
  if (cache) _cache.set(host, result);
128
300
  return result;
129
301
  }
@@ -164,6 +336,28 @@ function normaliseDomain(input) {
164
336
  if (!host) throw new AlterDiscoveryError(`Empty domain: "${input}"`);
165
337
  return host;
166
338
  }
339
+ function validateDiscoveredUrl(url, source) {
340
+ let parsed;
341
+ try {
342
+ parsed = new URL(url);
343
+ } catch {
344
+ throw new AlterDiscoveryError(`${source}: malformed URL ${url}`);
345
+ }
346
+ if (parsed.protocol !== "https:") {
347
+ throw new AlterDiscoveryError(
348
+ `${source}: non-https MCP endpoint rejected (got ${parsed.protocol}//${parsed.hostname})`
349
+ );
350
+ }
351
+ if (parsed.username || parsed.password) {
352
+ throw new AlterDiscoveryError(
353
+ `${source}: MCP endpoint must not contain userinfo (user:pass@host)`
354
+ );
355
+ }
356
+ if (!parsed.hostname) {
357
+ throw new AlterDiscoveryError(`${source}: MCP endpoint missing hostname`);
358
+ }
359
+ return parsed;
360
+ }
167
361
  async function tryDns(host) {
168
362
  let resolveTxt;
169
363
  try {
@@ -224,14 +418,17 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
224
418
  if (file === "mcp.json") {
225
419
  const remotes = doc.remotes || [];
226
420
  const remote = remotes.find((r) => r.transportType === "streamable-http" || r.transportType === "http");
227
- const url2 = remote?.url || doc.url;
228
- if (!url2) return null;
229
- return { url: url2, transport: "streamable-http", source: "mcp.json", raw: doc };
421
+ const rawUrl = remote?.url || doc.url;
422
+ if (!rawUrl) return null;
423
+ const parsed = validateDiscoveredUrl(rawUrl, "mcp.json");
424
+ return { url: parsed.toString().replace(/\/$/, ""), transport: "streamable-http", source: "mcp.json", raw: doc };
230
425
  }
231
426
  const mcpHost = doc.mcp;
232
427
  if (!mcpHost) return null;
428
+ const normalised = ensureMcpPath(mcpHost);
429
+ validateDiscoveredUrl(normalised, "alter.json");
233
430
  return {
234
- url: ensureMcpPath(mcpHost),
431
+ url: normalised,
235
432
  transport: "streamable-http",
236
433
  source: "alter.json",
237
434
  publicKey: doc.pk,
@@ -250,7 +447,11 @@ function ensureMcpPath(url) {
250
447
  }
251
448
  }
252
449
 
450
+ // src/mcp.ts
451
+ init_cjs_shims();
452
+
253
453
  // src/x402.ts
454
+ init_cjs_shims();
254
455
  var X402Client = class {
255
456
  signer;
256
457
  maxPerQuery;
@@ -277,11 +478,14 @@ var X402Client = class {
277
478
  if (!this.assets.has(envelope.asset)) {
278
479
  throw new AlterError("PAYMENT_REQUIRED", `asset ${envelope.asset} not permitted by client policy`);
279
480
  }
280
- if (this.maxPerQuery !== void 0 && Number(envelope.amount) > this.maxPerQuery) {
281
- throw new AlterError(
282
- "PAYMENT_REQUIRED",
283
- `quote ${envelope.amount} ${envelope.asset} exceeds maxPerQuery ${this.maxPerQuery}`
284
- );
481
+ if (this.maxPerQuery !== void 0) {
482
+ const amt = Number(envelope.amount);
483
+ if (!Number.isFinite(amt) || amt < 0 || amt > this.maxPerQuery) {
484
+ throw new AlterError(
485
+ "PAYMENT_REQUIRED",
486
+ `quote ${envelope.amount} ${envelope.asset} exceeds maxPerQuery ${this.maxPerQuery}`
487
+ );
488
+ }
285
489
  }
286
490
  if (!this.signer) {
287
491
  throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
@@ -339,6 +543,7 @@ var MCPClient = class {
339
543
  maxRetries;
340
544
  clientInfo;
341
545
  x402;
546
+ signing;
342
547
  requestCounter = 0;
343
548
  initialised = false;
344
549
  constructor(opts = {}) {
@@ -349,6 +554,7 @@ var MCPClient = class {
349
554
  this.maxRetries = opts.maxRetries ?? 2;
350
555
  this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
351
556
  this.x402 = opts.x402;
557
+ this.signing = opts.signing;
352
558
  }
353
559
  /**
354
560
  * Send the MCP `initialize` handshake and capture the resulting session
@@ -435,6 +641,7 @@ var MCPClient = class {
435
641
  method
436
642
  };
437
643
  if (params !== void 0) payload.params = params;
644
+ const signatureHeader = this.buildSignatureHeader(method, params);
438
645
  let attempt = 0;
439
646
  let lastErr = null;
440
647
  while (attempt <= this.maxRetries) {
@@ -445,7 +652,7 @@ var MCPClient = class {
445
652
  try {
446
653
  resp = await this.fetchImpl(this.endpoint, {
447
654
  method: "POST",
448
- headers: this.buildHeaders(),
655
+ headers: this.buildHeaders(signatureHeader),
449
656
  body: JSON.stringify(payload),
450
657
  signal: controller.signal
451
658
  });
@@ -472,7 +679,8 @@ var MCPClient = class {
472
679
  throw new AlterPaymentRequired(this.guessToolName(payload), envelope);
473
680
  }
474
681
  if (resp.status === 429) {
475
- const retryAfter = Number(resp.headers.get("Retry-After") ?? 60);
682
+ const rawRetryAfter = Number(resp.headers.get("Retry-After") ?? 60);
683
+ const retryAfter = Number.isFinite(rawRetryAfter) && rawRetryAfter >= 0 ? Math.min(rawRetryAfter, 300) : 60;
476
684
  if (attempt > this.maxRetries) {
477
685
  throw new AlterRateLimited(`HTTP 429 on ${method}`, retryAfter);
478
686
  }
@@ -508,7 +716,7 @@ var MCPClient = class {
508
716
  }
509
717
  throw lastErr ?? new AlterNetworkError(`MCP ${method}: exhausted retries`);
510
718
  }
511
- buildHeaders() {
719
+ buildHeaders(extra) {
512
720
  const headers = {
513
721
  "Content-Type": "application/json",
514
722
  Accept: "application/json",
@@ -516,8 +724,27 @@ var MCPClient = class {
516
724
  };
517
725
  if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
518
726
  if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
727
+ if (extra) Object.assign(headers, extra);
519
728
  return headers;
520
729
  }
730
+ /**
731
+ * Produce the `Mcp-Invocation-Signature` header for a `tools/call`
732
+ * payload, when signing is configured. Returns `undefined` when no
733
+ * signing key is attached or the method is not `tools/call`.
734
+ */
735
+ buildSignatureHeader(method, params) {
736
+ if (!this.signing) return void 0;
737
+ if (method !== "tools/call") return void 0;
738
+ const p = params;
739
+ if (!p?.name) return void 0;
740
+ const { signInvocation: signInvocation2 } = (init_signing(), __toCommonJS(signing_exports));
741
+ const headerValue = signInvocation2(p.name, p.arguments ?? {}, {
742
+ kid: this.signing.kid,
743
+ privateKey: this.signing.privateKey,
744
+ handle: this.signing.handle
745
+ });
746
+ return { "Mcp-Invocation-Signature": headerValue };
747
+ }
521
748
  async extractPaymentEnvelope(resp) {
522
749
  const headerValue = resp.headers.get("X-402-Payment") ?? resp.headers.get("x-402-payment");
523
750
  if (headerValue) {
@@ -555,6 +782,12 @@ async function safeText(resp) {
555
782
  return "";
556
783
  }
557
784
  }
785
+
786
+ // src/provenance.ts
787
+ init_cjs_shims();
788
+
789
+ // src/auth.ts
790
+ init_cjs_shims();
558
791
  ed25519__namespace.etc.sha512Sync = (...m) => sha512.sha512(ed25519__namespace.etc.concatBytes(...m));
559
792
  function generateKeypair() {
560
793
  const privateKey = utils.randomBytes(32);
@@ -593,14 +826,14 @@ async function verify(publicKeyHex, signatureHex, message) {
593
826
  }
594
827
  function encodeDid(publicKey) {
595
828
  const bytes = typeof publicKey === "string" ? utils.hexToBytes(publicKey) : publicKey;
596
- return `ed25519:${base64urlEncode(bytes)}`;
829
+ return `ed25519:${base64urlEncode2(bytes)}`;
597
830
  }
598
831
  function decodeDid(did) {
599
832
  const ed25519Match = did.match(/^ed25519:(.+)$/);
600
833
  if (ed25519Match) return base64urlDecode(ed25519Match[1]);
601
834
  throw new Error(`Unrecognised DID encoding: ${did}`);
602
835
  }
603
- function base64urlEncode(bytes) {
836
+ function base64urlEncode2(bytes) {
604
837
  let b64;
605
838
  if (typeof Buffer !== "undefined") {
606
839
  b64 = Buffer.from(bytes).toString("base64");
@@ -1069,7 +1302,14 @@ var AlterClient = class {
1069
1302
  }
1070
1303
  };
1071
1304
 
1305
+ // src/index.ts
1306
+ init_signing();
1307
+
1308
+ // src/adapters/claude-code.ts
1309
+ init_cjs_shims();
1310
+
1072
1311
  // src/adapters/generic-mcp.ts
1312
+ init_cjs_shims();
1073
1313
  function generateGenericMcpConfig(opts = {}) {
1074
1314
  const serverName = opts.serverName ?? "alter";
1075
1315
  const headers = { ...opts.headers ?? {} };
@@ -1089,11 +1329,488 @@ function generateClaudeConfig(opts = {}) {
1089
1329
  }
1090
1330
 
1091
1331
  // src/adapters/cursor.ts
1332
+ init_cjs_shims();
1092
1333
  function generateCursorConfig(opts = {}) {
1093
1334
  return generateGenericMcpConfig({ serverName: "alter", ...opts });
1094
1335
  }
1095
1336
 
1337
+ // src/adapters/claude-desktop.ts
1338
+ init_cjs_shims();
1339
+ function generateClaudeDesktopConfig(opts = {}) {
1340
+ const serverName = opts.serverName ?? "alter";
1341
+ const bridgeCommand = opts.bridgeCommand ?? "alter-mcp-bridge";
1342
+ const env2 = {};
1343
+ env2.ALTER_MCP_ENDPOINT = opts.endpoint ?? DEFAULT_ENDPOINT;
1344
+ if (opts.apiKey) env2.ALTER_API_KEY = opts.apiKey;
1345
+ const entry = {
1346
+ command: bridgeCommand,
1347
+ env: env2,
1348
+ description: "ALTER Identity \u2014 psychometric identity field for AI agents"
1349
+ };
1350
+ if (opts.extraArgs && opts.extraArgs.length > 0) {
1351
+ entry.args = [...opts.extraArgs];
1352
+ }
1353
+ return { mcpServers: { [serverName]: entry } };
1354
+ }
1355
+
1356
+ // src/wire/index.ts
1357
+ init_cjs_shims();
1358
+
1359
+ // src/meta.ts
1360
+ init_cjs_shims();
1361
+ var SDK_NAME = "@truealter/sdk";
1362
+ var SDK_VERSION = "0.3.0";
1363
+
1364
+ // src/wire/paths.ts
1365
+ init_cjs_shims();
1366
+ var HOME = os.homedir();
1367
+ var PLAT = os.platform();
1368
+ function appData() {
1369
+ return process$1.env.APPDATA ?? path.join(HOME, "AppData", "Roaming");
1370
+ }
1371
+ function xdgConfig() {
1372
+ return process$1.env.XDG_CONFIG_HOME ?? path.join(HOME, ".config");
1373
+ }
1374
+ function macAppSupport() {
1375
+ return path.join(HOME, "Library", "Application Support");
1376
+ }
1377
+ function claudeDesktopConfigPath() {
1378
+ if (PLAT === "darwin") return path.join(macAppSupport(), "Claude", "claude_desktop_config.json");
1379
+ if (PLAT === "win32") return path.join(appData(), "Claude", "claude_desktop_config.json");
1380
+ return path.join(xdgConfig(), "Claude", "claude_desktop_config.json");
1381
+ }
1382
+ function claudeDesktopDir() {
1383
+ if (PLAT === "darwin") return path.join(macAppSupport(), "Claude");
1384
+ if (PLAT === "win32") return path.join(appData(), "Claude");
1385
+ return path.join(xdgConfig(), "Claude");
1386
+ }
1387
+ function vscodeConfigPath() {
1388
+ if (PLAT === "darwin") return path.join(macAppSupport(), "Code", "User", "mcp.json");
1389
+ if (PLAT === "win32") return path.join(appData(), "Code", "User", "mcp.json");
1390
+ return path.join(xdgConfig(), "Code", "User", "mcp.json");
1391
+ }
1392
+ function vscodeDir() {
1393
+ if (PLAT === "darwin") return path.join(macAppSupport(), "Code", "User");
1394
+ if (PLAT === "win32") return path.join(appData(), "Code", "User");
1395
+ return path.join(xdgConfig(), "Code", "User");
1396
+ }
1397
+ var cursorDir = path.join(HOME, ".cursor");
1398
+ var cursorConfigPath = path.join(cursorDir, "mcp.json");
1399
+ var claudeCodeProbeDir = path.join(HOME, ".claude");
1400
+ var CLAUDE_CODE = {
1401
+ id: "claude-code",
1402
+ label: "Claude Code",
1403
+ configPath: null,
1404
+ probeDir: claudeCodeProbeDir,
1405
+ rootKey: "mcpServers"
1406
+ };
1407
+ var CURSOR = {
1408
+ id: "cursor",
1409
+ label: "Cursor",
1410
+ configPath: cursorConfigPath,
1411
+ probeDir: cursorDir,
1412
+ rootKey: "mcpServers"
1413
+ };
1414
+ var CLAUDE_DESKTOP = {
1415
+ id: "claude-desktop",
1416
+ label: "Claude Desktop",
1417
+ configPath: claudeDesktopConfigPath(),
1418
+ probeDir: claudeDesktopDir(),
1419
+ rootKey: "mcpServers"
1420
+ };
1421
+ var VSCODE = {
1422
+ id: "vscode",
1423
+ label: "VS Code",
1424
+ configPath: vscodeConfigPath(),
1425
+ probeDir: vscodeDir(),
1426
+ // VS Code's user-scoped mcp.json uses `servers`, not `mcpServers`.
1427
+ rootKey: "servers"
1428
+ };
1429
+ var ALL_CLIENTS = [CLAUDE_CODE, CURSOR, CLAUDE_DESKTOP, VSCODE];
1430
+ function alterConfigDir() {
1431
+ return path.join(xdgConfig(), "alter");
1432
+ }
1433
+ function wireStatePath() {
1434
+ return path.join(alterConfigDir(), "wire-state.json");
1435
+ }
1436
+
1437
+ // src/wire/probe.ts
1438
+ init_cjs_shims();
1439
+ function probeClaudeCode() {
1440
+ try {
1441
+ const result = child_process.spawnSync("claude", ["--version"], {
1442
+ encoding: "utf8",
1443
+ shell: process.platform === "win32",
1444
+ timeout: 5e3
1445
+ });
1446
+ if (result.error) {
1447
+ return {
1448
+ client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
1449
+ installed: false,
1450
+ reason: `claude binary not on PATH (${result.error.message})`
1451
+ };
1452
+ }
1453
+ if (result.status === 0) {
1454
+ return {
1455
+ client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
1456
+ installed: true,
1457
+ version: result.stdout.trim() || void 0,
1458
+ reason: "claude --version returned 0"
1459
+ };
1460
+ }
1461
+ return {
1462
+ client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
1463
+ installed: false,
1464
+ reason: `claude --version exited ${String(result.status)}`
1465
+ };
1466
+ } catch (err) {
1467
+ return {
1468
+ client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
1469
+ installed: false,
1470
+ reason: err.message
1471
+ };
1472
+ }
1473
+ }
1474
+ function probeByDir(id) {
1475
+ const client = ALL_CLIENTS.find((c) => c.id === id);
1476
+ if (!client) throw new Error(`unknown client id: ${id}`);
1477
+ const installed = fs.existsSync(client.probeDir);
1478
+ return {
1479
+ client,
1480
+ installed,
1481
+ reason: installed ? `found ${client.probeDir}` : `no directory at ${client.probeDir}`
1482
+ };
1483
+ }
1484
+ function probeAll() {
1485
+ return [
1486
+ probeClaudeCode(),
1487
+ probeByDir("cursor"),
1488
+ probeByDir("claude-desktop"),
1489
+ probeByDir("vscode")
1490
+ ];
1491
+ }
1492
+
1493
+ // src/wire/sync.ts
1494
+ init_cjs_shims();
1495
+ var SYNC_PREFIXES = [
1496
+ // iCloud Drive — both the new and legacy mounts.
1497
+ "Library/Mobile Documents/com~apple~CloudDocs",
1498
+ "iCloud Drive",
1499
+ // OneDrive variants Microsoft ships across editions.
1500
+ "OneDrive",
1501
+ "OneDrive - ",
1502
+ // Dropbox standard + enterprise mounts.
1503
+ "Dropbox",
1504
+ "Dropbox (",
1505
+ // Google Drive (ALTER does not integrate with Google; still refuse).
1506
+ "Google Drive",
1507
+ "GoogleDrive",
1508
+ "CloudStorage/GoogleDrive",
1509
+ // Box, pCloud, Sync.com, MEGA — high-signal names worth refusing.
1510
+ "Box Sync",
1511
+ "pCloud Drive",
1512
+ "Sync.com",
1513
+ "MEGAsync"
1514
+ ];
1515
+ function detectSyncedVolume(path$1) {
1516
+ const absolute = path.resolve(path$1);
1517
+ const normalised = os.platform() === "win32" ? absolute.replace(/\\/g, "/") : absolute;
1518
+ for (const prefix of SYNC_PREFIXES) {
1519
+ if (normalised.includes(`/${prefix}/`) || normalised.includes(`/${prefix}`)) {
1520
+ return { refused: true, matchedPrefix: prefix, resolvedPath: absolute };
1521
+ }
1522
+ }
1523
+ return null;
1524
+ }
1525
+
1526
+ // src/wire/state.ts
1527
+ init_cjs_shims();
1528
+ var WIRE_STATE_VERSION = 1;
1529
+ function readWireState() {
1530
+ const path = wireStatePath();
1531
+ if (!fs.existsSync(path)) return null;
1532
+ try {
1533
+ const parsed = JSON.parse(fs.readFileSync(path, "utf8"));
1534
+ if (parsed.version !== WIRE_STATE_VERSION) {
1535
+ throw new Error(
1536
+ `wire-state.json version ${String(parsed.version)} is not supported by this SDK (expected ${WIRE_STATE_VERSION})`
1537
+ );
1538
+ }
1539
+ return parsed;
1540
+ } catch (err) {
1541
+ throw new Error(`failed to parse wire-state.json: ${err.message}`);
1542
+ }
1543
+ }
1544
+ function writeWireState(state) {
1545
+ const path$1 = wireStatePath();
1546
+ fs.mkdirSync(path.dirname(path$1), { recursive: true, mode: 448 });
1547
+ fs.writeFileSync(path$1, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
1548
+ }
1549
+
1550
+ // src/wire/write.ts
1551
+ init_cjs_shims();
1552
+ function sha2562(bytes) {
1553
+ return crypto$1.createHash("sha256").update(bytes).digest("hex");
1554
+ }
1555
+ function atomicJsonMerge(opts) {
1556
+ const { path: path$1, timestamp, merge, idempotent = true } = opts;
1557
+ const tmpPath = `${path$1}.alter-tmp-${timestamp}`;
1558
+ const backupPath = `${path$1}.alter-backup-${timestamp}`;
1559
+ let existed = false;
1560
+ let preBytes = null;
1561
+ let parsed = {};
1562
+ if (fs.existsSync(path$1)) {
1563
+ existed = true;
1564
+ preBytes = fs.readFileSync(path$1, "utf8");
1565
+ if (preBytes.trim().length > 0) {
1566
+ try {
1567
+ parsed = JSON.parse(preBytes);
1568
+ } catch (err) {
1569
+ throw new Error(
1570
+ `refusing to wire ${path$1}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter-identity wire\`.`
1571
+ );
1572
+ }
1573
+ if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
1574
+ throw new Error(`refusing to wire ${path$1}: existing JSON root is not an object`);
1575
+ }
1576
+ }
1577
+ }
1578
+ const merged = merge(parsed);
1579
+ const serialised = JSON.stringify(merged, null, 2) + "\n";
1580
+ if (idempotent && preBytes !== null && preBytes === serialised) {
1581
+ return {
1582
+ path: path$1,
1583
+ backupPath: null,
1584
+ preSha256: sha2562(preBytes),
1585
+ postSha256: sha2562(preBytes),
1586
+ noop: true
1587
+ };
1588
+ }
1589
+ fs.mkdirSync(path.dirname(path$1), { recursive: true });
1590
+ fs.writeFileSync(tmpPath, serialised, { mode: 384 });
1591
+ try {
1592
+ if (existed) fs.copyFileSync(path$1, backupPath);
1593
+ fs.renameSync(tmpPath, path$1);
1594
+ } catch (err) {
1595
+ try {
1596
+ fs.unlinkSync(tmpPath);
1597
+ } catch {
1598
+ }
1599
+ throw err;
1600
+ }
1601
+ return {
1602
+ path: path$1,
1603
+ backupPath: existed ? backupPath : null,
1604
+ preSha256: preBytes === null ? null : sha2562(preBytes),
1605
+ postSha256: sha2562(serialised),
1606
+ noop: false
1607
+ };
1608
+ }
1609
+ function restoreFromBackup(path, backupPath) {
1610
+ if (backupPath === null) {
1611
+ if (fs.existsSync(path)) fs.unlinkSync(path);
1612
+ return;
1613
+ }
1614
+ if (!fs.existsSync(backupPath)) {
1615
+ throw new Error(`cannot restore ${path}: backup missing at ${backupPath}`);
1616
+ }
1617
+ fs.renameSync(backupPath, path);
1618
+ }
1619
+
1620
+ // src/wire/index.ts
1621
+ var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
1622
+ var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
1623
+ function clientById(id) {
1624
+ const hit = ALL_CLIENTS.find((c) => c.id === id);
1625
+ if (!hit) throw new Error(`unknown client id: ${id}`);
1626
+ return hit;
1627
+ }
1628
+ function wire(opts = {}) {
1629
+ const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
1630
+ const apiKey = opts.apiKey;
1631
+ const probes = probeAll();
1632
+ const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
1633
+ const ts = TIMESTAMP();
1634
+ const targets = [];
1635
+ for (const id of selection) {
1636
+ const probe = id === "claude-code" ? probeClaudeCode() : probeByDir(id);
1637
+ if (!probe.installed && opts.skipMissing !== false) {
1638
+ targets.push({
1639
+ client: id,
1640
+ method: id === "claude-code" ? "cli" : "file",
1641
+ status: "skipped",
1642
+ ...id === "claude-code" ? { command: "" } : { path: clientById(id).configPath ?? "", backupPath: null, rootKey: clientById(id).rootKey, serverName: "alter", preSha256: null, postSha256: "" },
1643
+ reason: probe.reason
1644
+ });
1645
+ continue;
1646
+ }
1647
+ try {
1648
+ if (id === "claude-code") {
1649
+ targets.push(wireClaudeCode({ endpoint, apiKey }));
1650
+ } else {
1651
+ targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
1652
+ }
1653
+ } catch (err) {
1654
+ const message = err.message;
1655
+ targets.push({
1656
+ client: id,
1657
+ method: id === "claude-code" ? "cli" : "file",
1658
+ status: "failed",
1659
+ ...id === "claude-code" ? { command: "" } : { path: clientById(id).configPath ?? "", backupPath: null, rootKey: clientById(id).rootKey, serverName: "alter", preSha256: null, postSha256: "" },
1660
+ reason: message
1661
+ });
1662
+ }
1663
+ }
1664
+ const state = {
1665
+ version: 1,
1666
+ sdkVersion: SDK_VERSION,
1667
+ writtenAt: ISO_NOW(),
1668
+ endpoint,
1669
+ targets
1670
+ };
1671
+ writeWireState(state);
1672
+ return { state, probes };
1673
+ }
1674
+ function wireFileTarget(args) {
1675
+ const client = clientById(args.id);
1676
+ if (!client.configPath) {
1677
+ throw new Error(`client ${client.id} has no file-based config path`);
1678
+ }
1679
+ const sync = detectSyncedVolume(client.configPath);
1680
+ if (sync) {
1681
+ throw new Error(
1682
+ `refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices \u2014 move the config off the sync root, or run wire on the device you want to target.`
1683
+ );
1684
+ }
1685
+ const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey });
1686
+ const rootKey = client.rootKey;
1687
+ const serverName = "alter";
1688
+ const result = atomicJsonMerge({
1689
+ path: client.configPath,
1690
+ timestamp: args.timestamp,
1691
+ merge: (existing) => {
1692
+ const bucket = existing[rootKey] ?? {};
1693
+ const source = entry.mcpServers.alter;
1694
+ return {
1695
+ ...existing,
1696
+ [rootKey]: {
1697
+ ...bucket,
1698
+ [serverName]: source
1699
+ }
1700
+ };
1701
+ }
1702
+ });
1703
+ return {
1704
+ client: args.id,
1705
+ method: "file",
1706
+ status: result.noop ? "already-wired" : "written",
1707
+ path: result.path,
1708
+ backupPath: result.backupPath,
1709
+ rootKey,
1710
+ serverName,
1711
+ preSha256: result.preSha256,
1712
+ postSha256: result.postSha256
1713
+ };
1714
+ }
1715
+ function wireClaudeCode(args) {
1716
+ const cmd = "claude";
1717
+ const argList = [
1718
+ "mcp",
1719
+ "add",
1720
+ "--scope",
1721
+ "user",
1722
+ "--transport",
1723
+ "http",
1724
+ "alter",
1725
+ args.endpoint
1726
+ ];
1727
+ if (args.apiKey) {
1728
+ argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
1729
+ }
1730
+ const full = `${cmd} ${argList.join(" ")}`;
1731
+ const run = child_process.spawnSync(cmd, argList, {
1732
+ encoding: "utf8",
1733
+ shell: process.platform === "win32",
1734
+ timeout: 1e4
1735
+ });
1736
+ if (run.error) {
1737
+ return {
1738
+ client: "claude-code",
1739
+ method: "cli",
1740
+ status: "failed",
1741
+ command: full,
1742
+ stdout: run.stdout,
1743
+ stderr: run.stderr,
1744
+ reason: run.error.message
1745
+ };
1746
+ }
1747
+ const stderr = (run.stderr ?? "").toLowerCase();
1748
+ const alreadyExists = stderr.includes("already exists") || stderr.includes("already configured");
1749
+ if (run.status === 0) {
1750
+ return { client: "claude-code", method: "cli", status: "written", command: full, stdout: run.stdout, stderr: run.stderr };
1751
+ }
1752
+ if (alreadyExists) {
1753
+ return { client: "claude-code", method: "cli", status: "already-wired", command: full, stdout: run.stdout, stderr: run.stderr };
1754
+ }
1755
+ return {
1756
+ client: "claude-code",
1757
+ method: "cli",
1758
+ status: "failed",
1759
+ command: full,
1760
+ stdout: run.stdout,
1761
+ stderr: run.stderr,
1762
+ reason: `claude mcp add exited ${String(run.status)}`
1763
+ };
1764
+ }
1765
+ function unwire() {
1766
+ const state = readWireState();
1767
+ const undone = [];
1768
+ if (!state || state.targets.length === 0) {
1769
+ return { state, undone };
1770
+ }
1771
+ for (const target of state.targets) {
1772
+ try {
1773
+ if (target.method === "file") {
1774
+ if (target.status === "written") {
1775
+ restoreFromBackup(target.path, target.backupPath);
1776
+ undone.push({ client: target.client, action: target.backupPath ? "restored" : "removed" });
1777
+ } else {
1778
+ undone.push({ client: target.client, action: "skipped", reason: `target status was ${target.status}` });
1779
+ }
1780
+ } else if (target.method === "cli") {
1781
+ if (target.status === "written") {
1782
+ const run = child_process.spawnSync("claude", ["mcp", "remove", "--scope", "user", "alter"], {
1783
+ encoding: "utf8",
1784
+ shell: process.platform === "win32",
1785
+ timeout: 1e4
1786
+ });
1787
+ if (run.error) {
1788
+ undone.push({ client: target.client, action: "failed", reason: run.error.message });
1789
+ } else if (run.status === 0) {
1790
+ undone.push({ client: target.client, action: "cli-removed" });
1791
+ } else {
1792
+ undone.push({ client: target.client, action: "failed", reason: `claude mcp remove exited ${String(run.status)}` });
1793
+ }
1794
+ } else {
1795
+ undone.push({ client: target.client, action: "skipped", reason: `target status was ${target.status}` });
1796
+ }
1797
+ }
1798
+ } catch (err) {
1799
+ undone.push({ client: target.client, action: "failed", reason: err.message });
1800
+ }
1801
+ }
1802
+ writeWireState({
1803
+ version: 1,
1804
+ sdkVersion: state.sdkVersion,
1805
+ writtenAt: ISO_NOW(),
1806
+ endpoint: state.endpoint,
1807
+ targets: []
1808
+ });
1809
+ return { state, undone };
1810
+ }
1811
+
1096
1812
  // src/types.ts
1813
+ init_cjs_shims();
1097
1814
  var FREE_TOOL_NAMES = [
1098
1815
  "hello_agent",
1099
1816
  "alter_resolve_handle",
@@ -1248,10 +1965,7 @@ var TOOL_BLAST_RADIUS = {
1248
1965
  query_graph_similarity: "high"
1249
1966
  };
1250
1967
 
1251
- // src/index.ts
1252
- var SDK_NAME = "@truealter/sdk";
1253
- var SDK_VERSION = "0.2.4";
1254
-
1968
+ exports.ALL_CLIENTS = ALL_CLIENTS;
1255
1969
  exports.AlterAuthError = AlterAuthError;
1256
1970
  exports.AlterClient = AlterClient;
1257
1971
  exports.AlterDiscoveryError = AlterDiscoveryError;
@@ -1263,6 +1977,9 @@ exports.AlterProvenanceError = AlterProvenanceError;
1263
1977
  exports.AlterRateLimited = AlterRateLimited;
1264
1978
  exports.AlterTimeoutError = AlterTimeoutError;
1265
1979
  exports.AlterToolError = AlterToolError;
1980
+ exports.CLAUDE_CODE = CLAUDE_CODE;
1981
+ exports.CLAUDE_DESKTOP = CLAUDE_DESKTOP;
1982
+ exports.CURSOR = CURSOR;
1266
1983
  exports.DEFAULT_DOMAIN = DEFAULT_DOMAIN;
1267
1984
  exports.DEFAULT_ENDPOINT = DEFAULT_ENDPOINT;
1268
1985
  exports.DEFAULT_VERIFY_AT_ALLOWLIST = DEFAULT_VERIFY_AT_ALLOWLIST;
@@ -1275,22 +1992,37 @@ exports.SDK_VERSION = SDK_VERSION;
1275
1992
  exports.TOOL_BLAST_RADIUS = TOOL_BLAST_RADIUS;
1276
1993
  exports.TOOL_COSTS = TOOL_COSTS;
1277
1994
  exports.TOOL_TIERS = TOOL_TIERS;
1995
+ exports.VSCODE = VSCODE;
1278
1996
  exports.X402Client = X402Client;
1279
1997
  exports.base64urlDecode = base64urlDecode;
1280
- exports.base64urlEncode = base64urlEncode;
1998
+ exports.base64urlEncode = base64urlEncode2;
1999
+ exports.canonicalArgsSha256 = canonicalArgsSha256;
2000
+ exports.canonicalStringify = canonicalStringify;
1281
2001
  exports.clearDiscoveryCache = clearDiscoveryCache;
1282
2002
  exports.decodeDid = decodeDid;
2003
+ exports.detectSyncedVolume = detectSyncedVolume;
1283
2004
  exports.discover = discover;
1284
2005
  exports.encodeDid = encodeDid;
1285
2006
  exports.fetchPublicKeys = fetchPublicKeys;
1286
2007
  exports.generateClaudeConfig = generateClaudeConfig;
2008
+ exports.generateClaudeDesktopConfig = generateClaudeDesktopConfig;
1287
2009
  exports.generateCursorConfig = generateCursorConfig;
1288
2010
  exports.generateGenericMcpConfig = generateGenericMcpConfig;
1289
2011
  exports.generateKeypair = generateKeypair;
1290
2012
  exports.keypairFromPrivateKey = keypairFromPrivateKey;
2013
+ exports.loadPrivateKey = loadPrivateKey;
1291
2014
  exports.parsePaymentHeader = parsePaymentHeader;
2015
+ exports.probeAll = probeAll;
2016
+ exports.probeByDir = probeByDir;
2017
+ exports.probeClaudeCode = probeClaudeCode;
2018
+ exports.readWireState = readWireState;
1292
2019
  exports.resolveVerifyAt = resolveVerifyAt;
2020
+ exports.sha256 = sha2562;
1293
2021
  exports.sign = sign;
2022
+ exports.signInvocation = signInvocation;
2023
+ exports.unwire = unwire;
1294
2024
  exports.verify = verify;
1295
2025
  exports.verifyProvenance = verifyProvenance;
1296
2026
  exports.verifyToolSignatures = verifyToolSignatures;
2027
+ exports.wire = wire;
2028
+ exports.writeWireState = writeWireState;