@truealter/sdk 0.5.0 → 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.
- package/README.md +161 -80
- package/dist/bin/alter-identity.js +532 -45
- package/dist/bin/mcp-bridge.js +56 -5
- package/dist/index.cjs +667 -60
- package/dist/index.d.cts +533 -144
- package/dist/index.d.ts +533 -144
- package/dist/index.js +645 -62
- package/package.json +6 -3
package/dist/index.cjs
CHANGED
|
@@ -4,13 +4,14 @@ var p256 = require('@noble/curves/p256');
|
|
|
4
4
|
var sha256 = require('@noble/hashes/sha256');
|
|
5
5
|
var utils = require('@noble/hashes/utils');
|
|
6
6
|
var crypto$1 = require('crypto');
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
var os = require('os');
|
|
9
|
+
var path = require('path');
|
|
7
10
|
var ed25519 = require('@noble/ed25519');
|
|
8
11
|
var sha512 = require('@noble/hashes/sha512');
|
|
12
|
+
var url = require('url');
|
|
9
13
|
var child_process = require('child_process');
|
|
10
|
-
var os = require('os');
|
|
11
|
-
var path = require('path');
|
|
12
14
|
var process$1 = require('process');
|
|
13
|
-
var fs = require('fs');
|
|
14
15
|
|
|
15
16
|
function _interopNamespace(e) {
|
|
16
17
|
if (e && e.__esModule) return e;
|
|
@@ -54,8 +55,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
54
55
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
55
56
|
|
|
56
57
|
// node_modules/tsup/assets/cjs_shims.js
|
|
58
|
+
var getImportMetaUrl, importMetaUrl;
|
|
57
59
|
var init_cjs_shims = __esm({
|
|
58
60
|
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
61
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
62
|
+
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
59
63
|
}
|
|
60
64
|
});
|
|
61
65
|
|
|
@@ -402,11 +406,11 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
|
|
|
402
406
|
}
|
|
403
407
|
if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
|
|
404
408
|
throw new AlterNetworkError(
|
|
405
|
-
`${url}
|
|
409
|
+
`${url} -> redirect rejected (discovery must not follow redirects; validate the server configuration)`
|
|
406
410
|
);
|
|
407
411
|
}
|
|
408
412
|
if (resp.status === 404) return null;
|
|
409
|
-
if (!resp.ok) throw new AlterNetworkError(`${url}
|
|
413
|
+
if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
|
|
410
414
|
const doc = await resp.json();
|
|
411
415
|
if (file === "mcp.json") {
|
|
412
416
|
const remotes = doc.remotes || [];
|
|
@@ -440,6 +444,313 @@ function ensureMcpPath(url) {
|
|
|
440
444
|
}
|
|
441
445
|
}
|
|
442
446
|
|
|
447
|
+
// src/floor-preflight.ts
|
|
448
|
+
init_cjs_shims();
|
|
449
|
+
|
|
450
|
+
// src/meta.ts
|
|
451
|
+
init_cjs_shims();
|
|
452
|
+
var SDK_NAME = "@truealter/sdk";
|
|
453
|
+
var SDK_VERSION = "0.5.3" ;
|
|
454
|
+
|
|
455
|
+
// src/floor-preflight.ts
|
|
456
|
+
var MIN_VERSION_ENDPOINT = "/v1/clients/min-version";
|
|
457
|
+
var CLIENT_ID = "alter-identity";
|
|
458
|
+
var CLIENT_CHANNEL = "npm";
|
|
459
|
+
var IN_MEMORY_TTL_DEFAULT_MS = 60 * 60 * 1e3;
|
|
460
|
+
var IN_MEMORY_TTL_MIN_MS = 60 * 1e3;
|
|
461
|
+
var IN_MEMORY_TTL_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
462
|
+
var DISK_FRESH_MS = 24 * 60 * 60 * 1e3;
|
|
463
|
+
var DISK_WARN_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
464
|
+
var FETCH_TIMEOUT_MS = 4e3;
|
|
465
|
+
function computeKeyId(publicKeyPem) {
|
|
466
|
+
if (!publicKeyPem) return "00000000";
|
|
467
|
+
const pub = crypto$1.createPublicKey({ key: publicKeyPem, format: "pem" });
|
|
468
|
+
const jwk = pub.export({ format: "jwk" });
|
|
469
|
+
const rawBytes = Buffer.from(jwk.x, "base64url");
|
|
470
|
+
return crypto$1.createHash("sha256").update(rawBytes).digest("hex").slice(0, 8);
|
|
471
|
+
}
|
|
472
|
+
function canonicalJson(obj) {
|
|
473
|
+
return JSON.stringify(sortKeysDeep(obj));
|
|
474
|
+
}
|
|
475
|
+
function sortKeysDeep(value) {
|
|
476
|
+
if (Array.isArray(value)) {
|
|
477
|
+
return value.map(sortKeysDeep);
|
|
478
|
+
}
|
|
479
|
+
if (value !== null && typeof value === "object") {
|
|
480
|
+
const obj = value;
|
|
481
|
+
const sorted = {};
|
|
482
|
+
for (const k of Object.keys(obj).sort()) {
|
|
483
|
+
sorted[k] = sortKeysDeep(obj[k]);
|
|
484
|
+
}
|
|
485
|
+
return sorted;
|
|
486
|
+
}
|
|
487
|
+
return value;
|
|
488
|
+
}
|
|
489
|
+
var KNOWN_FLOOR_PUBLIC_KEYS = {
|
|
490
|
+
"8aa59e05": `-----BEGIN PUBLIC KEY-----
|
|
491
|
+
MCowBQYDK2VwAyEAgqw28dlniOuiTE1f4BxCPSEgMLaPtHsO8wN5RWEwEhE=
|
|
492
|
+
-----END PUBLIC KEY-----`,
|
|
493
|
+
"640f7d9a": `-----BEGIN PUBLIC KEY-----
|
|
494
|
+
MCowBQYDK2VwAyEARzvAWayDwHvZRfOZizGZe+/a7PF082WGhyMS3tx06H4=
|
|
495
|
+
-----END PUBLIC KEY-----`
|
|
496
|
+
};
|
|
497
|
+
var BelowFloorError = class extends Error {
|
|
498
|
+
name = "BelowFloorError";
|
|
499
|
+
code = "client_below_floor";
|
|
500
|
+
client_version;
|
|
501
|
+
min_version;
|
|
502
|
+
upgrade_cmd;
|
|
503
|
+
channel;
|
|
504
|
+
envelope;
|
|
505
|
+
constructor(envelope) {
|
|
506
|
+
super(envelope.error.message);
|
|
507
|
+
this.envelope = envelope;
|
|
508
|
+
this.client_version = envelope.error.client_version;
|
|
509
|
+
this.min_version = envelope.error.min_version;
|
|
510
|
+
this.upgrade_cmd = envelope.error.upgrade_cmd;
|
|
511
|
+
this.channel = envelope.error.channel;
|
|
512
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
var memCache = null;
|
|
516
|
+
async function checkMinVersion(opts = {}) {
|
|
517
|
+
const apiBase = opts.apiBase ?? defaultApiBase();
|
|
518
|
+
const clientVersion = opts.clientVersion ?? SDK_VERSION;
|
|
519
|
+
const clientId = opts.clientId ?? CLIENT_ID;
|
|
520
|
+
const channel = opts.channel ?? CLIENT_CHANNEL;
|
|
521
|
+
const knownKeys = opts.knownFloorPublicKeys ?? KNOWN_FLOOR_PUBLIC_KEYS;
|
|
522
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
523
|
+
const now = opts.now ?? Date.now;
|
|
524
|
+
const cachePath = opts.diskCachePath === void 0 ? defaultDiskCachePath() : opts.diskCachePath;
|
|
525
|
+
const mem = readInMemoryCache(now);
|
|
526
|
+
if (mem) {
|
|
527
|
+
return compareAndPermit(mem, {
|
|
528
|
+
clientVersion,
|
|
529
|
+
clientId,
|
|
530
|
+
channel,
|
|
531
|
+
diagnostic: "mem-cache-hit"
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const disk = cachePath ? readDiskCache(cachePath, knownKeys) : null;
|
|
535
|
+
const diskAgeMs = disk ? now() - disk.fetched_at_ms : Number.POSITIVE_INFINITY;
|
|
536
|
+
let fetched = null;
|
|
537
|
+
let fetchError = null;
|
|
538
|
+
if (!disk || diskAgeMs > IN_MEMORY_TTL_DEFAULT_MS) {
|
|
539
|
+
try {
|
|
540
|
+
fetched = await fetchFloorDoc(apiBase, fetchImpl, knownKeys);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
fetchError = err.message ?? "fetch-error";
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (fetched) {
|
|
546
|
+
populateMemCache(fetched, now());
|
|
547
|
+
if (cachePath) writeDiskCache(cachePath, fetched, now());
|
|
548
|
+
return compareAndPermit(fetched, {
|
|
549
|
+
clientVersion,
|
|
550
|
+
clientId,
|
|
551
|
+
channel,
|
|
552
|
+
diagnostic: "fetched"
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (disk) {
|
|
556
|
+
populateMemCache(disk.doc, disk.fetched_at_ms);
|
|
557
|
+
if (diskAgeMs > DISK_WARN_MS) {
|
|
558
|
+
return compareAndPermit(disk.doc, {
|
|
559
|
+
clientVersion,
|
|
560
|
+
clientId,
|
|
561
|
+
channel,
|
|
562
|
+
diagnostic: "below-floor-offline-stale-or-permit",
|
|
563
|
+
warn: `floor cache is >7d old and backend unreachable (${fetchError ?? "no refresh attempted"}); permitting if above floor`
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (diskAgeMs > DISK_FRESH_MS) {
|
|
567
|
+
return compareAndPermit(disk.doc, {
|
|
568
|
+
clientVersion,
|
|
569
|
+
clientId,
|
|
570
|
+
channel,
|
|
571
|
+
diagnostic: "warn-stale-permit",
|
|
572
|
+
warn: `floor cache is ${Math.round(diskAgeMs / (60 * 60 * 1e3))}h old; refresh recommended`
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return compareAndPermit(disk.doc, {
|
|
576
|
+
clientVersion,
|
|
577
|
+
clientId,
|
|
578
|
+
channel,
|
|
579
|
+
diagnostic: "disk-cache-hit"
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
ok: true,
|
|
584
|
+
floor: null,
|
|
585
|
+
diagnostic: "no-cache-no-fetch-permit",
|
|
586
|
+
warn: `floor preflight skipped: backend unreachable (${fetchError ?? "unknown"})`
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function compareAndPermit(doc, ctx) {
|
|
590
|
+
const floor = lookupFloor(doc, ctx.clientId, ctx.channel);
|
|
591
|
+
if (!floor) {
|
|
592
|
+
return { ok: true, floor: null, diagnostic: `${ctx.diagnostic}+no-floor`, warn: ctx.warn };
|
|
593
|
+
}
|
|
594
|
+
if (compareSemver(ctx.clientVersion, floor.min_version) >= 0) {
|
|
595
|
+
return { ok: true, floor, diagnostic: ctx.diagnostic, warn: ctx.warn };
|
|
596
|
+
}
|
|
597
|
+
const envelope = {
|
|
598
|
+
error: {
|
|
599
|
+
code: "client_below_floor",
|
|
600
|
+
message: `Your ${ctx.clientId} is too old. Upgrade required.`,
|
|
601
|
+
client_version: ctx.clientVersion,
|
|
602
|
+
min_version: floor.min_version,
|
|
603
|
+
upgrade_cmd: floor.upgrade_cmd,
|
|
604
|
+
channel: ctx.channel
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
throw new BelowFloorError(envelope);
|
|
608
|
+
}
|
|
609
|
+
function lookupFloor(doc, clientId, channel) {
|
|
610
|
+
const entry = doc.floors[clientId];
|
|
611
|
+
if (!entry) return null;
|
|
612
|
+
if (isChannelFloor(entry)) return entry;
|
|
613
|
+
const exact = entry[channel];
|
|
614
|
+
if (exact) return exact;
|
|
615
|
+
const fallback = entry["unknown"];
|
|
616
|
+
if (fallback) return fallback;
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
function isChannelFloor(v) {
|
|
620
|
+
return typeof v.min_version === "string" && typeof v.upgrade_cmd === "string";
|
|
621
|
+
}
|
|
622
|
+
function compareSemver(a, b) {
|
|
623
|
+
const [aMaj, aMin, aPat, aPre] = parseSemver(a);
|
|
624
|
+
const [bMaj, bMin, bPat, bPre] = parseSemver(b);
|
|
625
|
+
if (aMaj !== bMaj) return aMaj - bMaj;
|
|
626
|
+
if (aMin !== bMin) return aMin - bMin;
|
|
627
|
+
if (aPat !== bPat) return aPat - bPat;
|
|
628
|
+
if (aPre && !bPre) return -1;
|
|
629
|
+
if (!aPre && bPre) return 1;
|
|
630
|
+
if (aPre && bPre) return aPre.localeCompare(bPre);
|
|
631
|
+
return 0;
|
|
632
|
+
}
|
|
633
|
+
function parseSemver(v) {
|
|
634
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);
|
|
635
|
+
if (!m) return [0, 0, 0, null];
|
|
636
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
|
|
637
|
+
}
|
|
638
|
+
function verifyFloorSignature(doc, keys = KNOWN_FLOOR_PUBLIC_KEYS) {
|
|
639
|
+
const pem = keys[doc.key_id];
|
|
640
|
+
if (!pem) return false;
|
|
641
|
+
if (computeKeyId(pem) !== doc.key_id) return false;
|
|
642
|
+
try {
|
|
643
|
+
const pubKeyObject = crypto$1.createPublicKey({ key: pem, format: "pem" });
|
|
644
|
+
const canonical = canonicalJson({
|
|
645
|
+
floors: doc.floors,
|
|
646
|
+
served_at: doc.served_at
|
|
647
|
+
});
|
|
648
|
+
return crypto$1.verify(
|
|
649
|
+
null,
|
|
650
|
+
Buffer.from(canonical, "utf-8"),
|
|
651
|
+
pubKeyObject,
|
|
652
|
+
Buffer.from(doc.signature, "hex")
|
|
653
|
+
);
|
|
654
|
+
} catch {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async function fetchFloorDoc(apiBase, fetchImpl, knownKeys) {
|
|
659
|
+
const url = `${apiBase.replace(/\/+$/, "")}${MIN_VERSION_ENDPOINT}`;
|
|
660
|
+
let response;
|
|
661
|
+
try {
|
|
662
|
+
response = await fetchImpl(url, {
|
|
663
|
+
headers: {
|
|
664
|
+
accept: "application/json",
|
|
665
|
+
"X-Alter-Client-Id": CLIENT_ID,
|
|
666
|
+
"X-Alter-Client-Version": SDK_VERSION,
|
|
667
|
+
"X-Alter-Client-Channel": CLIENT_CHANNEL,
|
|
668
|
+
"User-Agent": `${SDK_NAME}/${SDK_VERSION}`
|
|
669
|
+
},
|
|
670
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
671
|
+
});
|
|
672
|
+
} catch (err) {
|
|
673
|
+
throw new Error(`network: ${err.message ?? String(err)}`);
|
|
674
|
+
}
|
|
675
|
+
if (!response.ok) throw new Error(`http-${response.status}`);
|
|
676
|
+
const body = await response.json();
|
|
677
|
+
if (!body || !body.floors || !body.signature || !body.key_id) {
|
|
678
|
+
throw new Error("malformed-floor-doc");
|
|
679
|
+
}
|
|
680
|
+
if (!verifyFloorSignature(body, knownKeys)) {
|
|
681
|
+
throw new Error("signature-invalid");
|
|
682
|
+
}
|
|
683
|
+
return body;
|
|
684
|
+
}
|
|
685
|
+
function readInMemoryCache(now) {
|
|
686
|
+
if (!memCache) return null;
|
|
687
|
+
if (now() - memCache.fetched_at_ms > memCache.ttl_ms) {
|
|
688
|
+
memCache = null;
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
return memCache.doc;
|
|
692
|
+
}
|
|
693
|
+
function populateMemCache(doc, fetched_at_ms) {
|
|
694
|
+
const ttlSec = doc.cache_ttl_seconds ?? 3600;
|
|
695
|
+
const ttlMs = Math.min(
|
|
696
|
+
Math.max(ttlSec * 1e3, IN_MEMORY_TTL_MIN_MS),
|
|
697
|
+
IN_MEMORY_TTL_MAX_MS
|
|
698
|
+
);
|
|
699
|
+
memCache = { doc, fetched_at_ms, ttl_ms: ttlMs };
|
|
700
|
+
}
|
|
701
|
+
function defaultDiskCachePath() {
|
|
702
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
703
|
+
return path.join(xdg, "alter", "floor-cache.json");
|
|
704
|
+
}
|
|
705
|
+
function readDiskCache(path, knownKeys) {
|
|
706
|
+
if (process.platform !== "win32") {
|
|
707
|
+
let st;
|
|
708
|
+
try {
|
|
709
|
+
st = fs.statSync(path);
|
|
710
|
+
} catch {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
const euid = typeof process.geteuid === "function" ? process.geteuid() : st.uid;
|
|
714
|
+
if (st.uid !== euid) return null;
|
|
715
|
+
if ((st.mode & 511) !== 384) return null;
|
|
716
|
+
}
|
|
717
|
+
let raw;
|
|
718
|
+
try {
|
|
719
|
+
raw = fs.readFileSync(path, "utf-8");
|
|
720
|
+
} catch {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
let parsed;
|
|
724
|
+
try {
|
|
725
|
+
parsed = JSON.parse(raw);
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
if (!parsed.doc || typeof parsed.fetched_at_ms !== "number") return null;
|
|
730
|
+
if (!verifyFloorSignature(parsed.doc, knownKeys)) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
return parsed;
|
|
734
|
+
}
|
|
735
|
+
function writeDiskCache(path$1, doc, now_ms) {
|
|
736
|
+
const entry = { doc, fetched_at_ms: now_ms };
|
|
737
|
+
const payload = JSON.stringify(entry);
|
|
738
|
+
try {
|
|
739
|
+
fs.mkdirSync(path.dirname(path$1), { recursive: true, mode: 448 });
|
|
740
|
+
const tmp = `${path$1}.tmp`;
|
|
741
|
+
fs.writeFileSync(tmp, payload, { mode: 384 });
|
|
742
|
+
try {
|
|
743
|
+
fs.chmodSync(tmp, 384);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
fs.renameSync(tmp, path$1);
|
|
747
|
+
} catch {
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function defaultApiBase() {
|
|
751
|
+
return process.env.ALTER_API ?? "https://api.truealter.com";
|
|
752
|
+
}
|
|
753
|
+
|
|
443
754
|
// src/mcp.ts
|
|
444
755
|
init_cjs_shims();
|
|
445
756
|
|
|
@@ -450,11 +761,19 @@ var X402Client = class {
|
|
|
450
761
|
maxPerQuery;
|
|
451
762
|
networks;
|
|
452
763
|
assets;
|
|
764
|
+
// undefined = allowlist check disabled (backward-compatible default).
|
|
765
|
+
// Non-null = active allowlist; reject any recipient not in the set.
|
|
766
|
+
recipientAllowlist;
|
|
453
767
|
constructor(opts = {}) {
|
|
454
768
|
this.signer = opts.signer;
|
|
455
769
|
this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
|
|
456
770
|
this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
|
|
457
771
|
this.assets = new Set(opts.assets ?? ["USDC"]);
|
|
772
|
+
if (opts.recipientAllowlist !== void 0) {
|
|
773
|
+
this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
|
|
774
|
+
} else {
|
|
775
|
+
this.recipientAllowlist = void 0;
|
|
776
|
+
}
|
|
458
777
|
}
|
|
459
778
|
/**
|
|
460
779
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -480,6 +799,15 @@ var X402Client = class {
|
|
|
480
799
|
);
|
|
481
800
|
}
|
|
482
801
|
}
|
|
802
|
+
if (this.recipientAllowlist !== void 0) {
|
|
803
|
+
const recipientNorm = (envelope.recipient ?? "").toLowerCase();
|
|
804
|
+
if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
|
|
805
|
+
throw new AlterError(
|
|
806
|
+
"PAYMENT_REQUIRED",
|
|
807
|
+
`recipient "${envelope.recipient}" is not on the known-recipient allowlist`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
483
811
|
if (!this.signer) {
|
|
484
812
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
485
813
|
}
|
|
@@ -538,6 +866,9 @@ var MCPClient = class {
|
|
|
538
866
|
x402;
|
|
539
867
|
signing;
|
|
540
868
|
extraHeaders;
|
|
869
|
+
preflightHook;
|
|
870
|
+
preflightPromise = null;
|
|
871
|
+
preflightDone = false;
|
|
541
872
|
requestCounter = 0;
|
|
542
873
|
initialised = false;
|
|
543
874
|
constructor(opts = {}) {
|
|
@@ -546,17 +877,43 @@ var MCPClient = class {
|
|
|
546
877
|
this.fetchImpl = opts.fetch ?? fetch;
|
|
547
878
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
548
879
|
this.maxRetries = opts.maxRetries ?? 2;
|
|
549
|
-
this.clientInfo = opts.clientInfo ?? { name:
|
|
880
|
+
this.clientInfo = opts.clientInfo ?? { name: SDK_NAME, version: SDK_VERSION };
|
|
550
881
|
this.x402 = opts.x402;
|
|
551
882
|
this.signing = opts.signing;
|
|
552
883
|
this.extraHeaders = opts.extraHeaders;
|
|
884
|
+
this.preflightHook = opts.preflightHook;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Run the lazy preflight hook (D-MIN-VERSION-FLOOR-1) exactly once.
|
|
888
|
+
* Idempotent and serialised: concurrent callers share the same
|
|
889
|
+
* promise. Throws from the hook propagate to every concurrent caller.
|
|
890
|
+
*/
|
|
891
|
+
async runPreflight() {
|
|
892
|
+
if (this.preflightDone) return;
|
|
893
|
+
if (!this.preflightHook) {
|
|
894
|
+
this.preflightDone = true;
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (!this.preflightPromise) {
|
|
898
|
+
this.preflightPromise = this.preflightHook().then(
|
|
899
|
+
() => {
|
|
900
|
+
this.preflightDone = true;
|
|
901
|
+
},
|
|
902
|
+
(err) => {
|
|
903
|
+
this.preflightPromise = null;
|
|
904
|
+
throw err;
|
|
905
|
+
}
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
await this.preflightPromise;
|
|
553
909
|
}
|
|
554
910
|
/**
|
|
555
911
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
556
|
-
* id. Idempotent
|
|
912
|
+
* id. Idempotent: safe to call multiple times.
|
|
557
913
|
*/
|
|
558
914
|
async initialize() {
|
|
559
915
|
if (this.initialised) return null;
|
|
916
|
+
await this.runPreflight();
|
|
560
917
|
const result = await this.rpc("initialize", {
|
|
561
918
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
562
919
|
capabilities: {},
|
|
@@ -716,7 +1073,10 @@ var MCPClient = class {
|
|
|
716
1073
|
...this.extraHeaders ?? {},
|
|
717
1074
|
"Content-Type": "application/json",
|
|
718
1075
|
Accept: "application/json",
|
|
719
|
-
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}
|
|
1076
|
+
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`,
|
|
1077
|
+
"X-Alter-Client-Id": "alter-identity",
|
|
1078
|
+
"X-Alter-Client-Version": SDK_VERSION,
|
|
1079
|
+
"X-Alter-Client-Channel": "npm"
|
|
720
1080
|
};
|
|
721
1081
|
if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
|
|
722
1082
|
if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
|
|
@@ -952,9 +1312,46 @@ async function verifyProvenance(envelope, opts = {}) {
|
|
|
952
1312
|
kid: header.kid
|
|
953
1313
|
};
|
|
954
1314
|
}
|
|
1315
|
+
if (opts.expectedAud !== void 0 && opts.expectedAud !== "") {
|
|
1316
|
+
const tokenAud = payload.aud;
|
|
1317
|
+
const audList = tokenAud === void 0 ? [] : Array.isArray(tokenAud) ? tokenAud : [tokenAud];
|
|
1318
|
+
if (!audList.includes(opts.expectedAud)) {
|
|
1319
|
+
return {
|
|
1320
|
+
valid: false,
|
|
1321
|
+
reason: `aud mismatch: expected "${opts.expectedAud}", got ${JSON.stringify(tokenAud ?? null)}`,
|
|
1322
|
+
payload,
|
|
1323
|
+
kid: header.kid
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
955
1327
|
return { valid: true, payload, kid: header.kid };
|
|
956
1328
|
}
|
|
957
|
-
async function verifyToolSignatures(tools, signatures) {
|
|
1329
|
+
async function verifyToolSignatures(tools, signatures, opts = {}) {
|
|
1330
|
+
const jwksUrl = opts.jwksUrl ?? "https://api.truealter.com/.well-known/alter-keys.json";
|
|
1331
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
1332
|
+
if (!jwksUrl.startsWith("https://")) {
|
|
1333
|
+
return tools.map((t) => ({
|
|
1334
|
+
tool: t.name,
|
|
1335
|
+
valid: false,
|
|
1336
|
+
reason: `jwksUrl must be https: got ${jwksUrl}`
|
|
1337
|
+
}));
|
|
1338
|
+
}
|
|
1339
|
+
const needsJwks = tools.some((t) => {
|
|
1340
|
+
const sig = signatures[t.name];
|
|
1341
|
+
return sig && sig.signature;
|
|
1342
|
+
});
|
|
1343
|
+
let jwks = null;
|
|
1344
|
+
if (needsJwks) {
|
|
1345
|
+
try {
|
|
1346
|
+
jwks = await fetchJwks(jwksUrl, fetchImpl);
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
return tools.map((t) => ({
|
|
1349
|
+
tool: t.name,
|
|
1350
|
+
valid: false,
|
|
1351
|
+
reason: `jwks fetch failed: ${err.message}`
|
|
1352
|
+
}));
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
958
1355
|
const out = [];
|
|
959
1356
|
for (const tool of tools) {
|
|
960
1357
|
const sig = signatures[tool.name];
|
|
@@ -962,11 +1359,68 @@ async function verifyToolSignatures(tools, signatures) {
|
|
|
962
1359
|
out.push({ tool: tool.name, valid: false, reason: "no signature published" });
|
|
963
1360
|
continue;
|
|
964
1361
|
}
|
|
965
|
-
const expectedHash = await sha256Hex(
|
|
1362
|
+
const expectedHash = await sha256Hex(canonicalJson2(tool.inputSchema));
|
|
966
1363
|
if (expectedHash !== sig.schema_hash) {
|
|
967
1364
|
out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
|
|
968
1365
|
continue;
|
|
969
1366
|
}
|
|
1367
|
+
const jwsToken = sig.signature;
|
|
1368
|
+
if (!jwsToken) {
|
|
1369
|
+
out.push({ tool: tool.name, valid: true, warn_no_signature: true });
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
const jwksDoc = jwks;
|
|
1373
|
+
let jHeader;
|
|
1374
|
+
let jPayloadRaw;
|
|
1375
|
+
let jSigBytes;
|
|
1376
|
+
try {
|
|
1377
|
+
const parts2 = jwsToken.split(".");
|
|
1378
|
+
if (parts2.length !== 3) throw new Error("JWS must have three segments");
|
|
1379
|
+
jHeader = JSON.parse(new TextDecoder().decode(base64urlDecode(parts2[0])));
|
|
1380
|
+
jPayloadRaw = new TextDecoder().decode(base64urlDecode(parts2[1]));
|
|
1381
|
+
jSigBytes = base64urlDecode(parts2[2]);
|
|
1382
|
+
} catch (err) {
|
|
1383
|
+
out.push({ tool: tool.name, valid: false, reason: `malformed tool JWS: ${err.message}` });
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (jHeader.alg !== "ES256") {
|
|
1387
|
+
out.push({ tool: tool.name, valid: false, reason: `unsupported tool sig alg: ${jHeader.alg}` });
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (jPayloadRaw !== sig.schema_hash) {
|
|
1391
|
+
out.push({ tool: tool.name, valid: false, reason: "tool JWS payload does not match schema_hash" });
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
const jwk = jwksDoc.keys.find((k) => jHeader.kid ? k.kid === jHeader.kid : true);
|
|
1395
|
+
if (!jwk) {
|
|
1396
|
+
out.push({ tool: tool.name, valid: false, reason: `no JWK for kid=${jHeader.kid}` });
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
let publicKey;
|
|
1400
|
+
try {
|
|
1401
|
+
publicKey = await importEs256JwkAsPublicKey(jwk);
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
out.push({ tool: tool.name, valid: false, reason: `jwk import: ${err.message}` });
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
const parts = jwsToken.split(".");
|
|
1407
|
+
const signedInput = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
1408
|
+
let sigValid = false;
|
|
1409
|
+
try {
|
|
1410
|
+
sigValid = await crypto.subtle.verify(
|
|
1411
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
1412
|
+
publicKey,
|
|
1413
|
+
toArrayBuffer(jSigBytes),
|
|
1414
|
+
toArrayBuffer(signedInput)
|
|
1415
|
+
);
|
|
1416
|
+
} catch (err) {
|
|
1417
|
+
out.push({ tool: tool.name, valid: false, reason: `sig verify error: ${err.message}` });
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (!sigValid) {
|
|
1421
|
+
out.push({ tool: tool.name, valid: false, reason: "tool signature mismatch" });
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
970
1424
|
out.push({ tool: tool.name, valid: true });
|
|
971
1425
|
}
|
|
972
1426
|
return out;
|
|
@@ -989,23 +1443,23 @@ async function fetchJwks(url, fetchImpl) {
|
|
|
989
1443
|
}
|
|
990
1444
|
if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
|
|
991
1445
|
throw new AlterProvenanceError(
|
|
992
|
-
`${url}
|
|
1446
|
+
`${url} -> redirect rejected (allowlist enforces initial URL only)`
|
|
993
1447
|
);
|
|
994
1448
|
}
|
|
995
|
-
if (!resp.ok) throw new AlterNetworkError(`${url}
|
|
1449
|
+
if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
|
|
996
1450
|
const contentLength = resp.headers.get("content-length");
|
|
997
1451
|
if (contentLength !== null) {
|
|
998
1452
|
const n = Number.parseInt(contentLength, 10);
|
|
999
1453
|
if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
|
|
1000
1454
|
throw new AlterProvenanceError(
|
|
1001
|
-
`${url}
|
|
1455
|
+
`${url} -> JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
|
|
1002
1456
|
);
|
|
1003
1457
|
}
|
|
1004
1458
|
}
|
|
1005
1459
|
const body = await resp.text();
|
|
1006
1460
|
if (body.length > JWKS_MAX_BYTES) {
|
|
1007
1461
|
throw new AlterProvenanceError(
|
|
1008
|
-
`${url}
|
|
1462
|
+
`${url} -> JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
|
|
1009
1463
|
);
|
|
1010
1464
|
}
|
|
1011
1465
|
let doc;
|
|
@@ -1089,14 +1543,14 @@ async function sha256Hex(input) {
|
|
|
1089
1543
|
function toArrayBuffer(view) {
|
|
1090
1544
|
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
1091
1545
|
}
|
|
1092
|
-
function
|
|
1546
|
+
function canonicalJson2(value) {
|
|
1093
1547
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1094
1548
|
if (Array.isArray(value)) {
|
|
1095
|
-
return `[${value.map(
|
|
1549
|
+
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
1096
1550
|
}
|
|
1097
1551
|
const obj = value;
|
|
1098
1552
|
const keys = Object.keys(obj).sort();
|
|
1099
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${
|
|
1553
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson2(obj[k])}`).join(",")}}`;
|
|
1100
1554
|
}
|
|
1101
1555
|
|
|
1102
1556
|
// src/client.ts
|
|
@@ -1112,11 +1566,21 @@ var AlterClient = class {
|
|
|
1112
1566
|
this.options = options;
|
|
1113
1567
|
this.x402 = options.x402;
|
|
1114
1568
|
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
1115
|
-
|
|
1569
|
+
const preflightHook = options.unsafe_skipVersionCheck ? void 0 : () => checkMinVersion({
|
|
1570
|
+
apiBase: options.apiBase,
|
|
1571
|
+
knownFloorPublicKeys: options.knownFloorPublicKeys,
|
|
1572
|
+
fetchImpl: options.fetch
|
|
1573
|
+
}).then(() => void 0);
|
|
1574
|
+
this.mcp = new MCPClient({
|
|
1575
|
+
...options,
|
|
1576
|
+
endpoint,
|
|
1577
|
+
x402: options.x402,
|
|
1578
|
+
preflightHook
|
|
1579
|
+
});
|
|
1116
1580
|
}
|
|
1117
1581
|
/**
|
|
1118
1582
|
* Resolve the MCP endpoint via discovery if requested. Safe to call
|
|
1119
|
-
* multiple times
|
|
1583
|
+
* multiple times: the first successful lookup is cached.
|
|
1120
1584
|
*/
|
|
1121
1585
|
async discoverEndpoint() {
|
|
1122
1586
|
if (this.discovered) return this.discovered;
|
|
@@ -1129,7 +1593,7 @@ var AlterClient = class {
|
|
|
1129
1593
|
return this.discoveryPromise;
|
|
1130
1594
|
}
|
|
1131
1595
|
/**
|
|
1132
|
-
* Initialise the MCP session. Optional
|
|
1596
|
+
* Initialise the MCP session. Optional: every method calls
|
|
1133
1597
|
* `mcp.initialize()` lazily, but you can call this once at startup if
|
|
1134
1598
|
* you want fail-fast behaviour.
|
|
1135
1599
|
*/
|
|
@@ -1137,11 +1601,11 @@ var AlterClient = class {
|
|
|
1137
1601
|
await this.mcp.initialize();
|
|
1138
1602
|
}
|
|
1139
1603
|
// ── Free tier ────────────────────────────────────────────────────────
|
|
1140
|
-
/** First handshake
|
|
1604
|
+
/** First handshake: confirms the connection, returns trust tier and tool counts. */
|
|
1141
1605
|
async helloAgent() {
|
|
1142
1606
|
return this.mcp.callTool("hello_agent", {});
|
|
1143
1607
|
}
|
|
1144
|
-
/** Resolve a ~handle (e.g. ~
|
|
1608
|
+
/** Resolve a ~handle (e.g. ~example) to its canonical form and kind. No auth required. */
|
|
1145
1609
|
async resolveHandle(args) {
|
|
1146
1610
|
const payload = typeof args === "string" ? { query: args } : args;
|
|
1147
1611
|
return this.mcp.callTool("alter_resolve_handle", payload);
|
|
@@ -1149,7 +1613,7 @@ var AlterClient = class {
|
|
|
1149
1613
|
/** Verify a person is registered with ALTER (handle or id). */
|
|
1150
1614
|
async verify(handleOrId, claims) {
|
|
1151
1615
|
const args = handleOrId.includes("@") ? { member_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
|
|
1152
|
-
// ~handle
|
|
1616
|
+
// ~handle: server resolves these via the member_id field
|
|
1153
1617
|
{ member_id: handleOrId }
|
|
1154
1618
|
) : { member_id: handleOrId };
|
|
1155
1619
|
if (claims) args.claims = claims;
|
|
@@ -1249,7 +1713,7 @@ var AlterClient = class {
|
|
|
1249
1713
|
}
|
|
1250
1714
|
// ── Alter-to-Alter Messaging ─────────────────────────────────────────
|
|
1251
1715
|
// Wave 1: cross-handle direct messages between authenticated tilde
|
|
1252
|
-
// handles. Default closed
|
|
1716
|
+
// handles. Default closed: recipient must have granted the sender via
|
|
1253
1717
|
// alter_message_grant. Spec: docs/technical/Alter-to-Alter Messaging.md.
|
|
1254
1718
|
/** Send a direct message to another tilde handle. */
|
|
1255
1719
|
async messageSend(args) {
|
|
@@ -1283,7 +1747,7 @@ var AlterClient = class {
|
|
|
1283
1747
|
/**
|
|
1284
1748
|
* Verify the ES256 provenance attestation on a tool response.
|
|
1285
1749
|
* Accepts either a {@link ProvenanceEnvelope} or the raw `_meta`
|
|
1286
|
-
* object
|
|
1750
|
+
* object: the latter is more convenient for ad-hoc verification.
|
|
1287
1751
|
*/
|
|
1288
1752
|
async verifyProvenance(envelope) {
|
|
1289
1753
|
if (!envelope) return { valid: false, reason: "no provenance envelope" };
|
|
@@ -1323,7 +1787,7 @@ function generateGenericMcpConfig(opts = {}) {
|
|
|
1323
1787
|
const entry = {
|
|
1324
1788
|
url: opts.endpoint ?? DEFAULT_ENDPOINT,
|
|
1325
1789
|
transport: "streamable-http",
|
|
1326
|
-
description: "ALTER Identity
|
|
1790
|
+
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1327
1791
|
};
|
|
1328
1792
|
if (Object.keys(headers).length > 0) entry.headers = headers;
|
|
1329
1793
|
return { mcpServers: { [serverName]: entry } };
|
|
@@ -1351,7 +1815,7 @@ function generateClaudeDesktopConfig(opts = {}) {
|
|
|
1351
1815
|
const entry = {
|
|
1352
1816
|
command: bridgeCommand,
|
|
1353
1817
|
env: env2,
|
|
1354
|
-
description: "ALTER Identity
|
|
1818
|
+
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1355
1819
|
};
|
|
1356
1820
|
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
1357
1821
|
entry.args = [...opts.extraArgs];
|
|
@@ -1362,11 +1826,6 @@ function generateClaudeDesktopConfig(opts = {}) {
|
|
|
1362
1826
|
// src/wire/index.ts
|
|
1363
1827
|
init_cjs_shims();
|
|
1364
1828
|
|
|
1365
|
-
// src/meta.ts
|
|
1366
|
-
init_cjs_shims();
|
|
1367
|
-
var SDK_NAME = "@truealter/sdk";
|
|
1368
|
-
var SDK_VERSION = "0.3.0";
|
|
1369
|
-
|
|
1370
1829
|
// src/wire/paths.ts
|
|
1371
1830
|
init_cjs_shims();
|
|
1372
1831
|
var HOME = os.homedir();
|
|
@@ -1499,7 +1958,7 @@ function probeAll() {
|
|
|
1499
1958
|
// src/wire/sync.ts
|
|
1500
1959
|
init_cjs_shims();
|
|
1501
1960
|
var SYNC_PREFIXES = [
|
|
1502
|
-
// iCloud Drive
|
|
1961
|
+
// iCloud Drive: both the new and legacy mounts.
|
|
1503
1962
|
"Library/Mobile Documents/com~apple~CloudDocs",
|
|
1504
1963
|
"iCloud Drive",
|
|
1505
1964
|
// OneDrive variants Microsoft ships across editions.
|
|
@@ -1512,7 +1971,7 @@ var SYNC_PREFIXES = [
|
|
|
1512
1971
|
"Google Drive",
|
|
1513
1972
|
"GoogleDrive",
|
|
1514
1973
|
"CloudStorage/GoogleDrive",
|
|
1515
|
-
// Box, pCloud, Sync.com, MEGA
|
|
1974
|
+
// Box, pCloud, Sync.com, MEGA: high-signal names worth refusing.
|
|
1516
1975
|
"Box Sync",
|
|
1517
1976
|
"pCloud Drive",
|
|
1518
1977
|
"Sync.com",
|
|
@@ -1570,10 +2029,10 @@ function atomicJsonMerge(opts) {
|
|
|
1570
2029
|
preBytes = fs.readFileSync(path$1, "utf8");
|
|
1571
2030
|
if (preBytes.trim().length > 0) {
|
|
1572
2031
|
try {
|
|
1573
|
-
parsed = JSON.parse(preBytes);
|
|
2032
|
+
parsed = JSON.parse(preBytes.replace(/^\uFEFF/, ""));
|
|
1574
2033
|
} catch (err) {
|
|
1575
2034
|
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
|
|
2035
|
+
`refusing to wire ${path$1}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter wire\`.`
|
|
1577
2036
|
);
|
|
1578
2037
|
}
|
|
1579
2038
|
if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
|
|
@@ -1626,6 +2085,26 @@ function restoreFromBackup(path, backupPath) {
|
|
|
1626
2085
|
// src/wire/index.ts
|
|
1627
2086
|
var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
|
|
1628
2087
|
var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2088
|
+
function readCfAccessEnv() {
|
|
2089
|
+
const envPath = path.join(os.homedir(), ".config", "alter", "cf-access.env");
|
|
2090
|
+
try {
|
|
2091
|
+
const content = fs.readFileSync(envPath, "utf8");
|
|
2092
|
+
let clientId = "";
|
|
2093
|
+
let clientSecret = "";
|
|
2094
|
+
for (const line of content.split("\n")) {
|
|
2095
|
+
const trimmed = line.trim();
|
|
2096
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
2097
|
+
const eqIdx = trimmed.indexOf("=");
|
|
2098
|
+
const key = trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim();
|
|
2099
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
2100
|
+
if (key === "CF_ACCESS_CLIENT_ID") clientId = val;
|
|
2101
|
+
if (key === "CF_ACCESS_CLIENT_SECRET") clientSecret = val;
|
|
2102
|
+
}
|
|
2103
|
+
if (clientId && clientSecret) return { clientId, clientSecret };
|
|
2104
|
+
} catch {
|
|
2105
|
+
}
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
1629
2108
|
function clientById(id) {
|
|
1630
2109
|
const hit = ALL_CLIENTS.find((c) => c.id === id);
|
|
1631
2110
|
if (!hit) throw new Error(`unknown client id: ${id}`);
|
|
@@ -1634,6 +2113,7 @@ function clientById(id) {
|
|
|
1634
2113
|
function wire(opts = {}) {
|
|
1635
2114
|
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1636
2115
|
const apiKey = opts.apiKey;
|
|
2116
|
+
const cfAccess = opts.cfAccess ?? readCfAccessEnv();
|
|
1637
2117
|
const probes = probeAll();
|
|
1638
2118
|
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1639
2119
|
const ts = TIMESTAMP();
|
|
@@ -1652,9 +2132,9 @@ function wire(opts = {}) {
|
|
|
1652
2132
|
}
|
|
1653
2133
|
try {
|
|
1654
2134
|
if (id === "claude-code") {
|
|
1655
|
-
targets.push(wireClaudeCode({ endpoint, apiKey }));
|
|
2135
|
+
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
|
|
1656
2136
|
} else {
|
|
1657
|
-
targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
|
|
2137
|
+
targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
|
|
1658
2138
|
}
|
|
1659
2139
|
} catch (err) {
|
|
1660
2140
|
const message = err.message;
|
|
@@ -1685,10 +2165,15 @@ function wireFileTarget(args) {
|
|
|
1685
2165
|
const sync = detectSyncedVolume(client.configPath);
|
|
1686
2166
|
if (sync) {
|
|
1687
2167
|
throw new Error(
|
|
1688
|
-
`refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices
|
|
2168
|
+
`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.`
|
|
1689
2169
|
);
|
|
1690
2170
|
}
|
|
1691
|
-
const
|
|
2171
|
+
const cfHeaders = {};
|
|
2172
|
+
if (args.cfAccess) {
|
|
2173
|
+
cfHeaders["CF-Access-Client-Id"] = args.cfAccess.clientId;
|
|
2174
|
+
cfHeaders["CF-Access-Client-Secret"] = args.cfAccess.clientSecret;
|
|
2175
|
+
}
|
|
2176
|
+
const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey, headers: cfHeaders });
|
|
1692
2177
|
const rootKey = client.rootKey;
|
|
1693
2178
|
const serverName = "alter";
|
|
1694
2179
|
const result = atomicJsonMerge({
|
|
@@ -1720,7 +2205,8 @@ function wireFileTarget(args) {
|
|
|
1720
2205
|
}
|
|
1721
2206
|
function wireClaudeCode(args) {
|
|
1722
2207
|
const cmd = "claude";
|
|
1723
|
-
const
|
|
2208
|
+
const bridgePath = resolveBridgeScript();
|
|
2209
|
+
const argList = bridgePath ? ["mcp", "add", "--scope", "user", "alter", "--", "node", bridgePath] : [
|
|
1724
2210
|
"mcp",
|
|
1725
2211
|
"add",
|
|
1726
2212
|
"--scope",
|
|
@@ -1728,16 +2214,15 @@ function wireClaudeCode(args) {
|
|
|
1728
2214
|
"--transport",
|
|
1729
2215
|
"http",
|
|
1730
2216
|
"alter",
|
|
1731
|
-
args.endpoint
|
|
2217
|
+
args.endpoint,
|
|
2218
|
+
...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
|
|
1732
2219
|
];
|
|
1733
|
-
if (args.apiKey) {
|
|
1734
|
-
argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
|
|
1735
|
-
}
|
|
1736
2220
|
const full = `${cmd} ${argList.join(" ")}`;
|
|
1737
2221
|
const run = child_process.spawnSync(cmd, argList, {
|
|
1738
2222
|
encoding: "utf8",
|
|
1739
2223
|
shell: process.platform === "win32",
|
|
1740
|
-
timeout: 1e4
|
|
2224
|
+
timeout: 1e4,
|
|
2225
|
+
env: bridgePath ? { ...process.env, ALTER_PUBLIC_MCP_ENDPOINT: args.endpoint, ...args.apiKey ? { ALTER_API_KEY: args.apiKey } : {} } : void 0
|
|
1741
2226
|
});
|
|
1742
2227
|
if (run.error) {
|
|
1743
2228
|
return {
|
|
@@ -1768,6 +2253,16 @@ function wireClaudeCode(args) {
|
|
|
1768
2253
|
reason: `claude mcp add exited ${String(run.status)}`
|
|
1769
2254
|
};
|
|
1770
2255
|
}
|
|
2256
|
+
function resolveBridgeScript() {
|
|
2257
|
+
const here = path.dirname(url.fileURLToPath(importMetaUrl));
|
|
2258
|
+
const siblingBridge = path.join(here, "..", "dist", "mcp-bridge.js");
|
|
2259
|
+
if (fs.existsSync(siblingBridge)) return siblingBridge;
|
|
2260
|
+
const srcBridge = path.join(here, "..", "mcp-bridge.js");
|
|
2261
|
+
if (fs.existsSync(srcBridge)) return srcBridge;
|
|
2262
|
+
const npmGlobalBridge = path.join(here, "mcp-bridge.js");
|
|
2263
|
+
if (fs.existsSync(npmGlobalBridge)) return npmGlobalBridge;
|
|
2264
|
+
return null;
|
|
2265
|
+
}
|
|
1771
2266
|
function unwire() {
|
|
1772
2267
|
const state = readWireState();
|
|
1773
2268
|
const undone = [];
|
|
@@ -1919,19 +2414,19 @@ var TOOL_COSTS = {
|
|
|
1919
2414
|
complete_knot: 0,
|
|
1920
2415
|
check_golden_thread: 0,
|
|
1921
2416
|
thread_census: 0,
|
|
1922
|
-
// L1 ($0.
|
|
1923
|
-
assess_traits:
|
|
1924
|
-
get_trait_snapshot:
|
|
1925
|
-
// L2 ($0.
|
|
1926
|
-
get_full_trait_vector: 0.
|
|
1927
|
-
get_side_quest_graph: 0.
|
|
1928
|
-
// L3 ($0.
|
|
1929
|
-
query_graph_similarity: 0.
|
|
1930
|
-
// L4 ($0.
|
|
1931
|
-
compute_belonging: 0.
|
|
1932
|
-
// L5 ($
|
|
1933
|
-
get_match_recommendations:
|
|
1934
|
-
generate_match_narrative:
|
|
2417
|
+
// L1 ($0.01)
|
|
2418
|
+
assess_traits: 0.01,
|
|
2419
|
+
get_trait_snapshot: 0.01,
|
|
2420
|
+
// L2 ($0.10)
|
|
2421
|
+
get_full_trait_vector: 0.1,
|
|
2422
|
+
get_side_quest_graph: 0.1,
|
|
2423
|
+
// L3 ($0.30)
|
|
2424
|
+
query_graph_similarity: 0.3,
|
|
2425
|
+
// L4 ($0.60)
|
|
2426
|
+
compute_belonging: 0.6,
|
|
2427
|
+
// L5 ($1.00)
|
|
2428
|
+
get_match_recommendations: 1,
|
|
2429
|
+
generate_match_narrative: 1
|
|
1935
2430
|
};
|
|
1936
2431
|
var TOOL_BLAST_RADIUS = {
|
|
1937
2432
|
// Low: read-only reference
|
|
@@ -1971,13 +2466,110 @@ var TOOL_BLAST_RADIUS = {
|
|
|
1971
2466
|
query_graph_similarity: "high"
|
|
1972
2467
|
};
|
|
1973
2468
|
|
|
2469
|
+
// src/pricing.generated.ts
|
|
2470
|
+
init_cjs_shims();
|
|
2471
|
+
var GENERATED_TOOL_TIERS = {
|
|
2472
|
+
// L0 (free)
|
|
2473
|
+
alter_presence_read: 0,
|
|
2474
|
+
alter_resolve_handle: 0,
|
|
2475
|
+
check_assessment_status: 0,
|
|
2476
|
+
create_identity_stub: 0,
|
|
2477
|
+
dispute_attestation: 0,
|
|
2478
|
+
get_competencies: 0,
|
|
2479
|
+
get_earning_summary: 0,
|
|
2480
|
+
get_engagement_level: 0,
|
|
2481
|
+
get_identity_earnings: 0,
|
|
2482
|
+
get_identity_trust_score: 0,
|
|
2483
|
+
get_network_stats: 0,
|
|
2484
|
+
get_privacy_budget: 0,
|
|
2485
|
+
get_profile: 0,
|
|
2486
|
+
initiate_assessment: 0,
|
|
2487
|
+
list_archetypes: 0,
|
|
2488
|
+
query_matches: 0,
|
|
2489
|
+
recommend_tool: 0,
|
|
2490
|
+
search_identities: 0,
|
|
2491
|
+
verify_identity: 0,
|
|
2492
|
+
// L1 ($0.01)
|
|
2493
|
+
assess_traits: 1,
|
|
2494
|
+
attest_domain: 1,
|
|
2495
|
+
get_trait_snapshot: 1,
|
|
2496
|
+
poll_requirement_matches: 1,
|
|
2497
|
+
submit_context: 1,
|
|
2498
|
+
submit_social_links: 1,
|
|
2499
|
+
submit_structured_profile: 1,
|
|
2500
|
+
// L2 ($0.10)
|
|
2501
|
+
attest_claim_provenance: 2,
|
|
2502
|
+
get_full_trait_vector: 2,
|
|
2503
|
+
get_side_quest_graph: 2,
|
|
2504
|
+
submit_batch_context: 2,
|
|
2505
|
+
// L3 ($0.30)
|
|
2506
|
+
alter_alignment: 3,
|
|
2507
|
+
query_graph_similarity: 3,
|
|
2508
|
+
// L4 ($0.60)
|
|
2509
|
+
compute_belonging: 4,
|
|
2510
|
+
// L5 ($1.00)
|
|
2511
|
+
generate_match_narrative: 5,
|
|
2512
|
+
get_match_recommendations: 5,
|
|
2513
|
+
query_field: 5
|
|
2514
|
+
};
|
|
2515
|
+
var GENERATED_TOOL_PRICING = {
|
|
2516
|
+
// L1: $0.01
|
|
2517
|
+
assess_traits: 0.01,
|
|
2518
|
+
attest_domain: 0.01,
|
|
2519
|
+
get_trait_snapshot: 0.01,
|
|
2520
|
+
poll_requirement_matches: 0.01,
|
|
2521
|
+
submit_context: 0.01,
|
|
2522
|
+
submit_social_links: 0.01,
|
|
2523
|
+
submit_structured_profile: 0.01,
|
|
2524
|
+
// L2: $0.10
|
|
2525
|
+
attest_claim_provenance: 0.1,
|
|
2526
|
+
get_full_trait_vector: 0.1,
|
|
2527
|
+
get_side_quest_graph: 0.1,
|
|
2528
|
+
submit_batch_context: 0.1,
|
|
2529
|
+
// L3: $0.30
|
|
2530
|
+
alter_alignment: 0.3,
|
|
2531
|
+
query_graph_similarity: 0.3,
|
|
2532
|
+
// L4: $0.60
|
|
2533
|
+
compute_belonging: 0.6,
|
|
2534
|
+
// L5: $1.00
|
|
2535
|
+
generate_match_narrative: 1,
|
|
2536
|
+
get_match_recommendations: 1,
|
|
2537
|
+
query_field: 1
|
|
2538
|
+
};
|
|
2539
|
+
var TIER_PRICES = {
|
|
2540
|
+
L0: 0,
|
|
2541
|
+
L1: 0.01,
|
|
2542
|
+
L2: 0.1,
|
|
2543
|
+
L3: 0.3,
|
|
2544
|
+
L4: 0.6,
|
|
2545
|
+
L5: 1
|
|
2546
|
+
};
|
|
2547
|
+
var ADVERTISED_TOOL_COUNTS = {
|
|
2548
|
+
/** Free (L0) tools visible to anonymous / agent-class callers. */
|
|
2549
|
+
free: 26,
|
|
2550
|
+
/** Premium (L1-L5) tools requiring x402 payment. */
|
|
2551
|
+
premium: 8,
|
|
2552
|
+
/** Messaging tools (member-self-only; excluded from external advertisement). */
|
|
2553
|
+
messaging: 0,
|
|
2554
|
+
/** Total publicly advertised (free + premium). */
|
|
2555
|
+
total: 34
|
|
2556
|
+
};
|
|
2557
|
+
var REVENUE_SPLIT = {
|
|
2558
|
+
weaver: 0.75,
|
|
2559
|
+
// data subject (Identity Income)
|
|
2560
|
+
facilitator: 0.05,
|
|
2561
|
+
alter: 0.15,
|
|
2562
|
+
treasury: 0.05
|
|
2563
|
+
};
|
|
2564
|
+
|
|
1974
2565
|
// src/homepage.ts
|
|
1975
2566
|
init_cjs_shims();
|
|
1976
2567
|
var HOMEPAGE_LIMITS = {
|
|
1977
2568
|
whoami_max_chars: 240,
|
|
1978
2569
|
opener_max_chars: 280,
|
|
1979
2570
|
pronouns_max_chars: 32,
|
|
1980
|
-
attunement_glyph_max_chars: 16
|
|
2571
|
+
attunement_glyph_max_chars: 16,
|
|
2572
|
+
contact_email_max_chars: 254
|
|
1981
2573
|
};
|
|
1982
2574
|
|
|
1983
2575
|
// src/themes.ts
|
|
@@ -1991,6 +2583,7 @@ var THEME_LIMITS = {
|
|
|
1991
2583
|
};
|
|
1992
2584
|
var OSC8_ALLOWED_SCHEMES = ["https:", "mailto:"];
|
|
1993
2585
|
|
|
2586
|
+
exports.ADVERTISED_TOOL_COUNTS = ADVERTISED_TOOL_COUNTS;
|
|
1994
2587
|
exports.ALL_CLIENTS = ALL_CLIENTS;
|
|
1995
2588
|
exports.AlterAuthError = AlterAuthError;
|
|
1996
2589
|
exports.AlterClient = AlterClient;
|
|
@@ -2003,21 +2596,30 @@ exports.AlterProvenanceError = AlterProvenanceError;
|
|
|
2003
2596
|
exports.AlterRateLimited = AlterRateLimited;
|
|
2004
2597
|
exports.AlterTimeoutError = AlterTimeoutError;
|
|
2005
2598
|
exports.AlterToolError = AlterToolError;
|
|
2599
|
+
exports.BelowFloorError = BelowFloorError;
|
|
2006
2600
|
exports.CLAUDE_CODE = CLAUDE_CODE;
|
|
2007
2601
|
exports.CLAUDE_DESKTOP = CLAUDE_DESKTOP;
|
|
2602
|
+
exports.CLIENT_CHANNEL = CLIENT_CHANNEL;
|
|
2603
|
+
exports.CLIENT_ID = CLIENT_ID;
|
|
2008
2604
|
exports.CURSOR = CURSOR;
|
|
2009
2605
|
exports.DEFAULT_DOMAIN = DEFAULT_DOMAIN;
|
|
2010
2606
|
exports.DEFAULT_ENDPOINT = DEFAULT_ENDPOINT;
|
|
2011
2607
|
exports.DEFAULT_VERIFY_AT_ALLOWLIST = DEFAULT_VERIFY_AT_ALLOWLIST;
|
|
2012
2608
|
exports.FREE_TOOL_NAMES = FREE_TOOL_NAMES;
|
|
2609
|
+
exports.GENERATED_TOOL_PRICING = GENERATED_TOOL_PRICING;
|
|
2610
|
+
exports.GENERATED_TOOL_TIERS = GENERATED_TOOL_TIERS;
|
|
2013
2611
|
exports.HOMEPAGE_LIMITS = HOMEPAGE_LIMITS;
|
|
2612
|
+
exports.KNOWN_FLOOR_PUBLIC_KEYS = KNOWN_FLOOR_PUBLIC_KEYS;
|
|
2014
2613
|
exports.MCPClient = MCPClient;
|
|
2015
2614
|
exports.MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
|
|
2615
|
+
exports.MIN_VERSION_ENDPOINT = MIN_VERSION_ENDPOINT;
|
|
2016
2616
|
exports.OSC8_ALLOWED_SCHEMES = OSC8_ALLOWED_SCHEMES;
|
|
2017
2617
|
exports.PREMIUM_TOOL_NAMES = PREMIUM_TOOL_NAMES;
|
|
2618
|
+
exports.REVENUE_SPLIT = REVENUE_SPLIT;
|
|
2018
2619
|
exports.SDK_NAME = SDK_NAME;
|
|
2019
2620
|
exports.SDK_VERSION = SDK_VERSION;
|
|
2020
2621
|
exports.THEME_LIMITS = THEME_LIMITS;
|
|
2622
|
+
exports.TIER_PRICES = TIER_PRICES;
|
|
2021
2623
|
exports.TOOL_BLAST_RADIUS = TOOL_BLAST_RADIUS;
|
|
2022
2624
|
exports.TOOL_COSTS = TOOL_COSTS;
|
|
2023
2625
|
exports.TOOL_TIERS = TOOL_TIERS;
|
|
@@ -2026,8 +2628,12 @@ exports.X402Client = X402Client;
|
|
|
2026
2628
|
exports.base64urlDecode = base64urlDecode;
|
|
2027
2629
|
exports.base64urlEncode = base64urlEncode2;
|
|
2028
2630
|
exports.canonicalArgsSha256 = canonicalArgsSha256;
|
|
2631
|
+
exports.canonicalJson = canonicalJson;
|
|
2029
2632
|
exports.canonicalStringify = canonicalStringify;
|
|
2633
|
+
exports.checkMinVersion = checkMinVersion;
|
|
2030
2634
|
exports.clearDiscoveryCache = clearDiscoveryCache;
|
|
2635
|
+
exports.compareSemver = compareSemver;
|
|
2636
|
+
exports.computeKeyId = computeKeyId;
|
|
2031
2637
|
exports.decodeDid = decodeDid;
|
|
2032
2638
|
exports.detectSyncedVolume = detectSyncedVolume;
|
|
2033
2639
|
exports.discover = discover;
|
|
@@ -2051,6 +2657,7 @@ exports.sign = sign;
|
|
|
2051
2657
|
exports.signInvocation = signInvocation;
|
|
2052
2658
|
exports.unwire = unwire;
|
|
2053
2659
|
exports.verify = verify;
|
|
2660
|
+
exports.verifyFloorSignature = verifyFloorSignature;
|
|
2054
2661
|
exports.verifyProvenance = verifyProvenance;
|
|
2055
2662
|
exports.verifyToolSignatures = verifyToolSignatures;
|
|
2056
2663
|
exports.wire = wire;
|