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