@truealter/sdk 0.5.1 → 0.5.3

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.
@@ -2,26 +2,21 @@
2
2
  import { p256 } from '@noble/curves/p256';
3
3
  import { sha256 } from '@noble/hashes/sha256';
4
4
  import { hexToBytes, bytesToHex as bytesToHex$1, randomBytes } from '@noble/hashes/utils';
5
- import { createHash, createPrivateKey } from 'crypto';
6
- import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, renameSync, copyFileSync } from 'fs';
5
+ import { createPublicKey, verify, createHash, createPrivateKey } from 'crypto';
6
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, renameSync, statSync, chmodSync, copyFileSync } from 'fs';
7
7
  import { homedir, platform } from 'os';
8
8
  import { join, dirname, resolve } from 'path';
9
9
  import { env, stderr, exit, argv, stdout, stdin } from 'process';
10
10
  import { createInterface } from 'readline';
11
11
  import * as ed25519 from '@noble/ed25519';
12
12
  import { sha512 } from '@noble/hashes/sha512';
13
+ import { fileURLToPath } from 'url';
13
14
  import { spawnSync } from 'child_process';
14
15
 
15
16
  var __defProp = Object.defineProperty;
16
17
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
17
18
  var __getOwnPropNames = Object.getOwnPropertyNames;
18
19
  var __hasOwnProp = Object.prototype.hasOwnProperty;
19
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
20
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
21
- }) : x)(function(x) {
22
- if (typeof require !== "undefined") return require.apply(this, arguments);
23
- throw Error('Dynamic require of "' + x + '" is not supported');
24
- });
25
20
  var __esm = (fn, res) => function __init() {
26
21
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
27
22
  };
@@ -368,11 +363,11 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
368
363
  }
369
364
  if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
370
365
  throw new AlterNetworkError(
371
- `${url} \u2192 redirect rejected (discovery must not follow redirects; validate the server configuration)`
366
+ `${url} -> redirect rejected (discovery must not follow redirects; validate the server configuration)`
372
367
  );
373
368
  }
374
369
  if (resp.status === 404) return null;
375
- if (!resp.ok) throw new AlterNetworkError(`${url} \u2192 HTTP ${resp.status}`);
370
+ if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
376
371
  const doc = await resp.json();
377
372
  if (file === "mcp.json") {
378
373
  const remotes = doc.remotes || [];
@@ -405,6 +400,309 @@ function ensureMcpPath(url) {
405
400
  return url;
406
401
  }
407
402
  }
