svamp-cli 0.2.265 → 0.2.266

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.
Files changed (26) hide show
  1. package/dist/{agentCommands-CKd1jXfX.mjs → agentCommands-aCeb0xu5.mjs} +5 -5
  2. package/dist/{auth-Ctsyrv9N.mjs → auth-BZ1HwSaj.mjs} +1 -1
  3. package/dist/{cli-DtMJLEig.mjs → cli-DyIeeAe9.mjs} +63 -63
  4. package/dist/cli.mjs +2 -2
  5. package/dist/{commands-C_74tXoO.mjs → commands-BX6_mFbE.mjs} +1 -1
  6. package/dist/{commands-CMZS3jBt.mjs → commands-BgjoBiDv.mjs} +2 -2
  7. package/dist/{commands-De9crCdY.mjs → commands-Bp7U7HSm.mjs} +1 -1
  8. package/dist/{commands-BUhsW3bf.mjs → commands-BqYexHaV.mjs} +1 -1
  9. package/dist/{commands-Bx4WYv3I.mjs → commands-CWZSjny_.mjs} +6 -6
  10. package/dist/{commands-BSBxY9iQ.mjs → commands-f4bL9Ar6.mjs} +2 -2
  11. package/dist/{commands-BI7IDVIE.mjs → commands-mmoabAhq.mjs} +2 -2
  12. package/dist/{fleet-D3ycNACq.mjs → fleet-CvycbTRs.mjs} +1 -1
  13. package/dist/{frpc-Cwn_s0Fs.mjs → frpc-CxqX4sQ7.mjs} +1 -1
  14. package/dist/{headlessCli-nWnqozcZ.mjs → headlessCli-Bf9mDfRN.mjs} +2 -2
  15. package/dist/index.mjs +1 -1
  16. package/dist/package-CfLZkKfk.mjs +64 -0
  17. package/dist/{rpc-BnHMvDxQ.mjs → rpc-BB9tE2dR.mjs} +1 -1
  18. package/dist/{rpc-DwlZTOtK.mjs → rpc-BXmmzMmw.mjs} +1 -1
  19. package/dist/{run-DW-5GWjY.mjs → run-Byjz649i.mjs} +1 -1
  20. package/dist/{run-b-b_szy6.mjs → run-CetRvMab.mjs} +374 -26
  21. package/dist/{scheduler-BxijQDI8.mjs → scheduler-Xhs6PCCU.mjs} +1 -1
  22. package/dist/{serveCommands-CL6B6kEf.mjs → serveCommands-DGOH5VP9.mjs} +5 -5
  23. package/dist/{serveManager-Dy1ZZNaY.mjs → serveManager-Bk6Fkoha.mjs} +2 -2
  24. package/dist/{sideband-DywLL2xs.mjs → sideband-CWu6yjFl.mjs} +1 -1
  25. package/package.json +2 -2
  26. 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, rmSync, appendFileSync, unlinkSync } from 'node:fs';
10
- import { exec, execSync, spawn, execFile as execFile$1, execFileSync } from 'node:child_process';
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: ["openai-api-key", "chatgpt-oauth", "openai-gateway"]
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
- return { ...rest, secretPreview: previewSecret(secret) };
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.secret, detail: account.secret ? "Token stored \u2014 verified when a Codex session starts (per-account CODEX_HOME)" : "No token stored" };
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-Cwn_s0Fs.mjs');
3958
+ const { FrpcTunnel } = await import('./frpc-CxqX4sQ7.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-DywLL2xs.mjs');
4425
+ const { toolsForRole } = await import('./sideband-CWu6yjFl.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-C_74tXoO.mjs');
4524
+ const { queryCore } = await import('./commands-BX6_mFbE.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-CKd1jXfX.mjs');
14604
+ const { reconcileServiceLinks } = await import('./agentCommands-aCeb0xu5.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-Cwn_s0Fs.mjs');
14622
+ const { FrpcTunnel } = await import('./frpc-CxqX4sQ7.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-Dy1ZZNaY.mjs');
14643
+ const { ServeManager } = await import('./serveManager-Bk6Fkoha.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-BnHMvDxQ.mjs');
16790
+ const { issueRpc } = await import('./rpc-BB9tE2dR.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-DwlZTOtK.mjs');
16794
+ const { workflowRpc } = await import('./rpc-BXmmzMmw.mjs');
16459
16795
  return workflowRpc(params?.cwd || directory, params || {});
