svamp-cli 0.2.265 → 0.2.267
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/dist/{agentCommands-CKd1jXfX.mjs → agentCommands-C-GPVVEh.mjs} +5 -5
- package/dist/{auth-Ctsyrv9N.mjs → auth-D95JIId0.mjs} +1 -1
- package/dist/{cli-DtMJLEig.mjs → cli-Cf1ogBJ0.mjs} +63 -63
- package/dist/cli.mjs +2 -2
- package/dist/{commands-C_74tXoO.mjs → commands-BpoCTRmH.mjs} +1 -1
- package/dist/{commands-Bx4WYv3I.mjs → commands-ByvuMAH7.mjs} +6 -6
- package/dist/{commands-De9crCdY.mjs → commands-CI_YQANp.mjs} +1 -1
- package/dist/{commands-BUhsW3bf.mjs → commands-CZUC9mKz.mjs} +1 -1
- package/dist/{commands-BI7IDVIE.mjs → commands-Ddf4WJ7T.mjs} +2 -2
- package/dist/{commands-BSBxY9iQ.mjs → commands-DnZIJg2B.mjs} +2 -2
- package/dist/{commands-CMZS3jBt.mjs → commands-XaQfalRc.mjs} +2 -2
- package/dist/{fleet-D3ycNACq.mjs → fleet-D9WjmmSK.mjs} +1 -1
- package/dist/{frpc-Cwn_s0Fs.mjs → frpc-Dch3Eq_f.mjs} +1 -1
- package/dist/{headlessCli-nWnqozcZ.mjs → headlessCli-AfuoPfwV.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/package-B-3yvmAt.mjs +64 -0
- package/dist/{rpc-BnHMvDxQ.mjs → rpc-BctBAWZg.mjs} +1 -1
- package/dist/{rpc-DwlZTOtK.mjs → rpc-iRm5bxwr.mjs} +1 -1
- package/dist/{run-DW-5GWjY.mjs → run-BblWx9WQ.mjs} +1 -1
- package/dist/{run-b-b_szy6.mjs → run-Dn6ZqBs9.mjs} +384 -26
- package/dist/{scheduler-BxijQDI8.mjs → scheduler-D4CmcA1u.mjs} +1 -1
- package/dist/{serveCommands-CL6B6kEf.mjs → serveCommands-D6NMXj6M.mjs} +5 -5
- package/dist/{serveManager-Dy1ZZNaY.mjs → serveManager-DFt3WQpJ.mjs} +2 -2
- package/dist/{sideband-DywLL2xs.mjs → sideband-DAbvCvgz.mjs} +1 -1
- package/package.json +2 -2
- package/dist/package-BHHMByEa.mjs +0 -64
|
@@ -6,8 +6,8 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { execFile, spawn as spawn$1, execSync as execSync$1, exec as exec$1 } from 'child_process';
|
|
7
7
|
import { randomUUID as randomUUID$1 } from 'crypto';
|
|
8
8
|
import { randomUUID, randomBytes, createHash } from 'node:crypto';
|
|
9
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync, realpathSync, readdirSync, statSync, renameSync,
|
|
10
|
-
import { exec,
|
|
9
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync, rmSync, cpSync, realpathSync, readdirSync, statSync, renameSync, appendFileSync, unlinkSync } from 'node:fs';
|
|
10
|
+
import { exec, spawn, execSync, execFile as execFile$1, execFileSync } from 'node:child_process';
|
|
11
11
|
import { promisify } from 'util';
|
|
12
12
|
import os, { homedir, platform } from 'node:os';
|
|
13
13
|
import { join, extname, resolve, sep, basename, dirname } from 'node:path';
|
|
@@ -1472,9 +1472,13 @@ var codexProvider = /*#__PURE__*/Object.freeze({
|
|
|
1472
1472
|
});
|
|
1473
1473
|
|
|
1474
1474
|
const METHODS_BY_VENDOR = {
|
|
1475
|
-
anthropic: ["anthropic-api-key", "claude-oauth-token", "hypha-proxy", "anthropic-gateway"],
|
|
1476
|
-
openai: ["
|
|
1475
|
+
anthropic: ["anthropic-oauth", "anthropic-api-key", "claude-oauth-token", "hypha-proxy", "anthropic-gateway"],
|
|
1476
|
+
openai: ["chatgpt-oauth", "openai-api-key", "openai-gateway"]
|
|
1477
1477
|
};
|
|
1478
|
+
const OAUTH_METHODS = ["anthropic-oauth", "chatgpt-oauth"];
|
|
1479
|
+
function isOAuthMethod(method) {
|
|
1480
|
+
return OAUTH_METHODS.includes(method);
|
|
1481
|
+
}
|
|
1478
1482
|
function accountsFilePath(svampHome) {
|
|
1479
1483
|
const home = svampHome || process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1480
1484
|
return join(home, "accounts.json");
|
|
@@ -1511,8 +1515,13 @@ function previewSecret(secret) {
|
|
|
1511
1515
|
return `${secret.slice(0, 4)}\u2026${secret.slice(-4)}`;
|
|
1512
1516
|
}
|
|
1513
1517
|
function redactAccount(a) {
|
|
1514
|
-
const { secret, ...rest } = a;
|
|
1515
|
-
|
|
1518
|
+
const { secret, accessToken, refreshToken, ...rest } = a;
|
|
1519
|
+
const oauthConnected = isOAuthMethod(a.method) ? a.method === "anthropic-oauth" ? !!accessToken : true : void 0;
|
|
1520
|
+
return {
|
|
1521
|
+
...rest,
|
|
1522
|
+
secretPreview: previewSecret(secret) ?? (accessToken ? previewSecret(accessToken) : void 0),
|
|
1523
|
+
oauthConnected
|
|
1524
|
+
};
|
|
1516
1525
|
}
|
|
1517
1526
|
function redactAccounts(accounts) {
|
|
1518
1527
|
return accounts.map(redactAccount);
|
|
@@ -1536,7 +1545,7 @@ function validateAccountInput(input) {
|
|
|
1536
1545
|
if (!input.label || !input.label.trim()) {
|
|
1537
1546
|
throw new Error("Account label is required");
|
|
1538
1547
|
}
|
|
1539
|
-
const needsSecret = method !== "hypha-proxy";
|
|
1548
|
+
const needsSecret = method !== "hypha-proxy" && !isOAuthMethod(method);
|
|
1540
1549
|
if (needsSecret && !input.secret && !input.id) {
|
|
1541
1550
|
throw new Error(`A credential is required for method "${method}"`);
|
|
1542
1551
|
}
|
|
@@ -1557,6 +1566,10 @@ function upsertAccount(input, svampHome) {
|
|
|
1557
1566
|
method,
|
|
1558
1567
|
// Keep the old secret if the update left it blank (edit label/model without re-entry).
|
|
1559
1568
|
secret: input.secret && input.secret.length > 0 ? input.secret : existing?.secret,
|
|
1569
|
+
// OAuth token bundle (anthropic-oauth) — carried through on refresh/update; kept if unset.
|
|
1570
|
+
accessToken: input.accessToken ?? existing?.accessToken,
|
|
1571
|
+
refreshToken: input.refreshToken ?? existing?.refreshToken,
|
|
1572
|
+
expiresAt: input.expiresAt ?? existing?.expiresAt,
|
|
1560
1573
|
baseUrl: input.baseUrl?.trim() || void 0,
|
|
1561
1574
|
model: input.model?.trim() || void 0,
|
|
1562
1575
|
isDefault: input.isDefault ?? existing?.isDefault ?? false,
|
|
@@ -1656,6 +1669,16 @@ function resolveAccountEnv(account, env = process.env, svampHome) {
|
|
|
1656
1669
|
},
|
|
1657
1670
|
describe: `claude subscription token (${account.label})`
|
|
1658
1671
|
};
|
|
1672
|
+
case "anthropic-oauth":
|
|
1673
|
+
return {
|
|
1674
|
+
vendor: "anthropic",
|
|
1675
|
+
claudeEnv: {
|
|
1676
|
+
CLAUDE_CODE_OAUTH_TOKEN: account.accessToken,
|
|
1677
|
+
ANTHROPIC_BASE_URL: void 0,
|
|
1678
|
+
ANTHROPIC_API_KEY: void 0
|
|
1679
|
+
},
|
|
1680
|
+
describe: `claude subscription oauth (${account.label})`
|
|
1681
|
+
};
|
|
1659
1682
|
case "openai-api-key":
|
|
1660
1683
|
return {
|
|
1661
1684
|
vendor: "openai",
|
|
@@ -1690,13 +1713,6 @@ function resolveAccountEnv(account, env = process.env, svampHome) {
|
|
|
1690
1713
|
function ensureCodexHome(account, svampHome) {
|
|
1691
1714
|
const dir = accountCodexHomeDir(account.id, svampHome);
|
|
1692
1715
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1693
|
-
const authPath = join(dir, "auth.json");
|
|
1694
|
-
const body = JSON.stringify({ OPENAI_API_KEY: null, tokens: { access_token: account.secret || "" } }, null, 2);
|
|
1695
|
-
writeFileSync(authPath, body + "\n", { encoding: "utf-8", mode: 384 });
|
|
1696
|
-
try {
|
|
1697
|
-
chmodSync(authPath, 384);
|
|
1698
|
-
} catch {
|
|
1699
|
-
}
|
|
1700
1716
|
return dir;
|
|
1701
1717
|
}
|
|
1702
1718
|
async function probeAccount(account, env = process.env) {
|
|
@@ -1747,10 +1763,27 @@ async function probeAccount(account, env = process.env) {
|
|
|
1747
1763
|
if (res.ok || res.status === 400 || res.status === 429) return { ok: true, detail: `Reachable (HTTP ${res.status})` };
|
|
1748
1764
|
return { ok: true, detail: `Reachable, unexpected HTTP ${res.status}` };
|
|
1749
1765
|
}
|
|
1766
|
+
case "anthropic-oauth": {
|
|
1767
|
+
const tok = account.accessToken;
|
|
1768
|
+
if (!tok) return { ok: false, detail: "Not signed in \u2014 start the OAuth flow" };
|
|
1769
|
+
const res = await withTimeout("https://api.anthropic.com/v1/messages", {
|
|
1770
|
+
method: "POST",
|
|
1771
|
+
headers: {
|
|
1772
|
+
"content-type": "application/json",
|
|
1773
|
+
"authorization": `Bearer ${tok}`,
|
|
1774
|
+
"anthropic-version": "2023-06-01",
|
|
1775
|
+
"anthropic-beta": "oauth-2025-04-20"
|
|
1776
|
+
},
|
|
1777
|
+
body: JSON.stringify({ model: "claude-3-5-haiku-20241022", max_tokens: 1, messages: [{ role: "user", content: "hi" }] })
|
|
1778
|
+
});
|
|
1779
|
+
if (res.status === 401 || res.status === 403) return { ok: false, detail: `Sign-in expired or rejected (HTTP ${res.status}) \u2014 reconnect` };
|
|
1780
|
+
if (res.ok || res.status === 400 || res.status === 429) return { ok: true, detail: `Connected (HTTP ${res.status})` };
|
|
1781
|
+
return { ok: true, detail: `Connected, unexpected HTTP ${res.status}` };
|
|
1782
|
+
}
|
|
1750
1783
|
case "claude-oauth-token":
|
|
1751
1784
|
return { ok: !!account.secret, detail: account.secret ? "Token stored \u2014 verified when a session starts (Claude CLI uses CLAUDE_CODE_OAUTH_TOKEN)" : "No token stored" };
|
|
1752
1785
|
case "chatgpt-oauth":
|
|
1753
|
-
return { ok: !!account.
|
|
1786
|
+
return { ok: !!account.model || true, detail: "ChatGPT signed in via device auth \u2014 verified when a Codex session starts (per-account CODEX_HOME)" };
|
|
1754
1787
|
default:
|
|
1755
1788
|
return { ok: false, detail: "Unknown method" };
|
|
1756
1789
|
}
|
|
@@ -1758,6 +1791,242 @@ async function probeAccount(account, env = process.env) {
|
|
|
1758
1791
|
return { ok: false, detail: `Unreachable: ${e?.message || e}` };
|
|
1759
1792
|
}
|
|
1760
1793
|
}
|
|
1794
|
+
async function refreshDueAnthropicAccounts(refresh, log, svampHome, skewMs = 10 * 60 * 1e3) {
|
|
1795
|
+
const due = loadAccounts(svampHome).filter((a) => a.method === "anthropic-oauth" && a.refreshToken && Date.now() >= (a.expiresAt ?? 0) - skewMs);
|
|
1796
|
+
for (const a of due) {
|
|
1797
|
+
try {
|
|
1798
|
+
const t = await refresh(a.refreshToken);
|
|
1799
|
+
upsertAccount({
|
|
1800
|
+
id: a.id,
|
|
1801
|
+
label: a.label,
|
|
1802
|
+
vendor: a.vendor,
|
|
1803
|
+
method: a.method,
|
|
1804
|
+
accessToken: t.accessToken,
|
|
1805
|
+
refreshToken: t.refreshToken,
|
|
1806
|
+
expiresAt: t.expiresAt
|
|
1807
|
+
}, svampHome);
|
|
1808
|
+
log(`[oauth] refreshed anthropic-oauth account ${a.label}`);
|
|
1809
|
+
} catch (e) {
|
|
1810
|
+
log(`[oauth] refresh failed for account ${a.label}: ${e?.message || e}`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
const ANTHROPIC_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
1816
|
+
const ANTHROPIC_AUTH_ENDPOINT = "https://claude.ai/oauth/authorize";
|
|
1817
|
+
const ANTHROPIC_TOKEN_ENDPOINT = "https://platform.claude.com/v1/oauth/token";
|
|
1818
|
+
const ANTHROPIC_REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
|
|
1819
|
+
const ANTHROPIC_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
1820
|
+
function generatePkce() {
|
|
1821
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
1822
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
1823
|
+
return { verifier, challenge };
|
|
1824
|
+
}
|
|
1825
|
+
function buildAnthropicAuthUrl(challenge, state) {
|
|
1826
|
+
const params = new URLSearchParams({
|
|
1827
|
+
response_type: "code",
|
|
1828
|
+
client_id: ANTHROPIC_CLIENT_ID,
|
|
1829
|
+
redirect_uri: ANTHROPIC_REDIRECT_URI,
|
|
1830
|
+
code_challenge: challenge,
|
|
1831
|
+
code_challenge_method: "S256",
|
|
1832
|
+
state,
|
|
1833
|
+
scope: ANTHROPIC_SCOPES,
|
|
1834
|
+
oauth_beta: "oauth-2025-04-20"
|
|
1835
|
+
});
|
|
1836
|
+
return `${ANTHROPIC_AUTH_ENDPOINT}?${params.toString()}`;
|
|
1837
|
+
}
|
|
1838
|
+
function extractAuthCode(raw) {
|
|
1839
|
+
let s = (raw || "").trim();
|
|
1840
|
+
if (s.startsWith("http")) {
|
|
1841
|
+
try {
|
|
1842
|
+
const u = new URL(s);
|
|
1843
|
+
const c = u.searchParams.get("code");
|
|
1844
|
+
if (c) return c;
|
|
1845
|
+
} catch {
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if (s.includes("#")) s = s.split("#")[0];
|
|
1849
|
+
return s.trim();
|
|
1850
|
+
}
|
|
1851
|
+
function tokensFromResponse(j) {
|
|
1852
|
+
const expiresInSec = typeof j.expires_in === "number" ? j.expires_in : 3600;
|
|
1853
|
+
return {
|
|
1854
|
+
accessToken: j.access_token || "",
|
|
1855
|
+
refreshToken: j.refresh_token || "",
|
|
1856
|
+
expiresAt: Date.now() + expiresInSec * 1e3
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
async function exchangeAnthropicCode(code, verifier, state) {
|
|
1860
|
+
const res = await fetch(ANTHROPIC_TOKEN_ENDPOINT, {
|
|
1861
|
+
method: "POST",
|
|
1862
|
+
headers: { "content-type": "application/json" },
|
|
1863
|
+
body: JSON.stringify({
|
|
1864
|
+
grant_type: "authorization_code",
|
|
1865
|
+
code: extractAuthCode(code),
|
|
1866
|
+
redirect_uri: ANTHROPIC_REDIRECT_URI,
|
|
1867
|
+
client_id: ANTHROPIC_CLIENT_ID,
|
|
1868
|
+
code_verifier: verifier,
|
|
1869
|
+
state
|
|
1870
|
+
// Anthropic requires state echoed in the body
|
|
1871
|
+
})
|
|
1872
|
+
});
|
|
1873
|
+
if (!res.ok) {
|
|
1874
|
+
const t = await res.text().catch(() => "");
|
|
1875
|
+
throw new Error(`Token exchange failed (${res.status}): ${t.slice(0, 300)}`);
|
|
1876
|
+
}
|
|
1877
|
+
return tokensFromResponse(await res.json());
|
|
1878
|
+
}
|
|
1879
|
+
async function refreshAnthropicToken(refreshToken) {
|
|
1880
|
+
const res = await fetch(ANTHROPIC_TOKEN_ENDPOINT, {
|
|
1881
|
+
method: "POST",
|
|
1882
|
+
headers: { "content-type": "application/json" },
|
|
1883
|
+
body: JSON.stringify({
|
|
1884
|
+
grant_type: "refresh_token",
|
|
1885
|
+
refresh_token: refreshToken,
|
|
1886
|
+
client_id: ANTHROPIC_CLIENT_ID
|
|
1887
|
+
})
|
|
1888
|
+
});
|
|
1889
|
+
if (!res.ok) {
|
|
1890
|
+
const t = await res.text().catch(() => "");
|
|
1891
|
+
throw new Error(`Token refresh failed (${res.status}): ${t.slice(0, 300)}`);
|
|
1892
|
+
}
|
|
1893
|
+
const tokens = tokensFromResponse(await res.json());
|
|
1894
|
+
if (!tokens.refreshToken) tokens.refreshToken = refreshToken;
|
|
1895
|
+
return tokens;
|
|
1896
|
+
}
|
|
1897
|
+
function parseDeviceAuthOutput(text) {
|
|
1898
|
+
const out = {};
|
|
1899
|
+
const urlMatch = text.match(/https?:\/\/[^\s"'<>)]+/i);
|
|
1900
|
+
if (urlMatch) out.verificationUrl = urlMatch[0].replace(/[.,]$/, "");
|
|
1901
|
+
const codeMatch = text.match(/\b([A-Z0-9]{3,5}(?:-[A-Z0-9]{3,5})+)\b/);
|
|
1902
|
+
if (codeMatch) out.userCode = codeMatch[1];
|
|
1903
|
+
return out;
|
|
1904
|
+
}
|
|
1905
|
+
const flows = /* @__PURE__ */ new Map();
|
|
1906
|
+
const FLOW_TTL_MS = 15 * 60 * 1e3;
|
|
1907
|
+
function oauthFlowDir(flowId, svampHome) {
|
|
1908
|
+
const home = svampHome || process.env.SVAMP_HOME || join(homedir(), ".svamp");
|
|
1909
|
+
return join(home, "oauth-flows", flowId);
|
|
1910
|
+
}
|
|
1911
|
+
function pruneFlows() {
|
|
1912
|
+
const now = Date.now();
|
|
1913
|
+
for (const [id, f] of flows) {
|
|
1914
|
+
if (now - f.createdAt > FLOW_TTL_MS) {
|
|
1915
|
+
try {
|
|
1916
|
+
f.proc?.kill();
|
|
1917
|
+
} catch {
|
|
1918
|
+
}
|
|
1919
|
+
try {
|
|
1920
|
+
if (f.codexHome) rmSync(join(f.codexHome, ".."), { recursive: true, force: true });
|
|
1921
|
+
} catch {
|
|
1922
|
+
}
|
|
1923
|
+
flows.delete(id);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function startAnthropicFlow(label) {
|
|
1928
|
+
pruneFlows();
|
|
1929
|
+
const { verifier, challenge } = generatePkce();
|
|
1930
|
+
const state = randomBytes(32).toString("base64url");
|
|
1931
|
+
const flowId = randomUUID();
|
|
1932
|
+
const authorizeUrl = buildAnthropicAuthUrl(challenge, state);
|
|
1933
|
+
flows.set(flowId, { id: flowId, vendor: "anthropic", label, createdAt: Date.now(), status: "pending", state, verifier });
|
|
1934
|
+
return { flowId, authorizeUrl };
|
|
1935
|
+
}
|
|
1936
|
+
function startOpenAiDeviceFlow(label, log, svampHome) {
|
|
1937
|
+
pruneFlows();
|
|
1938
|
+
const flowId = randomUUID();
|
|
1939
|
+
const codexHome = join(oauthFlowDir(flowId, svampHome), "codex");
|
|
1940
|
+
mkdirSync(codexHome, { recursive: true });
|
|
1941
|
+
const flow = { id: flowId, vendor: "openai", label, createdAt: Date.now(), status: "pending", codexHome, outputBuffer: "" };
|
|
1942
|
+
flows.set(flowId, flow);
|
|
1943
|
+
return new Promise((resolve, reject) => {
|
|
1944
|
+
let settled = false;
|
|
1945
|
+
const proc = spawn("codex", ["login", "--device-auth"], {
|
|
1946
|
+
env: { ...process.env, CODEX_HOME: codexHome },
|
|
1947
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1948
|
+
});
|
|
1949
|
+
flow.proc = proc;
|
|
1950
|
+
const onData = (buf) => {
|
|
1951
|
+
const text = buf.toString();
|
|
1952
|
+
flow.outputBuffer = (flow.outputBuffer || "") + text;
|
|
1953
|
+
const parsed = parseDeviceAuthOutput(flow.outputBuffer);
|
|
1954
|
+
if (parsed.verificationUrl) flow.verificationUrl = parsed.verificationUrl;
|
|
1955
|
+
if (parsed.userCode) flow.userCode = parsed.userCode;
|
|
1956
|
+
if (!settled && flow.verificationUrl && flow.userCode) {
|
|
1957
|
+
settled = true;
|
|
1958
|
+
resolve({ flowId, verificationUrl: flow.verificationUrl, userCode: flow.userCode });
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
proc.stdout?.on("data", onData);
|
|
1962
|
+
proc.stderr?.on("data", onData);
|
|
1963
|
+
proc.on("exit", (code) => {
|
|
1964
|
+
if (code === 0 && flow.codexHome && existsSync(join(flow.codexHome, "auth.json"))) {
|
|
1965
|
+
flow.status = "authorized";
|
|
1966
|
+
} else {
|
|
1967
|
+
flow.status = "error";
|
|
1968
|
+
flow.error = flow.error || `codex login exited ${code}` + (/(device code|workspace admin|not.*enabled)/i.test(flow.outputBuffer || "") ? " \u2014 device-code login may be disabled for this ChatGPT workspace (ask an admin to enable it)." : "");
|
|
1969
|
+
if (!settled) {
|
|
1970
|
+
settled = true;
|
|
1971
|
+
reject(new Error(flow.error));
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
proc.on("error", (err) => {
|
|
1976
|
+
flow.status = "error";
|
|
1977
|
+
flow.error = `Failed to run codex login: ${err.message}. Is the Codex CLI installed on this machine?`;
|
|
1978
|
+
if (!settled) {
|
|
1979
|
+
settled = true;
|
|
1980
|
+
reject(new Error(flow.error));
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
setTimeout(() => {
|
|
1984
|
+
if (!settled) {
|
|
1985
|
+
settled = true;
|
|
1986
|
+
reject(new Error("Timed out waiting for the device code from codex login."));
|
|
1987
|
+
}
|
|
1988
|
+
}, 3e4);
|
|
1989
|
+
log(`[oauth] started codex device-auth flow ${flowId}`);
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
function getFlow(flowId) {
|
|
1993
|
+
return flows.get(flowId);
|
|
1994
|
+
}
|
|
1995
|
+
async function completeAnthropicFlow(flowId, code) {
|
|
1996
|
+
const flow = flows.get(flowId);
|
|
1997
|
+
if (!flow || flow.vendor !== "anthropic" || !flow.verifier || !flow.state) {
|
|
1998
|
+
throw new Error("Invalid or expired OAuth flow");
|
|
1999
|
+
}
|
|
2000
|
+
const tokens = await exchangeAnthropicCode(code, flow.verifier, flow.state);
|
|
2001
|
+
flows.delete(flowId);
|
|
2002
|
+
return tokens;
|
|
2003
|
+
}
|
|
2004
|
+
function finalizeOpenAiFlow(flowId, accountCodexHome) {
|
|
2005
|
+
const flow = flows.get(flowId);
|
|
2006
|
+
if (!flow || flow.vendor !== "openai") throw new Error("Invalid or expired OAuth flow");
|
|
2007
|
+
if (flow.status !== "authorized" || !flow.codexHome) throw new Error("Device authorization not complete yet");
|
|
2008
|
+
mkdirSync(accountCodexHome, { recursive: true });
|
|
2009
|
+
cpSync(flow.codexHome, accountCodexHome, { recursive: true });
|
|
2010
|
+
try {
|
|
2011
|
+
rmSync(join(flow.codexHome, ".."), { recursive: true, force: true });
|
|
2012
|
+
} catch {
|
|
2013
|
+
}
|
|
2014
|
+
flows.delete(flowId);
|
|
2015
|
+
}
|
|
2016
|
+
function cancelFlow(flowId) {
|
|
2017
|
+
const flow = flows.get(flowId);
|
|
2018
|
+
if (!flow) return;
|
|
2019
|
+
flow.status = "cancelled";
|
|
2020
|
+
try {
|
|
2021
|
+
flow.proc?.kill();
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
try {
|
|
2025
|
+
if (flow.codexHome) rmSync(join(flow.codexHome, ".."), { recursive: true, force: true });
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
flows.delete(flowId);
|
|
2029
|
+
}
|
|
1761
2030
|
|
|
1762
2031
|
const FIELD_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
|
|
1763
2032
|
function parseField(token, [min, max]) {
|
|
@@ -3178,6 +3447,73 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
3178
3447
|
if (!acct) return { ok: false, detail: "Account not found" };
|
|
3179
3448
|
return await probeAccount(acct);
|
|
3180
3449
|
},
|
|
3450
|
+
// ── Guided OAuth login (both vendors; admin-gated) ──
|
|
3451
|
+
// Anthropic: PKCE authorize URL → user pastes the shown code back.
|
|
3452
|
+
// OpenAI: device-auth → we show a verification URL + user code, then poll to completion.
|
|
3453
|
+
startBackendOAuth: async (vendor, label, context) => {
|
|
3454
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3455
|
+
const lbl = (label || "").trim() || (vendor === "anthropic" ? "Claude subscription" : "ChatGPT subscription");
|
|
3456
|
+
if (vendor === "anthropic") {
|
|
3457
|
+
const { flowId, authorizeUrl } = startAnthropicFlow(lbl);
|
|
3458
|
+
return { vendor, flowId, mode: "paste-code", authorizeUrl };
|
|
3459
|
+
}
|
|
3460
|
+
if (vendor === "openai") {
|
|
3461
|
+
const { flowId, verificationUrl, userCode } = await startOpenAiDeviceFlow(
|
|
3462
|
+
lbl,
|
|
3463
|
+
(m) => console.log(m),
|
|
3464
|
+
metadata.svampHomeDir
|
|
3465
|
+
);
|
|
3466
|
+
return { vendor, flowId, mode: "device-code", verificationUrl, userCode };
|
|
3467
|
+
}
|
|
3468
|
+
throw new Error(`Unsupported OAuth vendor: ${String(vendor)}`);
|
|
3469
|
+
},
|
|
3470
|
+
completeBackendOAuth: async (flowId, code, context) => {
|
|
3471
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3472
|
+
const flow = getFlow(flowId);
|
|
3473
|
+
if (!flow) throw new Error("OAuth flow expired \u2014 start again");
|
|
3474
|
+
const createdBy = context?.user?.email;
|
|
3475
|
+
if (flow.vendor === "anthropic") {
|
|
3476
|
+
if (!code || !code.trim()) throw new Error("Paste the authorization code");
|
|
3477
|
+
const tokens = await completeAnthropicFlow(flowId, code);
|
|
3478
|
+
const account = upsertAccount({
|
|
3479
|
+
label: flow.label,
|
|
3480
|
+
vendor: "anthropic",
|
|
3481
|
+
method: "anthropic-oauth",
|
|
3482
|
+
accessToken: tokens.accessToken,
|
|
3483
|
+
refreshToken: tokens.refreshToken,
|
|
3484
|
+
expiresAt: tokens.expiresAt,
|
|
3485
|
+
createdBy
|
|
3486
|
+
}, metadata.svampHomeDir);
|
|
3487
|
+
notifyListeners({ type: "update-machine", machineId, backendAccountsChanged: true });
|
|
3488
|
+
return { done: true, account: redactAccount(account) };
|
|
3489
|
+
}
|
|
3490
|
+
if (flow.status === "pending") {
|
|
3491
|
+
return { done: false, status: "pending", verificationUrl: flow.verificationUrl, userCode: flow.userCode };
|
|
3492
|
+
}
|
|
3493
|
+
if (flow.status === "error") throw new Error(flow.error || "Device authorization failed");
|
|
3494
|
+
if (flow.status === "authorized") {
|
|
3495
|
+
const account = upsertAccount({
|
|
3496
|
+
label: flow.label,
|
|
3497
|
+
vendor: "openai",
|
|
3498
|
+
method: "chatgpt-oauth",
|
|
3499
|
+
createdBy
|
|
3500
|
+
}, metadata.svampHomeDir);
|
|
3501
|
+
try {
|
|
3502
|
+
finalizeOpenAiFlow(flowId, accountCodexHomeDir(account.id, metadata.svampHomeDir));
|
|
3503
|
+
} catch (e) {
|
|
3504
|
+
deleteAccount(account.id, metadata.svampHomeDir);
|
|
3505
|
+
throw e;
|
|
3506
|
+
}
|
|
3507
|
+
notifyListeners({ type: "update-machine", machineId, backendAccountsChanged: true });
|
|
3508
|
+
return { done: true, account: redactAccount(account) };
|
|
3509
|
+
}
|
|
3510
|
+
throw new Error("Device authorization was cancelled");
|
|
3511
|
+
},
|
|
3512
|
+
cancelBackendOAuth: async (flowId, context) => {
|
|
3513
|
+
authorizeRequest(context, currentMetadata.sharing, "admin");
|
|
3514
|
+
cancelFlow(flowId);
|
|
3515
|
+
return { success: true };
|
|
3516
|
+
},
|
|
3181
3517
|
// Register a listener for real-time updates (app calls this with _rintf callback)
|
|
3182
3518
|
registerListener: async (callback, context) => {
|
|
3183
3519
|
trackInbound();
|
|
@@ -3619,7 +3955,7 @@ async function registerMachineService(server, machineId, metadata, daemonState,
|
|
|
3619
3955
|
const tunnels = handlers.tunnels;
|
|
3620
3956
|
if (!tunnels) throw new Error("Tunnel management not available");
|
|
3621
3957
|
if (tunnels.has(params.name)) throw new Error(`Tunnel '${params.name}' already running`);
|
|
3622
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
3958
|
+
const { FrpcTunnel } = await import('./frpc-Dch3Eq_f.mjs');
|
|
3623
3959
|
const tunnel = new FrpcTunnel({
|
|
3624
3960
|
name: params.name,
|
|
3625
3961
|
ports: params.ports,
|
|
@@ -4086,7 +4422,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
4086
4422
|
}
|
|
4087
4423
|
const deps = buildSessionDeps(rpc, { cwd, ownerEmail: owner });
|
|
4088
4424
|
const sender = { name: context?.user?.email || context?.user?.id || "user", kind: "user", verified: true };
|
|
4089
|
-
const { toolsForRole } = await import('./sideband-
|
|
4425
|
+
const { toolsForRole } = await import('./sideband-DAbvCvgz.mjs');
|
|
4090
4426
|
const r2 = await runWiseAgent({ message: params.message, sender, config: { tools: toolsForRole(role2) }, deps, transport, model: resolved.model });
|
|
4091
4427
|
return fmt(r2);
|
|
4092
4428
|
}
|
|
@@ -4185,7 +4521,7 @@ QUESTION: ${params.question || "Summarize this concisely."}` }
|
|
|
4185
4521
|
if (r.error || !r.sender) return { error: r.error || "unauthorized" };
|
|
4186
4522
|
const callId = "call_" + Math.random().toString(16).slice(2, 12);
|
|
4187
4523
|
const rendered = renderMessage(c, { sender: r.sender, body: { message: kwargs.message }, callId });
|
|
4188
|
-
const { queryCore } = await import('./commands-
|
|
4524
|
+
const { queryCore } = await import('./commands-BpoCTRmH.mjs');
|
|
4189
4525
|
const timeout = c.reply?.timeout_sec || 120;
|
|
4190
4526
|
let result;
|
|
4191
4527
|
try {
|
|
@@ -14265,7 +14601,7 @@ async function startDaemon(options) {
|
|
|
14265
14601
|
try {
|
|
14266
14602
|
const dir = loadSessionIndex()[sessionId]?.directory;
|
|
14267
14603
|
if (!dir) return;
|
|
14268
|
-
const { reconcileServiceLinks } = await import('./agentCommands-
|
|
14604
|
+
const { reconcileServiceLinks } = await import('./agentCommands-C-GPVVEh.mjs');
|
|
14269
14605
|
const configPath = getSvampConfigPath(dir, sessionId);
|
|
14270
14606
|
const config = readSvampConfig(configPath);
|
|
14271
14607
|
const entries = Array.from(urls.entries());
|
|
@@ -14283,7 +14619,7 @@ async function startDaemon(options) {
|
|
|
14283
14619
|
}
|
|
14284
14620
|
}
|
|
14285
14621
|
async function createExposedTunnel(spec) {
|
|
14286
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
14622
|
+
const { FrpcTunnel } = await import('./frpc-Dch3Eq_f.mjs');
|
|
14287
14623
|
const tunnel = new FrpcTunnel({
|
|
14288
14624
|
name: spec.name,
|
|
14289
14625
|
ports: spec.ports,
|
|
@@ -14304,7 +14640,7 @@ async function startDaemon(options) {
|
|
|
14304
14640
|
}
|
|
14305
14641
|
const tunnelRecreateState = /* @__PURE__ */ new Map();
|
|
14306
14642
|
const tunnelRecreateInFlight = /* @__PURE__ */ new Set();
|
|
14307
|
-
const { ServeManager } = await import('./serveManager-
|
|
14643
|
+
const { ServeManager } = await import('./serveManager-DFt3WQpJ.mjs');
|
|
14308
14644
|
const serveManager = new ServeManager(SVAMP_HOME, (msg) => logger.log(`[SERVE] ${msg}`), hyphaServerUrl);
|
|
14309
14645
|
ensureAutoInstalledSkills(logger).catch(() => {
|
|
14310
14646
|
});
|
|
@@ -16451,11 +16787,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16451
16787
|
});
|
|
16452
16788
|
},
|
|
16453
16789
|
onIssue: async (params) => {
|
|
16454
|
-
const { issueRpc } = await import('./rpc-
|
|
16790
|
+
const { issueRpc } = await import('./rpc-BctBAWZg.mjs');
|
|
16455
16791
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
16456
16792
|
},
|
|
16457
16793
|
onWorkflow: async (params) => {
|
|
16458
|
-
const { workflowRpc } = await import('./rpc-
|
|
16794
|
+
const { workflowRpc } = await import('./rpc-iRm5bxwr.mjs');
|
|
16459
16795
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
16460
16796
|
},
|
|
16461
16797
|
onRipgrep: async (args, cwd) => {
|
|
@@ -16755,6 +17091,7 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16755
17091
|
const allowedBashLiterals = /* @__PURE__ */ new Set();
|
|
16756
17092
|
const allowedBashPrefixes = /* @__PURE__ */ new Set();
|
|
16757
17093
|
const EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "Write", "NotebookEdit"]);
|
|
17094
|
+
let appliedAgentModel;
|
|
16758
17095
|
const { store: sessionService, rpcHandlers: sessionRPCHandlersAcp } = createSessionStore(
|
|
16759
17096
|
server,
|
|
16760
17097
|
sessionId,
|
|
@@ -16783,6 +17120,15 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
16783
17120
|
if (msgMeta?.permissionMode && msgMeta.permissionMode !== currentPermissionMode) {
|
|
16784
17121
|
logger.log(`[${agentName} Session ${sessionId}] Ignoring meta.permissionMode='${msgMeta.permissionMode}' from sendMessage (current: ${currentPermissionMode}; use switchMode to change)`);
|
|
16785
17122
|
}
|
|
17123
|
+
{
|
|
17124
|
+
const rawModel = typeof msgMeta?.model === "string" && msgMeta.model ? msgMeta.model : void 0;
|
|
17125
|
+
const desiredModel = rawModel || agentConfig?.default_model || void 0;
|
|
17126
|
+
if (desiredModel !== appliedAgentModel) {
|
|
17127
|
+
agentBackend?.setModel?.(desiredModel);
|
|
17128
|
+
appliedAgentModel = desiredModel;
|
|
17129
|
+
logger.log(`[${agentName} Session ${sessionId}] Model \u2192 ${desiredModel ?? "backend default"}`);
|
|
17130
|
+
}
|
|
17131
|
+
}
|
|
16786
17132
|
if (!acpBackendReady) {
|
|
16787
17133
|
logger.log(`[${agentName} Session ${sessionId}] Backend not ready \u2014 queuing message`);
|
|
16788
17134
|
const existingQueue = sessionMetadata.messageQueue || [];
|
|
@@ -17015,11 +17361,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
|
|
|
17015
17361
|
});
|
|
17016
17362
|
},
|
|
17017
17363
|
onIssue: async (params) => {
|
|
17018
|
-
const { issueRpc } = await import('./rpc-
|
|
17364
|
+
const { issueRpc } = await import('./rpc-BctBAWZg.mjs');
|
|
17019
17365
|
return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
|
|
17020
17366
|
},
|
|
17021
17367
|
onWorkflow: async (params) => {
|
|
17022
|
-
const { workflowRpc } = await import('./rpc-
|
|
17368
|
+
const { workflowRpc } = await import('./rpc-iRm5bxwr.mjs');
|
|
17023
17369
|
return workflowRpc(params?.cwd || directory, params || {});
|
|
17024
17370
|
},
|
|
17025
17371
|
onRipgrep: async (args, cwd) => {
|
|
@@ -18055,7 +18401,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
18055
18401
|
const PING_TIMEOUT_MS = 15e3;
|
|
18056
18402
|
const POST_RECONNECT_GRACE_MS = 2e4;
|
|
18057
18403
|
const RECONNECT_JITTER_MS = 2500;
|
|
18058
|
-
const { WorkflowScheduler } = await import('./scheduler-
|
|
18404
|
+
const { WorkflowScheduler } = await import('./scheduler-D4CmcA1u.mjs');
|
|
18059
18405
|
const workflowScheduler = new WorkflowScheduler({
|
|
18060
18406
|
projectRoots: () => {
|
|
18061
18407
|
const dirs = /* @__PURE__ */ new Set();
|
|
@@ -18073,6 +18419,18 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
|
|
|
18073
18419
|
logger.log(`[workflow] scheduler tick error: ${e?.message || e}`);
|
|
18074
18420
|
}
|
|
18075
18421
|
}, 2e4);
|
|
18422
|
+
const runOAuthAccountRefresh = () => {
|
|
18423
|
+
refreshDueAnthropicAccounts(
|
|
18424
|
+
async (rt) => {
|
|
18425
|
+
const t = await refreshAnthropicToken(rt);
|
|
18426
|
+
return t;
|
|
18427
|
+
},
|
|
18428
|
+
(m) => logger.log(m),
|
|
18429
|
+
SVAMP_HOME
|
|
18430
|
+
).catch((e) => logger.log(`[oauth] account refresh error: ${e?.message || e}`));
|
|
18431
|
+
};
|
|
18432
|
+
runOAuthAccountRefresh();
|
|
18433
|
+
const backendAccountRefreshInterval = setInterval(runOAuthAccountRefresh, 5 * 6e4);
|
|
18076
18434
|
let heartbeatRunning = false;
|
|
18077
18435
|
let lastReconnectAt = 0;
|
|
18078
18436
|
let heartbeatCycle = 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as resolveProjectRoot, F as listWorkflows, G as isWorkflowEnabled, H as workflowCrons, A as runWorkflow, I as cronMatches } from './run-
|
|
1
|
+
import { l as resolveProjectRoot, F as listWorkflows, G as isWorkflowEnabled, H as workflowCrons, A as runWorkflow, I as cronMatches } from './run-Dn6ZqBs9.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -54,7 +54,7 @@ async function handleServeCommand() {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
async function serveAdd(args, machineId) {
|
|
57
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
57
|
+
const { connectAndGetMachine } = await import('./commands-BpoCTRmH.mjs');
|
|
58
58
|
const pos = positionalArgs(args);
|
|
59
59
|
const name = pos[0];
|
|
60
60
|
if (!name) {
|
|
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
async function serveApply(args, machineId) {
|
|
96
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
96
|
+
const { connectAndGetMachine } = await import('./commands-BpoCTRmH.mjs');
|
|
97
97
|
const fs = await import('fs');
|
|
98
98
|
const yaml = await import('yaml');
|
|
99
99
|
const file = positionalArgs(args)[0];
|
|
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
async function serveRemove(args, machineId) {
|
|
185
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
185
|
+
const { connectAndGetMachine } = await import('./commands-BpoCTRmH.mjs');
|
|
186
186
|
const pos = positionalArgs(args);
|
|
187
187
|
const name = pos[0];
|
|
188
188
|
if (!name) {
|
|
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
async function serveList(args, machineId) {
|
|
205
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
205
|
+
const { connectAndGetMachine } = await import('./commands-BpoCTRmH.mjs');
|
|
206
206
|
const all = hasFlag(args, "--all", "-a");
|
|
207
207
|
const json = hasFlag(args, "--json");
|
|
208
208
|
const sessionId = getFlag(args, "--session");
|
|
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
async function serveInfo(machineId) {
|
|
238
|
-
const { connectAndGetMachine } = await import('./commands-
|
|
238
|
+
const { connectAndGetMachine } = await import('./commands-BpoCTRmH.mjs');
|
|
239
239
|
const { machine, server } = await connectAndGetMachine(machineId);
|
|
240
240
|
try {
|
|
241
241
|
const info = await machine.serveInfo();
|
|
@@ -5,7 +5,7 @@ import * as http from 'http';
|
|
|
5
5
|
import * as net from 'net';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { serveStaticMount } from './staticFileServer-CbYnj2bH.mjs';
|
|
8
|
-
import { j as getHyphaServerUrl, S as ServeAuth, k as hasCookieToken } from './run-
|
|
8
|
+
import { j as getHyphaServerUrl, S as ServeAuth, k as hasCookieToken } from './run-Dn6ZqBs9.mjs';
|
|
9
9
|
import 'os';
|
|
10
10
|
import 'fs/promises';
|
|
11
11
|
import 'url';
|
|
@@ -854,7 +854,7 @@ Connection: close\r
|
|
|
854
854
|
const mount = this.mounts.get(mountName);
|
|
855
855
|
const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
|
|
856
856
|
try {
|
|
857
|
-
const { FrpcTunnel } = await import('./frpc-
|
|
857
|
+
const { FrpcTunnel } = await import('./frpc-Dch3Eq_f.mjs');
|
|
858
858
|
let tunnel;
|
|
859
859
|
tunnel = new FrpcTunnel({
|
|
860
860
|
name: tunnelName,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as READ_ONLY_TOOLS, L as loadMachineContext, M as buildMachineInstructions, N as machineToolsForRole, O as buildMachineTools } from './run-
|
|
1
|
+
import { R as READ_ONLY_TOOLS, L as loadMachineContext, M as buildMachineInstructions, N as machineToolsForRole, O as buildMachineTools } from './run-Dn6ZqBs9.mjs';
|
|
2
2
|
import 'node:child_process';
|
|
3
3
|
import 'os';
|
|
4
4
|
import 'fs/promises';
|