403
+
404
+ // src/meta.ts
405
+ var SDK_NAME = "@truealter/sdk";
406
+ var SDK_VERSION = "0.5.3" ;
407
+
408
+ // src/floor-preflight.ts
409
+ var MIN_VERSION_ENDPOINT = "/v1/clients/min-version";
410
+ var CLIENT_ID = "alter-identity";
411
+ var CLIENT_CHANNEL = "npm";
412
+ var IN_MEMORY_TTL_DEFAULT_MS = 60 * 60 * 1e3;
413
+ var IN_MEMORY_TTL_MIN_MS = 60 * 1e3;
414
+ var IN_MEMORY_TTL_MAX_MS = 24 * 60 * 60 * 1e3;
415
+ var DISK_FRESH_MS = 24 * 60 * 60 * 1e3;
416
+ var DISK_WARN_MS = 7 * 24 * 60 * 60 * 1e3;
417
+ var FETCH_TIMEOUT_MS = 4e3;
418
+ function computeKeyId(publicKeyPem) {
419
+ if (!publicKeyPem) return "00000000";
420
+ const pub = createPublicKey({ key: publicKeyPem, format: "pem" });
421
+ const jwk = pub.export({ format: "jwk" });
422
+ const rawBytes = Buffer.from(jwk.x, "base64url");
423
+ return createHash("sha256").update(rawBytes).digest("hex").slice(0, 8);
424
+ }
425
+ function canonicalJson(obj) {
426
+ return JSON.stringify(sortKeysDeep(obj));
427
+ }
428
+ function sortKeysDeep(value) {
429
+ if (Array.isArray(value)) {
430
+ return value.map(sortKeysDeep);
431
+ }
432
+ if (value !== null && typeof value === "object") {
433
+ const obj = value;
434
+ const sorted = {};
435
+ for (const k of Object.keys(obj).sort()) {
436
+ sorted[k] = sortKeysDeep(obj[k]);
437
+ }
438
+ return sorted;
439
+ }
440
+ return value;
441
+ }
442
+ var KNOWN_FLOOR_PUBLIC_KEYS = {
443
+ "8aa59e05": `-----BEGIN PUBLIC KEY-----
444
+ MCowBQYDK2VwAyEAgqw28dlniOuiTE1f4BxCPSEgMLaPtHsO8wN5RWEwEhE=
445
+ -----END PUBLIC KEY-----`,
446
+ "640f7d9a": `-----BEGIN PUBLIC KEY-----
447
+ MCowBQYDK2VwAyEARzvAWayDwHvZRfOZizGZe+/a7PF082WGhyMS3tx06H4=
448
+ -----END PUBLIC KEY-----`
449
+ };
450
+ var BelowFloorError = class extends Error {
451
+ name = "BelowFloorError";
452
+ code = "client_below_floor";
453
+ client_version;
454
+ min_version;
455
+ upgrade_cmd;
456
+ channel;
457
+ envelope;
458
+ constructor(envelope) {
459
+ super(envelope.error.message);
460
+ this.envelope = envelope;
461
+ this.client_version = envelope.error.client_version;
462
+ this.min_version = envelope.error.min_version;
463
+ this.upgrade_cmd = envelope.error.upgrade_cmd;
464
+ this.channel = envelope.error.channel;
465
+ Object.setPrototypeOf(this, new.target.prototype);
466
+ }
467
+ };
468
+ var memCache = null;
469
+ async function checkMinVersion(opts = {}) {
470
+ const apiBase = opts.apiBase ?? defaultApiBase();
471
+ const clientVersion = opts.clientVersion ?? SDK_VERSION;
472
+ const clientId = opts.clientId ?? CLIENT_ID;
473
+ const channel = opts.channel ?? CLIENT_CHANNEL;
474
+ const knownKeys = opts.knownFloorPublicKeys ?? KNOWN_FLOOR_PUBLIC_KEYS;
475
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
476
+ const now = opts.now ?? Date.now;
477
+ const cachePath = opts.diskCachePath === void 0 ? defaultDiskCachePath() : opts.diskCachePath;
478
+ const mem = readInMemoryCache(now);
479
+ if (mem) {
480
+ return compareAndPermit(mem, {
481
+ clientVersion,
482
+ clientId,
483
+ channel,
484
+ diagnostic: "mem-cache-hit"
485
+ });
486
+ }
487
+ const disk = cachePath ? readDiskCache(cachePath, knownKeys) : null;
488
+ const diskAgeMs = disk ? now() - disk.fetched_at_ms : Number.POSITIVE_INFINITY;
489
+ let fetched = null;
490
+ let fetchError = null;
491
+ if (!disk || diskAgeMs > IN_MEMORY_TTL_DEFAULT_MS) {
492
+ try {
493
+ fetched = await fetchFloorDoc(apiBase, fetchImpl, knownKeys);
494
+ } catch (err) {
495
+ fetchError = err.message ?? "fetch-error";
496
+ }
497
+ }
498
+ if (fetched) {
499
+ populateMemCache(fetched, now());
500
+ if (cachePath) writeDiskCache(cachePath, fetched, now());
501
+ return compareAndPermit(fetched, {
502
+ clientVersion,
503
+ clientId,
504
+ channel,
505
+ diagnostic: "fetched"
506
+ });
507
+ }
508
+ if (disk) {
509
+ populateMemCache(disk.doc, disk.fetched_at_ms);
510
+ if (diskAgeMs > DISK_WARN_MS) {
511
+ return compareAndPermit(disk.doc, {
512
+ clientVersion,
513
+ clientId,
514
+ channel,
515
+ diagnostic: "below-floor-offline-stale-or-permit",
516
+ warn: `floor cache is >7d old and backend unreachable (${fetchError ?? "no refresh attempted"}); permitting if above floor`
517
+ });
518
+ }
519
+ if (diskAgeMs > DISK_FRESH_MS) {
520
+ return compareAndPermit(disk.doc, {
521
+ clientVersion,
522
+ clientId,
523
+ channel,
524
+ diagnostic: "warn-stale-permit",
525
+ warn: `floor cache is ${Math.round(diskAgeMs / (60 * 60 * 1e3))}h old; refresh recommended`
526
+ });
527
+ }
528
+ return compareAndPermit(disk.doc, {
529
+ clientVersion,
530
+ clientId,
531
+ channel,
532
+ diagnostic: "disk-cache-hit"
533
+ });
534
+ }
535
+ return {
536
+ ok: true,
537
+ floor: null,
538
+ diagnostic: "no-cache-no-fetch-permit",
539
+ warn: `floor preflight skipped: backend unreachable (${fetchError ?? "unknown"})`
540
+ };
541
+ }
542
+ function compareAndPermit(doc, ctx) {
543
+ const floor = lookupFloor(doc, ctx.clientId, ctx.channel);
544
+ if (!floor) {
545
+ return { ok: true, floor: null, diagnostic: `${ctx.diagnostic}+no-floor`, warn: ctx.warn };
546
+ }
547
+ if (compareSemver(ctx.clientVersion, floor.min_version) >= 0) {
548
+ return { ok: true, floor, diagnostic: ctx.diagnostic, warn: ctx.warn };
549
+ }
550
+ const envelope = {
551
+ error: {
552
+ code: "client_below_floor",
553
+ message: `Your ${ctx.clientId} is too old. Upgrade required.`,
554
+ client_version: ctx.clientVersion,
555
+ min_version: floor.min_version,
556
+ upgrade_cmd: floor.upgrade_cmd,
557
+ channel: ctx.channel
558
+ }
559
+ };
560
+ throw new BelowFloorError(envelope);
561
+ }
562
+ function lookupFloor(doc, clientId, channel) {
563
+ const entry = doc.floors[clientId];
564
+ if (!entry) return null;
565
+ if (isChannelFloor(entry)) return entry;
566
+ const exact = entry[channel];
567
+ if (exact) return exact;
568
+ const fallback = entry["unknown"];
569
+ if (fallback) return fallback;
570
+ return null;
571
+ }
572
+ function isChannelFloor(v) {
573
+ return typeof v.min_version === "string" && typeof v.upgrade_cmd === "string";
574
+ }
575
+ function compareSemver(a, b) {
576
+ const [aMaj, aMin, aPat, aPre] = parseSemver(a);
577
+ const [bMaj, bMin, bPat, bPre] = parseSemver(b);
578
+ if (aMaj !== bMaj) return aMaj - bMaj;
579
+ if (aMin !== bMin) return aMin - bMin;
580
+ if (aPat !== bPat) return aPat - bPat;
581
+ if (aPre && !bPre) return -1;
582
+ if (!aPre && bPre) return 1;
583
+ if (aPre && bPre) return aPre.localeCompare(bPre);
584
+ return 0;
585
+ }
586
+ function parseSemver(v) {
587
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);
588
+ if (!m) return [0, 0, 0, null];
589
+ return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
590
+ }
591
+ function verifyFloorSignature(doc, keys = KNOWN_FLOOR_PUBLIC_KEYS) {
592
+ const pem = keys[doc.key_id];
593
+ if (!pem) return false;
594
+ if (computeKeyId(pem) !== doc.key_id) return false;
595
+ try {
596
+ const pubKeyObject = createPublicKey({ key: pem, format: "pem" });
597
+ const canonical = canonicalJson({
598
+ floors: doc.floors,
599
+ served_at: doc.served_at
600
+ });
601
+ return verify(
602
+ null,
603
+ Buffer.from(canonical, "utf-8"),
604
+ pubKeyObject,
605
+ Buffer.from(doc.signature, "hex")
606
+ );
607
+ } catch {
608
+ return false;
609
+ }
610
+ }
611
+ async function fetchFloorDoc(apiBase, fetchImpl, knownKeys) {
612
+ const url = `${apiBase.replace(/\/+$/, "")}${MIN_VERSION_ENDPOINT}`;
613
+ let response;
614
+ try {
615
+ response = await fetchImpl(url, {
616
+ headers: {
617
+ accept: "application/json",
618
+ "X-Alter-Client-Id": CLIENT_ID,
619
+ "X-Alter-Client-Version": SDK_VERSION,
620
+ "X-Alter-Client-Channel": CLIENT_CHANNEL,
621
+ "User-Agent": `${SDK_NAME}/${SDK_VERSION}`
622
+ },
623
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
624
+ });
625
+ } catch (err) {
626
+ throw new Error(`network: ${err.message ?? String(err)}`);
627
+ }
628
+ if (!response.ok) throw new Error(`http-${response.status}`);
629
+ const body = await response.json();
630
+ if (!body || !body.floors || !body.signature || !body.key_id) {
631
+ throw new Error("malformed-floor-doc");
632
+ }
633
+ if (!verifyFloorSignature(body, knownKeys)) {
634
+ throw new Error("signature-invalid");
635
+ }
636
+ return body;
637
+ }
638
+ function readInMemoryCache(now) {
639
+ if (!memCache) return null;
640
+ if (now() - memCache.fetched_at_ms > memCache.ttl_ms) {
641
+ memCache = null;
642
+ return null;
643
+ }
644
+ return memCache.doc;
645
+ }
646
+ function populateMemCache(doc, fetched_at_ms) {
647
+ const ttlSec = doc.cache_ttl_seconds ?? 3600;
648
+ const ttlMs = Math.min(
649
+ Math.max(ttlSec * 1e3, IN_MEMORY_TTL_MIN_MS),
650
+ IN_MEMORY_TTL_MAX_MS
651
+ );
652
+ memCache = { doc, fetched_at_ms, ttl_ms: ttlMs };
653
+ }
654
+ function defaultDiskCachePath() {
655
+ const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
656
+ return join(xdg, "alter", "floor-cache.json");
657
+ }
658
+ function readDiskCache(path, knownKeys) {
659
+ if (process.platform !== "win32") {
660
+ let st;
661
+ try {
662
+ st = statSync(path);
663
+ } catch {
664
+ return null;
665
+ }
666
+ const euid = typeof process.geteuid === "function" ? process.geteuid() : st.uid;
667
+ if (st.uid !== euid) return null;
668
+ if ((st.mode & 511) !== 384) return null;
669
+ }
670
+ let raw;
671
+ try {
672
+ raw = readFileSync(path, "utf-8");
673
+ } catch {
674
+ return null;
675
+ }
676
+ let parsed;
677
+ try {
678
+ parsed = JSON.parse(raw);
679
+ } catch {
680
+ return null;
681
+ }
682
+ if (!parsed.doc || typeof parsed.fetched_at_ms !== "number") return null;
683
+ if (!verifyFloorSignature(parsed.doc, knownKeys)) {
684
+ return null;
685
+ }
686
+ return parsed;
687
+ }
688
+ function writeDiskCache(path, doc, now_ms) {
689
+ const entry = { doc, fetched_at_ms: now_ms };
690
+ const payload = JSON.stringify(entry);
691
+ try {
692
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
693
+ const tmp = `${path}.tmp`;
694
+ writeFileSync(tmp, payload, { mode: 384 });
695
+ try {
696
+ chmodSync(tmp, 384);
697
+ } catch {
698
+ }
699
+ renameSync(tmp, path);
700
+ } catch {
701
+ }
702
+ }
703
+ function defaultApiBase() {
704
+ return process.env.ALTER_API ?? "https://api.truealter.com";
705
+ }
408
706
  var X402Client = class {
409
707
  signer;
410
708
  maxPerQuery;
@@ -515,6 +813,9 @@ var MCPClient = class {
515
813
  x402;
516
814
  signing;
517
815
  extraHeaders;
816
+ preflightHook;
817
+ preflightPromise = null;
818
+ preflightDone = false;
518
819
  requestCounter = 0;
519
820
  initialised = false;
520
821
  constructor(opts = {}) {
@@ -523,17 +824,43 @@ var MCPClient = class {
523
824
  this.fetchImpl = opts.fetch ?? fetch;
524
825
  this.timeoutMs = opts.timeoutMs ?? 3e4;
525
826
  this.maxRetries = opts.maxRetries ?? 2;
526
- this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
827
+ this.clientInfo = opts.clientInfo ?? { name: SDK_NAME, version: SDK_VERSION };
527
828
  this.x402 = opts.x402;
528
829
  this.signing = opts.signing;
529
830
  this.extraHeaders = opts.extraHeaders;
831
+ this.preflightHook = opts.preflightHook;
832
+ }
833
+ /**
834
+ * Run the lazy preflight hook (D-MIN-VERSION-FLOOR-1) exactly once.
835
+ * Idempotent and serialised: concurrent callers share the same
836
+ * promise. Throws from the hook propagate to every concurrent caller.
837
+ */
838
+ async runPreflight() {
839
+ if (this.preflightDone) return;
840
+ if (!this.preflightHook) {
841
+ this.preflightDone = true;
842
+ return;
843
+ }
844
+ if (!this.preflightPromise) {
845
+ this.preflightPromise = this.preflightHook().then(
846
+ () => {
847
+ this.preflightDone = true;
848
+ },
849
+ (err) => {
850
+ this.preflightPromise = null;
851
+ throw err;
852
+ }
853
+ );
854
+ }
855
+ await this.preflightPromise;
530
856
  }
531
857
  /**
532
858
  * Send the MCP `initialize` handshake and capture the resulting session
533
- * id. Idempotent safe to call multiple times.
859
+ * id. Idempotent: safe to call multiple times.
534
860
  */
535
861
  async initialize() {
536
862
  if (this.initialised) return null;
863
+ await this.runPreflight();
537
864
  const result = await this.rpc("initialize", {
538
865
  protocolVersion: MCP_PROTOCOL_VERSION,
539
866
  capabilities: {},
@@ -693,7 +1020,10 @@ var MCPClient = class {
693
1020
  ...this.extraHeaders ?? {},
694
1021
  "Content-Type": "application/json",
695
1022
  Accept: "application/json",
696
- "User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`
1023
+ "User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`,
1024
+ "X-Alter-Client-Id": "alter-identity",
1025
+ "X-Alter-Client-Version": SDK_VERSION,
1026
+ "X-Alter-Client-Channel": "npm"
697
1027
  };
698
1028
  if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
699
1029
  if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
@@ -951,7 +1281,7 @@ async function verifyToolSignatures(tools, signatures, opts = {}) {
951
1281
  out.push({ tool: tool.name, valid: false, reason: "no signature published" });
952
1282
  continue;
953
1283
  }
954
- const expectedHash = await sha256Hex(canonicalJson(tool.inputSchema));
1284
+ const expectedHash = await sha256Hex(canonicalJson2(tool.inputSchema));
955
1285
  if (expectedHash !== sig.schema_hash) {
956
1286
  out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
957
1287
  continue;
@@ -1035,23 +1365,23 @@ async function fetchJwks(url, fetchImpl) {
1035
1365
  }
1036
1366
  if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
1037
1367
  throw new AlterProvenanceError(
1038
- `${url} \u2192 redirect rejected (allowlist enforces initial URL only)`
1368
+ `${url} -> redirect rejected (allowlist enforces initial URL only)`
1039
1369
  );
1040
1370
  }
1041
- if (!resp.ok) throw new AlterNetworkError(`${url} \u2192 HTTP ${resp.status}`);
1371
+ if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
1042
1372
  const contentLength = resp.headers.get("content-length");
1043
1373
  if (contentLength !== null) {
1044
1374
  const n = Number.parseInt(contentLength, 10);
1045
1375
  if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
1046
1376
  throw new AlterProvenanceError(
1047
- `${url} \u2192 JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
1377
+ `${url} -> JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
1048
1378
  );
1049
1379
  }
1050
1380
  }
1051
1381
  const body = await resp.text();
1052
1382
  if (body.length > JWKS_MAX_BYTES) {
1053
1383
  throw new AlterProvenanceError(
1054
- `${url} \u2192 JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
1384
+ `${url} -> JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
1055
1385
  );
1056
1386
  }
1057
1387
  let doc;
@@ -1135,14 +1465,14 @@ async function sha256Hex(input) {
1135
1465
  function toArrayBuffer(view) {
1136
1466
  return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
1137
1467
  }
1138
- function canonicalJson(value) {
1468
+ function canonicalJson2(value) {
1139
1469
  if (value === null || typeof value !== "object") return JSON.stringify(value);
1140
1470
  if (Array.isArray(value)) {
1141
- return `[${value.map(canonicalJson).join(",")}]`;
1471
+ return `[${value.map(canonicalJson2).join(",")}]`;
1142
1472
  }
1143
1473
  const obj = value;
1144
1474
  const keys = Object.keys(obj).sort();
1145
- return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
1475
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson2(obj[k])}`).join(",")}}`;
1146
1476
  }
1147
1477
 
1148
1478
  // src/client.ts
@@ -1158,11 +1488,21 @@ var AlterClient = class {
1158
1488
  this.options = options;
1159
1489
  this.x402 = options.x402;
1160
1490
  const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
1161
- this.mcp = new MCPClient({ ...options, endpoint, x402: options.x402 });
1491
+ const preflightHook = options.unsafe_skipVersionCheck ? void 0 : () => checkMinVersion({
1492
+ apiBase: options.apiBase,
1493
+ knownFloorPublicKeys: options.knownFloorPublicKeys,
1494
+ fetchImpl: options.fetch
1495
+ }).then(() => void 0);
1496
+ this.mcp = new MCPClient({
1497
+ ...options,
1498
+ endpoint,
1499
+ x402: options.x402,
1500
+ preflightHook
1501
+ });
1162
1502
  }
1163
1503
  /**
1164
1504
  * Resolve the MCP endpoint via discovery if requested. Safe to call
1165
- * multiple times the first successful lookup is cached.
1505
+ * multiple times: the first successful lookup is cached.
1166
1506
  */
1167
1507
  async discoverEndpoint() {
1168
1508
  if (this.discovered) return this.discovered;
@@ -1175,7 +1515,7 @@ var AlterClient = class {
1175
1515
  return this.discoveryPromise;
1176
1516
  }
1177
1517
  /**
1178
- * Initialise the MCP session. Optional every method calls
1518
+ * Initialise the MCP session. Optional: every method calls
1179
1519
  * `mcp.initialize()` lazily, but you can call this once at startup if
1180
1520
  * you want fail-fast behaviour.
1181
1521
  */
@@ -1183,11 +1523,11 @@ var AlterClient = class {
1183
1523
  await this.mcp.initialize();
1184
1524
  }
1185
1525
  // ── Free tier ────────────────────────────────────────────────────────
1186
- /** First handshake confirms the connection, returns trust tier and tool counts. */
1526
+ /** First handshake: confirms the connection, returns trust tier and tool counts. */
1187
1527
  async helloAgent() {
1188
1528
  return this.mcp.callTool("hello_agent", {});
1189
1529
  }
1190
- /** Resolve a ~handle (e.g. ~drew) to its canonical form and kind. No auth required. */
1530
+ /** Resolve a ~handle (e.g. ~example) to its canonical form and kind. No auth required. */
1191
1531
  async resolveHandle(args) {
1192
1532
  const payload = typeof args === "string" ? { query: args } : args;
1193
1533
  return this.mcp.callTool("alter_resolve_handle", payload);
@@ -1195,7 +1535,7 @@ var AlterClient = class {
1195
1535
  /** Verify a person is registered with ALTER (handle or id). */
1196
1536
  async verify(handleOrId, claims) {
1197
1537
  const args = handleOrId.includes("@") ? { member_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
1198
- // ~handle server resolves these via the member_id field
1538
+ // ~handle: server resolves these via the member_id field
1199
1539
  { member_id: handleOrId }
1200
1540
  ) : { member_id: handleOrId };
1201
1541
  if (claims) args.claims = claims;
@@ -1295,7 +1635,7 @@ var AlterClient = class {
1295
1635
  }
1296
1636
  // ── Alter-to-Alter Messaging ─────────────────────────────────────────
1297
1637
  // Wave 1: cross-handle direct messages between authenticated tilde
1298
- // handles. Default closed recipient must have granted the sender via
1638
+ // handles. Default closed: recipient must have granted the sender via
1299
1639
  // alter_message_grant. Spec: docs/technical/Alter-to-Alter Messaging.md.
1300
1640
  /** Send a direct message to another tilde handle. */
1301
1641
  async messageSend(args) {
@@ -1329,7 +1669,7 @@ var AlterClient = class {
1329
1669
  /**
1330
1670
  * Verify the ES256 provenance attestation on a tool response.
1331
1671
  * Accepts either a {@link ProvenanceEnvelope} or the raw `_meta`
1332
- * object the latter is more convenient for ad-hoc verification.
1672
+ * object: the latter is more convenient for ad-hoc verification.
1333
1673
  */
1334
1674
  async verifyProvenance(envelope) {
1335
1675
  if (!envelope) return { valid: false, reason: "no provenance envelope" };
@@ -1362,7 +1702,7 @@ function generateGenericMcpConfig(opts = {}) {
1362
1702
  const entry = {
1363
1703
  url: opts.endpoint ?? DEFAULT_ENDPOINT,
1364
1704
  transport: "streamable-http",
1365
- description: "ALTER Identity \u2014 psychometric identity field for AI agents"
1705
+ description: "ALTER Identity: psychometric identity field for AI agents"
1366
1706
  };
1367
1707
  if (Object.keys(headers).length > 0) entry.headers = headers;
1368
1708
  return { mcpServers: { [serverName]: entry } };
@@ -1388,17 +1728,13 @@ function generateClaudeDesktopConfig(opts = {}) {
1388
1728
  const entry = {
1389
1729
  command: bridgeCommand,
1390
1730
  env: env3,
1391
- description: "ALTER Identity \u2014 psychometric identity field for AI agents"
1731
+ description: "ALTER Identity: psychometric identity field for AI agents"
1392
1732
  };
1393
1733
  if (opts.extraArgs && opts.extraArgs.length > 0) {
1394
1734
  entry.args = [...opts.extraArgs];
1395
1735
  }
1396
1736
  return { mcpServers: { [serverName]: entry } };
1397
1737
  }
1398
-
1399
- // src/meta.ts
1400
- var SDK_NAME = "@truealter/sdk";
1401
- var SDK_VERSION = "0.3.0";
1402
1738
  var HOME = homedir();
1403
1739
  var PLAT = platform();
1404
1740
  function appData() {
@@ -1523,7 +1859,7 @@ function probeAll() {
1523
1859
  ];
1524
1860
  }
1525
1861
  var SYNC_PREFIXES = [
1526
- // iCloud Drive both the new and legacy mounts.
1862
+ // iCloud Drive: both the new and legacy mounts.
1527
1863
  "Library/Mobile Documents/com~apple~CloudDocs",
1528
1864
  "iCloud Drive",
1529
1865
  // OneDrive variants Microsoft ships across editions.
@@ -1536,7 +1872,7 @@ var SYNC_PREFIXES = [
1536
1872
  "Google Drive",
1537
1873
  "GoogleDrive",
1538
1874
  "CloudStorage/GoogleDrive",
1539
- // Box, pCloud, Sync.com, MEGA high-signal names worth refusing.
1875
+ // Box, pCloud, Sync.com, MEGA: high-signal names worth refusing.
1540
1876
  "Box Sync",
1541
1877
  "pCloud Drive",
1542
1878
  "Sync.com",
@@ -1588,10 +1924,10 @@ function atomicJsonMerge(opts) {
1588
1924
  preBytes = readFileSync(path, "utf8");
1589
1925
  if (preBytes.trim().length > 0) {
1590
1926
  try {
1591
- parsed = JSON.parse(preBytes);
1927
+ parsed = JSON.parse(preBytes.replace(/^\uFEFF/, ""));
1592
1928
  } catch (err) {
1593
1929
  throw new Error(
1594
- `refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter-identity wire\`.`
1930
+ `refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter wire\`.`
1595
1931
  );
1596
1932
  }
1597
1933
  if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
@@ -1724,7 +2060,7 @@ function wireFileTarget(args) {
1724
2060
  const sync = detectSyncedVolume(client.configPath);
1725
2061
  if (sync) {
1726
2062
  throw new Error(
1727
- `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.`
2063
+ `refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices: move the config off the sync root, or run wire on the device you want to target.`
1728
2064
  );
1729
2065
  }
1730
2066
  const cfHeaders = {};
@@ -1813,14 +2149,13 @@ function wireClaudeCode(args) {
1813
2149
  };
1814
2150
  }
1815
2151
  function resolveBridgeScript() {
1816
- const { existsSync: existsSync5 } = __require("fs");
1817
- const { join: join4, dirname: dirname4 } = __require("path");
1818
- const siblingBridge = join4(dirname4(__filename), "..", "dist", "mcp-bridge.js");
1819
- if (existsSync5(siblingBridge)) return siblingBridge;
1820
- const srcBridge = join4(dirname4(__filename), "..", "mcp-bridge.js");
1821
- if (existsSync5(srcBridge)) return srcBridge;
1822
- const npmGlobalBridge = join4(dirname4(__filename), "mcp-bridge.js");
1823
- if (existsSync5(npmGlobalBridge)) return npmGlobalBridge;
2152
+ const here = dirname(fileURLToPath(import.meta.url));
2153
+ const siblingBridge = join(here, "..", "dist", "mcp-bridge.js");
2154
+ if (existsSync(siblingBridge)) return siblingBridge;
2155
+ const srcBridge = join(here, "..", "mcp-bridge.js");
2156
+ if (existsSync(srcBridge)) return srcBridge;
2157
+ const npmGlobalBridge = join(here, "mcp-bridge.js");
2158
+ if (existsSync(npmGlobalBridge)) return npmGlobalBridge;
1824
2159
  return null;
1825
2160
  }
1826
2161
  function unwire() {