magic-spells 0.1.0 → 0.2.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 +41 -17
- package/dist/{chunk-YT2Z756Z.js → chunk-JZ7XESF4.js} +733 -32
- package/dist/{chunk-S5IUP7G2.js → chunk-PQFOO5A7.js} +1 -1
- package/dist/main.js +618 -64
- package/dist/{operations-P3DUAVPP.js → operations-GGMLIYEW.js} +2 -2
- package/dist/src-DJYPPJFM.js +16 -0
- package/dist/src-KJGFPRQP.js +17 -0
- package/dist/{src-4S3XHPWY.js → src-L2I546M7.js} +7 -234
- package/package.json +1 -1
|
@@ -25,6 +25,9 @@ var MagicError = class _MagicError extends Error {
|
|
|
25
25
|
return out;
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
+
function isMagicError(e) {
|
|
29
|
+
return e instanceof MagicError;
|
|
30
|
+
}
|
|
28
31
|
function exitCodeFor(code) {
|
|
29
32
|
switch (code) {
|
|
30
33
|
case "usage":
|
|
@@ -32,6 +35,7 @@ function exitCodeFor(code) {
|
|
|
32
35
|
case "not_logged_in":
|
|
33
36
|
case "auth_invalid":
|
|
34
37
|
case "auth_expired":
|
|
38
|
+
case "auth_cancelled":
|
|
35
39
|
return 3;
|
|
36
40
|
case "permission_denied":
|
|
37
41
|
return 4;
|
|
@@ -165,6 +169,8 @@ function configDir(opts = {}) {
|
|
|
165
169
|
|
|
166
170
|
// ../kit/src/config.ts
|
|
167
171
|
var DEFAULT_ACCOUNT_URL = "https://account.magicspells.io";
|
|
172
|
+
var ACCOUNT_CLIENT_ID_PLACEHOLDER = "set-magic-cli-client-id";
|
|
173
|
+
var DEFAULT_ACCOUNT_CLIENT_ID = "magic-cli";
|
|
168
174
|
function productEnvVar(id, suffix) {
|
|
169
175
|
return `MAGIC_${id.toUpperCase().replace(/-/g, "_")}_${suffix}`;
|
|
170
176
|
}
|
|
@@ -198,6 +204,7 @@ function loadCliConfig(opts) {
|
|
|
198
204
|
env.MAGIC_ACCOUNT_URL || file?.accountUrl || DEFAULT_ACCOUNT_URL,
|
|
199
205
|
"MAGIC_ACCOUNT_URL"
|
|
200
206
|
),
|
|
207
|
+
accountClientId: env.MAGIC_ACCOUNT_CLIENT_ID?.trim() || DEFAULT_ACCOUNT_CLIENT_ID,
|
|
201
208
|
products,
|
|
202
209
|
defaultWorkspace,
|
|
203
210
|
allowDestructive: env.MAGIC_ALLOW_DESTRUCTIVE === "1",
|
|
@@ -220,6 +227,17 @@ function normalizeUrl(value, source) {
|
|
|
220
227
|
return trimmed;
|
|
221
228
|
}
|
|
222
229
|
|
|
230
|
+
// ../kit/src/read.ts
|
|
231
|
+
function rec(v) {
|
|
232
|
+
return v && typeof v === "object" ? v : {};
|
|
233
|
+
}
|
|
234
|
+
function readStr(v) {
|
|
235
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
236
|
+
}
|
|
237
|
+
function readField(obj, key) {
|
|
238
|
+
return obj && typeof obj === "object" && key in obj ? obj[key] : void 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
223
241
|
// ../kit/src/keychain.ts
|
|
224
242
|
import { execFileSync } from "node:child_process";
|
|
225
243
|
import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
@@ -694,6 +712,533 @@ function usage(message, hint) {
|
|
|
694
712
|
// ../kit/src/cli.ts
|
|
695
713
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
696
714
|
|
|
715
|
+
// ../kit/src/sessions.ts
|
|
716
|
+
import { hostname } from "node:os";
|
|
717
|
+
|
|
718
|
+
// ../kit/src/account.ts
|
|
719
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
720
|
+
var ACCOUNT_PRODUCT = "account";
|
|
721
|
+
var GRANT_PREFIX = "magic_";
|
|
722
|
+
var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
723
|
+
var DEFAULT_GRANT_SECONDS = 60 * 24 * 60 * 60;
|
|
724
|
+
function createPkce(bytes = randomBytes(32)) {
|
|
725
|
+
const verifier = bytes.toString("base64url");
|
|
726
|
+
return { verifier, challenge: createHash("sha256").update(verifier).digest("base64url") };
|
|
727
|
+
}
|
|
728
|
+
function randomState() {
|
|
729
|
+
return randomBytes(24).toString("base64url");
|
|
730
|
+
}
|
|
731
|
+
function authorizeUrl(p) {
|
|
732
|
+
const url = new URL("/auth/authorize", p.accountUrl);
|
|
733
|
+
url.searchParams.set("client_id", p.clientId);
|
|
734
|
+
url.searchParams.set("redirect_uri", p.redirectUri);
|
|
735
|
+
url.searchParams.set("state", p.state);
|
|
736
|
+
url.searchParams.set("code_challenge", p.challenge);
|
|
737
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
738
|
+
url.searchParams.set("scope", "basic");
|
|
739
|
+
return url.toString();
|
|
740
|
+
}
|
|
741
|
+
function requireClientId(clientId) {
|
|
742
|
+
if (!clientId || clientId === ACCOUNT_CLIENT_ID_PLACEHOLDER) {
|
|
743
|
+
throw new MagicError("usage", "This build of magic has no Account client id.", {
|
|
744
|
+
product: ACCOUNT_PRODUCT,
|
|
745
|
+
hint: "Set MAGIC_ACCOUNT_CLIENT_ID to the magic application\u2019s client id."
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return clientId;
|
|
749
|
+
}
|
|
750
|
+
var AccountClient = class {
|
|
751
|
+
accountUrl;
|
|
752
|
+
id;
|
|
753
|
+
fetch;
|
|
754
|
+
constructor(opts) {
|
|
755
|
+
this.accountUrl = opts.accountUrl;
|
|
756
|
+
this.id = opts.clientId;
|
|
757
|
+
this.fetch = opts.fetch ?? fetch;
|
|
758
|
+
}
|
|
759
|
+
get clientId() {
|
|
760
|
+
return requireClientId(this.id);
|
|
761
|
+
}
|
|
762
|
+
/** Exchange a loopback `code` for the grant (`grant_type=authorization_code`). */
|
|
763
|
+
async exchangeCode(p) {
|
|
764
|
+
const res = await this.post(
|
|
765
|
+
"/auth/token",
|
|
766
|
+
{
|
|
767
|
+
grant_type: "authorization_code",
|
|
768
|
+
code: p.code,
|
|
769
|
+
code_verifier: p.verifier,
|
|
770
|
+
client_id: this.clientId,
|
|
771
|
+
redirect_uri: p.redirectUri
|
|
772
|
+
},
|
|
773
|
+
p.signal
|
|
774
|
+
);
|
|
775
|
+
if (!res.ok) throw tokenError(res.status, res.code);
|
|
776
|
+
return readToken(res.body);
|
|
777
|
+
}
|
|
778
|
+
/** Start the device flow (`POST /auth/device`). */
|
|
779
|
+
async startDevice(signal) {
|
|
780
|
+
const res = await this.post(
|
|
781
|
+
"/auth/device",
|
|
782
|
+
{ client_id: this.clientId, scope: "basic" },
|
|
783
|
+
signal
|
|
784
|
+
);
|
|
785
|
+
if (!res.ok) throw tokenError(res.status, res.code);
|
|
786
|
+
const b = res.body;
|
|
787
|
+
if (typeof b.device_code !== "string" || typeof b.user_code !== "string" || typeof b.verification_uri !== "string") {
|
|
788
|
+
throw new MagicError("unknown", "Account answered the device request without a code.", {
|
|
789
|
+
product: ACCOUNT_PRODUCT
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
device_code: b.device_code,
|
|
794
|
+
user_code: b.user_code,
|
|
795
|
+
verification_uri: b.verification_uri,
|
|
796
|
+
...typeof b.verification_uri_complete === "string" ? { verification_uri_complete: b.verification_uri_complete } : {},
|
|
797
|
+
expires_in: positive(b.expires_in, 600),
|
|
798
|
+
interval: positive(b.interval, 5)
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* One device poll. Pending and slow_down come back as values; access_denied
|
|
803
|
+
* (auth_cancelled) and expired_token (auth_expired) throw.
|
|
804
|
+
*/
|
|
805
|
+
async pollDevice(deviceCode, signal) {
|
|
806
|
+
const res = await this.post(
|
|
807
|
+
"/auth/token",
|
|
808
|
+
{ grant_type: DEVICE_GRANT_TYPE, device_code: deviceCode, client_id: this.clientId },
|
|
809
|
+
signal
|
|
810
|
+
);
|
|
811
|
+
if (res.ok) return { status: "approved", token: readToken(res.body) };
|
|
812
|
+
if (res.code === "authorization_pending") return { status: "pending" };
|
|
813
|
+
if (res.code === "slow_down") return { status: "slow_down" };
|
|
814
|
+
throw tokenError(res.status, res.code);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Revoke the grant (`POST /auth/revoke token=…`). `unsupported` when this Account
|
|
818
|
+
* has no revoke endpoint (404/405); other failures throw.
|
|
819
|
+
*/
|
|
820
|
+
async revoke(token, signal) {
|
|
821
|
+
const res = await this.post("/auth/revoke", { token }, signal);
|
|
822
|
+
if (res.ok) return "revoked";
|
|
823
|
+
if (res.status === 404 || res.status === 405) return "unsupported";
|
|
824
|
+
throw tokenError(res.status, res.code);
|
|
825
|
+
}
|
|
826
|
+
async post(path, form, signal) {
|
|
827
|
+
let res;
|
|
828
|
+
try {
|
|
829
|
+
res = await this.fetch(new URL(path, this.accountUrl), {
|
|
830
|
+
method: "POST",
|
|
831
|
+
headers: {
|
|
832
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
833
|
+
accept: "application/json"
|
|
834
|
+
},
|
|
835
|
+
body: new URLSearchParams(form).toString(),
|
|
836
|
+
...signal ? { signal } : {}
|
|
837
|
+
});
|
|
838
|
+
} catch (err) {
|
|
839
|
+
throw transportError(err, signal);
|
|
840
|
+
}
|
|
841
|
+
const text = await res.text().catch(() => "");
|
|
842
|
+
let body = null;
|
|
843
|
+
try {
|
|
844
|
+
body = text ? JSON.parse(text) : null;
|
|
845
|
+
} catch {
|
|
846
|
+
body = null;
|
|
847
|
+
}
|
|
848
|
+
return { ok: res.ok, status: res.status, code: errorCode(body), body };
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
async function waitForDevice(client, start, opts = {}) {
|
|
852
|
+
const sleep = opts.sleep ?? abortableSleep;
|
|
853
|
+
const now = opts.now ?? Date.now;
|
|
854
|
+
const deadline = now() + start.expires_in * 1e3;
|
|
855
|
+
let interval = start.interval * 1e3;
|
|
856
|
+
for (; ; ) {
|
|
857
|
+
const until = now() + interval;
|
|
858
|
+
while (now() < until) {
|
|
859
|
+
if (opts.signal?.aborted) throw cancelledError();
|
|
860
|
+
opts.onTick?.(Math.max(0, Math.ceil((deadline - now()) / 1e3)));
|
|
861
|
+
await sleep(Math.min(1e3, until - now()), opts.signal).catch((err) => {
|
|
862
|
+
throw opts.signal?.aborted ? cancelledError() : err;
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
if (opts.signal?.aborted) throw cancelledError();
|
|
866
|
+
if (now() >= deadline) throw expiredCodeError();
|
|
867
|
+
const poll = await client.pollDevice(start.device_code, opts.signal);
|
|
868
|
+
if (poll.status === "approved") return poll.token;
|
|
869
|
+
if (poll.status === "slow_down") interval += 5e3;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function abortableSleep(ms, signal) {
|
|
873
|
+
return new Promise((resolve, reject) => {
|
|
874
|
+
if (signal?.aborted) return reject(cancelledError());
|
|
875
|
+
const timer = setTimeout(() => {
|
|
876
|
+
signal?.removeEventListener("abort", onAbort);
|
|
877
|
+
resolve();
|
|
878
|
+
}, ms);
|
|
879
|
+
const onAbort = () => {
|
|
880
|
+
clearTimeout(timer);
|
|
881
|
+
reject(cancelledError());
|
|
882
|
+
};
|
|
883
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
function cancelledError() {
|
|
887
|
+
return new MagicError("auth_cancelled", "Login cancelled.", { product: ACCOUNT_PRODUCT });
|
|
888
|
+
}
|
|
889
|
+
function expiredCodeError() {
|
|
890
|
+
return new MagicError("auth_expired", "The code expired.", {
|
|
891
|
+
product: ACCOUNT_PRODUCT,
|
|
892
|
+
hint: "Run `magic login` again."
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
function errorCode(body) {
|
|
896
|
+
if (!body || typeof body !== "object" || !("error" in body)) return void 0;
|
|
897
|
+
const e = body.error;
|
|
898
|
+
if (typeof e === "string") return e;
|
|
899
|
+
if (e && typeof e === "object" && typeof e.code === "string") {
|
|
900
|
+
return e.code;
|
|
901
|
+
}
|
|
902
|
+
return void 0;
|
|
903
|
+
}
|
|
904
|
+
function tokenError(status, code) {
|
|
905
|
+
const product = ACCOUNT_PRODUCT;
|
|
906
|
+
switch (code) {
|
|
907
|
+
case "access_denied":
|
|
908
|
+
return cancelledError();
|
|
909
|
+
case "expired_token":
|
|
910
|
+
return expiredCodeError();
|
|
911
|
+
case "invalid_client":
|
|
912
|
+
return new MagicError("auth_invalid", "Account does not know this client.", {
|
|
913
|
+
product,
|
|
914
|
+
hint: "Check MAGIC_ACCOUNT_CLIENT_ID and MAGIC_ACCOUNT_URL."
|
|
915
|
+
});
|
|
916
|
+
case "invalid_grant":
|
|
917
|
+
return new MagicError("auth_invalid", "Account refused the sign-in code.", {
|
|
918
|
+
product,
|
|
919
|
+
hint: "Run `magic login` again."
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
if (status === 429 || code === "slow_down" || code === "rate_limited") {
|
|
923
|
+
return new MagicError("rate_limited", "Account is rate limiting sign-ins.", {
|
|
924
|
+
product,
|
|
925
|
+
hint: "Wait a minute and try again."
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
if (status >= 500) {
|
|
929
|
+
return new MagicError("product_unavailable", `Account is unavailable (HTTP ${status}).`, {
|
|
930
|
+
product
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
return new MagicError(
|
|
934
|
+
"auth_invalid",
|
|
935
|
+
`Account refused the request (HTTP ${status}${code ? `, ${code}` : ""}).`,
|
|
936
|
+
{ product }
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function transportError(err, signal) {
|
|
940
|
+
if (signal?.aborted) return cancelledError();
|
|
941
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
942
|
+
const cause = err instanceof Error && err.cause instanceof Error ? `: ${err.cause.message}` : "";
|
|
943
|
+
if (err instanceof Error && !isNetworkError(err)) {
|
|
944
|
+
return new MagicError("unknown", message, { product: ACCOUNT_PRODUCT });
|
|
945
|
+
}
|
|
946
|
+
return new MagicError("network", `Cannot reach Account (${message}${cause}).`, {
|
|
947
|
+
product: ACCOUNT_PRODUCT,
|
|
948
|
+
hint: "Check MAGIC_ACCOUNT_URL and your network connection."
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
function readToken(body) {
|
|
952
|
+
const b = body ?? {};
|
|
953
|
+
if (typeof b.access_token !== "string" || !b.access_token.startsWith(GRANT_PREFIX)) {
|
|
954
|
+
throw new MagicError("auth_invalid", "Account answered without a magic grant.", {
|
|
955
|
+
product: ACCOUNT_PRODUCT,
|
|
956
|
+
hint: "Check MAGIC_ACCOUNT_URL points at Magic Spells Account."
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
return {
|
|
960
|
+
token_type: "Bearer",
|
|
961
|
+
access_token: b.access_token,
|
|
962
|
+
expires_in: positive(b.expires_in, DEFAULT_GRANT_SECONDS),
|
|
963
|
+
grant_id: typeof b.grant_id === "string" ? b.grant_id : ""
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
function positive(v, fallback) {
|
|
967
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : fallback;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// ../kit/src/sessions.ts
|
|
971
|
+
var GRANT_ITEM = "account:grant";
|
|
972
|
+
var SESSIONS_ITEM = "account:sessions";
|
|
973
|
+
var GRANT_IDLE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
974
|
+
function sessionItem(product, handle) {
|
|
975
|
+
return `${product}:${handle}`;
|
|
976
|
+
}
|
|
977
|
+
function grantFromToken(token, accountUrl, now = /* @__PURE__ */ new Date()) {
|
|
978
|
+
return {
|
|
979
|
+
access_token: token.access_token,
|
|
980
|
+
grant_id: token.grant_id,
|
|
981
|
+
token_type: "Bearer",
|
|
982
|
+
obtained_at: now.toISOString(),
|
|
983
|
+
expires_at: new Date(now.getTime() + token.expires_in * 1e3).toISOString(),
|
|
984
|
+
last_used_at: now.toISOString(),
|
|
985
|
+
account_url: accountUrl
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function grantIdleExpiry(grant) {
|
|
989
|
+
const idle = Date.parse(grant.last_used_at) + GRANT_IDLE_MS;
|
|
990
|
+
const hard = Date.parse(grant.expires_at);
|
|
991
|
+
return new Date(Math.min(idle, hard)).toISOString();
|
|
992
|
+
}
|
|
993
|
+
function grantExpired(grant, now = Date.now()) {
|
|
994
|
+
return Date.parse(grantIdleExpiry(grant)) <= now;
|
|
995
|
+
}
|
|
996
|
+
async function readGrant(store) {
|
|
997
|
+
const raw = await store.get(GRANT_ITEM);
|
|
998
|
+
if (!raw) return null;
|
|
999
|
+
try {
|
|
1000
|
+
const g = JSON.parse(raw);
|
|
1001
|
+
if (typeof g.access_token !== "string" || !g.access_token.startsWith(GRANT_PREFIX)) return null;
|
|
1002
|
+
return g;
|
|
1003
|
+
} catch {
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
async function saveGrant(store, grant) {
|
|
1008
|
+
await store.set(GRANT_ITEM, JSON.stringify(grant));
|
|
1009
|
+
}
|
|
1010
|
+
async function readIndex(store) {
|
|
1011
|
+
const raw = await store.get(SESSIONS_ITEM);
|
|
1012
|
+
if (!raw) return {};
|
|
1013
|
+
try {
|
|
1014
|
+
const parsed = JSON.parse(raw);
|
|
1015
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1016
|
+
} catch {
|
|
1017
|
+
return {};
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
async function saveIndex(store, index) {
|
|
1021
|
+
if (Object.keys(index).length === 0) await store.delete(SESSIONS_ITEM);
|
|
1022
|
+
else await store.set(SESSIONS_ITEM, JSON.stringify(index));
|
|
1023
|
+
}
|
|
1024
|
+
async function readSession(store, product, handle) {
|
|
1025
|
+
const raw = await store.get(sessionItem(product, handle));
|
|
1026
|
+
if (!raw) return null;
|
|
1027
|
+
try {
|
|
1028
|
+
const s = JSON.parse(raw);
|
|
1029
|
+
return typeof s.key === "string" && s.key.length > 0 ? { ...s, product, key: s.key, source: "account" } : null;
|
|
1030
|
+
} catch {
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function sessionLabel(host = hostname()) {
|
|
1035
|
+
return `magic on ${host}`.slice(0, 80);
|
|
1036
|
+
}
|
|
1037
|
+
async function mintSessions(spec, opts) {
|
|
1038
|
+
const path = spec.sessions?.path;
|
|
1039
|
+
if (!path) throw new Error(`${spec.id} declares no sessions route`);
|
|
1040
|
+
const body = { label: opts.label ?? sessionLabel() };
|
|
1041
|
+
if (opts.workspace) body.workspace = opts.workspace;
|
|
1042
|
+
const res = await call(spec, opts, opts.apiUrl + path, {
|
|
1043
|
+
method: "POST",
|
|
1044
|
+
headers: {
|
|
1045
|
+
authorization: `Bearer ${opts.grant}`,
|
|
1046
|
+
"content-type": "application/json",
|
|
1047
|
+
accept: "application/json"
|
|
1048
|
+
},
|
|
1049
|
+
body: JSON.stringify(body)
|
|
1050
|
+
});
|
|
1051
|
+
if (!res.ok) throw sessionError(spec, res.status, res.body);
|
|
1052
|
+
return readSessions(spec, res.body);
|
|
1053
|
+
}
|
|
1054
|
+
async function revokeSessions(spec, opts) {
|
|
1055
|
+
const path = spec.sessions?.path;
|
|
1056
|
+
if (!path) throw new Error(`${spec.id} declares no sessions route`);
|
|
1057
|
+
const res = await call(spec, opts, opts.apiUrl + path, {
|
|
1058
|
+
method: "DELETE",
|
|
1059
|
+
headers: { authorization: `Bearer ${opts.key}`, accept: "application/json" }
|
|
1060
|
+
});
|
|
1061
|
+
if (!res.ok) {
|
|
1062
|
+
throw mapHttpError(res.status, res.body, { product: spec.id, title: spec.title });
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
async function call(spec, opts, url, init) {
|
|
1066
|
+
let res;
|
|
1067
|
+
try {
|
|
1068
|
+
res = await (opts.fetch ?? fetch)(url, {
|
|
1069
|
+
...init,
|
|
1070
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1071
|
+
});
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
const e = toMagicError(err, {
|
|
1074
|
+
product: spec.id,
|
|
1075
|
+
networkHint: `Check ${productEnvVar(spec.id, "URL")} and your network connection.`
|
|
1076
|
+
});
|
|
1077
|
+
const cause = err instanceof Error && err.cause instanceof Error ? `: ${err.cause.message}` : "";
|
|
1078
|
+
throw cause ? new MagicError(e.code, `${e.message}${cause}`, {
|
|
1079
|
+
product: e.product,
|
|
1080
|
+
...e.hint ? { hint: e.hint } : {}
|
|
1081
|
+
}) : e;
|
|
1082
|
+
}
|
|
1083
|
+
const text = await res.text().catch(() => "");
|
|
1084
|
+
let body = null;
|
|
1085
|
+
try {
|
|
1086
|
+
body = text ? JSON.parse(text) : null;
|
|
1087
|
+
} catch {
|
|
1088
|
+
body = null;
|
|
1089
|
+
}
|
|
1090
|
+
return { ok: res.ok, status: res.status, body };
|
|
1091
|
+
}
|
|
1092
|
+
function sessionError(spec, status, body) {
|
|
1093
|
+
const code = readStr(rec(rec(body).error).code);
|
|
1094
|
+
if (status === 401) {
|
|
1095
|
+
return new MagicError("not_logged_in", "Your Magic Spells sign-in is no longer valid.", {
|
|
1096
|
+
product: spec.id,
|
|
1097
|
+
hint: "Run `magic login`."
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
if (status === 403 && code === "no_workspaces") {
|
|
1101
|
+
return new MagicError("permission_denied", `You have no ${spec.title} workspaces.`, {
|
|
1102
|
+
product: spec.id,
|
|
1103
|
+
details: { reason: "no_workspaces" }
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
if (status === 404) {
|
|
1107
|
+
return new MagicError("not_found", `No ${spec.title} workspace by that handle.`, {
|
|
1108
|
+
product: spec.id,
|
|
1109
|
+
details: { kind: "workspace" }
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
return mapHttpError(status, body, { product: spec.id, title: spec.title });
|
|
1113
|
+
}
|
|
1114
|
+
function readSessions(spec, body) {
|
|
1115
|
+
const b = rec(body);
|
|
1116
|
+
const raw = Array.isArray(b.sessions) ? b.sessions : [];
|
|
1117
|
+
const sessions = [];
|
|
1118
|
+
for (const item of raw) {
|
|
1119
|
+
const s = rec(item);
|
|
1120
|
+
const key = readStr(s.key);
|
|
1121
|
+
const ws = rec(s.workspace);
|
|
1122
|
+
const handle = readStr(ws.handle);
|
|
1123
|
+
if (!key || !key.startsWith(spec.credential) || !handle) {
|
|
1124
|
+
throw new MagicError("unknown", `${spec.title} answered with a malformed session.`, {
|
|
1125
|
+
product: spec.id
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
sessions.push({
|
|
1129
|
+
product: spec.id,
|
|
1130
|
+
key,
|
|
1131
|
+
...readStr(s.key_id) ? { key_id: readStr(s.key_id) } : {},
|
|
1132
|
+
workspace: { id: readStr(ws.id) ?? "", handle, name: readStr(ws.name) ?? handle },
|
|
1133
|
+
...readStr(s.role) ? { role: readStr(s.role) } : {},
|
|
1134
|
+
...readStr(s.expires_at) ? { expires_at: readStr(s.expires_at) } : {},
|
|
1135
|
+
source: "account"
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
const workspaces = (Array.isArray(b.workspaces) ? b.workspaces : []).flatMap(
|
|
1139
|
+
(item) => {
|
|
1140
|
+
const w = rec(item);
|
|
1141
|
+
const handle = readStr(w.handle);
|
|
1142
|
+
if (!handle) return [];
|
|
1143
|
+
return [
|
|
1144
|
+
{
|
|
1145
|
+
id: readStr(w.id) ?? "",
|
|
1146
|
+
handle,
|
|
1147
|
+
name: readStr(w.name) ?? handle,
|
|
1148
|
+
role: readStr(w.role) ?? ""
|
|
1149
|
+
}
|
|
1150
|
+
];
|
|
1151
|
+
}
|
|
1152
|
+
);
|
|
1153
|
+
const u = rec(b.user);
|
|
1154
|
+
const email = readStr(u.email);
|
|
1155
|
+
const user = email ? { id: readStr(u.id) ?? "", display_name: readStr(u.display_name) ?? email, email } : void 0;
|
|
1156
|
+
return { sessions, workspaces, ...user ? { user } : {} };
|
|
1157
|
+
}
|
|
1158
|
+
async function storeSessions(store, product, res, now = /* @__PURE__ */ new Date()) {
|
|
1159
|
+
const index = await readIndex(store);
|
|
1160
|
+
const handles = new Set(res.sessions.map((s) => s.workspace.handle));
|
|
1161
|
+
for (const old of index[product]?.workspaces ?? []) {
|
|
1162
|
+
if (!handles.has(old.handle)) await store.delete(sessionItem(product, old.handle));
|
|
1163
|
+
}
|
|
1164
|
+
for (const s of res.sessions) {
|
|
1165
|
+
await store.set(sessionItem(product, s.workspace.handle), JSON.stringify(s));
|
|
1166
|
+
}
|
|
1167
|
+
const workspaces = res.sessions.map((s) => {
|
|
1168
|
+
const listed = res.workspaces.find((w) => w.handle === s.workspace.handle);
|
|
1169
|
+
return {
|
|
1170
|
+
id: s.workspace.id || listed?.id || "",
|
|
1171
|
+
handle: s.workspace.handle,
|
|
1172
|
+
name: s.workspace.name || listed?.name || s.workspace.handle,
|
|
1173
|
+
role: s.role ?? listed?.role ?? ""
|
|
1174
|
+
};
|
|
1175
|
+
});
|
|
1176
|
+
const entry = {
|
|
1177
|
+
workspaces,
|
|
1178
|
+
...workspaces.length === 1 ? { default: workspaces[0].handle } : {},
|
|
1179
|
+
minted_at: now.toISOString()
|
|
1180
|
+
};
|
|
1181
|
+
index[product] = entry;
|
|
1182
|
+
await saveIndex(store, index);
|
|
1183
|
+
return entry;
|
|
1184
|
+
}
|
|
1185
|
+
async function productSessions(store, product) {
|
|
1186
|
+
const entry = (await readIndex(store))[product];
|
|
1187
|
+
const out = [];
|
|
1188
|
+
for (const w of entry?.workspaces ?? []) {
|
|
1189
|
+
const s = await readSession(store, product, w.handle);
|
|
1190
|
+
if (s) out.push(s);
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1194
|
+
async function forgetSessions(store, product) {
|
|
1195
|
+
const index = await readIndex(store);
|
|
1196
|
+
const entry = index[product];
|
|
1197
|
+
if (!entry) return [];
|
|
1198
|
+
const items = entry.workspaces.map((w) => sessionItem(product, w.handle));
|
|
1199
|
+
for (const item of items) await store.delete(item);
|
|
1200
|
+
delete index[product];
|
|
1201
|
+
await saveIndex(store, index);
|
|
1202
|
+
return items;
|
|
1203
|
+
}
|
|
1204
|
+
async function touchGrant(store, grant) {
|
|
1205
|
+
const touched = { ...grant, last_used_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1206
|
+
await saveGrant(store, touched);
|
|
1207
|
+
return touched;
|
|
1208
|
+
}
|
|
1209
|
+
async function remintSession(spec, session, opts) {
|
|
1210
|
+
const grant = await readGrant(opts.store);
|
|
1211
|
+
const handle = session.workspace?.handle;
|
|
1212
|
+
if (!grant || !handle) {
|
|
1213
|
+
throw new MagicError("not_logged_in", `Your ${spec.title} session has expired.`, {
|
|
1214
|
+
product: spec.id,
|
|
1215
|
+
hint: "Run `magic login`."
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
const res = await mintSessions(spec, {
|
|
1219
|
+
apiUrl: opts.apiUrl,
|
|
1220
|
+
grant: grant.access_token,
|
|
1221
|
+
workspace: handle,
|
|
1222
|
+
...opts.fetch ? { fetch: opts.fetch } : {},
|
|
1223
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
1224
|
+
});
|
|
1225
|
+
const fresh = res.sessions.find((s) => s.workspace?.handle === handle);
|
|
1226
|
+
if (!fresh) {
|
|
1227
|
+
throw new MagicError(
|
|
1228
|
+
"not_found",
|
|
1229
|
+
`You no longer reach the ${spec.title} workspace "${handle}".`,
|
|
1230
|
+
{
|
|
1231
|
+
product: spec.id,
|
|
1232
|
+
details: { kind: "workspace" },
|
|
1233
|
+
hint: "Run `magic login` to refresh your workspaces."
|
|
1234
|
+
}
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
await opts.store.set(sessionItem(spec.id, handle), JSON.stringify(fresh));
|
|
1238
|
+
await touchGrant(opts.store, grant);
|
|
1239
|
+
return fresh;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
697
1242
|
// ../kit/src/credentials.ts
|
|
698
1243
|
function manualItem(product) {
|
|
699
1244
|
return `${product}:manual`;
|
|
@@ -719,7 +1264,8 @@ function parseStoredSession(product, value) {
|
|
|
719
1264
|
}
|
|
720
1265
|
function loginHint(spec) {
|
|
721
1266
|
const where = spec.keyHelp ? ` (${spec.keyHelp})` : "";
|
|
722
|
-
|
|
1267
|
+
const manual = `${productEnvVar(spec.id, "KEY")} or run \`magic login --product-key\` with a ${spec.credential}\u2026 key${where}.`;
|
|
1268
|
+
return spec.sessions ? `Run \`magic login\`, or set ${manual}` : `Set ${manual}`;
|
|
723
1269
|
}
|
|
724
1270
|
function checkPrefix(spec, key) {
|
|
725
1271
|
if (!key.startsWith(spec.credential)) {
|
|
@@ -740,16 +1286,40 @@ async function resolveCredential(spec, opts) {
|
|
|
740
1286
|
checkPrefix(spec, envKey);
|
|
741
1287
|
return { product: spec.id, key: envKey, source: "env" };
|
|
742
1288
|
}
|
|
1289
|
+
const minted = (await readIndex(opts.store))[spec.id];
|
|
1290
|
+
const handle = opts.workspace ?? minted?.default;
|
|
1291
|
+
if (minted && handle) {
|
|
1292
|
+
const account = await readSession(opts.store, spec.id, handle);
|
|
1293
|
+
if (account) {
|
|
1294
|
+
checkPrefix(spec, account.key);
|
|
1295
|
+
return account;
|
|
1296
|
+
}
|
|
1297
|
+
if (opts.workspace) {
|
|
1298
|
+
throw new MagicError("not_found", `No ${spec.title} session for workspace "${handle}".`, {
|
|
1299
|
+
product: spec.id,
|
|
1300
|
+
details: { kind: "workspace" },
|
|
1301
|
+
candidates: minted.workspaces.map((w) => w.handle),
|
|
1302
|
+
hint: "Pick one of the listed workspaces, or run `magic login` to refresh them."
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
743
1306
|
const stored = await opts.store.get(manualItem(spec.id));
|
|
744
1307
|
const session = stored ? parseStoredSession(spec.id, stored) : null;
|
|
745
|
-
if (
|
|
746
|
-
|
|
1308
|
+
if (session) {
|
|
1309
|
+
checkPrefix(spec, session.key);
|
|
1310
|
+
return session;
|
|
1311
|
+
}
|
|
1312
|
+
if (minted && minted.workspaces.length > 1) {
|
|
1313
|
+
throw new MagicError("usage", `You have several ${spec.title} workspaces; choose one.`, {
|
|
747
1314
|
product: spec.id,
|
|
748
|
-
|
|
1315
|
+
candidates: minted.workspaces.map((w) => w.handle),
|
|
1316
|
+
hint: "Pass --workspace <handle>, or set defaultWorkspace in the config file."
|
|
749
1317
|
});
|
|
750
1318
|
}
|
|
751
|
-
|
|
752
|
-
|
|
1319
|
+
throw new MagicError("not_logged_in", `No ${spec.title} key found.`, {
|
|
1320
|
+
product: spec.id,
|
|
1321
|
+
hint: loginHint(spec)
|
|
1322
|
+
});
|
|
753
1323
|
}
|
|
754
1324
|
async function saveProductKey(spec, store, key) {
|
|
755
1325
|
const trimmed = key.trim();
|
|
@@ -772,7 +1342,12 @@ function productConfig(product, deps, extra = {}) {
|
|
|
772
1342
|
});
|
|
773
1343
|
}
|
|
774
1344
|
async function buildContext(product, config, deps, requiresSession = true) {
|
|
775
|
-
const
|
|
1345
|
+
const workspace = config.defaultWorkspace[product.id];
|
|
1346
|
+
const session = requiresSession ? await resolveCredential(product, {
|
|
1347
|
+
env: deps.env ?? process.env,
|
|
1348
|
+
store: deps.store,
|
|
1349
|
+
...workspace ? { workspace } : {}
|
|
1350
|
+
}) : null;
|
|
776
1351
|
return contextFor(product, config, session, deps.signal);
|
|
777
1352
|
}
|
|
778
1353
|
function contextFor(product, config, session, signal = new AbortController().signal) {
|
|
@@ -783,6 +1358,27 @@ function contextFor(product, config, session, signal = new AbortController().sig
|
|
|
783
1358
|
signal
|
|
784
1359
|
};
|
|
785
1360
|
}
|
|
1361
|
+
function remintFor(product, store) {
|
|
1362
|
+
return async (ctx) => {
|
|
1363
|
+
if (ctx.session?.source !== "account" || !product.sessions) return null;
|
|
1364
|
+
const fresh = await remintSession(product, ctx.session, {
|
|
1365
|
+
store,
|
|
1366
|
+
apiUrl: ctx.config.products[product.id].apiUrl,
|
|
1367
|
+
signal: ctx.signal
|
|
1368
|
+
});
|
|
1369
|
+
return contextFor(product, ctx.config, fresh, ctx.signal);
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
async function runWithRemint(op, input, ctx, remint) {
|
|
1373
|
+
try {
|
|
1374
|
+
return await op.run(input, ctx);
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
if (!remint || !isMagicError(err) || err.code !== "auth_expired") throw err;
|
|
1377
|
+
const fresh = await remint(ctx);
|
|
1378
|
+
if (!fresh) throw err;
|
|
1379
|
+
return op.run(input, fresh);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
786
1382
|
|
|
787
1383
|
// ../kit/src/cli.ts
|
|
788
1384
|
async function runProductCli(product, argv, deps) {
|
|
@@ -840,7 +1436,7 @@ async function runProductCli(product, argv, deps) {
|
|
|
840
1436
|
if (!config.allowDestructive) throw destructiveDisabled(product.id);
|
|
841
1437
|
}
|
|
842
1438
|
const ctx = await buildContext(product, config, deps, op.meta?.requiresSession !== false);
|
|
843
|
-
const out = await op
|
|
1439
|
+
const out = await runWithRemint(op, input, ctx, remintFor(product, deps.store));
|
|
844
1440
|
render(out, opts, io);
|
|
845
1441
|
return 0;
|
|
846
1442
|
} catch (err) {
|
|
@@ -864,11 +1460,8 @@ function buildInput(cmd, parsed, productFlags, opts) {
|
|
|
864
1460
|
for (const [key, value] of Object.entries(productFlags)) {
|
|
865
1461
|
if (has(key) && raw[key] === void 0) raw[key] = value;
|
|
866
1462
|
}
|
|
867
|
-
if (opts.workspace) {
|
|
868
|
-
|
|
869
|
-
throw usage(`--workspace is not supported by ${cmd.words.join(" ")}`);
|
|
870
|
-
}
|
|
871
|
-
if (raw.workspace === void 0) raw.workspace = opts.workspace;
|
|
1463
|
+
if (opts.workspace && has("workspace") && raw.workspace === void 0) {
|
|
1464
|
+
raw.workspace = opts.workspace;
|
|
872
1465
|
}
|
|
873
1466
|
const result = cmd.op.input.safeParse(coerce(raw, fields));
|
|
874
1467
|
if (!result.success) {
|
|
@@ -926,17 +1519,6 @@ function summaryOf(op) {
|
|
|
926
1519
|
return op.meta?.destructive ? `${op.summary} [destructive]` : op.summary;
|
|
927
1520
|
}
|
|
928
1521
|
|
|
929
|
-
// ../kit/src/read.ts
|
|
930
|
-
function rec(v) {
|
|
931
|
-
return v && typeof v === "object" ? v : {};
|
|
932
|
-
}
|
|
933
|
-
function readStr(v) {
|
|
934
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
935
|
-
}
|
|
936
|
-
function readField(obj, key) {
|
|
937
|
-
return obj && typeof obj === "object" && key in obj ? obj[key] : void 0;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
1522
|
// ../kit/src/doctor.ts
|
|
941
1523
|
async function runDoctorChecks(checks, ctx, product = "magic") {
|
|
942
1524
|
const results = [];
|
|
@@ -957,6 +1539,102 @@ async function runDoctorChecks(checks, ctx, product = "magic") {
|
|
|
957
1539
|
return results;
|
|
958
1540
|
}
|
|
959
1541
|
|
|
1542
|
+
// ../kit/src/loopback.ts
|
|
1543
|
+
import { timingSafeEqual } from "node:crypto";
|
|
1544
|
+
import { createServer } from "node:http";
|
|
1545
|
+
var LOOPBACK_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
1546
|
+
var CODE_EXCHANGE_TIMEOUT_MS = 30 * 1e3;
|
|
1547
|
+
async function startLoopback(opts) {
|
|
1548
|
+
let settle;
|
|
1549
|
+
const result = new Promise((resolve, reject) => settle = { resolve, reject });
|
|
1550
|
+
result.catch(() => {
|
|
1551
|
+
});
|
|
1552
|
+
let done = false;
|
|
1553
|
+
let exchanging = false;
|
|
1554
|
+
const server = createServer((req, res) => void handle(req, res));
|
|
1555
|
+
const finish = (outcome) => {
|
|
1556
|
+
if (done) return;
|
|
1557
|
+
done = true;
|
|
1558
|
+
clearTimeout(timer);
|
|
1559
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
1560
|
+
server.close();
|
|
1561
|
+
server.closeIdleConnections();
|
|
1562
|
+
if ("value" in outcome) settle.resolve(outcome.value);
|
|
1563
|
+
else settle.reject(outcome.error);
|
|
1564
|
+
};
|
|
1565
|
+
const handle = async (req, res) => {
|
|
1566
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1567
|
+
const state = url.searchParams.get("state");
|
|
1568
|
+
if (done || exchanging || req.method !== "GET" || url.pathname !== "/callback") {
|
|
1569
|
+
return reply(res, 400, "Bad request.");
|
|
1570
|
+
}
|
|
1571
|
+
if (!sameState(state, opts.state)) return reply(res, 400, "Bad request.");
|
|
1572
|
+
const error = url.searchParams.get("error");
|
|
1573
|
+
if (error) {
|
|
1574
|
+
reply(res, 200, "Sign-in cancelled. You can close this tab and return to your terminal.");
|
|
1575
|
+
return finish({
|
|
1576
|
+
error: error === "access_denied" ? cancelledError() : new MagicError("auth_invalid", `Account refused the sign-in (${error}).`, {
|
|
1577
|
+
product: ACCOUNT_PRODUCT
|
|
1578
|
+
})
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
const code = url.searchParams.get("code");
|
|
1582
|
+
if (!code) return reply(res, 400, "Bad request.");
|
|
1583
|
+
exchanging = true;
|
|
1584
|
+
clearTimeout(timer);
|
|
1585
|
+
const signals = [AbortSignal.timeout(CODE_EXCHANGE_TIMEOUT_MS)];
|
|
1586
|
+
if (opts.signal) signals.push(opts.signal);
|
|
1587
|
+
try {
|
|
1588
|
+
const value = await opts.exchange(code, redirectUri, AbortSignal.any(signals));
|
|
1589
|
+
reply(res, 200, "Signed in to Magic Spells. You can close this tab.");
|
|
1590
|
+
finish({ value });
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
reply(res, 400, "Sign-in failed. Return to your terminal for details.");
|
|
1593
|
+
finish({ error: toMagicError(err, { product: ACCOUNT_PRODUCT }) });
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
const onAbort = () => finish({ error: cancelledError() });
|
|
1597
|
+
const timer = setTimeout(
|
|
1598
|
+
() => finish({
|
|
1599
|
+
error: new MagicError("auth_expired", "Sign-in timed out waiting for the browser.", {
|
|
1600
|
+
product: ACCOUNT_PRODUCT,
|
|
1601
|
+
hint: "Run `magic login --device` to sign in with a code instead."
|
|
1602
|
+
})
|
|
1603
|
+
}),
|
|
1604
|
+
opts.timeoutMs ?? LOOPBACK_TIMEOUT_MS
|
|
1605
|
+
);
|
|
1606
|
+
timer.unref?.();
|
|
1607
|
+
await new Promise((resolve, reject) => {
|
|
1608
|
+
server.once("error", reject);
|
|
1609
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1610
|
+
server.off("error", reject);
|
|
1611
|
+
resolve();
|
|
1612
|
+
});
|
|
1613
|
+
}).catch((err) => {
|
|
1614
|
+
clearTimeout(timer);
|
|
1615
|
+
throw toMagicError(err, { product: ACCOUNT_PRODUCT });
|
|
1616
|
+
});
|
|
1617
|
+
const redirectUri = `http://127.0.0.1:${server.address().port}/callback`;
|
|
1618
|
+
if (opts.signal?.aborted) onAbort();
|
|
1619
|
+
else opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1620
|
+
return { redirectUri, result };
|
|
1621
|
+
}
|
|
1622
|
+
function sameState(got, want) {
|
|
1623
|
+
if (got === null) return false;
|
|
1624
|
+
const a = Buffer.from(got);
|
|
1625
|
+
const b = Buffer.from(want);
|
|
1626
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
1627
|
+
}
|
|
1628
|
+
function reply(res, status, text) {
|
|
1629
|
+
res.writeHead(status, {
|
|
1630
|
+
"content-type": "text/plain; charset=utf-8",
|
|
1631
|
+
"cache-control": "no-store",
|
|
1632
|
+
connection: "close"
|
|
1633
|
+
});
|
|
1634
|
+
res.end(`${text}
|
|
1635
|
+
`);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
960
1638
|
// ../kit/src/mcp.ts
|
|
961
1639
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
962
1640
|
import { z } from "zod";
|
|
@@ -988,7 +1666,7 @@ ${text}` : text];
|
|
|
988
1666
|
}
|
|
989
1667
|
function registerProduct(server, p) {
|
|
990
1668
|
for (const op of p.operations) {
|
|
991
|
-
if (onSurface(op, "mcp")) registerTool(server, p.module.id, op, p.context);
|
|
1669
|
+
if (onSurface(op, "mcp")) registerTool(server, p.module.id, op, p.context, p.remint);
|
|
992
1670
|
}
|
|
993
1671
|
p.module.mcp?.register?.(server, p.context);
|
|
994
1672
|
}
|
|
@@ -1000,7 +1678,7 @@ function toolInputShape(op) {
|
|
|
1000
1678
|
yes: z.boolean().optional().describe("Confirm this destructive action (also needs MAGIC_ALLOW_DESTRUCTIVE=1).")
|
|
1001
1679
|
};
|
|
1002
1680
|
}
|
|
1003
|
-
async function callTool(product, op, context, args) {
|
|
1681
|
+
async function callTool(product, op, context, args, remint) {
|
|
1004
1682
|
try {
|
|
1005
1683
|
const ctx = await context();
|
|
1006
1684
|
const { yes, ...rest } = args ?? {};
|
|
@@ -1015,7 +1693,7 @@ async function callTool(product, op, context, args) {
|
|
|
1015
1693
|
}
|
|
1016
1694
|
const parsed = op.input.safeParse(op.meta?.destructive ? rest : args ?? {});
|
|
1017
1695
|
if (!parsed.success) throw invalidInput(product, parsed.error);
|
|
1018
|
-
const result = await op
|
|
1696
|
+
const result = await runWithRemint(op, parsed.data, ctx, remint);
|
|
1019
1697
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1020
1698
|
} catch (err) {
|
|
1021
1699
|
return toToolError(toMagicError(err, { product }));
|
|
@@ -1030,14 +1708,14 @@ function invalidInput(product, error) {
|
|
|
1030
1708
|
...field ? { details: { field } } : {}
|
|
1031
1709
|
});
|
|
1032
1710
|
}
|
|
1033
|
-
function registerTool(server, product, op, context) {
|
|
1711
|
+
function registerTool(server, product, op, context, remint) {
|
|
1034
1712
|
const description = op.detail ? `${op.summary}
|
|
1035
1713
|
|
|
1036
1714
|
${op.detail}` : op.summary;
|
|
1037
1715
|
server.registerTool(
|
|
1038
1716
|
toolName(op),
|
|
1039
1717
|
{ description, inputSchema: toolInputShape(op) },
|
|
1040
|
-
async (args) => callTool(product, op, context, args)
|
|
1718
|
+
async (args) => callTool(product, op, context, args, remint)
|
|
1041
1719
|
);
|
|
1042
1720
|
}
|
|
1043
1721
|
|
|
@@ -1095,12 +1773,35 @@ function readPackageVersion(moduleUrl) {
|
|
|
1095
1773
|
|
|
1096
1774
|
export {
|
|
1097
1775
|
MagicError,
|
|
1776
|
+
isMagicError,
|
|
1098
1777
|
exitCodeFor,
|
|
1099
1778
|
mapHttpError,
|
|
1100
1779
|
toMagicError,
|
|
1101
1780
|
productEnvVar,
|
|
1102
1781
|
configFilePath,
|
|
1103
1782
|
readConfigFile,
|
|
1783
|
+
loadCliConfig,
|
|
1784
|
+
createPkce,
|
|
1785
|
+
randomState,
|
|
1786
|
+
authorizeUrl,
|
|
1787
|
+
AccountClient,
|
|
1788
|
+
waitForDevice,
|
|
1789
|
+
rec,
|
|
1790
|
+
readStr,
|
|
1791
|
+
readField,
|
|
1792
|
+
GRANT_ITEM,
|
|
1793
|
+
sessionItem,
|
|
1794
|
+
grantFromToken,
|
|
1795
|
+
grantIdleExpiry,
|
|
1796
|
+
grantExpired,
|
|
1797
|
+
readGrant,
|
|
1798
|
+
saveGrant,
|
|
1799
|
+
readIndex,
|
|
1800
|
+
mintSessions,
|
|
1801
|
+
revokeSessions,
|
|
1802
|
+
storeSessions,
|
|
1803
|
+
productSessions,
|
|
1804
|
+
forgetSessions,
|
|
1104
1805
|
manualItem,
|
|
1105
1806
|
maskKey,
|
|
1106
1807
|
resolveCredential,
|
|
@@ -1117,12 +1818,12 @@ export {
|
|
|
1117
1818
|
productConfig,
|
|
1118
1819
|
buildContext,
|
|
1119
1820
|
contextFor,
|
|
1821
|
+
remintFor,
|
|
1822
|
+
runWithRemint,
|
|
1120
1823
|
runDoctorChecks,
|
|
1824
|
+
startLoopback,
|
|
1121
1825
|
runProductCli,
|
|
1122
1826
|
createMcpServer,
|
|
1123
1827
|
TtlCache,
|
|
1124
|
-
rec,
|
|
1125
|
-
readStr,
|
|
1126
|
-
readField,
|
|
1127
1828
|
readPackageVersion
|
|
1128
1829
|
};
|