magic-spells 0.2.0 → 0.3.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 +7 -0
- package/dist/{chunk-JZ7XESF4.js → chunk-DVHFBLQP.js} +177 -32
- package/dist/{chunk-PQFOO5A7.js → chunk-M2ECPY3Q.js} +1 -1
- package/dist/main.js +190 -35
- package/dist/{operations-GGMLIYEW.js → operations-V5BL44TP.js} +2 -2
- package/dist/{src-DJYPPJFM.js → src-GSMZBKYC.js} +1 -1
- package/dist/{src-L2I546M7.js → src-PD2ETWZD.js} +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ wins over a stored one; pasted keys are never re-minted.
|
|
|
48
48
|
| `magic logout [--product <p>]` | revoke and forget credentials |
|
|
49
49
|
| `magic whoami [--json]` | your account (name, email, sign-in expiry) and per product: key source (env, account, manual, none), workspaces, default, masked keys |
|
|
50
50
|
| `magic doctor [--json]` | Node, credential store, Account and your sign-in, and per product: API reachable, key present, product checks |
|
|
51
|
+
| `magic update [--check] [--json]` | update magic to the latest version: runs `npm install -g magic-spells@<latest>` for a global npm install; `--check` and `--json` only report |
|
|
51
52
|
| `magic mcp [--products a,b]` | the MCP server over stdio: every product's tools, prefixed (`pyramid_*`) |
|
|
52
53
|
| `magic pyramid <noun> <verb>` | Pyramid: `task next`, `task move WEB-42 --status "In Review"`, `task comment`, `project list`, `whoami`, … (`magic pyramid --help`) |
|
|
53
54
|
|
|
@@ -55,6 +56,11 @@ Global flags: `--json`, `--yes/-y`, `--no-color`, `--quiet/-q`, `--workspace <ha
|
|
|
55
56
|
Output is JSON whenever stdout is not a terminal; errors go to stderr as
|
|
56
57
|
`{ "error": { "code", "message", "hint", "product" } }`.
|
|
57
58
|
|
|
59
|
+
Once a day `magic` asks the npm registry for the latest version in the background
|
|
60
|
+
(cached under `updateCheck` in `config.json`) and, when a newer one exists, prints one
|
|
61
|
+
line to stderr after the command. Never under `--json`, `--quiet`, `magic mcp` or when
|
|
62
|
+
stdout or stderr is not a terminal.
|
|
63
|
+
|
|
58
64
|
## MCP
|
|
59
65
|
|
|
60
66
|
```sh
|
|
@@ -77,6 +83,7 @@ stored, without restarting the server. With several workspaces in a product, set
|
|
|
77
83
|
| `MAGIC_ALLOW_DESTRUCTIVE=1` | allow destructive operations (with `--yes`, or `yes: true` in MCP) |
|
|
78
84
|
| `MAGIC_CREDENTIALS_FILE=1` | store keys in the 0600 file instead of the OS keychain |
|
|
79
85
|
| `XDG_CONFIG_HOME` | where `magic/` (config and credentials file) lives |
|
|
86
|
+
| `MAGIC_NO_UPDATE_CHECK=1` | no update notice (also off under `CI`) |
|
|
80
87
|
| `NO_COLOR` | no colour |
|
|
81
88
|
|
|
82
89
|
## Exit codes
|
|
@@ -560,13 +560,16 @@ function matchCommand(cmds, argv) {
|
|
|
560
560
|
}
|
|
561
561
|
return void 0;
|
|
562
562
|
}
|
|
563
|
+
function defOf(schema) {
|
|
564
|
+
return schema?.def;
|
|
565
|
+
}
|
|
563
566
|
function baseType(schema) {
|
|
564
567
|
let cur = schema;
|
|
565
568
|
let optional = false;
|
|
566
569
|
for (let guard = 0; guard < 12; guard++) {
|
|
567
|
-
const def = cur
|
|
568
|
-
const name = def?.
|
|
569
|
-
if ((name === "
|
|
570
|
+
const def = defOf(cur);
|
|
571
|
+
const name = def?.type;
|
|
572
|
+
if ((name === "optional" || name === "nullable" || name === "default") && def?.innerType) {
|
|
570
573
|
optional = true;
|
|
571
574
|
cur = def.innerType;
|
|
572
575
|
continue;
|
|
@@ -580,31 +583,27 @@ function describeFields(input) {
|
|
|
580
583
|
if (!shape) return [];
|
|
581
584
|
return Object.entries(shape).map(([key, raw]) => {
|
|
582
585
|
const { type, optional } = baseType(raw);
|
|
583
|
-
const def = type
|
|
586
|
+
const def = defOf(type);
|
|
584
587
|
let kind = "string";
|
|
585
588
|
let values;
|
|
586
|
-
switch (def?.
|
|
587
|
-
case "
|
|
589
|
+
switch (def?.type) {
|
|
590
|
+
case "boolean":
|
|
588
591
|
kind = "boolean";
|
|
589
592
|
break;
|
|
590
|
-
case "
|
|
593
|
+
case "number":
|
|
591
594
|
kind = "number";
|
|
592
595
|
break;
|
|
593
|
-
case "
|
|
594
|
-
const el = def.
|
|
595
|
-
|
|
596
|
-
kind = elName === "ZodObject" ? "json" : "string[]";
|
|
596
|
+
case "array": {
|
|
597
|
+
const el = def.element ? baseType(def.element).type : void 0;
|
|
598
|
+
kind = defOf(el)?.type === "object" ? "json" : "string[]";
|
|
597
599
|
break;
|
|
598
600
|
}
|
|
599
|
-
case "
|
|
600
|
-
kind = "enum";
|
|
601
|
-
if (Array.isArray(def.values)) values = def.values;
|
|
602
|
-
break;
|
|
603
|
-
case "ZodNativeEnum":
|
|
601
|
+
case "enum":
|
|
604
602
|
kind = "enum";
|
|
603
|
+
if (def.entries) values = Object.values(def.entries).map(String);
|
|
605
604
|
break;
|
|
606
|
-
case "
|
|
607
|
-
case "
|
|
605
|
+
case "object":
|
|
606
|
+
case "record":
|
|
608
607
|
kind = "json";
|
|
609
608
|
break;
|
|
610
609
|
}
|
|
@@ -1566,12 +1565,17 @@ async function startLoopback(opts) {
|
|
|
1566
1565
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1567
1566
|
const state = url.searchParams.get("state");
|
|
1568
1567
|
if (done || exchanging || req.method !== "GET" || url.pathname !== "/callback") {
|
|
1569
|
-
return reply(res, 400, "Bad request.");
|
|
1568
|
+
return reply(res, 400, "Bad request", "Bad request.");
|
|
1570
1569
|
}
|
|
1571
|
-
if (!sameState(state, opts.state)) return reply(res, 400, "Bad request.");
|
|
1570
|
+
if (!sameState(state, opts.state)) return reply(res, 400, "Bad request", "Bad request.");
|
|
1572
1571
|
const error = url.searchParams.get("error");
|
|
1573
1572
|
if (error) {
|
|
1574
|
-
reply(
|
|
1573
|
+
reply(
|
|
1574
|
+
res,
|
|
1575
|
+
200,
|
|
1576
|
+
"Sign-in cancelled",
|
|
1577
|
+
"Sign-in cancelled. You can close this tab and return to your terminal."
|
|
1578
|
+
);
|
|
1575
1579
|
return finish({
|
|
1576
1580
|
error: error === "access_denied" ? cancelledError() : new MagicError("auth_invalid", `Account refused the sign-in (${error}).`, {
|
|
1577
1581
|
product: ACCOUNT_PRODUCT
|
|
@@ -1579,17 +1583,17 @@ async function startLoopback(opts) {
|
|
|
1579
1583
|
});
|
|
1580
1584
|
}
|
|
1581
1585
|
const code = url.searchParams.get("code");
|
|
1582
|
-
if (!code) return reply(res, 400, "Bad request.");
|
|
1586
|
+
if (!code) return reply(res, 400, "Bad request", "Bad request.");
|
|
1583
1587
|
exchanging = true;
|
|
1584
1588
|
clearTimeout(timer);
|
|
1585
1589
|
const signals = [AbortSignal.timeout(CODE_EXCHANGE_TIMEOUT_MS)];
|
|
1586
1590
|
if (opts.signal) signals.push(opts.signal);
|
|
1587
1591
|
try {
|
|
1588
1592
|
const value = await opts.exchange(code, redirectUri, AbortSignal.any(signals));
|
|
1589
|
-
reply(res, 200, "Signed in to Magic Spells. You can close this tab.");
|
|
1593
|
+
reply(res, 200, "Signed in", "Signed in to Magic Spells. You can close this tab.");
|
|
1590
1594
|
finish({ value });
|
|
1591
1595
|
} catch (err) {
|
|
1592
|
-
reply(res, 400, "Sign-in failed. Return to your terminal for details.");
|
|
1596
|
+
reply(res, 400, "Sign-in failed", "Sign-in failed. Return to your terminal for details.");
|
|
1593
1597
|
finish({ error: toMagicError(err, { product: ACCOUNT_PRODUCT }) });
|
|
1594
1598
|
}
|
|
1595
1599
|
};
|
|
@@ -1625,14 +1629,145 @@ function sameState(got, want) {
|
|
|
1625
1629
|
const b = Buffer.from(want);
|
|
1626
1630
|
return a.length === b.length && timingSafeEqual(a, b);
|
|
1627
1631
|
}
|
|
1628
|
-
function reply(res, status, text) {
|
|
1632
|
+
function reply(res, status, heading, text) {
|
|
1629
1633
|
res.writeHead(status, {
|
|
1630
|
-
"content-type": "text/
|
|
1634
|
+
"content-type": "text/html; charset=utf-8",
|
|
1631
1635
|
"cache-control": "no-store",
|
|
1636
|
+
"content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
|
|
1632
1637
|
connection: "close"
|
|
1633
1638
|
});
|
|
1634
|
-
res.end(
|
|
1639
|
+
res.end(page(heading, text));
|
|
1640
|
+
}
|
|
1641
|
+
function page(heading, text) {
|
|
1642
|
+
return `<!doctype html>
|
|
1643
|
+
<html lang="en">
|
|
1644
|
+
<head>
|
|
1645
|
+
<meta charset="utf-8">
|
|
1646
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1647
|
+
<title>Magic Spells</title>
|
|
1648
|
+
<style>
|
|
1649
|
+
:root { color-scheme: light dark; --bg: #ffffff; --fg: #111111; --muted: #555555; }
|
|
1650
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #111111; --fg: #f2f2f2; --muted: #a0a0a0; } }
|
|
1651
|
+
html, body { height: 100%; margin: 0; }
|
|
1652
|
+
body { display: flex; align-items: center; justify-content: center; padding: 0 16px; background: var(--bg); color: var(--fg); font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; text-align: center; }
|
|
1653
|
+
h1 { margin: 0 0 8px; font-size: 1.5rem; font-weight: 600; }
|
|
1654
|
+
p { margin: 0; color: var(--muted); line-height: 1.5; }
|
|
1655
|
+
</style>
|
|
1656
|
+
</head>
|
|
1657
|
+
<body>
|
|
1658
|
+
<main>
|
|
1659
|
+
<h1>${heading}</h1>
|
|
1660
|
+
<p>${text}</p>
|
|
1661
|
+
</main>
|
|
1662
|
+
</body>
|
|
1663
|
+
</html>
|
|
1664
|
+
`;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// ../kit/src/update.ts
|
|
1668
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1669
|
+
import { get as httpGet } from "node:http";
|
|
1670
|
+
import { get as httpsGet } from "node:https";
|
|
1671
|
+
import { dirname as dirname2 } from "node:path";
|
|
1672
|
+
var UPDATE_REGISTRY_URL = "https://registry.npmjs.org/magic-spells/latest";
|
|
1673
|
+
var VERSION_PATTERN = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
1674
|
+
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1675
|
+
function fetchLatestVersion(url, opts) {
|
|
1676
|
+
return new Promise((resolve, reject) => {
|
|
1677
|
+
const get = url.startsWith("https:") ? httpsGet : httpGet;
|
|
1678
|
+
const req = get(url, { headers: { accept: "application/json" } }, (res) => {
|
|
1679
|
+
if (res.statusCode !== 200) {
|
|
1680
|
+
res.resume();
|
|
1681
|
+
reject(new Error(`The npm registry answered HTTP ${res.statusCode}.`));
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
let body = "";
|
|
1685
|
+
res.setEncoding("utf8");
|
|
1686
|
+
res.on("data", (chunk) => body += chunk);
|
|
1687
|
+
res.on("error", reject);
|
|
1688
|
+
res.on("end", () => {
|
|
1689
|
+
let version;
|
|
1690
|
+
try {
|
|
1691
|
+
version = JSON.parse(body).version;
|
|
1692
|
+
} catch {
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof version === "string" && VERSION_PATTERN.test(version)) resolve(version);
|
|
1695
|
+
else reject(new Error("The npm registry answered without a valid version."));
|
|
1696
|
+
});
|
|
1697
|
+
});
|
|
1698
|
+
const timer = setTimeout(() => {
|
|
1699
|
+
const err = Object.assign(new Error("The npm registry did not answer in time."), {
|
|
1700
|
+
code: "ETIMEDOUT"
|
|
1701
|
+
});
|
|
1702
|
+
req.destroy(err);
|
|
1703
|
+
}, opts.timeoutMs);
|
|
1704
|
+
req.on("close", () => clearTimeout(timer));
|
|
1705
|
+
req.on("error", reject);
|
|
1706
|
+
if (opts.unref) {
|
|
1707
|
+
timer.unref();
|
|
1708
|
+
req.on("socket", (socket) => socket.unref());
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
function isNewer(latest, current) {
|
|
1713
|
+
const parse = (v) => {
|
|
1714
|
+
const [core = "", pre] = v.split("-", 2);
|
|
1715
|
+
return [core.split(".").map(Number), pre !== void 0];
|
|
1716
|
+
};
|
|
1717
|
+
const [a, aPre] = parse(latest);
|
|
1718
|
+
const [b, bPre] = parse(current);
|
|
1719
|
+
for (let i = 0; i < 3; i++) {
|
|
1720
|
+
if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
|
|
1721
|
+
}
|
|
1722
|
+
return bPre && !aPre;
|
|
1723
|
+
}
|
|
1724
|
+
var WINDOWS_GLOBAL = /\\(npm|nodejs|nvm\\v[^\\]+|volta\\tools\\image\\packages\\magic-spells)\\node_modules\\magic-spells\\/i;
|
|
1725
|
+
function installMethod(binPath) {
|
|
1726
|
+
if (/[/\\]_npx[/\\]/.test(binPath)) return "npx";
|
|
1727
|
+
if (binPath.includes("/lib/node_modules/magic-spells/")) return "npm-global";
|
|
1728
|
+
if (WINDOWS_GLOBAL.test(binPath)) return "npm-global";
|
|
1729
|
+
return "other";
|
|
1730
|
+
}
|
|
1731
|
+
function readUpdateCheck(file) {
|
|
1732
|
+
const c = file?.updateCheck;
|
|
1733
|
+
if (!c || typeof c.checkedAt !== "string" || typeof c.latest !== "string") return null;
|
|
1734
|
+
if (!VERSION_PATTERN.test(c.latest) || Number.isNaN(Date.parse(c.checkedAt))) return null;
|
|
1735
|
+
return { checkedAt: c.checkedAt, latest: c.latest };
|
|
1736
|
+
}
|
|
1737
|
+
function updateCheckDue(check, now) {
|
|
1738
|
+
return !check || now - Date.parse(check.checkedAt) >= UPDATE_CHECK_INTERVAL_MS;
|
|
1739
|
+
}
|
|
1740
|
+
function writeUpdateCheck(path, check) {
|
|
1741
|
+
let text = null;
|
|
1742
|
+
try {
|
|
1743
|
+
text = readFileSync4(path, "utf8");
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
let file = {};
|
|
1747
|
+
if (text !== null) {
|
|
1748
|
+
let parsed;
|
|
1749
|
+
try {
|
|
1750
|
+
parsed = JSON.parse(text);
|
|
1751
|
+
} catch {
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
1755
|
+
file = parsed;
|
|
1756
|
+
}
|
|
1757
|
+
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
1758
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
1759
|
+
writeFileSync2(tmp, `${JSON.stringify({ ...file, updateCheck: check }, null, 2)}
|
|
1635
1760
|
`);
|
|
1761
|
+
renameSync(tmp, path);
|
|
1762
|
+
}
|
|
1763
|
+
function updateNotice(current, latest) {
|
|
1764
|
+
return isNewer(latest, current) ? `A newer magic is available: ${latest} (you have ${current}). Run \`magic update\`.` : null;
|
|
1765
|
+
}
|
|
1766
|
+
function registryError(err) {
|
|
1767
|
+
const message = err instanceof Error ? err.message : "The npm registry could not be reached.";
|
|
1768
|
+
return new MagicError("network", message, {
|
|
1769
|
+
hint: "Check your connection, or run npm install -g magic-spells@latest."
|
|
1770
|
+
});
|
|
1636
1771
|
}
|
|
1637
1772
|
|
|
1638
1773
|
// ../kit/src/mcp.ts
|
|
@@ -1753,18 +1888,18 @@ var TtlCache = class {
|
|
|
1753
1888
|
};
|
|
1754
1889
|
|
|
1755
1890
|
// ../kit/src/version.ts
|
|
1756
|
-
import { readFileSync as
|
|
1757
|
-
import { dirname as
|
|
1891
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1892
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1758
1893
|
import { fileURLToPath } from "node:url";
|
|
1759
1894
|
function readPackageVersion(moduleUrl) {
|
|
1760
|
-
let dir =
|
|
1895
|
+
let dir = dirname3(fileURLToPath(moduleUrl));
|
|
1761
1896
|
for (let i = 0; i < 8; i++) {
|
|
1762
1897
|
try {
|
|
1763
|
-
const pkg = JSON.parse(
|
|
1898
|
+
const pkg = JSON.parse(readFileSync5(join4(dir, "package.json"), "utf8"));
|
|
1764
1899
|
if (typeof pkg.version === "string") return pkg.version;
|
|
1765
1900
|
} catch {
|
|
1766
1901
|
}
|
|
1767
|
-
const parent =
|
|
1902
|
+
const parent = dirname3(dir);
|
|
1768
1903
|
if (parent === dir) break;
|
|
1769
1904
|
dir = parent;
|
|
1770
1905
|
}
|
|
@@ -1822,6 +1957,16 @@ export {
|
|
|
1822
1957
|
runWithRemint,
|
|
1823
1958
|
runDoctorChecks,
|
|
1824
1959
|
startLoopback,
|
|
1960
|
+
UPDATE_REGISTRY_URL,
|
|
1961
|
+
VERSION_PATTERN,
|
|
1962
|
+
fetchLatestVersion,
|
|
1963
|
+
isNewer,
|
|
1964
|
+
installMethod,
|
|
1965
|
+
readUpdateCheck,
|
|
1966
|
+
updateCheckDue,
|
|
1967
|
+
writeUpdateCheck,
|
|
1968
|
+
updateNotice,
|
|
1969
|
+
registryError,
|
|
1825
1970
|
runProductCli,
|
|
1826
1971
|
createMcpServer,
|
|
1827
1972
|
TtlCache,
|
package/dist/main.js
CHANGED
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
AccountClient,
|
|
3
3
|
GRANT_ITEM,
|
|
4
4
|
MagicError,
|
|
5
|
+
UPDATE_REGISTRY_URL,
|
|
6
|
+
VERSION_PATTERN,
|
|
5
7
|
authorizeUrl,
|
|
6
8
|
buildContext,
|
|
7
9
|
cell,
|
|
@@ -11,12 +13,15 @@ import {
|
|
|
11
13
|
createMcpServer,
|
|
12
14
|
createPkce,
|
|
13
15
|
exitCodeFor,
|
|
16
|
+
fetchLatestVersion,
|
|
14
17
|
forgetProductKey,
|
|
15
18
|
forgetSessions,
|
|
16
19
|
grantExpired,
|
|
17
20
|
grantFromToken,
|
|
18
21
|
grantIdleExpiry,
|
|
22
|
+
installMethod,
|
|
19
23
|
isMagicError,
|
|
24
|
+
isNewer,
|
|
20
25
|
jsonMode,
|
|
21
26
|
loadCliConfig,
|
|
22
27
|
manualItem,
|
|
@@ -34,7 +39,9 @@ import {
|
|
|
34
39
|
readIndex,
|
|
35
40
|
readPackageVersion,
|
|
36
41
|
readStr,
|
|
42
|
+
readUpdateCheck,
|
|
37
43
|
rec,
|
|
44
|
+
registryError,
|
|
38
45
|
remintFor,
|
|
39
46
|
render,
|
|
40
47
|
renderError,
|
|
@@ -49,9 +56,12 @@ import {
|
|
|
49
56
|
startLoopback,
|
|
50
57
|
storeSessions,
|
|
51
58
|
toMagicError,
|
|
59
|
+
updateCheckDue,
|
|
60
|
+
updateNotice,
|
|
52
61
|
usage,
|
|
53
|
-
waitForDevice
|
|
54
|
-
|
|
62
|
+
waitForDevice,
|
|
63
|
+
writeUpdateCheck
|
|
64
|
+
} from "./chunk-DVHFBLQP.js";
|
|
55
65
|
|
|
56
66
|
// src/browser.ts
|
|
57
67
|
import { spawn } from "node:child_process";
|
|
@@ -821,8 +831,8 @@ async function serveMcp(shell, argv) {
|
|
|
821
831
|
process.stdin.on("close", () => void shutdown("stdin closed"));
|
|
822
832
|
process.stdin.on("end", () => void shutdown("stdin end"));
|
|
823
833
|
await built.server.connect(transport);
|
|
824
|
-
const
|
|
825
|
-
err(`ready on stdio \xB7 ${
|
|
834
|
+
const summary2 = built.products.map((p) => `${p.module.id} (${p.operations.length} tools)`);
|
|
835
|
+
err(`ready on stdio \xB7 ${summary2.join(", ")}`);
|
|
826
836
|
for (const { module } of built.products) {
|
|
827
837
|
try {
|
|
828
838
|
await resolveCredential(module, { env: shell.env, store: shell.store });
|
|
@@ -845,6 +855,133 @@ function guardStdout() {
|
|
|
845
855
|
console.debug = toErr;
|
|
846
856
|
}
|
|
847
857
|
|
|
858
|
+
// src/commands/update.ts
|
|
859
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
860
|
+
import { realpathSync } from "node:fs";
|
|
861
|
+
var LOOKUP_TIMEOUT_MS = 5e3;
|
|
862
|
+
var BACKGROUND_TIMEOUT_MS = 2e3;
|
|
863
|
+
var MANUAL = "npm install -g magic-spells@latest";
|
|
864
|
+
async function update(shell, argv) {
|
|
865
|
+
if (wantsHelp(argv)) {
|
|
866
|
+
shell.io.err(
|
|
867
|
+
"Usage: magic update [--check] [--json]\n\nUpdate magic to the latest version; --check only reports.\n"
|
|
868
|
+
);
|
|
869
|
+
return 0;
|
|
870
|
+
}
|
|
871
|
+
const args = [...argv];
|
|
872
|
+
rejectWorkspace("update", shell.opts);
|
|
873
|
+
const check = takeSwitch(args, "check");
|
|
874
|
+
noMoreArgs("update", args);
|
|
875
|
+
const deps = shell.update;
|
|
876
|
+
let latest;
|
|
877
|
+
try {
|
|
878
|
+
latest = await fetchLatestVersion(deps.registryUrl ?? UPDATE_REGISTRY_URL, {
|
|
879
|
+
timeoutMs: LOOKUP_TIMEOUT_MS
|
|
880
|
+
});
|
|
881
|
+
} catch (err) {
|
|
882
|
+
throw registryError(err);
|
|
883
|
+
}
|
|
884
|
+
const binPath = deps.binPath ?? runningBin();
|
|
885
|
+
const current = version();
|
|
886
|
+
const report2 = {
|
|
887
|
+
current,
|
|
888
|
+
latest,
|
|
889
|
+
upToDate: !isNewer(latest, current),
|
|
890
|
+
method: installMethod(binPath)
|
|
891
|
+
};
|
|
892
|
+
if (check || shell.opts.json) {
|
|
893
|
+
if (jsonMode(shell.opts, shell.io)) render(report2, shell.opts, shell.io);
|
|
894
|
+
else shell.io.out(`${summary(report2)}
|
|
895
|
+
`);
|
|
896
|
+
return 0;
|
|
897
|
+
}
|
|
898
|
+
if (report2.upToDate) {
|
|
899
|
+
shell.io.out(`${summary(report2)}
|
|
900
|
+
`);
|
|
901
|
+
return 0;
|
|
902
|
+
}
|
|
903
|
+
if (report2.method === "npx") {
|
|
904
|
+
shell.io.out(
|
|
905
|
+
"You're running magic through npx, which already fetches the latest version; nothing to update.\n"
|
|
906
|
+
);
|
|
907
|
+
return 0;
|
|
908
|
+
}
|
|
909
|
+
if (report2.method === "other") {
|
|
910
|
+
shell.io.out(
|
|
911
|
+
`${summary(report2)}
|
|
912
|
+
This magic runs from ${binPath}, not a global npm install. To install the latest globally:
|
|
913
|
+
${MANUAL}
|
|
914
|
+
`
|
|
915
|
+
);
|
|
916
|
+
return 0;
|
|
917
|
+
}
|
|
918
|
+
if (!VERSION_PATTERN.test(latest)) throw registryError(new Error("Invalid version."));
|
|
919
|
+
const spec = `magic-spells@${latest}`;
|
|
920
|
+
if (!shell.opts.quiet)
|
|
921
|
+
shell.io.err(`Updating magic ${current} \u2192 ${latest}: npm install -g ${spec}
|
|
922
|
+
`);
|
|
923
|
+
const code = await (deps.npmInstall ?? npmInstall)(spec);
|
|
924
|
+
if (code !== 0) {
|
|
925
|
+
throw new MagicError("unknown", `npm install -g ${spec} failed (exit ${code}).`, {
|
|
926
|
+
hint: `Run ${MANUAL} yourself to see why.`
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
shell.io.out(`magic ${latest} installed.
|
|
930
|
+
`);
|
|
931
|
+
return 0;
|
|
932
|
+
}
|
|
933
|
+
function summary(r) {
|
|
934
|
+
return r.upToDate ? `magic ${r.current} is the latest version.` : `magic ${r.latest} is available (you have ${r.current}).`;
|
|
935
|
+
}
|
|
936
|
+
function runningBin() {
|
|
937
|
+
const bin = process.argv[1] ?? "";
|
|
938
|
+
try {
|
|
939
|
+
return realpathSync(bin);
|
|
940
|
+
} catch {
|
|
941
|
+
return bin;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function npmInstall(spec) {
|
|
945
|
+
const win = process.platform === "win32";
|
|
946
|
+
return new Promise((resolve, reject) => {
|
|
947
|
+
const child = spawn2(win ? "npm.cmd" : "npm", ["install", "-g", spec], {
|
|
948
|
+
stdio: "inherit",
|
|
949
|
+
shell: win
|
|
950
|
+
});
|
|
951
|
+
child.on("error", reject);
|
|
952
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
var NO_NOTICE = /* @__PURE__ */ new Set(["update", "mcp"]);
|
|
956
|
+
function startUpdateNotice(ctx) {
|
|
957
|
+
const has = (...flags) => flags.some((f) => ctx.argv.includes(f));
|
|
958
|
+
if (NO_NOTICE.has(ctx.cmd) || has("--json", "--quiet", "-q", "--help", "-h")) return noop;
|
|
959
|
+
if (!ctx.stderrIsTTY || !ctx.io.isTTY()) return noop;
|
|
960
|
+
const file = readConfigFile(ctx.configPath);
|
|
961
|
+
try {
|
|
962
|
+
if (!loadCliConfig({ products: [], env: ctx.env, file }).updateCheck) return noop;
|
|
963
|
+
} catch {
|
|
964
|
+
return noop;
|
|
965
|
+
}
|
|
966
|
+
const cached2 = readUpdateCheck(file);
|
|
967
|
+
const now = ctx.deps.now ?? Date.now;
|
|
968
|
+
if (updateCheckDue(cached2, now())) {
|
|
969
|
+
const done = fetchLatestVersion(ctx.deps.registryUrl ?? UPDATE_REGISTRY_URL, {
|
|
970
|
+
timeoutMs: BACKGROUND_TIMEOUT_MS,
|
|
971
|
+
unref: true
|
|
972
|
+
}).then((latest) => {
|
|
973
|
+
writeUpdateCheck(ctx.configPath, { checkedAt: new Date(now()).toISOString(), latest });
|
|
974
|
+
}).catch(() => {
|
|
975
|
+
});
|
|
976
|
+
ctx.deps.onBackgroundCheck?.(done);
|
|
977
|
+
}
|
|
978
|
+
const line = cached2 ? updateNotice(version(), cached2.latest) : null;
|
|
979
|
+
return line ? () => ctx.io.err(`${line}
|
|
980
|
+
`) : noop;
|
|
981
|
+
}
|
|
982
|
+
function noop() {
|
|
983
|
+
}
|
|
984
|
+
|
|
848
985
|
// src/commands/whoami.ts
|
|
849
986
|
async function whoami(shell, argv) {
|
|
850
987
|
if (wantsHelp(argv)) {
|
|
@@ -974,12 +1111,12 @@ var PRODUCTS = [
|
|
|
974
1111
|
{
|
|
975
1112
|
id: "pyramid",
|
|
976
1113
|
title: "Pyramid \u2014 projects and tasks",
|
|
977
|
-
load: async () => (await import("./src-
|
|
1114
|
+
load: async () => (await import("./src-PD2ETWZD.js")).default
|
|
978
1115
|
},
|
|
979
1116
|
{
|
|
980
1117
|
id: "sites",
|
|
981
1118
|
title: "Sites \u2014 sign-in only until its commands arrive",
|
|
982
|
-
load: async () => (await import("./src-
|
|
1119
|
+
load: async () => (await import("./src-GSMZBKYC.js")).default
|
|
983
1120
|
},
|
|
984
1121
|
{
|
|
985
1122
|
id: "marketing",
|
|
@@ -1038,7 +1175,8 @@ var COMMANDS = {
|
|
|
1038
1175
|
logout: { summary: "revoke and forget credentials: logout [--product <p>]", run: logout },
|
|
1039
1176
|
whoami: { summary: "your account and your workspaces in each product", run: whoami },
|
|
1040
1177
|
doctor: { summary: "check the install, the sign-in and each product", run: doctor },
|
|
1041
|
-
mcp: { summary: "serve every product\u2019s tools over stdio: mcp [--products a,b]", run: serveMcp }
|
|
1178
|
+
mcp: { summary: "serve every product\u2019s tools over stdio: mcp [--products a,b]", run: serveMcp },
|
|
1179
|
+
update: { summary: "update magic to the latest version: update [--check]", run: update }
|
|
1042
1180
|
};
|
|
1043
1181
|
function help(products) {
|
|
1044
1182
|
const lines = [
|
|
@@ -1075,34 +1213,51 @@ async function run(argv, deps = {}) {
|
|
|
1075
1213
|
return 0;
|
|
1076
1214
|
}
|
|
1077
1215
|
const store = () => deps.store ?? createCredentialStore({ env });
|
|
1078
|
-
const
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1216
|
+
const configPath = configFilePath({ env });
|
|
1217
|
+
const readConfig = () => readConfigFile(configPath);
|
|
1218
|
+
const updateDeps = deps.update ?? {};
|
|
1219
|
+
const notice = startUpdateNotice({
|
|
1220
|
+
cmd,
|
|
1221
|
+
argv,
|
|
1222
|
+
io,
|
|
1223
|
+
env,
|
|
1224
|
+
stderrIsTTY: deps.stderrIsTTY ?? process.stderr.isTTY === true,
|
|
1225
|
+
configPath,
|
|
1226
|
+
deps: updateDeps
|
|
1227
|
+
});
|
|
1228
|
+
const code = await dispatch();
|
|
1229
|
+
notice();
|
|
1230
|
+
return code;
|
|
1231
|
+
async function dispatch() {
|
|
1232
|
+
const entry = products.find((p) => p.id === cmd);
|
|
1233
|
+
if (entry) {
|
|
1234
|
+
const product = await entry.load();
|
|
1235
|
+
return runProductCli(product, rest, { io, env, store: store(), readConfig });
|
|
1236
|
+
}
|
|
1237
|
+
let opts = { json: argv.includes("--json"), yes: false, quiet: false };
|
|
1238
|
+
try {
|
|
1239
|
+
const command = COMMANDS[cmd];
|
|
1240
|
+
if (!command) throw usage(`unknown command "${cmd}"`, "Run `magic --help` for usage.");
|
|
1241
|
+
const parsed = parseGlobals(rest, [], env);
|
|
1242
|
+
opts = parsed.opts;
|
|
1243
|
+
const shell = {
|
|
1244
|
+
io,
|
|
1245
|
+
env,
|
|
1246
|
+
store: store(),
|
|
1247
|
+
entries: products,
|
|
1248
|
+
opts,
|
|
1249
|
+
readConfig,
|
|
1250
|
+
readSecret: deps.readSecret ?? readSecret,
|
|
1251
|
+
openBrowser: deps.openBrowser ?? openBrowser,
|
|
1252
|
+
confirm: deps.confirm !== void 0 ? deps.confirm : process.stdin.isTTY ? confirmOnTty : null,
|
|
1253
|
+
platform: deps.platform ?? process.platform,
|
|
1254
|
+
login: deps.login ?? {},
|
|
1255
|
+
update: updateDeps
|
|
1256
|
+
};
|
|
1257
|
+
return await command.run(shell, parsed.rest);
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
return renderError(toMagicError(err), opts, io);
|
|
1260
|
+
}
|
|
1106
1261
|
}
|
|
1107
1262
|
}
|
|
1108
1263
|
export {
|
|
@@ -3,7 +3,7 @@ var sites = {
|
|
|
3
3
|
id: "sites",
|
|
4
4
|
title: "Sites",
|
|
5
5
|
credential: "sites_",
|
|
6
|
-
apiUrl: "https://api.
|
|
6
|
+
apiUrl: "https://sites-api.magicspells.io",
|
|
7
7
|
keyHelp: "Sites \u2192 Settings \u2192 Access keys",
|
|
8
8
|
sessions: { path: "/v1/cli/sessions" },
|
|
9
9
|
load: async () => [],
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
operationsByName,
|
|
4
4
|
pyramidError,
|
|
5
5
|
toPyramidError
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-M2ECPY3Q.js";
|
|
7
7
|
import {
|
|
8
8
|
MagicError,
|
|
9
9
|
TtlCache,
|
|
10
10
|
readField,
|
|
11
11
|
readStr
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-DVHFBLQP.js";
|
|
13
13
|
|
|
14
14
|
// ../pyramid/src/cache/resolver.ts
|
|
15
15
|
var TTL_MS = 6e4;
|
|
@@ -884,10 +884,10 @@ var PYRAMID_CREDENTIAL = {
|
|
|
884
884
|
credential: "pyramid_",
|
|
885
885
|
keyHelp: "Pyramid \u2192 Settings \u2192 API Keys"
|
|
886
886
|
};
|
|
887
|
-
var DEFAULT_API_URL = "https://
|
|
887
|
+
var DEFAULT_API_URL = "https://pyramid.magicspells.io";
|
|
888
888
|
|
|
889
889
|
// ../pyramid/src/doctor.ts
|
|
890
|
-
var whoamiOp = async () => (await import("./operations-
|
|
890
|
+
var whoamiOp = async () => (await import("./operations-V5BL44TP.js")).operationsByName.get("pyramid.whoami");
|
|
891
891
|
var doctorChecks = [
|
|
892
892
|
{
|
|
893
893
|
name: "pyramid auth",
|
|
@@ -1049,7 +1049,7 @@ var pyramid = {
|
|
|
1049
1049
|
// `magic login` mints workspace keys here from the Account grant (API-PYRAMID-CLI-SESSIONS).
|
|
1050
1050
|
sessions: { path: "/v1/cli/sessions" },
|
|
1051
1051
|
// The operations graph loads only when a Pyramid command runs.
|
|
1052
|
-
load: async () => (await import("./operations-
|
|
1052
|
+
load: async () => (await import("./operations-V5BL44TP.js")).operations,
|
|
1053
1053
|
client(session, config) {
|
|
1054
1054
|
const api = new PyramidClient({
|
|
1055
1055
|
apiKey: session.key,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magic-spells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Magic CLI — one command line and MCP server for Magic Spells' hosted services.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
23
23
|
"undici": "^6.19.0",
|
|
24
|
-
"zod": "^
|
|
24
|
+
"zod": "^4.6.7"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"bundle": "node scripts/bundle.mjs",
|