@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.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { p256 } from '@noble/curves/p256';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
3
|
import { randomBytes, bytesToHex as bytesToHex$1, hexToBytes } from '@noble/hashes/utils';
|
|
4
|
-
import { createPrivateKey, createHash } from 'crypto';
|
|
4
|
+
import { createPrivateKey, createPublicKey, createHash, verify as verify$1 } from 'crypto';
|
|
5
|
+
import { statSync, readFileSync, mkdirSync, writeFileSync, chmodSync, renameSync, existsSync, copyFileSync, unlinkSync } from 'fs';
|
|
6
|
+
import { homedir, platform } from 'os';
|
|
7
|
+
import { join, dirname, resolve } from 'path';
|
|
5
8
|
import * as ed25519 from '@noble/ed25519';
|
|
6
9
|
import { sha512 } from '@noble/hashes/sha512';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
7
11
|
import { spawnSync } from 'child_process';
|
|
8
|
-
import { homedir, platform } from 'os';
|
|
9
|
-
import { join, resolve, dirname } from 'path';
|
|
10
12
|
import { env } from 'process';
|
|
11
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, renameSync, unlinkSync } from 'fs';
|
|
12
13
|
|
|
13
14
|
var __defProp = Object.defineProperty;
|
|
14
15
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -363,11 +364,11 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
|
|
|
363
364
|
}
|
|
364
365
|
if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
|
|
365
366
|
throw new AlterNetworkError(
|
|
366
|
-
`${url}
|
|
367
|
+
`${url} -> redirect rejected (discovery must not follow redirects; validate the server configuration)`
|
|
367
368
|
);
|
|
368
369
|
}
|
|
369
370
|
if (resp.status === 404) return null;
|
|
370
|
-
if (!resp.ok) throw new AlterNetworkError(`${url}
|
|
371
|
+
if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
|
|
371
372
|
const doc = await resp.json();
|
|
372
373
|
if (file === "mcp.json") {
|
|
373
374
|
const remotes = doc.remotes || [];
|
|
@@ -401,17 +402,326 @@ function ensureMcpPath(url) {
|
|
|
401
402
|
}
|
|
402
403
|
}
|
|
403
404
|
|
|
404
|
-
// src/
|
|
405
|
+
// src/meta.ts
|
|
406
|
+
var SDK_NAME = "@truealter/sdk";
|
|
407
|
+
var SDK_VERSION = "0.5.3" ;
|
|
408
|
+
|
|
409
|
+
// src/floor-preflight.ts
|
|
410
|
+
var MIN_VERSION_ENDPOINT = "/v1/clients/min-version";
|
|
411
|
+
var CLIENT_ID = "alter-identity";
|
|
412
|
+
var CLIENT_CHANNEL = "npm";
|
|
413
|
+
var IN_MEMORY_TTL_DEFAULT_MS = 60 * 60 * 1e3;
|
|
414
|
+
var IN_MEMORY_TTL_MIN_MS = 60 * 1e3;
|
|
415
|
+
var IN_MEMORY_TTL_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
416
|
+
var DISK_FRESH_MS = 24 * 60 * 60 * 1e3;
|
|
417
|
+
var DISK_WARN_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
418
|
+
var FETCH_TIMEOUT_MS = 4e3;
|
|
419
|
+
function computeKeyId(publicKeyPem) {
|
|
420
|
+
if (!publicKeyPem) return "00000000";
|
|
421
|
+
const pub = createPublicKey({ key: publicKeyPem, format: "pem" });
|
|
422
|
+
const jwk = pub.export({ format: "jwk" });
|
|
423
|
+
const rawBytes = Buffer.from(jwk.x, "base64url");
|
|
424
|
+
return createHash("sha256").update(rawBytes).digest("hex").slice(0, 8);
|
|
425
|
+
}
|
|
426
|
+
function canonicalJson(obj) {
|
|
427
|
+
return JSON.stringify(sortKeysDeep(obj));
|
|
428
|
+
}
|
|
429
|
+
function sortKeysDeep(value) {
|
|
430
|
+
if (Array.isArray(value)) {
|
|
431
|
+
return value.map(sortKeysDeep);
|
|
432
|
+
}
|
|
433
|
+
if (value !== null && typeof value === "object") {
|
|
434
|
+
const obj = value;
|
|
435
|
+
const sorted = {};
|
|
436
|
+
for (const k of Object.keys(obj).sort()) {
|
|
437
|
+
sorted[k] = sortKeysDeep(obj[k]);
|
|
438
|
+
}
|
|
439
|
+
return sorted;
|
|
440
|
+
}
|
|
441
|
+
return value;
|
|
442
|
+
}
|
|
443
|
+
var KNOWN_FLOOR_PUBLIC_KEYS = {
|
|
444
|
+
"8aa59e05": `-----BEGIN PUBLIC KEY-----
|
|
445
|
+
MCowBQYDK2VwAyEAgqw28dlniOuiTE1f4BxCPSEgMLaPtHsO8wN5RWEwEhE=
|
|
446
|
+
-----END PUBLIC KEY-----`,
|
|
447
|
+
"640f7d9a": `-----BEGIN PUBLIC KEY-----
|
|
448
|
+
MCowBQYDK2VwAyEARzvAWayDwHvZRfOZizGZe+/a7PF082WGhyMS3tx06H4=
|
|
449
|
+
-----END PUBLIC KEY-----`
|
|
450
|
+
};
|
|
451
|
+
var BelowFloorError = class extends Error {
|
|
452
|
+
name = "BelowFloorError";
|
|
453
|
+
code = "client_below_floor";
|
|
454
|
+
client_version;
|
|
455
|
+
min_version;
|
|
456
|
+
upgrade_cmd;
|
|
457
|
+
channel;
|
|
458
|
+
envelope;
|
|
459
|
+
constructor(envelope) {
|
|
460
|
+
super(envelope.error.message);
|
|
461
|
+
this.envelope = envelope;
|
|
462
|
+
this.client_version = envelope.error.client_version;
|
|
463
|
+
this.min_version = envelope.error.min_version;
|
|
464
|
+
this.upgrade_cmd = envelope.error.upgrade_cmd;
|
|
465
|
+
this.channel = envelope.error.channel;
|
|
466
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
var memCache = null;
|
|
470
|
+
async function checkMinVersion(opts = {}) {
|
|
471
|
+
const apiBase = opts.apiBase ?? defaultApiBase();
|
|
472
|
+
const clientVersion = opts.clientVersion ?? SDK_VERSION;
|
|
473
|
+
const clientId = opts.clientId ?? CLIENT_ID;
|
|
474
|
+
const channel = opts.channel ?? CLIENT_CHANNEL;
|
|
475
|
+
const knownKeys = opts.knownFloorPublicKeys ?? KNOWN_FLOOR_PUBLIC_KEYS;
|
|
476
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
477
|
+
const now = opts.now ?? Date.now;
|
|
478
|
+
const cachePath = opts.diskCachePath === void 0 ? defaultDiskCachePath() : opts.diskCachePath;
|
|
479
|
+
const mem = readInMemoryCache(now);
|
|
480
|
+
if (mem) {
|
|
481
|
+
return compareAndPermit(mem, {
|
|
482
|
+
clientVersion,
|
|
483
|
+
clientId,
|
|
484
|
+
channel,
|
|
485
|
+
diagnostic: "mem-cache-hit"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const disk = cachePath ? readDiskCache(cachePath, knownKeys) : null;
|
|
489
|
+
const diskAgeMs = disk ? now() - disk.fetched_at_ms : Number.POSITIVE_INFINITY;
|
|
490
|
+
let fetched = null;
|
|
491
|
+
let fetchError = null;
|
|
492
|
+
if (!disk || diskAgeMs > IN_MEMORY_TTL_DEFAULT_MS) {
|
|
493
|
+
try {
|
|
494
|
+
fetched = await fetchFloorDoc(apiBase, fetchImpl, knownKeys);
|
|
495
|
+
} catch (err) {
|
|
496
|
+
fetchError = err.message ?? "fetch-error";
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (fetched) {
|
|
500
|
+
populateMemCache(fetched, now());
|
|
501
|
+
if (cachePath) writeDiskCache(cachePath, fetched, now());
|
|
502
|
+
return compareAndPermit(fetched, {
|
|
503
|
+
clientVersion,
|
|
504
|
+
clientId,
|
|
505
|
+
channel,
|
|
506
|
+
diagnostic: "fetched"
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
if (disk) {
|
|
510
|
+
populateMemCache(disk.doc, disk.fetched_at_ms);
|
|
511
|
+
if (diskAgeMs > DISK_WARN_MS) {
|
|
512
|
+
return compareAndPermit(disk.doc, {
|
|
513
|
+
clientVersion,
|
|
514
|
+
clientId,
|
|
515
|
+
channel,
|
|
516
|
+
diagnostic: "below-floor-offline-stale-or-permit",
|
|
517
|
+
warn: `floor cache is >7d old and backend unreachable (${fetchError ?? "no refresh attempted"}); permitting if above floor`
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
if (diskAgeMs > DISK_FRESH_MS) {
|
|
521
|
+
return compareAndPermit(disk.doc, {
|
|
522
|
+
clientVersion,
|
|
523
|
+
clientId,
|
|
524
|
+
channel,
|
|
525
|
+
diagnostic: "warn-stale-permit",
|
|
526
|
+
warn: `floor cache is ${Math.round(diskAgeMs / (60 * 60 * 1e3))}h old; refresh recommended`
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return compareAndPermit(disk.doc, {
|
|
530
|
+
clientVersion,
|
|
531
|
+
clientId,
|
|
532
|
+
channel,
|
|
533
|
+
diagnostic: "disk-cache-hit"
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
ok: true,
|
|
538
|
+
floor: null,
|
|
539
|
+
diagnostic: "no-cache-no-fetch-permit",
|
|
540
|
+
warn: `floor preflight skipped: backend unreachable (${fetchError ?? "unknown"})`
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function compareAndPermit(doc, ctx) {
|
|
544
|
+
const floor = lookupFloor(doc, ctx.clientId, ctx.channel);
|
|
545
|
+
if (!floor) {
|
|
546
|
+
return { ok: true, floor: null, diagnostic: `${ctx.diagnostic}+no-floor`, warn: ctx.warn };
|
|
547
|
+
}
|
|
548
|
+
if (compareSemver(ctx.clientVersion, floor.min_version) >= 0) {
|
|
549
|
+
return { ok: true, floor, diagnostic: ctx.diagnostic, warn: ctx.warn };
|
|
550
|
+
}
|
|
551
|
+
const envelope = {
|
|
552
|
+
error: {
|
|
553
|
+
code: "client_below_floor",
|
|
554
|
+
message: `Your ${ctx.clientId} is too old. Upgrade required.`,
|
|
555
|
+
client_version: ctx.clientVersion,
|
|
556
|
+
min_version: floor.min_version,
|
|
557
|
+
upgrade_cmd: floor.upgrade_cmd,
|
|
558
|
+
channel: ctx.channel
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
throw new BelowFloorError(envelope);
|
|
562
|
+
}
|
|
563
|
+
function lookupFloor(doc, clientId, channel) {
|
|
564
|
+
const entry = doc.floors[clientId];
|
|
565
|
+
if (!entry) return null;
|
|
566
|
+
if (isChannelFloor(entry)) return entry;
|
|
567
|
+
const exact = entry[channel];
|
|
568
|
+
if (exact) return exact;
|
|
569
|
+
const fallback = entry["unknown"];
|
|
570
|
+
if (fallback) return fallback;
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
function isChannelFloor(v) {
|
|
574
|
+
return typeof v.min_version === "string" && typeof v.upgrade_cmd === "string";
|
|
575
|
+
}
|
|
576
|
+
function compareSemver(a, b) {
|
|
577
|
+
const [aMaj, aMin, aPat, aPre] = parseSemver(a);
|
|
578
|
+
const [bMaj, bMin, bPat, bPre] = parseSemver(b);
|
|
579
|
+
if (aMaj !== bMaj) return aMaj - bMaj;
|
|
580
|
+
if (aMin !== bMin) return aMin - bMin;
|
|
581
|
+
if (aPat !== bPat) return aPat - bPat;
|
|
582
|
+
if (aPre && !bPre) return -1;
|
|
583
|
+
if (!aPre && bPre) return 1;
|
|
584
|
+
if (aPre && bPre) return aPre.localeCompare(bPre);
|
|
585
|
+
return 0;
|
|
586
|
+
}
|
|
587
|
+
function parseSemver(v) {
|
|
588
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);
|
|
589
|
+
if (!m) return [0, 0, 0, null];
|
|
590
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] ?? null];
|
|
591
|
+
}
|
|
592
|
+
function verifyFloorSignature(doc, keys = KNOWN_FLOOR_PUBLIC_KEYS) {
|
|
593
|
+
const pem = keys[doc.key_id];
|
|
594
|
+
if (!pem) return false;
|
|
595
|
+
if (computeKeyId(pem) !== doc.key_id) return false;
|
|
596
|
+
try {
|
|
597
|
+
const pubKeyObject = createPublicKey({ key: pem, format: "pem" });
|
|
598
|
+
const canonical = canonicalJson({
|
|
599
|
+
floors: doc.floors,
|
|
600
|
+
served_at: doc.served_at
|
|
601
|
+
});
|
|
602
|
+
return verify$1(
|
|
603
|
+
null,
|
|
604
|
+
Buffer.from(canonical, "utf-8"),
|
|
605
|
+
pubKeyObject,
|
|
606
|
+
Buffer.from(doc.signature, "hex")
|
|
607
|
+
);
|
|
608
|
+
} catch {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async function fetchFloorDoc(apiBase, fetchImpl, knownKeys) {
|
|
613
|
+
const url = `${apiBase.replace(/\/+$/, "")}${MIN_VERSION_ENDPOINT}`;
|
|
614
|
+
let response;
|
|
615
|
+
try {
|
|
616
|
+
response = await fetchImpl(url, {
|
|
617
|
+
headers: {
|
|
618
|
+
accept: "application/json",
|
|
619
|
+
"X-Alter-Client-Id": CLIENT_ID,
|
|
620
|
+
"X-Alter-Client-Version": SDK_VERSION,
|
|
621
|
+
"X-Alter-Client-Channel": CLIENT_CHANNEL,
|
|
622
|
+
"User-Agent": `${SDK_NAME}/${SDK_VERSION}`
|
|
623
|
+
},
|
|
624
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
625
|
+
});
|
|
626
|
+
} catch (err) {
|
|
627
|
+
throw new Error(`network: ${err.message ?? String(err)}`);
|
|
628
|
+
}
|
|
629
|
+
if (!response.ok) throw new Error(`http-${response.status}`);
|
|
630
|
+
const body = await response.json();
|
|
631
|
+
if (!body || !body.floors || !body.signature || !body.key_id) {
|
|
632
|
+
throw new Error("malformed-floor-doc");
|
|
633
|
+
}
|
|
634
|
+
if (!verifyFloorSignature(body, knownKeys)) {
|
|
635
|
+
throw new Error("signature-invalid");
|
|
636
|
+
}
|
|
637
|
+
return body;
|
|
638
|
+
}
|
|
639
|
+
function readInMemoryCache(now) {
|
|
640
|
+
if (!memCache) return null;
|
|
641
|
+
if (now() - memCache.fetched_at_ms > memCache.ttl_ms) {
|
|
642
|
+
memCache = null;
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
return memCache.doc;
|
|
646
|
+
}
|
|
647
|
+
function populateMemCache(doc, fetched_at_ms) {
|
|
648
|
+
const ttlSec = doc.cache_ttl_seconds ?? 3600;
|
|
649
|
+
const ttlMs = Math.min(
|
|
650
|
+
Math.max(ttlSec * 1e3, IN_MEMORY_TTL_MIN_MS),
|
|
651
|
+
IN_MEMORY_TTL_MAX_MS
|
|
652
|
+
);
|
|
653
|
+
memCache = { doc, fetched_at_ms, ttl_ms: ttlMs };
|
|
654
|
+
}
|
|
655
|
+
function defaultDiskCachePath() {
|
|
656
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
657
|
+
return join(xdg, "alter", "floor-cache.json");
|
|
658
|
+
}
|
|
659
|
+
function readDiskCache(path, knownKeys) {
|
|
660
|
+
if (process.platform !== "win32") {
|
|
661
|
+
let st;
|
|
662
|
+
try {
|
|
663
|
+
st = statSync(path);
|
|
664
|
+
} catch {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const euid = typeof process.geteuid === "function" ? process.geteuid() : st.uid;
|
|
668
|
+
if (st.uid !== euid) return null;
|
|
669
|
+
if ((st.mode & 511) !== 384) return null;
|
|
670
|
+
}
|
|
671
|
+
let raw;
|
|
672
|
+
try {
|
|
673
|
+
raw = readFileSync(path, "utf-8");
|
|
674
|
+
} catch {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
let parsed;
|
|
678
|
+
try {
|
|
679
|
+
parsed = JSON.parse(raw);
|
|
680
|
+
} catch {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
if (!parsed.doc || typeof parsed.fetched_at_ms !== "number") return null;
|
|
684
|
+
if (!verifyFloorSignature(parsed.doc, knownKeys)) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
return parsed;
|
|
688
|
+
}
|
|
689
|
+
function writeDiskCache(path, doc, now_ms) {
|
|
690
|
+
const entry = { doc, fetched_at_ms: now_ms };
|
|
691
|
+
const payload = JSON.stringify(entry);
|
|
692
|
+
try {
|
|
693
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
694
|
+
const tmp = `${path}.tmp`;
|
|
695
|
+
writeFileSync(tmp, payload, { mode: 384 });
|
|
696
|
+
try {
|
|
697
|
+
chmodSync(tmp, 384);
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
renameSync(tmp, path);
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function defaultApiBase() {
|
|
705
|
+
return process.env.ALTER_API ?? "https://api.truealter.com";
|
|
706
|
+
}
|
|
405
707
|
var X402Client = class {
|
|
406
708
|
signer;
|
|
407
709
|
maxPerQuery;
|
|
408
710
|
networks;
|
|
409
711
|
assets;
|
|
712
|
+
// undefined = allowlist check disabled (backward-compatible default).
|
|
713
|
+
// Non-null = active allowlist; reject any recipient not in the set.
|
|
714
|
+
recipientAllowlist;
|
|
410
715
|
constructor(opts = {}) {
|
|
411
716
|
this.signer = opts.signer;
|
|
412
717
|
this.maxPerQuery = opts.maxPerQuery !== void 0 ? Number(opts.maxPerQuery) : void 0;
|
|
413
718
|
this.networks = new Set(opts.networks ?? ["base", "base-sepolia"]);
|
|
414
719
|
this.assets = new Set(opts.assets ?? ["USDC"]);
|
|
720
|
+
if (opts.recipientAllowlist !== void 0) {
|
|
721
|
+
this.recipientAllowlist = opts.recipientAllowlist.length === 0 ? void 0 : new Set(opts.recipientAllowlist.map((a) => a.toLowerCase()));
|
|
722
|
+
} else {
|
|
723
|
+
this.recipientAllowlist = void 0;
|
|
724
|
+
}
|
|
415
725
|
}
|
|
416
726
|
/**
|
|
417
727
|
* Validate the envelope against this client's policy and, if a signer
|
|
@@ -437,6 +747,15 @@ var X402Client = class {
|
|
|
437
747
|
);
|
|
438
748
|
}
|
|
439
749
|
}
|
|
750
|
+
if (this.recipientAllowlist !== void 0) {
|
|
751
|
+
const recipientNorm = (envelope.recipient ?? "").toLowerCase();
|
|
752
|
+
if (!recipientNorm || !this.recipientAllowlist.has(recipientNorm)) {
|
|
753
|
+
throw new AlterError(
|
|
754
|
+
"PAYMENT_REQUIRED",
|
|
755
|
+
`recipient "${envelope.recipient}" is not on the known-recipient allowlist`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
440
759
|
if (!this.signer) {
|
|
441
760
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
442
761
|
}
|
|
@@ -495,6 +814,9 @@ var MCPClient = class {
|
|
|
495
814
|
x402;
|
|
496
815
|
signing;
|
|
497
816
|
extraHeaders;
|
|
817
|
+
preflightHook;
|
|
818
|
+
preflightPromise = null;
|
|
819
|
+
preflightDone = false;
|
|
498
820
|
requestCounter = 0;
|
|
499
821
|
initialised = false;
|
|
500
822
|
constructor(opts = {}) {
|
|
@@ -503,17 +825,43 @@ var MCPClient = class {
|
|
|
503
825
|
this.fetchImpl = opts.fetch ?? fetch;
|
|
504
826
|
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
505
827
|
this.maxRetries = opts.maxRetries ?? 2;
|
|
506
|
-
this.clientInfo = opts.clientInfo ?? { name:
|
|
828
|
+
this.clientInfo = opts.clientInfo ?? { name: SDK_NAME, version: SDK_VERSION };
|
|
507
829
|
this.x402 = opts.x402;
|
|
508
830
|
this.signing = opts.signing;
|
|
509
831
|
this.extraHeaders = opts.extraHeaders;
|
|
832
|
+
this.preflightHook = opts.preflightHook;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Run the lazy preflight hook (D-MIN-VERSION-FLOOR-1) exactly once.
|
|
836
|
+
* Idempotent and serialised: concurrent callers share the same
|
|
837
|
+
* promise. Throws from the hook propagate to every concurrent caller.
|
|
838
|
+
*/
|
|
839
|
+
async runPreflight() {
|
|
840
|
+
if (this.preflightDone) return;
|
|
841
|
+
if (!this.preflightHook) {
|
|
842
|
+
this.preflightDone = true;
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (!this.preflightPromise) {
|
|
846
|
+
this.preflightPromise = this.preflightHook().then(
|
|
847
|
+
() => {
|
|
848
|
+
this.preflightDone = true;
|
|
849
|
+
},
|
|
850
|
+
(err) => {
|
|
851
|
+
this.preflightPromise = null;
|
|
852
|
+
throw err;
|
|
853
|
+
}
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
await this.preflightPromise;
|
|
510
857
|
}
|
|
511
858
|
/**
|
|
512
859
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
513
|
-
* id. Idempotent
|
|
860
|
+
* id. Idempotent: safe to call multiple times.
|
|
514
861
|
*/
|
|
515
862
|
async initialize() {
|
|
516
863
|
if (this.initialised) return null;
|
|
864
|
+
await this.runPreflight();
|
|
517
865
|
const result = await this.rpc("initialize", {
|
|
518
866
|
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
519
867
|
capabilities: {},
|
|
@@ -673,7 +1021,10 @@ var MCPClient = class {
|
|
|
673
1021
|
...this.extraHeaders ?? {},
|
|
674
1022
|
"Content-Type": "application/json",
|
|
675
1023
|
Accept: "application/json",
|
|
676
|
-
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}
|
|
1024
|
+
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`,
|
|
1025
|
+
"X-Alter-Client-Id": "alter-identity",
|
|
1026
|
+
"X-Alter-Client-Version": SDK_VERSION,
|
|
1027
|
+
"X-Alter-Client-Channel": "npm"
|
|
677
1028
|
};
|
|
678
1029
|
if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
|
|
679
1030
|
if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
|
|
@@ -903,9 +1254,46 @@ async function verifyProvenance(envelope, opts = {}) {
|
|
|
903
1254
|
kid: header.kid
|
|
904
1255
|
};
|
|
905
1256
|
}
|
|
1257
|
+
if (opts.expectedAud !== void 0 && opts.expectedAud !== "") {
|
|
1258
|
+
const tokenAud = payload.aud;
|
|
1259
|
+
const audList = tokenAud === void 0 ? [] : Array.isArray(tokenAud) ? tokenAud : [tokenAud];
|
|
1260
|
+
if (!audList.includes(opts.expectedAud)) {
|
|
1261
|
+
return {
|
|
1262
|
+
valid: false,
|
|
1263
|
+
reason: `aud mismatch: expected "${opts.expectedAud}", got ${JSON.stringify(tokenAud ?? null)}`,
|
|
1264
|
+
payload,
|
|
1265
|
+
kid: header.kid
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
906
1269
|
return { valid: true, payload, kid: header.kid };
|
|
907
1270
|
}
|
|
908
|
-
async function verifyToolSignatures(tools, signatures) {
|
|
1271
|
+
async function verifyToolSignatures(tools, signatures, opts = {}) {
|
|
1272
|
+
const jwksUrl = opts.jwksUrl ?? "https://api.truealter.com/.well-known/alter-keys.json";
|
|
1273
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
1274
|
+
if (!jwksUrl.startsWith("https://")) {
|
|
1275
|
+
return tools.map((t) => ({
|
|
1276
|
+
tool: t.name,
|
|
1277
|
+
valid: false,
|
|
1278
|
+
reason: `jwksUrl must be https: got ${jwksUrl}`
|
|
1279
|
+
}));
|
|
1280
|
+
}
|
|
1281
|
+
const needsJwks = tools.some((t) => {
|
|
1282
|
+
const sig = signatures[t.name];
|
|
1283
|
+
return sig && sig.signature;
|
|
1284
|
+
});
|
|
1285
|
+
let jwks = null;
|
|
1286
|
+
if (needsJwks) {
|
|
1287
|
+
try {
|
|
1288
|
+
jwks = await fetchJwks(jwksUrl, fetchImpl);
|
|
1289
|
+
} catch (err) {
|
|
1290
|
+
return tools.map((t) => ({
|
|
1291
|
+
tool: t.name,
|
|
1292
|
+
valid: false,
|
|
1293
|
+
reason: `jwks fetch failed: ${err.message}`
|
|
1294
|
+
}));
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
909
1297
|
const out = [];
|
|
910
1298
|
for (const tool of tools) {
|
|
911
1299
|
const sig = signatures[tool.name];
|
|
@@ -913,11 +1301,68 @@ async function verifyToolSignatures(tools, signatures) {
|
|
|
913
1301
|
out.push({ tool: tool.name, valid: false, reason: "no signature published" });
|
|
914
1302
|
continue;
|
|
915
1303
|
}
|
|
916
|
-
const expectedHash = await sha256Hex(
|
|
1304
|
+
const expectedHash = await sha256Hex(canonicalJson2(tool.inputSchema));
|
|
917
1305
|
if (expectedHash !== sig.schema_hash) {
|
|
918
1306
|
out.push({ tool: tool.name, valid: false, reason: "schema hash mismatch" });
|
|
919
1307
|
continue;
|
|
920
1308
|
}
|
|
1309
|
+
const jwsToken = sig.signature;
|
|
1310
|
+
if (!jwsToken) {
|
|
1311
|
+
out.push({ tool: tool.name, valid: true, warn_no_signature: true });
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const jwksDoc = jwks;
|
|
1315
|
+
let jHeader;
|
|
1316
|
+
let jPayloadRaw;
|
|
1317
|
+
let jSigBytes;
|
|
1318
|
+
try {
|
|
1319
|
+
const parts2 = jwsToken.split(".");
|
|
1320
|
+
if (parts2.length !== 3) throw new Error("JWS must have three segments");
|
|
1321
|
+
jHeader = JSON.parse(new TextDecoder().decode(base64urlDecode(parts2[0])));
|
|
1322
|
+
jPayloadRaw = new TextDecoder().decode(base64urlDecode(parts2[1]));
|
|
1323
|
+
jSigBytes = base64urlDecode(parts2[2]);
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
out.push({ tool: tool.name, valid: false, reason: `malformed tool JWS: ${err.message}` });
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (jHeader.alg !== "ES256") {
|
|
1329
|
+
out.push({ tool: tool.name, valid: false, reason: `unsupported tool sig alg: ${jHeader.alg}` });
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if (jPayloadRaw !== sig.schema_hash) {
|
|
1333
|
+
out.push({ tool: tool.name, valid: false, reason: "tool JWS payload does not match schema_hash" });
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
const jwk = jwksDoc.keys.find((k) => jHeader.kid ? k.kid === jHeader.kid : true);
|
|
1337
|
+
if (!jwk) {
|
|
1338
|
+
out.push({ tool: tool.name, valid: false, reason: `no JWK for kid=${jHeader.kid}` });
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
let publicKey;
|
|
1342
|
+
try {
|
|
1343
|
+
publicKey = await importEs256JwkAsPublicKey(jwk);
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
out.push({ tool: tool.name, valid: false, reason: `jwk import: ${err.message}` });
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
const parts = jwsToken.split(".");
|
|
1349
|
+
const signedInput = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
1350
|
+
let sigValid = false;
|
|
1351
|
+
try {
|
|
1352
|
+
sigValid = await crypto.subtle.verify(
|
|
1353
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
1354
|
+
publicKey,
|
|
1355
|
+
toArrayBuffer(jSigBytes),
|
|
1356
|
+
toArrayBuffer(signedInput)
|
|
1357
|
+
);
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
out.push({ tool: tool.name, valid: false, reason: `sig verify error: ${err.message}` });
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
if (!sigValid) {
|
|
1363
|
+
out.push({ tool: tool.name, valid: false, reason: "tool signature mismatch" });
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
921
1366
|
out.push({ tool: tool.name, valid: true });
|
|
922
1367
|
}
|
|
923
1368
|
return out;
|
|
@@ -940,23 +1385,23 @@ async function fetchJwks(url, fetchImpl) {
|
|
|
940
1385
|
}
|
|
941
1386
|
if (resp.type === "opaqueredirect" || resp.status >= 300 && resp.status < 400) {
|
|
942
1387
|
throw new AlterProvenanceError(
|
|
943
|
-
`${url}
|
|
1388
|
+
`${url} -> redirect rejected (allowlist enforces initial URL only)`
|
|
944
1389
|
);
|
|
945
1390
|
}
|
|
946
|
-
if (!resp.ok) throw new AlterNetworkError(`${url}
|
|
1391
|
+
if (!resp.ok) throw new AlterNetworkError(`${url} -> HTTP ${resp.status}`);
|
|
947
1392
|
const contentLength = resp.headers.get("content-length");
|
|
948
1393
|
if (contentLength !== null) {
|
|
949
1394
|
const n = Number.parseInt(contentLength, 10);
|
|
950
1395
|
if (Number.isFinite(n) && n > JWKS_MAX_BYTES) {
|
|
951
1396
|
throw new AlterProvenanceError(
|
|
952
|
-
`${url}
|
|
1397
|
+
`${url} -> JWKS too large: ${n} > ${JWKS_MAX_BYTES} bytes`
|
|
953
1398
|
);
|
|
954
1399
|
}
|
|
955
1400
|
}
|
|
956
1401
|
const body = await resp.text();
|
|
957
1402
|
if (body.length > JWKS_MAX_BYTES) {
|
|
958
1403
|
throw new AlterProvenanceError(
|
|
959
|
-
`${url}
|
|
1404
|
+
`${url} -> JWKS too large: ${body.length} > ${JWKS_MAX_BYTES} bytes`
|
|
960
1405
|
);
|
|
961
1406
|
}
|
|
962
1407
|
let doc;
|
|
@@ -1040,14 +1485,14 @@ async function sha256Hex(input) {
|
|
|
1040
1485
|
function toArrayBuffer(view) {
|
|
1041
1486
|
return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
1042
1487
|
}
|
|
1043
|
-
function
|
|
1488
|
+
function canonicalJson2(value) {
|
|
1044
1489
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1045
1490
|
if (Array.isArray(value)) {
|
|
1046
|
-
return `[${value.map(
|
|
1491
|
+
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
1047
1492
|
}
|
|
1048
1493
|
const obj = value;
|
|
1049
1494
|
const keys = Object.keys(obj).sort();
|
|
1050
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${
|
|
1495
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson2(obj[k])}`).join(",")}}`;
|
|
1051
1496
|
}
|
|
1052
1497
|
|
|
1053
1498
|
// src/client.ts
|
|
@@ -1063,11 +1508,21 @@ var AlterClient = class {
|
|
|
1063
1508
|
this.options = options;
|
|
1064
1509
|
this.x402 = options.x402;
|
|
1065
1510
|
const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
|
|
1066
|
-
|
|
1511
|
+
const preflightHook = options.unsafe_skipVersionCheck ? void 0 : () => checkMinVersion({
|
|
1512
|
+
apiBase: options.apiBase,
|
|
1513
|
+
knownFloorPublicKeys: options.knownFloorPublicKeys,
|
|
1514
|
+
fetchImpl: options.fetch
|
|
1515
|
+
}).then(() => void 0);
|
|
1516
|
+
this.mcp = new MCPClient({
|
|
1517
|
+
...options,
|
|
1518
|
+
endpoint,
|
|
1519
|
+
x402: options.x402,
|
|
1520
|
+
preflightHook
|
|
1521
|
+
});
|
|
1067
1522
|
}
|
|
1068
1523
|
/**
|
|
1069
1524
|
* Resolve the MCP endpoint via discovery if requested. Safe to call
|
|
1070
|
-
* multiple times
|
|
1525
|
+
* multiple times: the first successful lookup is cached.
|
|
1071
1526
|
*/
|
|
1072
1527
|
async discoverEndpoint() {
|
|
1073
1528
|
if (this.discovered) return this.discovered;
|
|
@@ -1080,7 +1535,7 @@ var AlterClient = class {
|
|
|
1080
1535
|
return this.discoveryPromise;
|
|
1081
1536
|
}
|
|
1082
1537
|
/**
|
|
1083
|
-
* Initialise the MCP session. Optional
|
|
1538
|
+
* Initialise the MCP session. Optional: every method calls
|
|
1084
1539
|
* `mcp.initialize()` lazily, but you can call this once at startup if
|
|
1085
1540
|
* you want fail-fast behaviour.
|
|
1086
1541
|
*/
|
|
@@ -1088,11 +1543,11 @@ var AlterClient = class {
|
|
|
1088
1543
|
await this.mcp.initialize();
|
|
1089
1544
|
}
|
|
1090
1545
|
// ── Free tier ────────────────────────────────────────────────────────
|
|
1091
|
-
/** First handshake
|
|
1546
|
+
/** First handshake: confirms the connection, returns trust tier and tool counts. */
|
|
1092
1547
|
async helloAgent() {
|
|
1093
1548
|
return this.mcp.callTool("hello_agent", {});
|
|
1094
1549
|
}
|
|
1095
|
-
/** Resolve a ~handle (e.g. ~
|
|
1550
|
+
/** Resolve a ~handle (e.g. ~example) to its canonical form and kind. No auth required. */
|
|
1096
1551
|
async resolveHandle(args) {
|
|
1097
1552
|
const payload = typeof args === "string" ? { query: args } : args;
|
|
1098
1553
|
return this.mcp.callTool("alter_resolve_handle", payload);
|
|
@@ -1100,7 +1555,7 @@ var AlterClient = class {
|
|
|
1100
1555
|
/** Verify a person is registered with ALTER (handle or id). */
|
|
1101
1556
|
async verify(handleOrId, claims) {
|
|
1102
1557
|
const args = handleOrId.includes("@") ? { member_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
|
|
1103
|
-
// ~handle
|
|
1558
|
+
// ~handle: server resolves these via the member_id field
|
|
1104
1559
|
{ member_id: handleOrId }
|
|
1105
1560
|
) : { member_id: handleOrId };
|
|
1106
1561
|
if (claims) args.claims = claims;
|
|
@@ -1200,7 +1655,7 @@ var AlterClient = class {
|
|
|
1200
1655
|
}
|
|
1201
1656
|
// ── Alter-to-Alter Messaging ─────────────────────────────────────────
|
|
1202
1657
|
// Wave 1: cross-handle direct messages between authenticated tilde
|
|
1203
|
-
// handles. Default closed
|
|
1658
|
+
// handles. Default closed: recipient must have granted the sender via
|
|
1204
1659
|
// alter_message_grant. Spec: docs/technical/Alter-to-Alter Messaging.md.
|
|
1205
1660
|
/** Send a direct message to another tilde handle. */
|
|
1206
1661
|
async messageSend(args) {
|
|
@@ -1234,7 +1689,7 @@ var AlterClient = class {
|
|
|
1234
1689
|
/**
|
|
1235
1690
|
* Verify the ES256 provenance attestation on a tool response.
|
|
1236
1691
|
* Accepts either a {@link ProvenanceEnvelope} or the raw `_meta`
|
|
1237
|
-
* object
|
|
1692
|
+
* object: the latter is more convenient for ad-hoc verification.
|
|
1238
1693
|
*/
|
|
1239
1694
|
async verifyProvenance(envelope) {
|
|
1240
1695
|
if (!envelope) return { valid: false, reason: "no provenance envelope" };
|
|
@@ -1270,7 +1725,7 @@ function generateGenericMcpConfig(opts = {}) {
|
|
|
1270
1725
|
const entry = {
|
|
1271
1726
|
url: opts.endpoint ?? DEFAULT_ENDPOINT,
|
|
1272
1727
|
transport: "streamable-http",
|
|
1273
|
-
description: "ALTER Identity
|
|
1728
|
+
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1274
1729
|
};
|
|
1275
1730
|
if (Object.keys(headers).length > 0) entry.headers = headers;
|
|
1276
1731
|
return { mcpServers: { [serverName]: entry } };
|
|
@@ -1296,17 +1751,13 @@ function generateClaudeDesktopConfig(opts = {}) {
|
|
|
1296
1751
|
const entry = {
|
|
1297
1752
|
command: bridgeCommand,
|
|
1298
1753
|
env: env2,
|
|
1299
|
-
description: "ALTER Identity
|
|
1754
|
+
description: "ALTER Identity: psychometric identity field for AI agents"
|
|
1300
1755
|
};
|
|
1301
1756
|
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
1302
1757
|
entry.args = [...opts.extraArgs];
|
|
1303
1758
|
}
|
|
1304
1759
|
return { mcpServers: { [serverName]: entry } };
|
|
1305
1760
|
}
|
|
1306
|
-
|
|
1307
|
-
// src/meta.ts
|
|
1308
|
-
var SDK_NAME = "@truealter/sdk";
|
|
1309
|
-
var SDK_VERSION = "0.3.0";
|
|
1310
1761
|
var HOME = homedir();
|
|
1311
1762
|
var PLAT = platform();
|
|
1312
1763
|
function appData() {
|
|
@@ -1431,7 +1882,7 @@ function probeAll() {
|
|
|
1431
1882
|
];
|
|
1432
1883
|
}
|
|
1433
1884
|
var SYNC_PREFIXES = [
|
|
1434
|
-
// iCloud Drive
|
|
1885
|
+
// iCloud Drive: both the new and legacy mounts.
|
|
1435
1886
|
"Library/Mobile Documents/com~apple~CloudDocs",
|
|
1436
1887
|
"iCloud Drive",
|
|
1437
1888
|
// OneDrive variants Microsoft ships across editions.
|
|
@@ -1444,7 +1895,7 @@ var SYNC_PREFIXES = [
|
|
|
1444
1895
|
"Google Drive",
|
|
1445
1896
|
"GoogleDrive",
|
|
1446
1897
|
"CloudStorage/GoogleDrive",
|
|
1447
|
-
// Box, pCloud, Sync.com, MEGA
|
|
1898
|
+
// Box, pCloud, Sync.com, MEGA: high-signal names worth refusing.
|
|
1448
1899
|
"Box Sync",
|
|
1449
1900
|
"pCloud Drive",
|
|
1450
1901
|
"Sync.com",
|
|
@@ -1496,10 +1947,10 @@ function atomicJsonMerge(opts) {
|
|
|
1496
1947
|
preBytes = readFileSync(path, "utf8");
|
|
1497
1948
|
if (preBytes.trim().length > 0) {
|
|
1498
1949
|
try {
|
|
1499
|
-
parsed = JSON.parse(preBytes);
|
|
1950
|
+
parsed = JSON.parse(preBytes.replace(/^\uFEFF/, ""));
|
|
1500
1951
|
} catch (err) {
|
|
1501
1952
|
throw new Error(
|
|
1502
|
-
`refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter
|
|
1953
|
+
`refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter wire\`.`
|
|
1503
1954
|
);
|
|
1504
1955
|
}
|
|
1505
1956
|
if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
|
|
@@ -1552,6 +2003,26 @@ function restoreFromBackup(path, backupPath) {
|
|
|
1552
2003
|
// src/wire/index.ts
|
|
1553
2004
|
var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
|
|
1554
2005
|
var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
2006
|
+
function readCfAccessEnv() {
|
|
2007
|
+
const envPath = join(homedir(), ".config", "alter", "cf-access.env");
|
|
2008
|
+
try {
|
|
2009
|
+
const content = readFileSync(envPath, "utf8");
|
|
2010
|
+
let clientId = "";
|
|
2011
|
+
let clientSecret = "";
|
|
2012
|
+
for (const line of content.split("\n")) {
|
|
2013
|
+
const trimmed = line.trim();
|
|
2014
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
2015
|
+
const eqIdx = trimmed.indexOf("=");
|
|
2016
|
+
const key = trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim();
|
|
2017
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
2018
|
+
if (key === "CF_ACCESS_CLIENT_ID") clientId = val;
|
|
2019
|
+
if (key === "CF_ACCESS_CLIENT_SECRET") clientSecret = val;
|
|
2020
|
+
}
|
|
2021
|
+
if (clientId && clientSecret) return { clientId, clientSecret };
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
return void 0;
|
|
2025
|
+
}
|
|
1555
2026
|
function clientById(id) {
|
|
1556
2027
|
const hit = ALL_CLIENTS.find((c) => c.id === id);
|
|
1557
2028
|
if (!hit) throw new Error(`unknown client id: ${id}`);
|
|
@@ -1560,6 +2031,7 @@ function clientById(id) {
|
|
|
1560
2031
|
function wire(opts = {}) {
|
|
1561
2032
|
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1562
2033
|
const apiKey = opts.apiKey;
|
|
2034
|
+
const cfAccess = opts.cfAccess ?? readCfAccessEnv();
|
|
1563
2035
|
const probes = probeAll();
|
|
1564
2036
|
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1565
2037
|
const ts = TIMESTAMP();
|
|
@@ -1578,9 +2050,9 @@ function wire(opts = {}) {
|
|
|
1578
2050
|
}
|
|
1579
2051
|
try {
|
|
1580
2052
|
if (id === "claude-code") {
|
|
1581
|
-
targets.push(wireClaudeCode({ endpoint, apiKey }));
|
|
2053
|
+
targets.push(wireClaudeCode({ endpoint, apiKey, cfAccess }));
|
|
1582
2054
|
} else {
|
|
1583
|
-
targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
|
|
2055
|
+
targets.push(wireFileTarget({ id, endpoint, apiKey, cfAccess, timestamp: ts }));
|
|
1584
2056
|
}
|
|
1585
2057
|
} catch (err) {
|
|
1586
2058
|
const message = err.message;
|
|
@@ -1611,10 +2083,15 @@ function wireFileTarget(args) {
|
|
|
1611
2083
|
const sync = detectSyncedVolume(client.configPath);
|
|
1612
2084
|
if (sync) {
|
|
1613
2085
|
throw new Error(
|
|
1614
|
-
`refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices
|
|
2086
|
+
`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.`
|
|
1615
2087
|
);
|
|
1616
2088
|
}
|
|
1617
|
-
const
|
|
2089
|
+
const cfHeaders = {};
|
|
2090
|
+
if (args.cfAccess) {
|
|
2091
|
+
cfHeaders["CF-Access-Client-Id"] = args.cfAccess.clientId;
|
|
2092
|
+
cfHeaders["CF-Access-Client-Secret"] = args.cfAccess.clientSecret;
|
|
2093
|
+
}
|
|
2094
|
+
const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey, headers: cfHeaders });
|
|
1618
2095
|
const rootKey = client.rootKey;
|
|
1619
2096
|
const serverName = "alter";
|
|
1620
2097
|
const result = atomicJsonMerge({
|
|
@@ -1646,7 +2123,8 @@ function wireFileTarget(args) {
|
|
|
1646
2123
|
}
|
|
1647
2124
|
function wireClaudeCode(args) {
|
|
1648
2125
|
const cmd = "claude";
|
|
1649
|
-
const
|
|
2126
|
+
const bridgePath = resolveBridgeScript();
|
|
2127
|
+
const argList = bridgePath ? ["mcp", "add", "--scope", "user", "alter", "--", "node", bridgePath] : [
|
|
1650
2128
|
"mcp",
|
|
1651
2129
|
"add",
|
|
1652
2130
|
"--scope",
|
|
@@ -1654,16 +2132,15 @@ function wireClaudeCode(args) {
|
|
|
1654
2132
|
"--transport",
|
|
1655
2133
|
"http",
|
|
1656
2134
|
"alter",
|
|
1657
|
-
args.endpoint
|
|
2135
|
+
args.endpoint,
|
|
2136
|
+
...args.apiKey ? ["--header", `X-ALTER-API-Key:${args.apiKey}`] : []
|
|
1658
2137
|
];
|
|
1659
|
-
if (args.apiKey) {
|
|
1660
|
-
argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
|
|
1661
|
-
}
|
|
1662
2138
|
const full = `${cmd} ${argList.join(" ")}`;
|
|
1663
2139
|
const run = spawnSync(cmd, argList, {
|
|
1664
2140
|
encoding: "utf8",
|
|
1665
2141
|
shell: process.platform === "win32",
|
|
1666
|
-
timeout: 1e4
|
|
2142
|
+
timeout: 1e4,
|
|
2143
|
+
env: bridgePath ? { ...process.env, ALTER_PUBLIC_MCP_ENDPOINT: args.endpoint, ...args.apiKey ? { ALTER_API_KEY: args.apiKey } : {} } : void 0
|
|
1667
2144
|
});
|
|
1668
2145
|
if (run.error) {
|
|
1669
2146
|
return {
|
|
@@ -1694,6 +2171,16 @@ function wireClaudeCode(args) {
|
|
|
1694
2171
|
reason: `claude mcp add exited ${String(run.status)}`
|
|
1695
2172
|
};
|
|
1696
2173
|
}
|
|
2174
|
+
function resolveBridgeScript() {
|
|
2175
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
2176
|
+
const siblingBridge = join(here, "..", "dist", "mcp-bridge.js");
|
|
2177
|
+
if (existsSync(siblingBridge)) return siblingBridge;
|
|
2178
|
+
const srcBridge = join(here, "..", "mcp-bridge.js");
|
|
2179
|
+
if (existsSync(srcBridge)) return srcBridge;
|
|
2180
|
+
const npmGlobalBridge = join(here, "mcp-bridge.js");
|
|
2181
|
+
if (existsSync(npmGlobalBridge)) return npmGlobalBridge;
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
1697
2184
|
function unwire() {
|
|
1698
2185
|
const state = readWireState();
|
|
1699
2186
|
const undone = [];
|
|
@@ -1844,19 +2331,19 @@ var TOOL_COSTS = {
|
|
|
1844
2331
|
complete_knot: 0,
|
|
1845
2332
|
check_golden_thread: 0,
|
|
1846
2333
|
thread_census: 0,
|
|
1847
|
-
// L1 ($0.
|
|
1848
|
-
assess_traits:
|
|
1849
|
-
get_trait_snapshot:
|
|
1850
|
-
// L2 ($0.
|
|
1851
|
-
get_full_trait_vector: 0.
|
|
1852
|
-
get_side_quest_graph: 0.
|
|
1853
|
-
// L3 ($0.
|
|
1854
|
-
query_graph_similarity: 0.
|
|
1855
|
-
// L4 ($0.
|
|
1856
|
-
compute_belonging: 0.
|
|
1857
|
-
// L5 ($
|
|
1858
|
-
get_match_recommendations:
|
|
1859
|
-
generate_match_narrative:
|
|
2334
|
+
// L1 ($0.01)
|
|
2335
|
+
assess_traits: 0.01,
|
|
2336
|
+
get_trait_snapshot: 0.01,
|
|
2337
|
+
// L2 ($0.10)
|
|
2338
|
+
get_full_trait_vector: 0.1,
|
|
2339
|
+
get_side_quest_graph: 0.1,
|
|
2340
|
+
// L3 ($0.30)
|
|
2341
|
+
query_graph_similarity: 0.3,
|
|
2342
|
+
// L4 ($0.60)
|
|
2343
|
+
compute_belonging: 0.6,
|
|
2344
|
+
// L5 ($1.00)
|
|
2345
|
+
get_match_recommendations: 1,
|
|
2346
|
+
generate_match_narrative: 1
|
|
1860
2347
|
};
|
|
1861
2348
|
var TOOL_BLAST_RADIUS = {
|
|
1862
2349
|
// Low: read-only reference
|
|
@@ -1896,12 +2383,108 @@ var TOOL_BLAST_RADIUS = {
|
|
|
1896
2383
|
query_graph_similarity: "high"
|
|
1897
2384
|
};
|
|
1898
2385
|
|
|
2386
|
+
// src/pricing.generated.ts
|
|
2387
|
+
var GENERATED_TOOL_TIERS = {
|
|
2388
|
+
// L0 (free)
|
|
2389
|
+
alter_presence_read: 0,
|
|
2390
|
+
alter_resolve_handle: 0,
|
|
2391
|
+
check_assessment_status: 0,
|
|
2392
|
+
create_identity_stub: 0,
|
|
2393
|
+
dispute_attestation: 0,
|
|
2394
|
+
get_competencies: 0,
|
|
2395
|
+
get_earning_summary: 0,
|
|
2396
|
+
get_engagement_level: 0,
|
|
2397
|
+
get_identity_earnings: 0,
|
|
2398
|
+
get_identity_trust_score: 0,
|
|
2399
|
+
get_network_stats: 0,
|
|
2400
|
+
get_privacy_budget: 0,
|
|
2401
|
+
get_profile: 0,
|
|
2402
|
+
initiate_assessment: 0,
|
|
2403
|
+
list_archetypes: 0,
|
|
2404
|
+
query_matches: 0,
|
|
2405
|
+
recommend_tool: 0,
|
|
2406
|
+
search_identities: 0,
|
|
2407
|
+
verify_identity: 0,
|
|
2408
|
+
// L1 ($0.01)
|
|
2409
|
+
assess_traits: 1,
|
|
2410
|
+
attest_domain: 1,
|
|
2411
|
+
get_trait_snapshot: 1,
|
|
2412
|
+
poll_requirement_matches: 1,
|
|
2413
|
+
submit_context: 1,
|
|
2414
|
+
submit_social_links: 1,
|
|
2415
|
+
submit_structured_profile: 1,
|
|
2416
|
+
// L2 ($0.10)
|
|
2417
|
+
attest_claim_provenance: 2,
|
|
2418
|
+
get_full_trait_vector: 2,
|
|
2419
|
+
get_side_quest_graph: 2,
|
|
2420
|
+
submit_batch_context: 2,
|
|
2421
|
+
// L3 ($0.30)
|
|
2422
|
+
alter_alignment: 3,
|
|
2423
|
+
query_graph_similarity: 3,
|
|
2424
|
+
// L4 ($0.60)
|
|
2425
|
+
compute_belonging: 4,
|
|
2426
|
+
// L5 ($1.00)
|
|
2427
|
+
generate_match_narrative: 5,
|
|
2428
|
+
get_match_recommendations: 5,
|
|
2429
|
+
query_field: 5
|
|
2430
|
+
};
|
|
2431
|
+
var GENERATED_TOOL_PRICING = {
|
|
2432
|
+
// L1: $0.01
|
|
2433
|
+
assess_traits: 0.01,
|
|
2434
|
+
attest_domain: 0.01,
|
|
2435
|
+
get_trait_snapshot: 0.01,
|
|
2436
|
+
poll_requirement_matches: 0.01,
|
|
2437
|
+
submit_context: 0.01,
|
|
2438
|
+
submit_social_links: 0.01,
|
|
2439
|
+
submit_structured_profile: 0.01,
|
|
2440
|
+
// L2: $0.10
|
|
2441
|
+
attest_claim_provenance: 0.1,
|
|
2442
|
+
get_full_trait_vector: 0.1,
|
|
2443
|
+
get_side_quest_graph: 0.1,
|
|
2444
|
+
submit_batch_context: 0.1,
|
|
2445
|
+
// L3: $0.30
|
|
2446
|
+
alter_alignment: 0.3,
|
|
2447
|
+
query_graph_similarity: 0.3,
|
|
2448
|
+
// L4: $0.60
|
|
2449
|
+
compute_belonging: 0.6,
|
|
2450
|
+
// L5: $1.00
|
|
2451
|
+
generate_match_narrative: 1,
|
|
2452
|
+
get_match_recommendations: 1,
|
|
2453
|
+
query_field: 1
|
|
2454
|
+
};
|
|
2455
|
+
var TIER_PRICES = {
|
|
2456
|
+
L0: 0,
|
|
2457
|
+
L1: 0.01,
|
|
2458
|
+
L2: 0.1,
|
|
2459
|
+
L3: 0.3,
|
|
2460
|
+
L4: 0.6,
|
|
2461
|
+
L5: 1
|
|
2462
|
+
};
|
|
2463
|
+
var ADVERTISED_TOOL_COUNTS = {
|
|
2464
|
+
/** Free (L0) tools visible to anonymous / agent-class callers. */
|
|
2465
|
+
free: 26,
|
|
2466
|
+
/** Premium (L1-L5) tools requiring x402 payment. */
|
|
2467
|
+
premium: 8,
|
|
2468
|
+
/** Messaging tools (member-self-only; excluded from external advertisement). */
|
|
2469
|
+
messaging: 0,
|
|
2470
|
+
/** Total publicly advertised (free + premium). */
|
|
2471
|
+
total: 34
|
|
2472
|
+
};
|
|
2473
|
+
var REVENUE_SPLIT = {
|
|
2474
|
+
weaver: 0.75,
|
|
2475
|
+
// data subject (Identity Income)
|
|
2476
|
+
facilitator: 0.05,
|
|
2477
|
+
alter: 0.15,
|
|
2478
|
+
treasury: 0.05
|
|
2479
|
+
};
|
|
2480
|
+
|
|
1899
2481
|
// src/homepage.ts
|
|
1900
2482
|
var HOMEPAGE_LIMITS = {
|
|
1901
2483
|
whoami_max_chars: 240,
|
|
1902
2484
|
opener_max_chars: 280,
|
|
1903
2485
|
pronouns_max_chars: 32,
|
|
1904
|
-
attunement_glyph_max_chars: 16
|
|
2486
|
+
attunement_glyph_max_chars: 16,
|
|
2487
|
+
contact_email_max_chars: 254
|
|
1905
2488
|
};
|
|
1906
2489
|
|
|
1907
2490
|
// src/themes.ts
|
|
@@ -1914,4 +2497,4 @@ var THEME_LIMITS = {
|
|
|
1914
2497
|
};
|
|
1915
2498
|
var OSC8_ALLOWED_SCHEMES = ["https:", "mailto:"];
|
|
1916
2499
|
|
|
1917
|
-
export { ALL_CLIENTS, AlterAuthError, AlterClient, AlterDiscoveryError, AlterError, AlterInvalidResponse, AlterNetworkError, AlterPaymentRequired, AlterProvenanceError, AlterRateLimited, AlterTimeoutError, AlterToolError, CLAUDE_CODE, CLAUDE_DESKTOP, CURSOR, DEFAULT_DOMAIN, DEFAULT_ENDPOINT, DEFAULT_VERIFY_AT_ALLOWLIST, FREE_TOOL_NAMES, HOMEPAGE_LIMITS, MCPClient, MCP_PROTOCOL_VERSION, OSC8_ALLOWED_SCHEMES, PREMIUM_TOOL_NAMES, SDK_NAME, SDK_VERSION, THEME_LIMITS, TOOL_BLAST_RADIUS, TOOL_COSTS, TOOL_TIERS, VSCODE, X402Client, base64urlDecode, base64urlEncode2 as base64urlEncode, canonicalArgsSha256, canonicalStringify, clearDiscoveryCache, decodeDid, detectSyncedVolume, discover, encodeDid, fetchPublicKeys, generateClaudeConfig, generateClaudeDesktopConfig, generateCursorConfig, generateGenericMcpConfig, generateKeypair, keypairFromPrivateKey, loadPrivateKey, parsePaymentHeader, probeAll, probeByDir, probeClaudeCode, readWireState, resolveVerifyAt, sha2562 as sha256, sign, signInvocation, unwire, verify, verifyProvenance, verifyToolSignatures, wire, writeWireState };
|
|
2500
|
+
export { ADVERTISED_TOOL_COUNTS, ALL_CLIENTS, AlterAuthError, AlterClient, AlterDiscoveryError, AlterError, AlterInvalidResponse, AlterNetworkError, AlterPaymentRequired, AlterProvenanceError, AlterRateLimited, AlterTimeoutError, AlterToolError, BelowFloorError, CLAUDE_CODE, CLAUDE_DESKTOP, CLIENT_CHANNEL, CLIENT_ID, CURSOR, DEFAULT_DOMAIN, DEFAULT_ENDPOINT, DEFAULT_VERIFY_AT_ALLOWLIST, FREE_TOOL_NAMES, GENERATED_TOOL_PRICING, GENERATED_TOOL_TIERS, HOMEPAGE_LIMITS, KNOWN_FLOOR_PUBLIC_KEYS, MCPClient, MCP_PROTOCOL_VERSION, MIN_VERSION_ENDPOINT, OSC8_ALLOWED_SCHEMES, PREMIUM_TOOL_NAMES, REVENUE_SPLIT, SDK_NAME, SDK_VERSION, THEME_LIMITS, TIER_PRICES, TOOL_BLAST_RADIUS, TOOL_COSTS, TOOL_TIERS, VSCODE, X402Client, base64urlDecode, base64urlEncode2 as base64urlEncode, canonicalArgsSha256, canonicalJson, canonicalStringify, checkMinVersion, clearDiscoveryCache, compareSemver, computeKeyId, decodeDid, detectSyncedVolume, discover, encodeDid, fetchPublicKeys, generateClaudeConfig, generateClaudeDesktopConfig, generateCursorConfig, generateGenericMcpConfig, generateKeypair, keypairFromPrivateKey, loadPrivateKey, parsePaymentHeader, probeAll, probeByDir, probeClaudeCode, readWireState, resolveVerifyAt, sha2562 as sha256, sign, signInvocation, unwire, verify, verifyFloorSignature, verifyProvenance, verifyToolSignatures, wire, writeWireState };
|