magic-spells 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +90 -2
- package/bin/magic.js +12 -0
- package/dist/chunk-JZ7XESF4.js +1829 -0
- package/dist/chunk-PQFOO5A7.js +1057 -0
- package/dist/main.js +1111 -0
- package/dist/operations-GGMLIYEW.js +9 -0
- package/dist/src-DJYPPJFM.js +16 -0
- package/dist/src-KJGFPRQP.js +17 -0
- package/dist/src-L2I546M7.js +1071 -0
- package/package.json +33 -5
|
@@ -0,0 +1,1829 @@
|
|
|
1
|
+
// ../kit/src/errors.ts
|
|
2
|
+
var SHELL_PRODUCT = "magic";
|
|
3
|
+
var MagicError = class _MagicError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
product;
|
|
6
|
+
hint;
|
|
7
|
+
candidates;
|
|
8
|
+
details;
|
|
9
|
+
constructor(code, message, opts = {}) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "MagicError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.product = opts.product ?? SHELL_PRODUCT;
|
|
14
|
+
if (opts.hint !== void 0) this.hint = opts.hint;
|
|
15
|
+
if (opts.candidates !== void 0) this.candidates = opts.candidates;
|
|
16
|
+
if (opts.details !== void 0) this.details = opts.details;
|
|
17
|
+
Object.setPrototypeOf(this, _MagicError.prototype);
|
|
18
|
+
}
|
|
19
|
+
/** The inner envelope: what `{ error: … }` wraps on stderr and in MCP results. */
|
|
20
|
+
toJSON() {
|
|
21
|
+
const out = { code: this.code, message: this.message, product: this.product };
|
|
22
|
+
if (this.hint !== void 0) out.hint = this.hint;
|
|
23
|
+
if (this.candidates !== void 0) out.candidates = this.candidates;
|
|
24
|
+
if (this.details !== void 0) out.details = this.details;
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function isMagicError(e) {
|
|
29
|
+
return e instanceof MagicError;
|
|
30
|
+
}
|
|
31
|
+
function exitCodeFor(code) {
|
|
32
|
+
switch (code) {
|
|
33
|
+
case "usage":
|
|
34
|
+
return 2;
|
|
35
|
+
case "not_logged_in":
|
|
36
|
+
case "auth_invalid":
|
|
37
|
+
case "auth_expired":
|
|
38
|
+
case "auth_cancelled":
|
|
39
|
+
return 3;
|
|
40
|
+
case "permission_denied":
|
|
41
|
+
return 4;
|
|
42
|
+
case "not_found":
|
|
43
|
+
return 5;
|
|
44
|
+
case "ambiguous_name":
|
|
45
|
+
return 6;
|
|
46
|
+
case "validation_failed":
|
|
47
|
+
case "conflict":
|
|
48
|
+
case "diagnostics":
|
|
49
|
+
return 7;
|
|
50
|
+
case "destructive_action_disabled":
|
|
51
|
+
return 8;
|
|
52
|
+
case "rate_limited":
|
|
53
|
+
return 9;
|
|
54
|
+
case "network":
|
|
55
|
+
return 10;
|
|
56
|
+
case "product_unavailable":
|
|
57
|
+
return 11;
|
|
58
|
+
case "unknown":
|
|
59
|
+
return 1;
|
|
60
|
+
default: {
|
|
61
|
+
const never = code;
|
|
62
|
+
void never;
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function toToolError(e) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: "text", text: JSON.stringify({ error: e.toJSON() }) }],
|
|
70
|
+
isError: true
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function readEnvelope(body) {
|
|
74
|
+
if (!body || typeof body !== "object" || !("error" in body)) return {};
|
|
75
|
+
const err = body.error;
|
|
76
|
+
if (!err || typeof err !== "object") return {};
|
|
77
|
+
const e = err;
|
|
78
|
+
const rawField = e.details && typeof e.details === "object" ? e.details.field : void 0;
|
|
79
|
+
return {
|
|
80
|
+
code: typeof e.code === "string" ? e.code : void 0,
|
|
81
|
+
message: typeof e.message === "string" ? e.message : void 0,
|
|
82
|
+
field: typeof rawField === "string" && rawField.length > 0 ? rawField : void 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function mapHttpError(status, body, opts) {
|
|
86
|
+
const env = readEnvelope(body);
|
|
87
|
+
const product = opts.product;
|
|
88
|
+
const message = env.message ?? `${opts.title ?? product} request failed (HTTP ${status}).`;
|
|
89
|
+
if (status === 401) {
|
|
90
|
+
const expired = `${env.code ?? ""} ${env.message ?? ""}`.toLowerCase().includes("expired");
|
|
91
|
+
return new MagicError(expired ? "auth_expired" : "auth_invalid", message, {
|
|
92
|
+
product,
|
|
93
|
+
...opts.authHint ? { hint: opts.authHint } : {}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (status === 403) return new MagicError("permission_denied", message, { product });
|
|
97
|
+
if (status === 404) {
|
|
98
|
+
const hay = `${env.code ?? ""} ${env.message ?? ""}`.toLowerCase();
|
|
99
|
+
const kind = opts.notFoundKinds?.find((k) => hay.includes(k));
|
|
100
|
+
return new MagicError("not_found", message, {
|
|
101
|
+
product,
|
|
102
|
+
...kind ? { details: { kind } } : {}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (status === 400 || status === 422) {
|
|
106
|
+
return env.field === void 0 ? new MagicError("validation_failed", message, { product }) : new MagicError("validation_failed", message, {
|
|
107
|
+
product,
|
|
108
|
+
hint: `The server rejected the \`${env.field}\` field.`,
|
|
109
|
+
details: { field: env.field }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (status === 409) {
|
|
113
|
+
return new MagicError("conflict", message, {
|
|
114
|
+
product,
|
|
115
|
+
hint: "The resource changed since you last read it \u2014 reread it and try again."
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (status === 429) {
|
|
119
|
+
return new MagicError("rate_limited", message, { product, hint: "Slow down and retry." });
|
|
120
|
+
}
|
|
121
|
+
return new MagicError("unknown", message, { product });
|
|
122
|
+
}
|
|
123
|
+
function toMagicError(e, opts = {}) {
|
|
124
|
+
if (e instanceof MagicError) return e;
|
|
125
|
+
const product = opts.product ?? SHELL_PRODUCT;
|
|
126
|
+
if (e instanceof Error) {
|
|
127
|
+
if (isNetworkError(e)) {
|
|
128
|
+
return new MagicError("network", e.message, {
|
|
129
|
+
product,
|
|
130
|
+
hint: opts.networkHint ?? "Check your network connection."
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return new MagicError("unknown", e.message, { product });
|
|
134
|
+
}
|
|
135
|
+
return new MagicError("unknown", typeof e === "string" ? e : "Unknown error.", { product });
|
|
136
|
+
}
|
|
137
|
+
function isNetworkError(e) {
|
|
138
|
+
const code = e.code ?? "";
|
|
139
|
+
if ([
|
|
140
|
+
"ECONNREFUSED",
|
|
141
|
+
"ENOTFOUND",
|
|
142
|
+
"ECONNRESET",
|
|
143
|
+
"ETIMEDOUT",
|
|
144
|
+
"EAI_AGAIN",
|
|
145
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
146
|
+
"UND_ERR_SOCKET",
|
|
147
|
+
"UND_ERR_HEADERS_TIMEOUT"
|
|
148
|
+
].includes(code)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (e.name === "AbortError" || e.name === "TimeoutError") return true;
|
|
152
|
+
return /fetch failed|network|socket|connect/i.test(e.message ?? "");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ../kit/src/config.ts
|
|
156
|
+
import { readFileSync } from "node:fs";
|
|
157
|
+
import { join as join2 } from "node:path";
|
|
158
|
+
|
|
159
|
+
// ../kit/src/paths.ts
|
|
160
|
+
import { homedir } from "node:os";
|
|
161
|
+
import { join } from "node:path";
|
|
162
|
+
function configDir(opts = {}) {
|
|
163
|
+
const platform = opts.platform ?? process.platform;
|
|
164
|
+
const env = opts.env ?? process.env;
|
|
165
|
+
const home = opts.home ?? homedir();
|
|
166
|
+
if (platform === "win32") return join(env.APPDATA ?? join(home, "AppData", "Roaming"), "magic");
|
|
167
|
+
return join(env.XDG_CONFIG_HOME || join(home, ".config"), "magic");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ../kit/src/config.ts
|
|
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";
|
|
174
|
+
function productEnvVar(id, suffix) {
|
|
175
|
+
return `MAGIC_${id.toUpperCase().replace(/-/g, "_")}_${suffix}`;
|
|
176
|
+
}
|
|
177
|
+
function configFilePath(opts = {}) {
|
|
178
|
+
return join2(configDir(opts), "config.json");
|
|
179
|
+
}
|
|
180
|
+
function readConfigFile(path = configFilePath(), read = (p) => readFileSync(p, "utf8")) {
|
|
181
|
+
try {
|
|
182
|
+
const parsed = JSON.parse(read(path));
|
|
183
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function loadCliConfig(opts) {
|
|
189
|
+
const env = opts.env ?? process.env;
|
|
190
|
+
const file = opts.file ?? null;
|
|
191
|
+
const products = {};
|
|
192
|
+
const defaultWorkspace = {};
|
|
193
|
+
for (const p of opts.products) {
|
|
194
|
+
const envVar = productEnvVar(p.id, "URL");
|
|
195
|
+
products[p.id] = {
|
|
196
|
+
apiUrl: normalizeUrl(env[envVar] || file?.products?.[p.id]?.apiUrl || p.apiUrl, envVar)
|
|
197
|
+
};
|
|
198
|
+
const ws = opts.workspace ?? file?.defaultWorkspace?.[p.id];
|
|
199
|
+
if (ws) defaultWorkspace[p.id] = ws;
|
|
200
|
+
}
|
|
201
|
+
const fileTelemetry = file?.telemetry;
|
|
202
|
+
return {
|
|
203
|
+
accountUrl: normalizeUrl(
|
|
204
|
+
env.MAGIC_ACCOUNT_URL || file?.accountUrl || DEFAULT_ACCOUNT_URL,
|
|
205
|
+
"MAGIC_ACCOUNT_URL"
|
|
206
|
+
),
|
|
207
|
+
accountClientId: env.MAGIC_ACCOUNT_CLIENT_ID?.trim() || DEFAULT_ACCOUNT_CLIENT_ID,
|
|
208
|
+
products,
|
|
209
|
+
defaultWorkspace,
|
|
210
|
+
allowDestructive: env.MAGIC_ALLOW_DESTRUCTIVE === "1",
|
|
211
|
+
telemetry: fileTelemetry === "on" || fileTelemetry === "off" ? fileTelemetry : "unset",
|
|
212
|
+
updateCheck: !env.CI && !env.MAGIC_NO_UPDATE_CHECK,
|
|
213
|
+
output: {
|
|
214
|
+
json: opts.output?.json ?? false,
|
|
215
|
+
color: opts.output?.color ?? !env.NO_COLOR,
|
|
216
|
+
quiet: opts.output?.quiet ?? false
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function normalizeUrl(value, source) {
|
|
221
|
+
const trimmed = value.endsWith("/") ? value.slice(0, -1) : value;
|
|
222
|
+
try {
|
|
223
|
+
new URL(trimmed);
|
|
224
|
+
} catch {
|
|
225
|
+
throw new MagicError("usage", `${source} is not a valid URL: "${value}".`);
|
|
226
|
+
}
|
|
227
|
+
return trimmed;
|
|
228
|
+
}
|
|
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
|
+
|
|
241
|
+
// ../kit/src/keychain.ts
|
|
242
|
+
import { execFileSync } from "node:child_process";
|
|
243
|
+
import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
244
|
+
import { dirname, join as join3 } from "node:path";
|
|
245
|
+
var KEYCHAIN_SERVICE = "io.magicspells.cli";
|
|
246
|
+
var SECURITY = "/usr/bin/security";
|
|
247
|
+
function credentialsFilePath(opts = {}) {
|
|
248
|
+
return join3(configDir(opts), "credentials.json");
|
|
249
|
+
}
|
|
250
|
+
var realExec = (file, args) => execFileSync(file, args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
251
|
+
function createCredentialStore(opts = {}) {
|
|
252
|
+
const platform = opts.platform ?? process.platform;
|
|
253
|
+
const env = opts.env ?? process.env;
|
|
254
|
+
const chosen = env.MAGIC_CREDENTIALS_FILE === "1";
|
|
255
|
+
const warn = chosen ? () => {
|
|
256
|
+
} : opts.warn ?? defaultWarn;
|
|
257
|
+
const file = createFileStore(credentialsFilePath(opts), warn);
|
|
258
|
+
if (platform !== "darwin" || chosen) return file;
|
|
259
|
+
return createMacStore(opts.exec ?? realExec, file);
|
|
260
|
+
}
|
|
261
|
+
function defaultWarn(line2) {
|
|
262
|
+
process.stderr.write(`${line2}
|
|
263
|
+
`);
|
|
264
|
+
}
|
|
265
|
+
function createFileStore(path, warn) {
|
|
266
|
+
let warned = false;
|
|
267
|
+
const read = () => {
|
|
268
|
+
try {
|
|
269
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
270
|
+
return { items: parsed.items && typeof parsed.items === "object" ? parsed.items : {} };
|
|
271
|
+
} catch {
|
|
272
|
+
return { items: {} };
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const write = (data) => {
|
|
276
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
277
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}
|
|
278
|
+
`, { mode: 384 });
|
|
279
|
+
chmodSync(path, 384);
|
|
280
|
+
};
|
|
281
|
+
const value = (item) => {
|
|
282
|
+
const v = read().items[item];
|
|
283
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
284
|
+
};
|
|
285
|
+
return {
|
|
286
|
+
backend: "file",
|
|
287
|
+
async get(item) {
|
|
288
|
+
return value(item);
|
|
289
|
+
},
|
|
290
|
+
async locate(item) {
|
|
291
|
+
return value(item) === null ? null : "file";
|
|
292
|
+
},
|
|
293
|
+
async set(item, secret) {
|
|
294
|
+
if (!warned) {
|
|
295
|
+
warn(`warning: storing credentials in ${path} (mode 0600), not the OS keychain.`);
|
|
296
|
+
warned = true;
|
|
297
|
+
}
|
|
298
|
+
const data = read();
|
|
299
|
+
data.items[item] = secret;
|
|
300
|
+
write(data);
|
|
301
|
+
},
|
|
302
|
+
async delete(item) {
|
|
303
|
+
const data = read();
|
|
304
|
+
if (!(item in data.items)) return;
|
|
305
|
+
delete data.items[item];
|
|
306
|
+
write(data);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function createMacStore(exec, file) {
|
|
311
|
+
const fromKeychain = (item) => {
|
|
312
|
+
try {
|
|
313
|
+
const v = exec(SECURITY, [
|
|
314
|
+
"find-generic-password",
|
|
315
|
+
"-s",
|
|
316
|
+
KEYCHAIN_SERVICE,
|
|
317
|
+
"-a",
|
|
318
|
+
item,
|
|
319
|
+
"-w"
|
|
320
|
+
]).trim();
|
|
321
|
+
return v.length > 0 ? v : null;
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
return {
|
|
327
|
+
backend: "keyring",
|
|
328
|
+
async get(item) {
|
|
329
|
+
return fromKeychain(item) ?? file.get(item);
|
|
330
|
+
},
|
|
331
|
+
async locate(item) {
|
|
332
|
+
return fromKeychain(item) !== null ? "keyring" : file.locate(item);
|
|
333
|
+
},
|
|
334
|
+
async set(item, secret) {
|
|
335
|
+
try {
|
|
336
|
+
exec(SECURITY, [
|
|
337
|
+
"add-generic-password",
|
|
338
|
+
"-U",
|
|
339
|
+
"-s",
|
|
340
|
+
KEYCHAIN_SERVICE,
|
|
341
|
+
"-a",
|
|
342
|
+
item,
|
|
343
|
+
"-w",
|
|
344
|
+
secret
|
|
345
|
+
]);
|
|
346
|
+
} catch {
|
|
347
|
+
await file.set(item, secret);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
async delete(item) {
|
|
351
|
+
try {
|
|
352
|
+
exec(SECURITY, ["delete-generic-password", "-s", KEYCHAIN_SERVICE, "-a", item]);
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
await file.delete(item);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ../kit/src/render.ts
|
|
361
|
+
var processIO = {
|
|
362
|
+
out: (text) => void process.stdout.write(text),
|
|
363
|
+
err: (text) => void process.stderr.write(text),
|
|
364
|
+
isTTY: () => process.stdout.isTTY === true
|
|
365
|
+
};
|
|
366
|
+
function line(write, text) {
|
|
367
|
+
write(text.endsWith("\n") ? text : `${text}
|
|
368
|
+
`);
|
|
369
|
+
}
|
|
370
|
+
function jsonMode(opts, io = processIO) {
|
|
371
|
+
return opts.json || !io.isTTY();
|
|
372
|
+
}
|
|
373
|
+
function render(result, opts, io = processIO) {
|
|
374
|
+
if (jsonMode(opts, io)) {
|
|
375
|
+
line(io.out, JSON.stringify(result, null, 2));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
renderHuman(result, opts, io);
|
|
379
|
+
}
|
|
380
|
+
function isPage(v) {
|
|
381
|
+
return typeof v === "object" && v !== null && Array.isArray(v.items) && "next_cursor" in v;
|
|
382
|
+
}
|
|
383
|
+
function renderHuman(result, opts, io) {
|
|
384
|
+
const out = (t) => line(io.out, t);
|
|
385
|
+
if (result === null || result === void 0) {
|
|
386
|
+
out("(none)");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (isPage(result)) {
|
|
390
|
+
renderRows(result.items, out);
|
|
391
|
+
if (result.next_cursor && !opts.quiet) line(io.err, `more: --cursor ${result.next_cursor}`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (Array.isArray(result)) {
|
|
395
|
+
renderRows(result, out);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (typeof result !== "object") {
|
|
399
|
+
out(String(result));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const rec2 = result;
|
|
403
|
+
const keys = Object.keys(rec2);
|
|
404
|
+
if (keys.length === 0) {
|
|
405
|
+
out("(empty)");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const width = Math.max(...keys.map((k) => k.length));
|
|
409
|
+
for (const k of keys) out(`${k.padEnd(width)} ${cell(rec2[k])}`);
|
|
410
|
+
}
|
|
411
|
+
function renderRows(rows, out) {
|
|
412
|
+
if (rows.length === 0) {
|
|
413
|
+
out("(no results)");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (rows.every((r) => typeof r !== "object" || r === null)) {
|
|
417
|
+
for (const r of rows) out(cell(r));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const objs = rows.map((r) => r && typeof r === "object" ? r : {});
|
|
421
|
+
const cols = [];
|
|
422
|
+
for (const o of objs) for (const k of Object.keys(o)) if (!cols.includes(k)) cols.push(k);
|
|
423
|
+
const matrix = objs.map((o) => cols.map((c) => cell(o[c])));
|
|
424
|
+
const widths = cols.map((c, i) => Math.max(c.length, ...matrix.map((row) => row[i].length)));
|
|
425
|
+
out(cols.map((c, i) => c.padEnd(widths[i])).join(" "));
|
|
426
|
+
for (const row of matrix) out(row.map((v, i) => v.padEnd(widths[i])).join(" "));
|
|
427
|
+
}
|
|
428
|
+
function cell(v) {
|
|
429
|
+
if (v === null || v === void 0) return "";
|
|
430
|
+
if (typeof v === "string") return v;
|
|
431
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
432
|
+
if (Array.isArray(v)) return v.map(cell).join(", ");
|
|
433
|
+
if (typeof v === "object") {
|
|
434
|
+
const o = v;
|
|
435
|
+
if (typeof o.name === "string") return o.name;
|
|
436
|
+
if (typeof o.display_name === "string") {
|
|
437
|
+
return typeof o.job_title === "string" && o.job_title.length > 0 ? `${o.display_name} (${o.job_title})` : o.display_name;
|
|
438
|
+
}
|
|
439
|
+
if (typeof o.key === "string") return o.key;
|
|
440
|
+
if (typeof o.id === "string") return o.id;
|
|
441
|
+
return JSON.stringify(v);
|
|
442
|
+
}
|
|
443
|
+
return String(v);
|
|
444
|
+
}
|
|
445
|
+
function renderError(e, opts, io = processIO) {
|
|
446
|
+
const shape = e.toJSON();
|
|
447
|
+
if (jsonMode(opts, io)) {
|
|
448
|
+
line(io.err, JSON.stringify({ error: shape }, null, 2));
|
|
449
|
+
} else {
|
|
450
|
+
line(io.err, `error [${shape.code}]: ${shape.message}`);
|
|
451
|
+
if (shape.hint) line(io.err, ` hint: ${shape.hint}`);
|
|
452
|
+
if (shape.candidates && shape.candidates.length > 0) {
|
|
453
|
+
line(io.err, ` candidates: ${shape.candidates.join(", ")}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return exitCodeFor(e.code);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ../kit/src/operation.ts
|
|
460
|
+
function toolName(op) {
|
|
461
|
+
return op.name.replace(/[.-]/g, "_");
|
|
462
|
+
}
|
|
463
|
+
function commandWords(op) {
|
|
464
|
+
return op.name.split(".").slice(1);
|
|
465
|
+
}
|
|
466
|
+
function onSurface(op, surface) {
|
|
467
|
+
return op.meta?.surfaces?.includes(surface) ?? true;
|
|
468
|
+
}
|
|
469
|
+
function assertRegistry(product, ops) {
|
|
470
|
+
const names = /* @__PURE__ */ new Set();
|
|
471
|
+
const tools = /* @__PURE__ */ new Set();
|
|
472
|
+
for (const op of ops) {
|
|
473
|
+
const parts = op.name.split(".");
|
|
474
|
+
if (parts[0] !== product || parts.length < 2 || parts.length > 3) {
|
|
475
|
+
throw new Error(`operation "${op.name}" must be named ${product}.<noun>.<verb>`);
|
|
476
|
+
}
|
|
477
|
+
if (!parts.every((p) => /^[a-z][a-z0-9-]*$/.test(p))) {
|
|
478
|
+
throw new Error(`operation "${op.name}" must be lower case words`);
|
|
479
|
+
}
|
|
480
|
+
if (names.has(op.name)) throw new Error(`duplicate operation "${op.name}"`);
|
|
481
|
+
if (tools.has(toolName(op))) throw new Error(`duplicate MCP tool "${toolName(op)}"`);
|
|
482
|
+
names.add(op.name);
|
|
483
|
+
tools.add(toolName(op));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ../kit/src/argv.ts
|
|
488
|
+
function parseGlobals(argv, productFlags = [], env = process.env) {
|
|
489
|
+
const opts = {
|
|
490
|
+
json: false,
|
|
491
|
+
yes: false,
|
|
492
|
+
quiet: false,
|
|
493
|
+
...env.NO_COLOR ? { color: false } : {}
|
|
494
|
+
};
|
|
495
|
+
const flags = {};
|
|
496
|
+
const rest = [];
|
|
497
|
+
const valueFlag = (name, set, i) => {
|
|
498
|
+
const v = argv[i + 1];
|
|
499
|
+
if (v === void 0 || v.startsWith("--")) {
|
|
500
|
+
throw new MagicError("usage", `flag --${name} expects a value`);
|
|
501
|
+
}
|
|
502
|
+
set(v);
|
|
503
|
+
return i + 1;
|
|
504
|
+
};
|
|
505
|
+
for (let i = 0; i < argv.length; i++) {
|
|
506
|
+
const tok = argv[i];
|
|
507
|
+
switch (tok) {
|
|
508
|
+
case "--json":
|
|
509
|
+
opts.json = true;
|
|
510
|
+
continue;
|
|
511
|
+
case "--yes":
|
|
512
|
+
case "-y":
|
|
513
|
+
opts.yes = true;
|
|
514
|
+
continue;
|
|
515
|
+
case "--quiet":
|
|
516
|
+
case "-q":
|
|
517
|
+
opts.quiet = true;
|
|
518
|
+
continue;
|
|
519
|
+
case "--no-color":
|
|
520
|
+
opts.color = false;
|
|
521
|
+
continue;
|
|
522
|
+
case "--workspace":
|
|
523
|
+
i = valueFlag("workspace", (v) => opts.workspace = v, i);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (tok.startsWith("--workspace=")) {
|
|
527
|
+
opts.workspace = tok.slice("--workspace=".length);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const pf = productFlags.find((f) => tok === `--${f}` || tok.startsWith(`--${f}=`));
|
|
531
|
+
if (pf) {
|
|
532
|
+
const key = pf.replace(/-/g, "_");
|
|
533
|
+
if (tok.includes("=")) flags[key] = tok.slice(tok.indexOf("=") + 1);
|
|
534
|
+
else i = valueFlag(pf, (v) => flags[key] = v, i);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
rest.push(tok);
|
|
538
|
+
}
|
|
539
|
+
return { opts, productFlags: flags, rest };
|
|
540
|
+
}
|
|
541
|
+
function buildCommands(ops) {
|
|
542
|
+
return ops.map((op) => ({
|
|
543
|
+
op,
|
|
544
|
+
words: commandWords(op),
|
|
545
|
+
verbAliases: op.meta?.cli?.verbAliases ?? [],
|
|
546
|
+
positionals: op.meta?.cli?.positionals ?? []
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
function matchCommand(cmds, argv) {
|
|
550
|
+
const sorted = [...cmds].sort((a, b) => b.words.length - a.words.length);
|
|
551
|
+
for (const cmd of sorted) {
|
|
552
|
+
const n = cmd.words.length;
|
|
553
|
+
if (argv.length < n) continue;
|
|
554
|
+
const head = argv.slice(0, n - 1);
|
|
555
|
+
const last = argv[n - 1];
|
|
556
|
+
const lastOk = last === cmd.words[n - 1] || cmd.verbAliases.includes(last);
|
|
557
|
+
if (lastOk && head.every((w, i) => w === cmd.words[i])) {
|
|
558
|
+
return { cmd, args: argv.slice(n) };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
function baseType(schema) {
|
|
564
|
+
let cur = schema;
|
|
565
|
+
let optional = false;
|
|
566
|
+
for (let guard = 0; guard < 12; guard++) {
|
|
567
|
+
const def = cur._def;
|
|
568
|
+
const name = def?.typeName;
|
|
569
|
+
if ((name === "ZodOptional" || name === "ZodNullable" || name === "ZodDefault") && def?.innerType) {
|
|
570
|
+
optional = true;
|
|
571
|
+
cur = def.innerType;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
return { type: cur, optional };
|
|
577
|
+
}
|
|
578
|
+
function describeFields(input) {
|
|
579
|
+
const shape = input.shape;
|
|
580
|
+
if (!shape) return [];
|
|
581
|
+
return Object.entries(shape).map(([key, raw]) => {
|
|
582
|
+
const { type, optional } = baseType(raw);
|
|
583
|
+
const def = type._def;
|
|
584
|
+
let kind = "string";
|
|
585
|
+
let values;
|
|
586
|
+
switch (def?.typeName) {
|
|
587
|
+
case "ZodBoolean":
|
|
588
|
+
kind = "boolean";
|
|
589
|
+
break;
|
|
590
|
+
case "ZodNumber":
|
|
591
|
+
kind = "number";
|
|
592
|
+
break;
|
|
593
|
+
case "ZodArray": {
|
|
594
|
+
const el = def.type ? baseType(def.type).type : void 0;
|
|
595
|
+
const elName = el?._def?.typeName;
|
|
596
|
+
kind = elName === "ZodObject" ? "json" : "string[]";
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
case "ZodEnum":
|
|
600
|
+
kind = "enum";
|
|
601
|
+
if (Array.isArray(def.values)) values = def.values;
|
|
602
|
+
break;
|
|
603
|
+
case "ZodNativeEnum":
|
|
604
|
+
kind = "enum";
|
|
605
|
+
break;
|
|
606
|
+
case "ZodObject":
|
|
607
|
+
case "ZodRecord":
|
|
608
|
+
kind = "json";
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
const spec = { key, kind, required: !optional };
|
|
612
|
+
if (values) spec.values = values;
|
|
613
|
+
return spec;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
function keyToFlag(key) {
|
|
617
|
+
return key.replace(/_/g, "-");
|
|
618
|
+
}
|
|
619
|
+
function flagToKey(flag, fields, aliases = {}) {
|
|
620
|
+
if (aliases[flag]) return aliases[flag];
|
|
621
|
+
const snake = flag.replace(/-/g, "_");
|
|
622
|
+
const direct = fields.find((f) => f.key === snake || keyToFlag(f.key) === flag);
|
|
623
|
+
if (direct) return direct.key;
|
|
624
|
+
const plural = fields.find((f) => f.kind === "string[]" && f.key === `${snake}s`);
|
|
625
|
+
if (plural) return plural.key;
|
|
626
|
+
const seg = fields.filter((f) => f.key.split("_")[0] === snake);
|
|
627
|
+
if (seg.length === 1) return seg[0].key;
|
|
628
|
+
return void 0;
|
|
629
|
+
}
|
|
630
|
+
function parseCommandArgs(cmd, args) {
|
|
631
|
+
const cli = cmd.op.meta?.cli ?? {};
|
|
632
|
+
const fields = describeFields(cmd.op.input);
|
|
633
|
+
const byKey = new Map(fields.map((f) => [f.key, f]));
|
|
634
|
+
const switchNames = /* @__PURE__ */ new Set([
|
|
635
|
+
...Object.keys(cli.presets ?? {}),
|
|
636
|
+
...Object.keys(cli.routes ?? {})
|
|
637
|
+
]);
|
|
638
|
+
const raw = {};
|
|
639
|
+
const parsed = { raw, switches: [] };
|
|
640
|
+
const queue = [...cmd.positionals];
|
|
641
|
+
const label = cmd.words.join(" ");
|
|
642
|
+
const pushArray = (key, value) => {
|
|
643
|
+
const parts = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
644
|
+
raw[key] = [...Array.isArray(raw[key]) ? raw[key] : [], ...parts];
|
|
645
|
+
};
|
|
646
|
+
for (let i = 0; i < args.length; i++) {
|
|
647
|
+
const tok = args[i];
|
|
648
|
+
if (tok.startsWith("--")) {
|
|
649
|
+
let name = tok.slice(2);
|
|
650
|
+
let inline;
|
|
651
|
+
const eq = name.indexOf("=");
|
|
652
|
+
if (eq >= 0) {
|
|
653
|
+
inline = name.slice(eq + 1);
|
|
654
|
+
name = name.slice(0, eq);
|
|
655
|
+
}
|
|
656
|
+
if (switchNames.has(name)) {
|
|
657
|
+
parsed.switches.push(name);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (cli.fileInput && name === "file") {
|
|
661
|
+
const v = inline ?? args[++i];
|
|
662
|
+
if (v === void 0) throw usage(`${label}: flag --file expects a path`);
|
|
663
|
+
parsed.file = v;
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const key = flagToKey(name, fields, cli.aliases);
|
|
667
|
+
if (!key) throw usage(`${label}: unknown flag --${name}`);
|
|
668
|
+
const spec = byKey.get(key);
|
|
669
|
+
if (spec.kind === "boolean") {
|
|
670
|
+
raw[key] = inline === void 0 ? true : inline !== "false";
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const value = inline ?? args[++i];
|
|
674
|
+
if (value === void 0) throw usage(`${label}: flag --${name} expects a value`);
|
|
675
|
+
if (spec.kind === "string[]") pushArray(key, value);
|
|
676
|
+
else if (spec.kind === "json") raw[key] = parseJsonFlag(label, name, value);
|
|
677
|
+
else raw[key] = value;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const posKey = queue.shift();
|
|
681
|
+
if (!posKey) throw usage(`${label}: unexpected argument "${tok}"`);
|
|
682
|
+
if (byKey.get(posKey)?.kind === "string[]") pushArray(posKey, tok);
|
|
683
|
+
else raw[posKey] = tok;
|
|
684
|
+
}
|
|
685
|
+
return parsed;
|
|
686
|
+
}
|
|
687
|
+
function parseJsonFlag(label, flag, value) {
|
|
688
|
+
try {
|
|
689
|
+
return JSON.parse(value);
|
|
690
|
+
} catch {
|
|
691
|
+
throw usage(`${label}: flag --${flag} expects JSON`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function coerce(raw, fields) {
|
|
695
|
+
const out = {};
|
|
696
|
+
for (const f of fields) {
|
|
697
|
+
const v = raw[f.key];
|
|
698
|
+
if (v === void 0) continue;
|
|
699
|
+
if (f.kind === "number" && typeof v === "string") {
|
|
700
|
+
const n = Number(v);
|
|
701
|
+
out[f.key] = Number.isNaN(n) ? v : n;
|
|
702
|
+
} else {
|
|
703
|
+
out[f.key] = v;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return out;
|
|
707
|
+
}
|
|
708
|
+
function usage(message, hint) {
|
|
709
|
+
return new MagicError("usage", message, hint ? { hint } : {});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ../kit/src/cli.ts
|
|
713
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
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
|
+
|
|
1242
|
+
// ../kit/src/credentials.ts
|
|
1243
|
+
function manualItem(product) {
|
|
1244
|
+
return `${product}:manual`;
|
|
1245
|
+
}
|
|
1246
|
+
function maskKey(key, prefix) {
|
|
1247
|
+
const m = new RegExp(`^${escapeRegExp(prefix)}([^_]+)_(.+)$`).exec(key);
|
|
1248
|
+
if (!m) return `${prefix}\u2026`;
|
|
1249
|
+
return `${prefix}${m[1]}_\u2026${m[2].slice(-4)}`;
|
|
1250
|
+
}
|
|
1251
|
+
function escapeRegExp(s) {
|
|
1252
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1253
|
+
}
|
|
1254
|
+
function parseStoredSession(product, value) {
|
|
1255
|
+
try {
|
|
1256
|
+
const parsed = JSON.parse(value);
|
|
1257
|
+
if (parsed && typeof parsed.key === "string" && parsed.key.length > 0) {
|
|
1258
|
+
return { ...parsed, product, key: parsed.key, source: parsed.source ?? "manual" };
|
|
1259
|
+
}
|
|
1260
|
+
} catch {
|
|
1261
|
+
if (value.trim().length > 0) return { product, key: value.trim(), source: "manual" };
|
|
1262
|
+
}
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
function loginHint(spec) {
|
|
1266
|
+
const where = spec.keyHelp ? ` (${spec.keyHelp})` : "";
|
|
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}`;
|
|
1269
|
+
}
|
|
1270
|
+
function checkPrefix(spec, key) {
|
|
1271
|
+
if (!key.startsWith(spec.credential)) {
|
|
1272
|
+
throw new MagicError(
|
|
1273
|
+
"auth_invalid",
|
|
1274
|
+
`${spec.title} key must start with "${spec.credential}".`,
|
|
1275
|
+
{
|
|
1276
|
+
product: spec.id,
|
|
1277
|
+
hint: loginHint(spec)
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
async function resolveCredential(spec, opts) {
|
|
1283
|
+
const env = opts.env ?? process.env;
|
|
1284
|
+
const envKey = env[productEnvVar(spec.id, "KEY")]?.trim();
|
|
1285
|
+
if (envKey) {
|
|
1286
|
+
checkPrefix(spec, envKey);
|
|
1287
|
+
return { product: spec.id, key: envKey, source: "env" };
|
|
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
|
+
}
|
|
1306
|
+
const stored = await opts.store.get(manualItem(spec.id));
|
|
1307
|
+
const session = stored ? parseStoredSession(spec.id, stored) : null;
|
|
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.`, {
|
|
1314
|
+
product: spec.id,
|
|
1315
|
+
candidates: minted.workspaces.map((w) => w.handle),
|
|
1316
|
+
hint: "Pass --workspace <handle>, or set defaultWorkspace in the config file."
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
throw new MagicError("not_logged_in", `No ${spec.title} key found.`, {
|
|
1320
|
+
product: spec.id,
|
|
1321
|
+
hint: loginHint(spec)
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
async function saveProductKey(spec, store, key) {
|
|
1325
|
+
const trimmed = key.trim();
|
|
1326
|
+
checkPrefix(spec, trimmed);
|
|
1327
|
+
const session = { product: spec.id, key: trimmed, source: "manual" };
|
|
1328
|
+
await store.set(manualItem(spec.id), JSON.stringify(session));
|
|
1329
|
+
return session;
|
|
1330
|
+
}
|
|
1331
|
+
async function forgetProductKey(spec, store) {
|
|
1332
|
+
await store.delete(manualItem(spec.id));
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// ../kit/src/context.ts
|
|
1336
|
+
function productConfig(product, deps, extra = {}) {
|
|
1337
|
+
return loadCliConfig({
|
|
1338
|
+
products: [{ id: product.id, apiUrl: product.apiUrl }],
|
|
1339
|
+
env: deps.env ?? process.env,
|
|
1340
|
+
file: (deps.readConfig ?? (() => readConfigFile()))(),
|
|
1341
|
+
...extra
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
async function buildContext(product, config, deps, requiresSession = true) {
|
|
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;
|
|
1351
|
+
return contextFor(product, config, session, deps.signal);
|
|
1352
|
+
}
|
|
1353
|
+
function contextFor(product, config, session, signal = new AbortController().signal) {
|
|
1354
|
+
return {
|
|
1355
|
+
config,
|
|
1356
|
+
session,
|
|
1357
|
+
client: product.client(session ?? { product: product.id, key: "", source: "env" }, config),
|
|
1358
|
+
signal
|
|
1359
|
+
};
|
|
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
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// ../kit/src/cli.ts
|
|
1384
|
+
async function runProductCli(product, argv, deps) {
|
|
1385
|
+
const io = deps.io ?? processIO;
|
|
1386
|
+
let opts = { json: false, yes: false, quiet: false };
|
|
1387
|
+
try {
|
|
1388
|
+
const parsed = parseGlobals(argv, product.globalFlags, deps.env ?? process.env);
|
|
1389
|
+
opts = parsed.opts;
|
|
1390
|
+
const { rest, productFlags } = parsed;
|
|
1391
|
+
const extra = rest[0] ? product.commands?.[rest[0]] : void 0;
|
|
1392
|
+
if (extra) {
|
|
1393
|
+
if (opts.workspace) throw usage(`--workspace is not supported by ${rest[0]}`);
|
|
1394
|
+
return await extra.run(rest.slice(1));
|
|
1395
|
+
}
|
|
1396
|
+
const ops = await product.load();
|
|
1397
|
+
assertRegistry(product.id, ops);
|
|
1398
|
+
const cmds = buildCommands(ops.filter((op2) => onSurface(op2, "cli")));
|
|
1399
|
+
if (rest.length === 0 || ["help", "--help", "-h"].includes(rest[0])) {
|
|
1400
|
+
io.err(helpText(product, cmds));
|
|
1401
|
+
return 0;
|
|
1402
|
+
}
|
|
1403
|
+
const matched = matchCommand(cmds, rest);
|
|
1404
|
+
if (!matched) {
|
|
1405
|
+
throw usage(
|
|
1406
|
+
`unknown command: ${product.id} ${rest.join(" ")}`,
|
|
1407
|
+
`Run \`magic ${product.id} --help\` for usage.`
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
let { cmd } = matched;
|
|
1411
|
+
let args = matched.args;
|
|
1412
|
+
let parsedArgs = parseCommandArgs(cmd, args);
|
|
1413
|
+
const routes = cmd.op.meta?.cli?.routes ?? {};
|
|
1414
|
+
const route = parsedArgs.switches.find((s) => routes[s]);
|
|
1415
|
+
if (route) {
|
|
1416
|
+
const target = cmds.find((c) => c.op.name === routes[route]);
|
|
1417
|
+
if (!target) throw new Error(`route --${route} names a missing operation`);
|
|
1418
|
+
cmd = target;
|
|
1419
|
+
args = args.filter((a) => a !== `--${route}`);
|
|
1420
|
+
parsedArgs = parseCommandArgs(cmd, args);
|
|
1421
|
+
}
|
|
1422
|
+
const input = buildInput(cmd, parsedArgs, productFlags, opts);
|
|
1423
|
+
const op = cmd.op;
|
|
1424
|
+
const config = productConfig(product, deps, {
|
|
1425
|
+
output: {
|
|
1426
|
+
json: opts.json,
|
|
1427
|
+
quiet: opts.quiet,
|
|
1428
|
+
...opts.color === false ? { color: false } : {}
|
|
1429
|
+
},
|
|
1430
|
+
...opts.workspace ? { workspace: opts.workspace } : {}
|
|
1431
|
+
});
|
|
1432
|
+
if (op.meta?.destructive) {
|
|
1433
|
+
if (!opts.yes) {
|
|
1434
|
+
throw usage(`${cmd.words.join(" ")} is destructive \u2014 re-run with --yes to confirm.`);
|
|
1435
|
+
}
|
|
1436
|
+
if (!config.allowDestructive) throw destructiveDisabled(product.id);
|
|
1437
|
+
}
|
|
1438
|
+
const ctx = await buildContext(product, config, deps, op.meta?.requiresSession !== false);
|
|
1439
|
+
const out = await runWithRemint(op, input, ctx, remintFor(product, deps.store));
|
|
1440
|
+
render(out, opts, io);
|
|
1441
|
+
return 0;
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
return renderError(toMagicError(err, { product: product.id }), opts, io);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
function destructiveDisabled(product) {
|
|
1447
|
+
return new MagicError("destructive_action_disabled", "This action is destructive and disabled.", {
|
|
1448
|
+
product,
|
|
1449
|
+
hint: "Set MAGIC_ALLOW_DESTRUCTIVE=1 to enable destructive operations."
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
function buildInput(cmd, parsed, productFlags, opts) {
|
|
1453
|
+
const cli = cmd.op.meta?.cli ?? {};
|
|
1454
|
+
const fields = describeFields(cmd.op.input);
|
|
1455
|
+
const has = (key) => fields.some((f) => f.key === key);
|
|
1456
|
+
const raw = parsed.raw;
|
|
1457
|
+
for (const s of parsed.switches) Object.assign(raw, cli.presets?.[s] ?? {});
|
|
1458
|
+
if (cli.fileInput && parsed.file !== void 0)
|
|
1459
|
+
raw[cli.fileInput] = loadFileInput(parsed.file, cli.fileInput);
|
|
1460
|
+
for (const [key, value] of Object.entries(productFlags)) {
|
|
1461
|
+
if (has(key) && raw[key] === void 0) raw[key] = value;
|
|
1462
|
+
}
|
|
1463
|
+
if (opts.workspace && has("workspace") && raw.workspace === void 0) {
|
|
1464
|
+
raw.workspace = opts.workspace;
|
|
1465
|
+
}
|
|
1466
|
+
const result = cmd.op.input.safeParse(coerce(raw, fields));
|
|
1467
|
+
if (!result.success) {
|
|
1468
|
+
const first = result.error.issues[0];
|
|
1469
|
+
const where = first?.path.length ? ` (${first.path.join(".")})` : "";
|
|
1470
|
+
throw usage(
|
|
1471
|
+
`${cmd.words.join(" ")}: invalid arguments${where}: ${first?.message ?? "validation failed"}`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return result.data;
|
|
1475
|
+
}
|
|
1476
|
+
function loadFileInput(path, key) {
|
|
1477
|
+
let text;
|
|
1478
|
+
try {
|
|
1479
|
+
text = readFileSync3(path, "utf8");
|
|
1480
|
+
} catch (err) {
|
|
1481
|
+
throw usage(`cannot read --file ${path}: ${err.message}`);
|
|
1482
|
+
}
|
|
1483
|
+
let parsed;
|
|
1484
|
+
try {
|
|
1485
|
+
parsed = JSON.parse(text);
|
|
1486
|
+
} catch (err) {
|
|
1487
|
+
throw usage(`--file ${path} is not valid JSON: ${err.message}`);
|
|
1488
|
+
}
|
|
1489
|
+
if (Array.isArray(parsed)) return parsed;
|
|
1490
|
+
const inner = parsed && typeof parsed === "object" ? parsed[key] : void 0;
|
|
1491
|
+
if (Array.isArray(inner)) return inner;
|
|
1492
|
+
throw usage(`--file ${path} must be a JSON array (or { "${key}": [...] }).`);
|
|
1493
|
+
}
|
|
1494
|
+
function helpText(product, cmds) {
|
|
1495
|
+
const lines = [
|
|
1496
|
+
`magic ${product.id} \u2014 ${product.title}`,
|
|
1497
|
+
"",
|
|
1498
|
+
`Usage: magic ${product.id} <command> [args] [--flags]`,
|
|
1499
|
+
"",
|
|
1500
|
+
"Commands:"
|
|
1501
|
+
];
|
|
1502
|
+
const rows = cmds.map((c) => {
|
|
1503
|
+
const last = c.words[c.words.length - 1];
|
|
1504
|
+
const verb = c.verbAliases.length ? `${last} (${c.verbAliases.join(", ")})` : last;
|
|
1505
|
+
const name = [...c.words.slice(0, -1), verb].join(" ");
|
|
1506
|
+
const pos = c.positionals.map((p) => `<${p}>`).join(" ");
|
|
1507
|
+
return [`${name} ${pos}`.trim(), summaryOf(c.op)];
|
|
1508
|
+
});
|
|
1509
|
+
for (const [name, cmd] of Object.entries(product.commands ?? {})) rows.push([name, cmd.summary]);
|
|
1510
|
+
const width = Math.min(40, Math.max(...rows.map(([n]) => n.length)) + 2);
|
|
1511
|
+
for (const [name, summary] of rows) lines.push(` ${name.padEnd(width)}${summary}`);
|
|
1512
|
+
lines.push("");
|
|
1513
|
+
const pf = (product.globalFlags ?? []).map((f) => ` --${f} <${f}>`).join("");
|
|
1514
|
+
lines.push(`Flags: --json --yes/-y --no-color --quiet/-q --workspace <handle>${pf}`);
|
|
1515
|
+
return `${lines.join("\n")}
|
|
1516
|
+
`;
|
|
1517
|
+
}
|
|
1518
|
+
function summaryOf(op) {
|
|
1519
|
+
return op.meta?.destructive ? `${op.summary} [destructive]` : op.summary;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// ../kit/src/doctor.ts
|
|
1523
|
+
async function runDoctorChecks(checks, ctx, product = "magic") {
|
|
1524
|
+
const results = [];
|
|
1525
|
+
for (const check of checks) {
|
|
1526
|
+
try {
|
|
1527
|
+
results.push({ name: check.name, product, ...await check.run(ctx) });
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
const e = toMagicError(err, { product });
|
|
1530
|
+
results.push({
|
|
1531
|
+
name: check.name,
|
|
1532
|
+
product,
|
|
1533
|
+
ok: false,
|
|
1534
|
+
detail: e.hint ? `${e.message} \u2014 ${e.hint}` : e.message,
|
|
1535
|
+
code: e.code
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return results;
|
|
1540
|
+
}
|
|
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
|
+
|
|
1638
|
+
// ../kit/src/mcp.ts
|
|
1639
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1640
|
+
import { z } from "zod";
|
|
1641
|
+
function createMcpServer(opts) {
|
|
1642
|
+
const products = opts.products.filter((p) => {
|
|
1643
|
+
try {
|
|
1644
|
+
registerProduct(new McpServer({ name: opts.name, version: opts.version }), p);
|
|
1645
|
+
return true;
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
if (!opts.onSkip) throw err;
|
|
1648
|
+
opts.onSkip(p.module.id, err);
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
const sections = products.flatMap((p) => {
|
|
1653
|
+
const text = p.module.mcp?.instructions?.(opts.version);
|
|
1654
|
+
if (!text) return [];
|
|
1655
|
+
return [opts.preamble ? `## ${p.module.title}
|
|
1656
|
+
|
|
1657
|
+
${text}` : text];
|
|
1658
|
+
});
|
|
1659
|
+
const instructions = [...opts.preamble ? [opts.preamble] : [], ...sections].join("\n\n");
|
|
1660
|
+
const server = new McpServer(
|
|
1661
|
+
{ name: opts.name, version: opts.version },
|
|
1662
|
+
instructions ? { instructions } : {}
|
|
1663
|
+
);
|
|
1664
|
+
for (const p of products) registerProduct(server, p);
|
|
1665
|
+
return server;
|
|
1666
|
+
}
|
|
1667
|
+
function registerProduct(server, p) {
|
|
1668
|
+
for (const op of p.operations) {
|
|
1669
|
+
if (onSurface(op, "mcp")) registerTool(server, p.module.id, op, p.context, p.remint);
|
|
1670
|
+
}
|
|
1671
|
+
p.module.mcp?.register?.(server, p.context);
|
|
1672
|
+
}
|
|
1673
|
+
function toolInputShape(op) {
|
|
1674
|
+
const shape = op.input.shape ?? {};
|
|
1675
|
+
if (!op.meta?.destructive) return shape;
|
|
1676
|
+
return {
|
|
1677
|
+
...shape,
|
|
1678
|
+
yes: z.boolean().optional().describe("Confirm this destructive action (also needs MAGIC_ALLOW_DESTRUCTIVE=1).")
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
async function callTool(product, op, context, args, remint) {
|
|
1682
|
+
try {
|
|
1683
|
+
const ctx = await context();
|
|
1684
|
+
const { yes, ...rest } = args ?? {};
|
|
1685
|
+
if (op.meta?.destructive) {
|
|
1686
|
+
if (!ctx.config.allowDestructive) throw destructiveDisabled(product);
|
|
1687
|
+
if (yes !== true) {
|
|
1688
|
+
throw new MagicError("destructive_action_disabled", "This action is destructive.", {
|
|
1689
|
+
product,
|
|
1690
|
+
hint: "Pass yes: true to confirm it."
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
const parsed = op.input.safeParse(op.meta?.destructive ? rest : args ?? {});
|
|
1695
|
+
if (!parsed.success) throw invalidInput(product, parsed.error);
|
|
1696
|
+
const result = await runWithRemint(op, parsed.data, ctx, remint);
|
|
1697
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
return toToolError(toMagicError(err, { product }));
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function invalidInput(product, error) {
|
|
1703
|
+
const first = error.issues[0];
|
|
1704
|
+
const field = first?.path.join(".") ?? "";
|
|
1705
|
+
const message = first?.message ?? "Invalid input.";
|
|
1706
|
+
return new MagicError("validation_failed", field ? `${field}: ${message}` : message, {
|
|
1707
|
+
product,
|
|
1708
|
+
...field ? { details: { field } } : {}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
function registerTool(server, product, op, context, remint) {
|
|
1712
|
+
const description = op.detail ? `${op.summary}
|
|
1713
|
+
|
|
1714
|
+
${op.detail}` : op.summary;
|
|
1715
|
+
server.registerTool(
|
|
1716
|
+
toolName(op),
|
|
1717
|
+
{ description, inputSchema: toolInputShape(op) },
|
|
1718
|
+
async (args) => callTool(product, op, context, args, remint)
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// ../kit/src/cache.ts
|
|
1723
|
+
var TtlCache = class {
|
|
1724
|
+
constructor(ttlMs, now = () => Date.now()) {
|
|
1725
|
+
this.ttlMs = ttlMs;
|
|
1726
|
+
this.now = now;
|
|
1727
|
+
}
|
|
1728
|
+
entries = /* @__PURE__ */ new Map();
|
|
1729
|
+
get(key) {
|
|
1730
|
+
const hit = this.entries.get(key);
|
|
1731
|
+
if (!hit) return void 0;
|
|
1732
|
+
if (hit.expiresAt > this.now()) return hit.value;
|
|
1733
|
+
this.entries.delete(key);
|
|
1734
|
+
return void 0;
|
|
1735
|
+
}
|
|
1736
|
+
set(key, value) {
|
|
1737
|
+
this.entries.set(key, { value, expiresAt: this.now() + this.ttlMs });
|
|
1738
|
+
}
|
|
1739
|
+
delete(key) {
|
|
1740
|
+
this.entries.delete(key);
|
|
1741
|
+
}
|
|
1742
|
+
clear() {
|
|
1743
|
+
this.entries.clear();
|
|
1744
|
+
}
|
|
1745
|
+
/** Return the live entry, or load, store and return a fresh one. */
|
|
1746
|
+
async getOrLoad(key, load) {
|
|
1747
|
+
const hit = this.get(key);
|
|
1748
|
+
if (hit !== void 0) return hit;
|
|
1749
|
+
const value = await load();
|
|
1750
|
+
this.set(key, value);
|
|
1751
|
+
return value;
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
// ../kit/src/version.ts
|
|
1756
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
1757
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
1758
|
+
import { fileURLToPath } from "node:url";
|
|
1759
|
+
function readPackageVersion(moduleUrl) {
|
|
1760
|
+
let dir = dirname2(fileURLToPath(moduleUrl));
|
|
1761
|
+
for (let i = 0; i < 8; i++) {
|
|
1762
|
+
try {
|
|
1763
|
+
const pkg = JSON.parse(readFileSync4(join4(dir, "package.json"), "utf8"));
|
|
1764
|
+
if (typeof pkg.version === "string") return pkg.version;
|
|
1765
|
+
} catch {
|
|
1766
|
+
}
|
|
1767
|
+
const parent = dirname2(dir);
|
|
1768
|
+
if (parent === dir) break;
|
|
1769
|
+
dir = parent;
|
|
1770
|
+
}
|
|
1771
|
+
return "0.0.0";
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
export {
|
|
1775
|
+
MagicError,
|
|
1776
|
+
isMagicError,
|
|
1777
|
+
exitCodeFor,
|
|
1778
|
+
mapHttpError,
|
|
1779
|
+
toMagicError,
|
|
1780
|
+
productEnvVar,
|
|
1781
|
+
configFilePath,
|
|
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,
|
|
1805
|
+
manualItem,
|
|
1806
|
+
maskKey,
|
|
1807
|
+
resolveCredential,
|
|
1808
|
+
saveProductKey,
|
|
1809
|
+
forgetProductKey,
|
|
1810
|
+
createCredentialStore,
|
|
1811
|
+
processIO,
|
|
1812
|
+
jsonMode,
|
|
1813
|
+
render,
|
|
1814
|
+
cell,
|
|
1815
|
+
renderError,
|
|
1816
|
+
parseGlobals,
|
|
1817
|
+
usage,
|
|
1818
|
+
productConfig,
|
|
1819
|
+
buildContext,
|
|
1820
|
+
contextFor,
|
|
1821
|
+
remintFor,
|
|
1822
|
+
runWithRemint,
|
|
1823
|
+
runDoctorChecks,
|
|
1824
|
+
startLoopback,
|
|
1825
|
+
runProductCli,
|
|
1826
|
+
createMcpServer,
|
|
1827
|
+
TtlCache,
|
|
1828
|
+
readPackageVersion
|
|
1829
|
+
};
|