@truealter/sdk 0.2.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -36
- package/dist/bin/alter-identity.js +841 -31
- package/dist/bin/mcp-bridge.js +201 -9
- package/dist/index.cjs +785 -24
- package/dist/index.d.cts +677 -41
- package/dist/index.d.ts +677 -41
- package/dist/index.js +712 -28
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,149 @@
|
|
|
1
|
+
import { p256 } from '@noble/curves/p256';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
3
|
+
import { randomBytes, bytesToHex as bytesToHex$1, hexToBytes } from '@noble/hashes/utils';
|
|
4
|
+
import { createPrivateKey, createHash } from 'crypto';
|
|
1
5
|
import * as ed25519 from '@noble/ed25519';
|
|
2
6
|
import { sha512 } from '@noble/hashes/sha512';
|
|
3
|
-
import {
|
|
7
|
+
import { spawnSync } from 'child_process';
|
|
8
|
+
import { homedir, platform } from 'os';
|
|
9
|
+
import { join, resolve, dirname } from 'path';
|
|
10
|
+
import { env } from 'process';
|
|
11
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, renameSync, unlinkSync } from 'fs';
|
|
12
|
+
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
15
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
17
|
+
var __esm = (fn, res) => function __init() {
|
|
18
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
19
|
+
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
|
+
};
|
|
24
|
+
var __copyProps = (to, from, except, desc) => {
|
|
25
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
26
|
+
for (let key of __getOwnPropNames(from))
|
|
27
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
28
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
29
|
+
}
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
|
+
|
|
34
|
+
// src/signing.ts
|
|
35
|
+
var signing_exports = {};
|
|
36
|
+
__export(signing_exports, {
|
|
37
|
+
canonicalArgsSha256: () => canonicalArgsSha256,
|
|
38
|
+
canonicalStringify: () => canonicalStringify,
|
|
39
|
+
loadPrivateKey: () => loadPrivateKey,
|
|
40
|
+
signInvocation: () => signInvocation
|
|
41
|
+
});
|
|
42
|
+
function canonicalStringify(value) {
|
|
43
|
+
return stringifyInner(value);
|
|
44
|
+
}
|
|
45
|
+
function stringifyInner(value) {
|
|
46
|
+
if (value === null) return "null";
|
|
47
|
+
if (value === void 0) {
|
|
48
|
+
throw new TypeError("canonicalStringify: undefined is not representable in JSON");
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
51
|
+
if (typeof value === "number") {
|
|
52
|
+
if (!Number.isFinite(value)) {
|
|
53
|
+
throw new TypeError("canonicalStringify: non-finite numbers are not representable");
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(value);
|
|
56
|
+
}
|
|
57
|
+
if (typeof value === "string") return encodeString(value);
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
return "[" + value.map((v) => stringifyInner(v)).join(",") + "]";
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === "object") {
|
|
62
|
+
const obj = value;
|
|
63
|
+
const keys = Object.keys(obj).sort();
|
|
64
|
+
return "{" + keys.map((k) => encodeString(k) + ":" + stringifyInner(obj[k])).join(",") + "}";
|
|
65
|
+
}
|
|
66
|
+
throw new TypeError(`canonicalStringify: unsupported type ${typeof value}`);
|
|
67
|
+
}
|
|
68
|
+
function encodeString(s) {
|
|
69
|
+
return JSON.stringify(s);
|
|
70
|
+
}
|
|
71
|
+
function canonicalArgsSha256(toolArgs) {
|
|
72
|
+
const canonical = canonicalStringify(toolArgs ?? {});
|
|
73
|
+
const bytes = new TextEncoder().encode(canonical);
|
|
74
|
+
const digest = sha256(bytes);
|
|
75
|
+
return bytesToHex(digest);
|
|
76
|
+
}
|
|
77
|
+
function bytesToHex(bytes) {
|
|
78
|
+
let out = "";
|
|
79
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
80
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function base64urlEncode(bytes) {
|
|
85
|
+
const raw = typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes;
|
|
86
|
+
if (typeof Buffer !== "undefined") {
|
|
87
|
+
return Buffer.from(raw).toString("base64url");
|
|
88
|
+
}
|
|
89
|
+
let binary = "";
|
|
90
|
+
for (let i = 0; i < raw.length; i++) binary += String.fromCharCode(raw[i]);
|
|
91
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
92
|
+
}
|
|
93
|
+
function loadPrivateKey(key) {
|
|
94
|
+
if (key instanceof Uint8Array) {
|
|
95
|
+
if (key.length !== 32) {
|
|
96
|
+
throw new TypeError("ES256 raw private key must be 32 bytes.");
|
|
97
|
+
}
|
|
98
|
+
return key;
|
|
99
|
+
}
|
|
100
|
+
if (typeof key === "string" && key.includes("-----BEGIN")) {
|
|
101
|
+
const keyObj = createPrivateKey({ key, format: "pem" });
|
|
102
|
+
const jwk = keyObj.export({ format: "jwk" });
|
|
103
|
+
if (jwk.crv !== "P-256" || !jwk.d) {
|
|
104
|
+
throw new TypeError("PEM is not a P-256 private key.");
|
|
105
|
+
}
|
|
106
|
+
return base64urlDecodeToBytes(jwk.d);
|
|
107
|
+
}
|
|
108
|
+
throw new TypeError("loadPrivateKey: expected Uint8Array(32) or PEM string.");
|
|
109
|
+
}
|
|
110
|
+
function base64urlDecodeToBytes(s) {
|
|
111
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
112
|
+
const b64 = (s + pad).replace(/-/g, "+").replace(/_/g, "/");
|
|
113
|
+
if (typeof Buffer !== "undefined") {
|
|
114
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
115
|
+
}
|
|
116
|
+
const binary = atob(b64);
|
|
117
|
+
const out = new Uint8Array(binary.length);
|
|
118
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function signInvocation(toolName, toolArgs, options) {
|
|
122
|
+
const { kid, privateKey, handle } = options;
|
|
123
|
+
const nonce = options.nonce ?? base64urlEncode(randomBytes(24));
|
|
124
|
+
const iat = options.iatSeconds ?? Math.floor(Date.now() / 1e3);
|
|
125
|
+
const claims = {
|
|
126
|
+
tool: toolName,
|
|
127
|
+
args_sha256: canonicalArgsSha256(toolArgs ?? {}),
|
|
128
|
+
nonce,
|
|
129
|
+
iat,
|
|
130
|
+
iss: handle
|
|
131
|
+
};
|
|
132
|
+
const headerB64 = base64urlEncode(JSON.stringify({ alg: "ES256", kid }));
|
|
133
|
+
const payloadB64 = base64urlEncode(JSON.stringify(claims));
|
|
134
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
135
|
+
const signingBytes = new TextEncoder().encode(signingInput);
|
|
136
|
+
const dBytes = loadPrivateKey(privateKey);
|
|
137
|
+
const digest = sha256(signingBytes);
|
|
138
|
+
const sig = p256.sign(digest, dBytes, { prehash: false });
|
|
139
|
+
const sigBytes = sig.toCompactRawBytes();
|
|
140
|
+
const sigB64 = base64urlEncode(sigBytes);
|
|
141
|
+
return `${signingInput}.${sigB64}`;
|
|
142
|
+
}
|
|
143
|
+
var init_signing = __esm({
|
|
144
|
+
"src/signing.ts"() {
|
|
145
|
+
}
|
|
146
|
+
});
|
|
4
147
|
|
|
5
148
|
// src/errors.ts
|
|
6
149
|
var AlterError = class extends Error {
|
|
@@ -101,7 +244,12 @@ async function discover(domain, opts = {}) {
|
|
|
101
244
|
try {
|
|
102
245
|
const dnsHit = await tryDns(host);
|
|
103
246
|
if (dnsHit) {
|
|
104
|
-
const
|
|
247
|
+
const parsed = validateDiscoveredUrl(dnsHit, "dns");
|
|
248
|
+
const result = {
|
|
249
|
+
url: parsed.toString().replace(/\/$/, ""),
|
|
250
|
+
transport: "streamable-http",
|
|
251
|
+
source: "dns"
|
|
252
|
+
};
|
|
105
253
|
if (cache) _cache.set(host, result);
|
|
106
254
|
return result;
|
|
107
255
|
}
|
|
@@ -142,6 +290,28 @@ function normaliseDomain(input) {
|
|
|
142
290
|
if (!host) throw new AlterDiscoveryError(`Empty domain: "${input}"`);
|
|
143
291
|
return host;
|
|
144
292
|
}
|
|
293
|
+
function validateDiscoveredUrl(url, source) {
|
|
294
|
+
let parsed;
|
|
295
|
+
try {
|
|
296
|
+
parsed = new URL(url);
|
|
297
|
+
} catch {
|
|
298
|
+
throw new AlterDiscoveryError(`${source}: malformed URL ${url}`);
|
|
299
|
+
}
|
|
300
|
+
if (parsed.protocol !== "https:") {
|
|
301
|
+
throw new AlterDiscoveryError(
|
|
302
|
+
`${source}: non-https MCP endpoint rejected (got ${parsed.protocol}//${parsed.hostname})`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (parsed.username || parsed.password) {
|
|
306
|
+
throw new AlterDiscoveryError(
|
|
307
|
+
`${source}: MCP endpoint must not contain userinfo (user:pass@host)`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
if (!parsed.hostname) {
|
|
311
|
+
throw new AlterDiscoveryError(`${source}: MCP endpoint missing hostname`);
|
|
312
|
+
}
|
|
313
|
+
return parsed;
|
|
314
|
+
}
|
|
145
315
|
async function tryDns(host) {
|
|
146
316
|
let resolveTxt;
|
|
147
317
|
try {
|
|
@@ -202,14 +372,17 @@ async function tryWellKnown(host, file, timeoutMs, fetchImpl) {
|
|
|
202
372
|
if (file === "mcp.json") {
|
|
203
373
|
const remotes = doc.remotes || [];
|
|
204
374
|
const remote = remotes.find((r) => r.transportType === "streamable-http" || r.transportType === "http");
|
|
205
|
-
const
|
|
206
|
-
if (!
|
|
207
|
-
|
|
375
|
+
const rawUrl = remote?.url || doc.url;
|
|
376
|
+
if (!rawUrl) return null;
|
|
377
|
+
const parsed = validateDiscoveredUrl(rawUrl, "mcp.json");
|
|
378
|
+
return { url: parsed.toString().replace(/\/$/, ""), transport: "streamable-http", source: "mcp.json", raw: doc };
|
|
208
379
|
}
|
|
209
380
|
const mcpHost = doc.mcp;
|
|
210
381
|
if (!mcpHost) return null;
|
|
382
|
+
const normalised = ensureMcpPath(mcpHost);
|
|
383
|
+
validateDiscoveredUrl(normalised, "alter.json");
|
|
211
384
|
return {
|
|
212
|
-
url:
|
|
385
|
+
url: normalised,
|
|
213
386
|
transport: "streamable-http",
|
|
214
387
|
source: "alter.json",
|
|
215
388
|
publicKey: doc.pk,
|
|
@@ -255,11 +428,14 @@ var X402Client = class {
|
|
|
255
428
|
if (!this.assets.has(envelope.asset)) {
|
|
256
429
|
throw new AlterError("PAYMENT_REQUIRED", `asset ${envelope.asset} not permitted by client policy`);
|
|
257
430
|
}
|
|
258
|
-
if (this.maxPerQuery !== void 0
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
431
|
+
if (this.maxPerQuery !== void 0) {
|
|
432
|
+
const amt = Number(envelope.amount);
|
|
433
|
+
if (!Number.isFinite(amt) || amt < 0 || amt > this.maxPerQuery) {
|
|
434
|
+
throw new AlterError(
|
|
435
|
+
"PAYMENT_REQUIRED",
|
|
436
|
+
`quote ${envelope.amount} ${envelope.asset} exceeds maxPerQuery ${this.maxPerQuery}`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
263
439
|
}
|
|
264
440
|
if (!this.signer) {
|
|
265
441
|
throw new AlterPaymentRequired(envelope.resource ?? "unknown", envelope);
|
|
@@ -317,6 +493,8 @@ var MCPClient = class {
|
|
|
317
493
|
maxRetries;
|
|
318
494
|
clientInfo;
|
|
319
495
|
x402;
|
|
496
|
+
signing;
|
|
497
|
+
extraHeaders;
|
|
320
498
|
requestCounter = 0;
|
|
321
499
|
initialised = false;
|
|
322
500
|
constructor(opts = {}) {
|
|
@@ -327,6 +505,8 @@ var MCPClient = class {
|
|
|
327
505
|
this.maxRetries = opts.maxRetries ?? 2;
|
|
328
506
|
this.clientInfo = opts.clientInfo ?? { name: "@truealter/sdk", version: "0.2.0" };
|
|
329
507
|
this.x402 = opts.x402;
|
|
508
|
+
this.signing = opts.signing;
|
|
509
|
+
this.extraHeaders = opts.extraHeaders;
|
|
330
510
|
}
|
|
331
511
|
/**
|
|
332
512
|
* Send the MCP `initialize` handshake and capture the resulting session
|
|
@@ -413,6 +593,7 @@ var MCPClient = class {
|
|
|
413
593
|
method
|
|
414
594
|
};
|
|
415
595
|
if (params !== void 0) payload.params = params;
|
|
596
|
+
const signatureHeader = this.buildSignatureHeader(method, params);
|
|
416
597
|
let attempt = 0;
|
|
417
598
|
let lastErr = null;
|
|
418
599
|
while (attempt <= this.maxRetries) {
|
|
@@ -423,7 +604,7 @@ var MCPClient = class {
|
|
|
423
604
|
try {
|
|
424
605
|
resp = await this.fetchImpl(this.endpoint, {
|
|
425
606
|
method: "POST",
|
|
426
|
-
headers: this.buildHeaders(),
|
|
607
|
+
headers: this.buildHeaders(signatureHeader),
|
|
427
608
|
body: JSON.stringify(payload),
|
|
428
609
|
signal: controller.signal
|
|
429
610
|
});
|
|
@@ -450,7 +631,8 @@ var MCPClient = class {
|
|
|
450
631
|
throw new AlterPaymentRequired(this.guessToolName(payload), envelope);
|
|
451
632
|
}
|
|
452
633
|
if (resp.status === 429) {
|
|
453
|
-
const
|
|
634
|
+
const rawRetryAfter = Number(resp.headers.get("Retry-After") ?? 60);
|
|
635
|
+
const retryAfter = Number.isFinite(rawRetryAfter) && rawRetryAfter >= 0 ? Math.min(rawRetryAfter, 300) : 60;
|
|
454
636
|
if (attempt > this.maxRetries) {
|
|
455
637
|
throw new AlterRateLimited(`HTTP 429 on ${method}`, retryAfter);
|
|
456
638
|
}
|
|
@@ -486,16 +668,36 @@ var MCPClient = class {
|
|
|
486
668
|
}
|
|
487
669
|
throw lastErr ?? new AlterNetworkError(`MCP ${method}: exhausted retries`);
|
|
488
670
|
}
|
|
489
|
-
buildHeaders() {
|
|
671
|
+
buildHeaders(extra) {
|
|
490
672
|
const headers = {
|
|
673
|
+
...this.extraHeaders ?? {},
|
|
491
674
|
"Content-Type": "application/json",
|
|
492
675
|
Accept: "application/json",
|
|
493
676
|
"User-Agent": `${this.clientInfo.name}/${this.clientInfo.version}`
|
|
494
677
|
};
|
|
495
678
|
if (this.apiKey) headers["X-ALTER-API-Key"] = this.apiKey;
|
|
496
679
|
if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
|
|
680
|
+
if (extra) Object.assign(headers, extra);
|
|
497
681
|
return headers;
|
|
498
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Produce the `Mcp-Invocation-Signature` header for a `tools/call`
|
|
685
|
+
* payload, when signing is configured. Returns `undefined` when no
|
|
686
|
+
* signing key is attached or the method is not `tools/call`.
|
|
687
|
+
*/
|
|
688
|
+
buildSignatureHeader(method, params) {
|
|
689
|
+
if (!this.signing) return void 0;
|
|
690
|
+
if (method !== "tools/call") return void 0;
|
|
691
|
+
const p = params;
|
|
692
|
+
if (!p?.name) return void 0;
|
|
693
|
+
const { signInvocation: signInvocation2 } = (init_signing(), __toCommonJS(signing_exports));
|
|
694
|
+
const headerValue = signInvocation2(p.name, p.arguments ?? {}, {
|
|
695
|
+
kid: this.signing.kid,
|
|
696
|
+
privateKey: this.signing.privateKey,
|
|
697
|
+
handle: this.signing.handle
|
|
698
|
+
});
|
|
699
|
+
return { "Mcp-Invocation-Signature": headerValue };
|
|
700
|
+
}
|
|
499
701
|
async extractPaymentEnvelope(resp) {
|
|
500
702
|
const headerValue = resp.headers.get("X-402-Payment") ?? resp.headers.get("x-402-payment");
|
|
501
703
|
if (headerValue) {
|
|
@@ -538,8 +740,8 @@ function generateKeypair() {
|
|
|
538
740
|
const privateKey = randomBytes(32);
|
|
539
741
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
540
742
|
return {
|
|
541
|
-
privateKey: bytesToHex(privateKey),
|
|
542
|
-
publicKey: bytesToHex(publicKey),
|
|
743
|
+
privateKey: bytesToHex$1(privateKey),
|
|
744
|
+
publicKey: bytesToHex$1(publicKey),
|
|
543
745
|
did: encodeDid(publicKey)
|
|
544
746
|
};
|
|
545
747
|
}
|
|
@@ -551,7 +753,7 @@ function keypairFromPrivateKey(privateKeyHex) {
|
|
|
551
753
|
const publicKey = ed25519.getPublicKey(privateKey);
|
|
552
754
|
return {
|
|
553
755
|
privateKey: privateKeyHex,
|
|
554
|
-
publicKey: bytesToHex(publicKey),
|
|
756
|
+
publicKey: bytesToHex$1(publicKey),
|
|
555
757
|
did: encodeDid(publicKey)
|
|
556
758
|
};
|
|
557
759
|
}
|
|
@@ -559,7 +761,7 @@ async function sign(privateKeyHex, message) {
|
|
|
559
761
|
const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
560
762
|
const privateKey = hexToBytes(privateKeyHex);
|
|
561
763
|
const sig = await ed25519.signAsync(msgBytes, privateKey);
|
|
562
|
-
return bytesToHex(sig);
|
|
764
|
+
return bytesToHex$1(sig);
|
|
563
765
|
}
|
|
564
766
|
async function verify(publicKeyHex, signatureHex, message) {
|
|
565
767
|
try {
|
|
@@ -571,14 +773,14 @@ async function verify(publicKeyHex, signatureHex, message) {
|
|
|
571
773
|
}
|
|
572
774
|
function encodeDid(publicKey) {
|
|
573
775
|
const bytes = typeof publicKey === "string" ? hexToBytes(publicKey) : publicKey;
|
|
574
|
-
return `ed25519:${
|
|
776
|
+
return `ed25519:${base64urlEncode2(bytes)}`;
|
|
575
777
|
}
|
|
576
778
|
function decodeDid(did) {
|
|
577
779
|
const ed25519Match = did.match(/^ed25519:(.+)$/);
|
|
578
780
|
if (ed25519Match) return base64urlDecode(ed25519Match[1]);
|
|
579
781
|
throw new Error(`Unrecognised DID encoding: ${did}`);
|
|
580
782
|
}
|
|
581
|
-
function
|
|
783
|
+
function base64urlEncode2(bytes) {
|
|
582
784
|
let b64;
|
|
583
785
|
if (typeof Buffer !== "undefined") {
|
|
584
786
|
b64 = Buffer.from(bytes).toString("base64");
|
|
@@ -609,6 +811,7 @@ var DEFAULT_VERIFY_AT_ALLOWLIST = Object.freeze([
|
|
|
609
811
|
"api.truealter.com",
|
|
610
812
|
"mcp.truealter.com"
|
|
611
813
|
]);
|
|
814
|
+
var ALTER_PLATFORM_ISS = "did:alter:platform";
|
|
612
815
|
async function verifyProvenance(envelope, opts = {}) {
|
|
613
816
|
const token = typeof envelope === "string" ? envelope : envelope.token;
|
|
614
817
|
if (!token) return { valid: false, reason: "empty token" };
|
|
@@ -691,6 +894,15 @@ async function verifyProvenance(envelope, opts = {}) {
|
|
|
691
894
|
if (typeof payload.iat === "number" && payload.iat > now + 300) {
|
|
692
895
|
return { valid: false, reason: "issued in the future", payload, kid: header.kid };
|
|
693
896
|
}
|
|
897
|
+
const expectedIss = opts.expectedIss !== void 0 ? opts.expectedIss : ALTER_PLATFORM_ISS;
|
|
898
|
+
if (expectedIss !== "" && payload.iss !== expectedIss) {
|
|
899
|
+
return {
|
|
900
|
+
valid: false,
|
|
901
|
+
reason: `iss mismatch: expected "${expectedIss}", got "${payload.iss}"`,
|
|
902
|
+
payload,
|
|
903
|
+
kid: header.kid
|
|
904
|
+
};
|
|
905
|
+
}
|
|
694
906
|
return { valid: true, payload, kid: header.kid };
|
|
695
907
|
}
|
|
696
908
|
async function verifyToolSignatures(tools, signatures) {
|
|
@@ -887,10 +1099,10 @@ var AlterClient = class {
|
|
|
887
1099
|
}
|
|
888
1100
|
/** Verify a person is registered with ALTER (handle or id). */
|
|
889
1101
|
async verify(handleOrId, claims) {
|
|
890
|
-
const args = handleOrId.includes("@") ? {
|
|
891
|
-
// ~handle — server resolves these via the
|
|
892
|
-
{
|
|
893
|
-
) : {
|
|
1102
|
+
const args = handleOrId.includes("@") ? { member_id: "", email: handleOrId } : handleOrId.startsWith("~") ? (
|
|
1103
|
+
// ~handle — server resolves these via the member_id field
|
|
1104
|
+
{ member_id: handleOrId }
|
|
1105
|
+
) : { member_id: handleOrId };
|
|
894
1106
|
if (claims) args.claims = claims;
|
|
895
1107
|
return this.mcp.callTool("verify_identity", args);
|
|
896
1108
|
}
|
|
@@ -1047,6 +1259,9 @@ var AlterClient = class {
|
|
|
1047
1259
|
}
|
|
1048
1260
|
};
|
|
1049
1261
|
|
|
1262
|
+
// src/index.ts
|
|
1263
|
+
init_signing();
|
|
1264
|
+
|
|
1050
1265
|
// src/adapters/generic-mcp.ts
|
|
1051
1266
|
function generateGenericMcpConfig(opts = {}) {
|
|
1052
1267
|
const serverName = opts.serverName ?? "alter";
|
|
@@ -1071,6 +1286,461 @@ function generateCursorConfig(opts = {}) {
|
|
|
1071
1286
|
return generateGenericMcpConfig({ serverName: "alter", ...opts });
|
|
1072
1287
|
}
|
|
1073
1288
|
|
|
1289
|
+
// src/adapters/claude-desktop.ts
|
|
1290
|
+
function generateClaudeDesktopConfig(opts = {}) {
|
|
1291
|
+
const serverName = opts.serverName ?? "alter";
|
|
1292
|
+
const bridgeCommand = opts.bridgeCommand ?? "alter-mcp-bridge";
|
|
1293
|
+
const env2 = {};
|
|
1294
|
+
env2.ALTER_MCP_ENDPOINT = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1295
|
+
if (opts.apiKey) env2.ALTER_API_KEY = opts.apiKey;
|
|
1296
|
+
const entry = {
|
|
1297
|
+
command: bridgeCommand,
|
|
1298
|
+
env: env2,
|
|
1299
|
+
description: "ALTER Identity \u2014 psychometric identity field for AI agents"
|
|
1300
|
+
};
|
|
1301
|
+
if (opts.extraArgs && opts.extraArgs.length > 0) {
|
|
1302
|
+
entry.args = [...opts.extraArgs];
|
|
1303
|
+
}
|
|
1304
|
+
return { mcpServers: { [serverName]: entry } };
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/meta.ts
|
|
1308
|
+
var SDK_NAME = "@truealter/sdk";
|
|
1309
|
+
var SDK_VERSION = "0.3.0";
|
|
1310
|
+
var HOME = homedir();
|
|
1311
|
+
var PLAT = platform();
|
|
1312
|
+
function appData() {
|
|
1313
|
+
return env.APPDATA ?? join(HOME, "AppData", "Roaming");
|
|
1314
|
+
}
|
|
1315
|
+
function xdgConfig() {
|
|
1316
|
+
return env.XDG_CONFIG_HOME ?? join(HOME, ".config");
|
|
1317
|
+
}
|
|
1318
|
+
function macAppSupport() {
|
|
1319
|
+
return join(HOME, "Library", "Application Support");
|
|
1320
|
+
}
|
|
1321
|
+
function claudeDesktopConfigPath() {
|
|
1322
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Claude", "claude_desktop_config.json");
|
|
1323
|
+
if (PLAT === "win32") return join(appData(), "Claude", "claude_desktop_config.json");
|
|
1324
|
+
return join(xdgConfig(), "Claude", "claude_desktop_config.json");
|
|
1325
|
+
}
|
|
1326
|
+
function claudeDesktopDir() {
|
|
1327
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Claude");
|
|
1328
|
+
if (PLAT === "win32") return join(appData(), "Claude");
|
|
1329
|
+
return join(xdgConfig(), "Claude");
|
|
1330
|
+
}
|
|
1331
|
+
function vscodeConfigPath() {
|
|
1332
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Code", "User", "mcp.json");
|
|
1333
|
+
if (PLAT === "win32") return join(appData(), "Code", "User", "mcp.json");
|
|
1334
|
+
return join(xdgConfig(), "Code", "User", "mcp.json");
|
|
1335
|
+
}
|
|
1336
|
+
function vscodeDir() {
|
|
1337
|
+
if (PLAT === "darwin") return join(macAppSupport(), "Code", "User");
|
|
1338
|
+
if (PLAT === "win32") return join(appData(), "Code", "User");
|
|
1339
|
+
return join(xdgConfig(), "Code", "User");
|
|
1340
|
+
}
|
|
1341
|
+
var cursorDir = join(HOME, ".cursor");
|
|
1342
|
+
var cursorConfigPath = join(cursorDir, "mcp.json");
|
|
1343
|
+
var claudeCodeProbeDir = join(HOME, ".claude");
|
|
1344
|
+
var CLAUDE_CODE = {
|
|
1345
|
+
id: "claude-code",
|
|
1346
|
+
label: "Claude Code",
|
|
1347
|
+
configPath: null,
|
|
1348
|
+
probeDir: claudeCodeProbeDir,
|
|
1349
|
+
rootKey: "mcpServers"
|
|
1350
|
+
};
|
|
1351
|
+
var CURSOR = {
|
|
1352
|
+
id: "cursor",
|
|
1353
|
+
label: "Cursor",
|
|
1354
|
+
configPath: cursorConfigPath,
|
|
1355
|
+
probeDir: cursorDir,
|
|
1356
|
+
rootKey: "mcpServers"
|
|
1357
|
+
};
|
|
1358
|
+
var CLAUDE_DESKTOP = {
|
|
1359
|
+
id: "claude-desktop",
|
|
1360
|
+
label: "Claude Desktop",
|
|
1361
|
+
configPath: claudeDesktopConfigPath(),
|
|
1362
|
+
probeDir: claudeDesktopDir(),
|
|
1363
|
+
rootKey: "mcpServers"
|
|
1364
|
+
};
|
|
1365
|
+
var VSCODE = {
|
|
1366
|
+
id: "vscode",
|
|
1367
|
+
label: "VS Code",
|
|
1368
|
+
configPath: vscodeConfigPath(),
|
|
1369
|
+
probeDir: vscodeDir(),
|
|
1370
|
+
// VS Code's user-scoped mcp.json uses `servers`, not `mcpServers`.
|
|
1371
|
+
rootKey: "servers"
|
|
1372
|
+
};
|
|
1373
|
+
var ALL_CLIENTS = [CLAUDE_CODE, CURSOR, CLAUDE_DESKTOP, VSCODE];
|
|
1374
|
+
function alterConfigDir() {
|
|
1375
|
+
return join(xdgConfig(), "alter");
|
|
1376
|
+
}
|
|
1377
|
+
function wireStatePath() {
|
|
1378
|
+
return join(alterConfigDir(), "wire-state.json");
|
|
1379
|
+
}
|
|
1380
|
+
function probeClaudeCode() {
|
|
1381
|
+
try {
|
|
1382
|
+
const result = spawnSync("claude", ["--version"], {
|
|
1383
|
+
encoding: "utf8",
|
|
1384
|
+
shell: process.platform === "win32",
|
|
1385
|
+
timeout: 5e3
|
|
1386
|
+
});
|
|
1387
|
+
if (result.error) {
|
|
1388
|
+
return {
|
|
1389
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1390
|
+
installed: false,
|
|
1391
|
+
reason: `claude binary not on PATH (${result.error.message})`
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
if (result.status === 0) {
|
|
1395
|
+
return {
|
|
1396
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1397
|
+
installed: true,
|
|
1398
|
+
version: result.stdout.trim() || void 0,
|
|
1399
|
+
reason: "claude --version returned 0"
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1404
|
+
installed: false,
|
|
1405
|
+
reason: `claude --version exited ${String(result.status)}`
|
|
1406
|
+
};
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
return {
|
|
1409
|
+
client: ALL_CLIENTS.find((c) => c.id === "claude-code"),
|
|
1410
|
+
installed: false,
|
|
1411
|
+
reason: err.message
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function probeByDir(id) {
|
|
1416
|
+
const client = ALL_CLIENTS.find((c) => c.id === id);
|
|
1417
|
+
if (!client) throw new Error(`unknown client id: ${id}`);
|
|
1418
|
+
const installed = existsSync(client.probeDir);
|
|
1419
|
+
return {
|
|
1420
|
+
client,
|
|
1421
|
+
installed,
|
|
1422
|
+
reason: installed ? `found ${client.probeDir}` : `no directory at ${client.probeDir}`
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
function probeAll() {
|
|
1426
|
+
return [
|
|
1427
|
+
probeClaudeCode(),
|
|
1428
|
+
probeByDir("cursor"),
|
|
1429
|
+
probeByDir("claude-desktop"),
|
|
1430
|
+
probeByDir("vscode")
|
|
1431
|
+
];
|
|
1432
|
+
}
|
|
1433
|
+
var SYNC_PREFIXES = [
|
|
1434
|
+
// iCloud Drive — both the new and legacy mounts.
|
|
1435
|
+
"Library/Mobile Documents/com~apple~CloudDocs",
|
|
1436
|
+
"iCloud Drive",
|
|
1437
|
+
// OneDrive variants Microsoft ships across editions.
|
|
1438
|
+
"OneDrive",
|
|
1439
|
+
"OneDrive - ",
|
|
1440
|
+
// Dropbox standard + enterprise mounts.
|
|
1441
|
+
"Dropbox",
|
|
1442
|
+
"Dropbox (",
|
|
1443
|
+
// Google Drive (ALTER does not integrate with Google; still refuse).
|
|
1444
|
+
"Google Drive",
|
|
1445
|
+
"GoogleDrive",
|
|
1446
|
+
"CloudStorage/GoogleDrive",
|
|
1447
|
+
// Box, pCloud, Sync.com, MEGA — high-signal names worth refusing.
|
|
1448
|
+
"Box Sync",
|
|
1449
|
+
"pCloud Drive",
|
|
1450
|
+
"Sync.com",
|
|
1451
|
+
"MEGAsync"
|
|
1452
|
+
];
|
|
1453
|
+
function detectSyncedVolume(path) {
|
|
1454
|
+
const absolute = resolve(path);
|
|
1455
|
+
const normalised = platform() === "win32" ? absolute.replace(/\\/g, "/") : absolute;
|
|
1456
|
+
for (const prefix of SYNC_PREFIXES) {
|
|
1457
|
+
if (normalised.includes(`/${prefix}/`) || normalised.includes(`/${prefix}`)) {
|
|
1458
|
+
return { refused: true, matchedPrefix: prefix, resolvedPath: absolute };
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
var WIRE_STATE_VERSION = 1;
|
|
1464
|
+
function readWireState() {
|
|
1465
|
+
const path = wireStatePath();
|
|
1466
|
+
if (!existsSync(path)) return null;
|
|
1467
|
+
try {
|
|
1468
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1469
|
+
if (parsed.version !== WIRE_STATE_VERSION) {
|
|
1470
|
+
throw new Error(
|
|
1471
|
+
`wire-state.json version ${String(parsed.version)} is not supported by this SDK (expected ${WIRE_STATE_VERSION})`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return parsed;
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
throw new Error(`failed to parse wire-state.json: ${err.message}`);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
function writeWireState(state) {
|
|
1480
|
+
const path = wireStatePath();
|
|
1481
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
1482
|
+
writeFileSync(path, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
1483
|
+
}
|
|
1484
|
+
function sha2562(bytes) {
|
|
1485
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
1486
|
+
}
|
|
1487
|
+
function atomicJsonMerge(opts) {
|
|
1488
|
+
const { path, timestamp, merge, idempotent = true } = opts;
|
|
1489
|
+
const tmpPath = `${path}.alter-tmp-${timestamp}`;
|
|
1490
|
+
const backupPath = `${path}.alter-backup-${timestamp}`;
|
|
1491
|
+
let existed = false;
|
|
1492
|
+
let preBytes = null;
|
|
1493
|
+
let parsed = {};
|
|
1494
|
+
if (existsSync(path)) {
|
|
1495
|
+
existed = true;
|
|
1496
|
+
preBytes = readFileSync(path, "utf8");
|
|
1497
|
+
if (preBytes.trim().length > 0) {
|
|
1498
|
+
try {
|
|
1499
|
+
parsed = JSON.parse(preBytes);
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
throw new Error(
|
|
1502
|
+
`refusing to wire ${path}: existing file is not valid JSON (${err.message}). Hand-fix the file, then re-run \`alter-identity wire\`.`
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
if (typeof parsed !== "object" || Array.isArray(parsed) || parsed === null) {
|
|
1506
|
+
throw new Error(`refusing to wire ${path}: existing JSON root is not an object`);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
const merged = merge(parsed);
|
|
1511
|
+
const serialised = JSON.stringify(merged, null, 2) + "\n";
|
|
1512
|
+
if (idempotent && preBytes !== null && preBytes === serialised) {
|
|
1513
|
+
return {
|
|
1514
|
+
path,
|
|
1515
|
+
backupPath: null,
|
|
1516
|
+
preSha256: sha2562(preBytes),
|
|
1517
|
+
postSha256: sha2562(preBytes),
|
|
1518
|
+
noop: true
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1522
|
+
writeFileSync(tmpPath, serialised, { mode: 384 });
|
|
1523
|
+
try {
|
|
1524
|
+
if (existed) copyFileSync(path, backupPath);
|
|
1525
|
+
renameSync(tmpPath, path);
|
|
1526
|
+
} catch (err) {
|
|
1527
|
+
try {
|
|
1528
|
+
unlinkSync(tmpPath);
|
|
1529
|
+
} catch {
|
|
1530
|
+
}
|
|
1531
|
+
throw err;
|
|
1532
|
+
}
|
|
1533
|
+
return {
|
|
1534
|
+
path,
|
|
1535
|
+
backupPath: existed ? backupPath : null,
|
|
1536
|
+
preSha256: preBytes === null ? null : sha2562(preBytes),
|
|
1537
|
+
postSha256: sha2562(serialised),
|
|
1538
|
+
noop: false
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function restoreFromBackup(path, backupPath) {
|
|
1542
|
+
if (backupPath === null) {
|
|
1543
|
+
if (existsSync(path)) unlinkSync(path);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
if (!existsSync(backupPath)) {
|
|
1547
|
+
throw new Error(`cannot restore ${path}: backup missing at ${backupPath}`);
|
|
1548
|
+
}
|
|
1549
|
+
renameSync(backupPath, path);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// src/wire/index.ts
|
|
1553
|
+
var TIMESTAMP = () => String(Math.floor(Date.now() / 1e3));
|
|
1554
|
+
var ISO_NOW = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1555
|
+
function clientById(id) {
|
|
1556
|
+
const hit = ALL_CLIENTS.find((c) => c.id === id);
|
|
1557
|
+
if (!hit) throw new Error(`unknown client id: ${id}`);
|
|
1558
|
+
return hit;
|
|
1559
|
+
}
|
|
1560
|
+
function wire(opts = {}) {
|
|
1561
|
+
const endpoint = opts.endpoint ?? DEFAULT_ENDPOINT;
|
|
1562
|
+
const apiKey = opts.apiKey;
|
|
1563
|
+
const probes = probeAll();
|
|
1564
|
+
const selection = opts.only ?? probes.filter((p) => p.installed).map((p) => p.client.id);
|
|
1565
|
+
const ts = TIMESTAMP();
|
|
1566
|
+
const targets = [];
|
|
1567
|
+
for (const id of selection) {
|
|
1568
|
+
const probe = id === "claude-code" ? probeClaudeCode() : probeByDir(id);
|
|
1569
|
+
if (!probe.installed && opts.skipMissing !== false) {
|
|
1570
|
+
targets.push({
|
|
1571
|
+
client: id,
|
|
1572
|
+
method: id === "claude-code" ? "cli" : "file",
|
|
1573
|
+
status: "skipped",
|
|
1574
|
+
...id === "claude-code" ? { command: "" } : { path: clientById(id).configPath ?? "", backupPath: null, rootKey: clientById(id).rootKey, serverName: "alter", preSha256: null, postSha256: "" },
|
|
1575
|
+
reason: probe.reason
|
|
1576
|
+
});
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
try {
|
|
1580
|
+
if (id === "claude-code") {
|
|
1581
|
+
targets.push(wireClaudeCode({ endpoint, apiKey }));
|
|
1582
|
+
} else {
|
|
1583
|
+
targets.push(wireFileTarget({ id, endpoint, apiKey, timestamp: ts }));
|
|
1584
|
+
}
|
|
1585
|
+
} catch (err) {
|
|
1586
|
+
const message = err.message;
|
|
1587
|
+
targets.push({
|
|
1588
|
+
client: id,
|
|
1589
|
+
method: id === "claude-code" ? "cli" : "file",
|
|
1590
|
+
status: "failed",
|
|
1591
|
+
...id === "claude-code" ? { command: "" } : { path: clientById(id).configPath ?? "", backupPath: null, rootKey: clientById(id).rootKey, serverName: "alter", preSha256: null, postSha256: "" },
|
|
1592
|
+
reason: message
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
const state = {
|
|
1597
|
+
version: 1,
|
|
1598
|
+
sdkVersion: SDK_VERSION,
|
|
1599
|
+
writtenAt: ISO_NOW(),
|
|
1600
|
+
endpoint,
|
|
1601
|
+
targets
|
|
1602
|
+
};
|
|
1603
|
+
writeWireState(state);
|
|
1604
|
+
return { state, probes };
|
|
1605
|
+
}
|
|
1606
|
+
function wireFileTarget(args) {
|
|
1607
|
+
const client = clientById(args.id);
|
|
1608
|
+
if (!client.configPath) {
|
|
1609
|
+
throw new Error(`client ${client.id} has no file-based config path`);
|
|
1610
|
+
}
|
|
1611
|
+
const sync = detectSyncedVolume(client.configPath);
|
|
1612
|
+
if (sync) {
|
|
1613
|
+
throw new Error(
|
|
1614
|
+
`refusing to wire ${client.label}: config path ${sync.resolvedPath} lives under ${sync.matchedPrefix}. Synced volumes propagate credentials across devices \u2014 move the config off the sync root, or run wire on the device you want to target.`
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
const entry = args.id === "claude-desktop" ? generateClaudeDesktopConfig({ endpoint: args.endpoint, apiKey: args.apiKey }) : generateGenericMcpConfig({ endpoint: args.endpoint, apiKey: args.apiKey });
|
|
1618
|
+
const rootKey = client.rootKey;
|
|
1619
|
+
const serverName = "alter";
|
|
1620
|
+
const result = atomicJsonMerge({
|
|
1621
|
+
path: client.configPath,
|
|
1622
|
+
timestamp: args.timestamp,
|
|
1623
|
+
merge: (existing) => {
|
|
1624
|
+
const bucket = existing[rootKey] ?? {};
|
|
1625
|
+
const source = entry.mcpServers.alter;
|
|
1626
|
+
return {
|
|
1627
|
+
...existing,
|
|
1628
|
+
[rootKey]: {
|
|
1629
|
+
...bucket,
|
|
1630
|
+
[serverName]: source
|
|
1631
|
+
}
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
return {
|
|
1636
|
+
client: args.id,
|
|
1637
|
+
method: "file",
|
|
1638
|
+
status: result.noop ? "already-wired" : "written",
|
|
1639
|
+
path: result.path,
|
|
1640
|
+
backupPath: result.backupPath,
|
|
1641
|
+
rootKey,
|
|
1642
|
+
serverName,
|
|
1643
|
+
preSha256: result.preSha256,
|
|
1644
|
+
postSha256: result.postSha256
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function wireClaudeCode(args) {
|
|
1648
|
+
const cmd = "claude";
|
|
1649
|
+
const argList = [
|
|
1650
|
+
"mcp",
|
|
1651
|
+
"add",
|
|
1652
|
+
"--scope",
|
|
1653
|
+
"user",
|
|
1654
|
+
"--transport",
|
|
1655
|
+
"http",
|
|
1656
|
+
"alter",
|
|
1657
|
+
args.endpoint
|
|
1658
|
+
];
|
|
1659
|
+
if (args.apiKey) {
|
|
1660
|
+
argList.push("--header", `X-ALTER-API-Key:${args.apiKey}`);
|
|
1661
|
+
}
|
|
1662
|
+
const full = `${cmd} ${argList.join(" ")}`;
|
|
1663
|
+
const run = spawnSync(cmd, argList, {
|
|
1664
|
+
encoding: "utf8",
|
|
1665
|
+
shell: process.platform === "win32",
|
|
1666
|
+
timeout: 1e4
|
|
1667
|
+
});
|
|
1668
|
+
if (run.error) {
|
|
1669
|
+
return {
|
|
1670
|
+
client: "claude-code",
|
|
1671
|
+
method: "cli",
|
|
1672
|
+
status: "failed",
|
|
1673
|
+
command: full,
|
|
1674
|
+
stdout: run.stdout,
|
|
1675
|
+
stderr: run.stderr,
|
|
1676
|
+
reason: run.error.message
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
const stderr = (run.stderr ?? "").toLowerCase();
|
|
1680
|
+
const alreadyExists = stderr.includes("already exists") || stderr.includes("already configured");
|
|
1681
|
+
if (run.status === 0) {
|
|
1682
|
+
return { client: "claude-code", method: "cli", status: "written", command: full, stdout: run.stdout, stderr: run.stderr };
|
|
1683
|
+
}
|
|
1684
|
+
if (alreadyExists) {
|
|
1685
|
+
return { client: "claude-code", method: "cli", status: "already-wired", command: full, stdout: run.stdout, stderr: run.stderr };
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
client: "claude-code",
|
|
1689
|
+
method: "cli",
|
|
1690
|
+
status: "failed",
|
|
1691
|
+
command: full,
|
|
1692
|
+
stdout: run.stdout,
|
|
1693
|
+
stderr: run.stderr,
|
|
1694
|
+
reason: `claude mcp add exited ${String(run.status)}`
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
function unwire() {
|
|
1698
|
+
const state = readWireState();
|
|
1699
|
+
const undone = [];
|
|
1700
|
+
if (!state || state.targets.length === 0) {
|
|
1701
|
+
return { state, undone };
|
|
1702
|
+
}
|
|
1703
|
+
for (const target of state.targets) {
|
|
1704
|
+
try {
|
|
1705
|
+
if (target.method === "file") {
|
|
1706
|
+
if (target.status === "written") {
|
|
1707
|
+
restoreFromBackup(target.path, target.backupPath);
|
|
1708
|
+
undone.push({ client: target.client, action: target.backupPath ? "restored" : "removed" });
|
|
1709
|
+
} else {
|
|
1710
|
+
undone.push({ client: target.client, action: "skipped", reason: `target status was ${target.status}` });
|
|
1711
|
+
}
|
|
1712
|
+
} else if (target.method === "cli") {
|
|
1713
|
+
if (target.status === "written") {
|
|
1714
|
+
const run = spawnSync("claude", ["mcp", "remove", "--scope", "user", "alter"], {
|
|
1715
|
+
encoding: "utf8",
|
|
1716
|
+
shell: process.platform === "win32",
|
|
1717
|
+
timeout: 1e4
|
|
1718
|
+
});
|
|
1719
|
+
if (run.error) {
|
|
1720
|
+
undone.push({ client: target.client, action: "failed", reason: run.error.message });
|
|
1721
|
+
} else if (run.status === 0) {
|
|
1722
|
+
undone.push({ client: target.client, action: "cli-removed" });
|
|
1723
|
+
} else {
|
|
1724
|
+
undone.push({ client: target.client, action: "failed", reason: `claude mcp remove exited ${String(run.status)}` });
|
|
1725
|
+
}
|
|
1726
|
+
} else {
|
|
1727
|
+
undone.push({ client: target.client, action: "skipped", reason: `target status was ${target.status}` });
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
} catch (err) {
|
|
1731
|
+
undone.push({ client: target.client, action: "failed", reason: err.message });
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
writeWireState({
|
|
1735
|
+
version: 1,
|
|
1736
|
+
sdkVersion: state.sdkVersion,
|
|
1737
|
+
writtenAt: ISO_NOW(),
|
|
1738
|
+
endpoint: state.endpoint,
|
|
1739
|
+
targets: []
|
|
1740
|
+
});
|
|
1741
|
+
return { state, undone };
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1074
1744
|
// src/types.ts
|
|
1075
1745
|
var FREE_TOOL_NAMES = [
|
|
1076
1746
|
"hello_agent",
|
|
@@ -1226,8 +1896,22 @@ var TOOL_BLAST_RADIUS = {
|
|
|
1226
1896
|
query_graph_similarity: "high"
|
|
1227
1897
|
};
|
|
1228
1898
|
|
|
1229
|
-
// src/
|
|
1230
|
-
var
|
|
1231
|
-
|
|
1899
|
+
// src/homepage.ts
|
|
1900
|
+
var HOMEPAGE_LIMITS = {
|
|
1901
|
+
whoami_max_chars: 240,
|
|
1902
|
+
opener_max_chars: 280,
|
|
1903
|
+
pronouns_max_chars: 32,
|
|
1904
|
+
attunement_glyph_max_chars: 16
|
|
1905
|
+
};
|
|
1906
|
+
|
|
1907
|
+
// src/themes.ts
|
|
1908
|
+
var THEME_LIMITS = {
|
|
1909
|
+
meta_name_pattern: /^[a-z][a-z0-9-]{0,63}$/,
|
|
1910
|
+
meta_description_max_chars: 240,
|
|
1911
|
+
opener_library_max_entries: 32,
|
|
1912
|
+
opener_entry_max_chars: 240,
|
|
1913
|
+
share_note_max_chars: 280
|
|
1914
|
+
};
|
|
1915
|
+
var OSC8_ALLOWED_SCHEMES = ["https:", "mailto:"];
|
|
1232
1916
|
|
|
1233
|
-
export { AlterAuthError, AlterClient, AlterDiscoveryError, AlterError, AlterInvalidResponse, AlterNetworkError, AlterPaymentRequired, AlterProvenanceError, AlterRateLimited, AlterTimeoutError, AlterToolError, DEFAULT_DOMAIN, DEFAULT_ENDPOINT, DEFAULT_VERIFY_AT_ALLOWLIST, FREE_TOOL_NAMES, MCPClient, MCP_PROTOCOL_VERSION, PREMIUM_TOOL_NAMES, SDK_NAME, SDK_VERSION, TOOL_BLAST_RADIUS, TOOL_COSTS, TOOL_TIERS, X402Client, base64urlDecode, base64urlEncode, clearDiscoveryCache, decodeDid, discover, encodeDid, fetchPublicKeys, generateClaudeConfig, generateCursorConfig, generateGenericMcpConfig, generateKeypair, keypairFromPrivateKey, parsePaymentHeader, resolveVerifyAt, sign, verify, verifyProvenance, verifyToolSignatures };
|
|
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 };
|