16460
16796
  },
16461
16797
  onRipgrep: async (args, cwd) => {
@@ -17015,11 +17351,11 @@ ${capturedError}${buildClaudeErrorHint(capturedError)}`;
17015
17351
  });
17016
17352
  },
17017
17353
  onIssue: async (params) => {
17018
- const { issueRpc } = await import('./rpc-BnHMvDxQ.mjs');
17354
+ const { issueRpc } = await import('./rpc-BB9tE2dR.mjs');
17019
17355
  return issueRpc(params?.cwd || directory, params || {}, { notifySession: notifyIssueOwner, rekickLoopOwner });
17020
17356
  },
17021
17357
  onWorkflow: async (params) => {
17022
- const { workflowRpc } = await import('./rpc-DwlZTOtK.mjs');
17358
+ const { workflowRpc } = await import('./rpc-BXmmzMmw.mjs');
17023
17359
  return workflowRpc(params?.cwd || directory, params || {});
17024
17360
  },
17025
17361
  onRipgrep: async (args, cwd) => {
@@ -18055,7 +18391,7 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
18055
18391
  const PING_TIMEOUT_MS = 15e3;
18056
18392
  const POST_RECONNECT_GRACE_MS = 2e4;
18057
18393
  const RECONNECT_JITTER_MS = 2500;
18058
- const { WorkflowScheduler } = await import('./scheduler-BxijQDI8.mjs');
18394
+ const { WorkflowScheduler } = await import('./scheduler-Xhs6PCCU.mjs');
18059
18395
  const workflowScheduler = new WorkflowScheduler({
18060
18396
  projectRoots: () => {
18061
18397
  const dirs = /* @__PURE__ */ new Set();
@@ -18073,6 +18409,18 @@ ${oracle.output.trim().slice(0, 500)}`, "\u{1F501} Continuing loop");
18073
18409
  logger.log(`[workflow] scheduler tick error: ${e?.message || e}`);
18074
18410
  }
18075
18411
  }, 2e4);
18412
+ const runOAuthAccountRefresh = () => {
18413
+ refreshDueAnthropicAccounts(
18414
+ async (rt) => {
18415
+ const t = await refreshAnthropicToken(rt);
18416
+ return t;
18417
+ },
18418
+ (m) => logger.log(m),
18419
+ SVAMP_HOME
18420
+ ).catch((e) => logger.log(`[oauth] account refresh error: ${e?.message || e}`));
18421
+ };
18422
+ runOAuthAccountRefresh();
18423
+ const backendAccountRefreshInterval = setInterval(runOAuthAccountRefresh, 5 * 6e4);
18076
18424
  let heartbeatRunning = false;
18077
18425
  let lastReconnectAt = 0;
18078
18426
  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-b-b_szy6.mjs';
1
+ import { l as resolveProjectRoot, F as listWorkflows, G as isWorkflowEnabled, H as workflowCrons, A as runWorkflow, I as cronMatches } from './run-CetRvMab.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-C_74tXoO.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-BX6_mFbE.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-C_74tXoO.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-BX6_mFbE.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-C_74tXoO.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-BX6_mFbE.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-C_74tXoO.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-BX6_mFbE.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-C_74tXoO.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-BX6_mFbE.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-b-b_szy6.mjs';
8
+ import { j as getHyphaServerUrl, S as ServeAuth, k as hasCookieToken } from './run-CetRvMab.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-Cwn_s0Fs.mjs');
857
+ const { FrpcTunnel } = await import('./frpc-CxqX4sQ7.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-b-b_szy6.mjs';
1
+ import { R as READ_ONLY_TOOLS, L as loadMachineContext, M as buildMachineInstructions, N as machineToolsForRole, O as buildMachineTools } from './run-CetRvMab.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.265",
3
+ "version": "0.2.266",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-backend-accounts.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-app-server.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-workflow-idle.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-ws-upgrade.mjs && npx tsx test/test-serve-auth.mjs && npx tsx test/test-static-file-server.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-outbox-reload.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-graceful-restart.mjs && npx tsx test/test-flush-exit.mjs",
23
+ "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-backend-accounts.mjs && npx tsx test/test-backend-oauth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-app-server.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-workflow-idle.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-ws-upgrade.mjs && npx tsx test/test-serve-auth.mjs && npx tsx test/test-static-file-server.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-outbox-reload.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-graceful-restart.mjs && npx tsx test/test-flush-exit.mjs",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",