@spekoai/mcp-calls 0.2.1 → 0.3.1

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 CHANGED
@@ -15,9 +15,12 @@ Desktop, and any MCP client. Powered by [Speko](https://speko.ai).
15
15
  npx @spekoai/mcp-calls@latest init
16
16
  ```
17
17
 
18
- The wizard opens the Speko dashboard for an API key, verifies it, writes the MCP into your
19
- client config (Claude Code or Claude Desktop), and installs a companion skill. Then just ask
20
- your agent to call a business.
18
+ The wizard signs you in **with your browser** (OAuth — no key to copy or paste), fetches your
19
+ key automatically, writes the MCP into your client config (Claude Code or Claude Desktop), and
20
+ installs a companion skill. Then just ask your agent to call a business.
21
+
22
+ Already have a key, or on a headless box? `--token sk_...` or `--paste` skips the browser.
23
+ Re-authenticate anytime with `npx @spekoai/mcp-calls login`.
21
24
 
22
25
  It runs **single-process**: give it your `SPEKO_API_KEY` and it calls `api.speko.dev`
23
26
  directly — no separate server to run.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // ../server/dist/config.js
13
- import { createHash } from "crypto";
13
+ import { createHash as createHash2 } from "crypto";
14
14
  import { existsSync as existsSync3 } from "fs";
15
15
  import { dirname as dirname3, resolve as resolve3 } from "path";
16
16
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -93,7 +93,7 @@ function loadConfig() {
93
93
  return cached;
94
94
  }
95
95
  function serverBearerHash(cfg) {
96
- return createHash("sha256").update(cfg.speko.apiKey, "utf-8").digest("hex").slice(0, 16);
96
+ return createHash2("sha256").update(cfg.speko.apiKey, "utf-8").digest("hex").slice(0, 16);
97
97
  }
98
98
  var ConfigError, cached;
99
99
  var init_config = __esm({
@@ -842,9 +842,9 @@ var init_objective = __esm({
842
842
  });
843
843
 
844
844
  // ../server/dist/safety/prompt.js
845
- import { randomBytes } from "crypto";
845
+ import { randomBytes as randomBytes2 } from "crypto";
846
846
  function delimitedBlock(label, content) {
847
- const nonce = randomBytes(8).toString("hex");
847
+ const nonce = randomBytes2(8).toString("hex");
848
848
  return `${BLOCK_RULE} ${label} ${nonce} ${BLOCK_RULE}
849
849
  ${content}
850
850
  ${BLOCK_RULE} END ${label} ${nonce} ${BLOCK_RULE}`;
@@ -1374,13 +1374,191 @@ var init_core = __esm({
1374
1374
  import { MCPServer } from "mcp-framework";
1375
1375
 
1376
1376
  // src/cli/init.ts
1377
- import { spawn, spawnSync } from "child_process";
1377
+ import { spawn as spawn2, spawnSync } from "child_process";
1378
1378
  import { createInterface } from "readline";
1379
1379
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1380
- import { homedir, platform } from "os";
1380
+ import { homedir, platform as platform2 } from "os";
1381
1381
  import { dirname, join, resolve } from "path";
1382
1382
  import { fileURLToPath } from "url";
1383
+
1384
+ // src/cli/login.ts
1385
+ import { createServer } from "http";
1386
+ import { randomBytes, createHash } from "crypto";
1387
+ import { spawn } from "child_process";
1388
+ import { platform } from "os";
1383
1389
  var API_BASE = (process.env.SPEKOAI_API_URL || "https://api.speko.dev").replace(/\/+$/, "");
1390
+ var AUTH_DISCOVERY = process.env.SPEKO_OAUTH_DISCOVERY || "https://platform.speko.dev/.well-known/oauth-authorization-server/api/auth";
1391
+ var LOGIN_TIMEOUT_MS = 5 * 6e4;
1392
+ function b64url(buf) {
1393
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1394
+ }
1395
+ function escapeHtml(s) {
1396
+ return s.replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]);
1397
+ }
1398
+ function resultPage(title, body) {
1399
+ return `<!doctype html><meta charset="utf-8"><title>${escapeHtml(title)}</title>
1400
+ <body style="font-family:system-ui,-apple-system,sans-serif;max-width:30rem;margin:18vh auto;text-align:center;color:#111">
1401
+ <div style="font-size:2.5rem">\u{1F4DE}</div>
1402
+ <h1 style="font-size:1.35rem;margin:.5rem 0">${escapeHtml(title)}</h1>
1403
+ <p style="color:#555;line-height:1.5">${escapeHtml(body)}</p></body>`;
1404
+ }
1405
+ function openBrowser(url) {
1406
+ if (["1", "true", "yes"].includes((process.env.SPEKO_NO_BROWSER ?? "").toLowerCase())) return;
1407
+ try {
1408
+ const p = platform();
1409
+ const cmd2 = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
1410
+ const args = p === "win32" ? ["/c", "start", "", url] : [url];
1411
+ const child = spawn(cmd2, args, { stdio: "ignore", detached: true });
1412
+ child.on("error", () => {
1413
+ });
1414
+ child.unref();
1415
+ } catch {
1416
+ }
1417
+ }
1418
+ async function discover() {
1419
+ const r = await fetch(AUTH_DISCOVERY, { signal: AbortSignal.timeout(15e3) });
1420
+ if (!r.ok) throw new Error(`OAuth discovery failed (HTTP ${r.status}) at ${AUTH_DISCOVERY}`);
1421
+ const d = await r.json();
1422
+ if (!d.authorization_endpoint || !d.token_endpoint || !d.registration_endpoint || !d.issuer) {
1423
+ throw new Error("OAuth discovery doc is missing required endpoints");
1424
+ }
1425
+ return d;
1426
+ }
1427
+ async function registerClient(registrationEndpoint, redirectUri) {
1428
+ const r = await fetch(registrationEndpoint, {
1429
+ method: "POST",
1430
+ headers: { "content-type": "application/json" },
1431
+ body: JSON.stringify({
1432
+ client_name: "Speko Calls CLI",
1433
+ redirect_uris: [redirectUri],
1434
+ grant_types: ["authorization_code"],
1435
+ response_types: ["code"],
1436
+ token_endpoint_auth_method: "none",
1437
+ type: "native",
1438
+ scope: "openid profile email"
1439
+ }),
1440
+ signal: AbortSignal.timeout(15e3)
1441
+ });
1442
+ if (!r.ok) throw new Error(`client registration failed (HTTP ${r.status})`);
1443
+ const j = await r.json();
1444
+ if (!j.client_id) throw new Error("client registration returned no client_id");
1445
+ return j.client_id;
1446
+ }
1447
+ async function fetchOrgKey(bearer2) {
1448
+ const r = await fetch(`${API_BASE}/v1/api-keys/organization-credentials`, {
1449
+ headers: { authorization: `Bearer ${bearer2}` },
1450
+ signal: AbortSignal.timeout(15e3)
1451
+ });
1452
+ if (r.status === 403) {
1453
+ throw new Error("your account has no organization yet \u2014 finish signup at platform.speko.dev, then retry");
1454
+ }
1455
+ if (!r.ok) {
1456
+ const body = await r.text().catch(() => "");
1457
+ throw new Error(`couldn't fetch your API key (HTTP ${r.status})${body ? `: ${body.slice(0, 160)}` : ""}`);
1458
+ }
1459
+ const j = await r.json();
1460
+ const key = j.mcpApiKey?.key;
1461
+ if (!key) throw new Error("API-key response was missing mcpApiKey.key");
1462
+ return key;
1463
+ }
1464
+ function startLoopback(expectedState) {
1465
+ return new Promise((resolve4, reject) => {
1466
+ let resolveCode;
1467
+ let rejectCode;
1468
+ const waitForCode = new Promise((res, rej) => {
1469
+ resolveCode = res;
1470
+ rejectCode = rej;
1471
+ });
1472
+ const timeout = setTimeout(
1473
+ () => rejectCode(new Error("login timed out (5 min) \u2014 no redirect received")),
1474
+ LOGIN_TIMEOUT_MS
1475
+ );
1476
+ if (typeof timeout.unref === "function") timeout.unref();
1477
+ const server2 = createServer((req, res) => {
1478
+ const u = new URL(req.url ?? "/", "http://127.0.0.1");
1479
+ if (u.pathname !== "/callback") {
1480
+ res.writeHead(404);
1481
+ res.end();
1482
+ return;
1483
+ }
1484
+ const send = (status, title, body) => {
1485
+ res.writeHead(status, { "content-type": "text/html; charset=utf-8" });
1486
+ res.end(resultPage(title, body));
1487
+ };
1488
+ const err = u.searchParams.get("error");
1489
+ const code = u.searchParams.get("code");
1490
+ const state = u.searchParams.get("state");
1491
+ clearTimeout(timeout);
1492
+ if (err) {
1493
+ send(400, "Sign-in failed", `Authorization was denied (${err}). You can close this tab and try again.`);
1494
+ rejectCode(new Error(`authorization denied: ${err}`));
1495
+ return;
1496
+ }
1497
+ if (!code || state !== expectedState) {
1498
+ send(400, "Sign-in failed", "The response was invalid or didn't match. Close this tab and re-run the login.");
1499
+ rejectCode(new Error("state mismatch or missing authorization code"));
1500
+ return;
1501
+ }
1502
+ send(200, "You're connected \u2713", "Speko Calls is signed in. You can close this tab and return to your terminal.");
1503
+ resolveCode(code);
1504
+ });
1505
+ server2.on("error", reject);
1506
+ server2.listen(0, "127.0.0.1", () => {
1507
+ const port = server2.address().port;
1508
+ resolve4({ server: server2, redirectUri: `http://127.0.0.1:${port}/callback`, waitForCode });
1509
+ });
1510
+ });
1511
+ }
1512
+ async function browserLogin(log = () => {
1513
+ }) {
1514
+ const disc = await discover();
1515
+ const verifier = b64url(randomBytes(32));
1516
+ const challenge = b64url(createHash("sha256").update(verifier).digest());
1517
+ const state = b64url(randomBytes(16));
1518
+ const { server: server2, redirectUri, waitForCode } = await startLoopback(state);
1519
+ try {
1520
+ const clientId = await registerClient(disc.registration_endpoint, redirectUri);
1521
+ const authUrl = new URL(disc.authorization_endpoint);
1522
+ authUrl.searchParams.set("response_type", "code");
1523
+ authUrl.searchParams.set("client_id", clientId);
1524
+ authUrl.searchParams.set("redirect_uri", redirectUri);
1525
+ authUrl.searchParams.set("scope", "openid profile email");
1526
+ authUrl.searchParams.set("state", state);
1527
+ authUrl.searchParams.set("code_challenge", challenge);
1528
+ authUrl.searchParams.set("code_challenge_method", "S256");
1529
+ log("Opening your browser to sign in to Speko\u2026");
1530
+ log(`If it doesn't open, paste this URL into your browser:
1531
+ ${authUrl.toString()}`);
1532
+ openBrowser(authUrl.toString());
1533
+ log("Waiting for you to finish signing in\u2026");
1534
+ const code = await waitForCode;
1535
+ const tok = await fetch(disc.token_endpoint, {
1536
+ method: "POST",
1537
+ headers: { "content-type": "application/x-www-form-urlencoded" },
1538
+ body: new URLSearchParams({
1539
+ grant_type: "authorization_code",
1540
+ code,
1541
+ redirect_uri: redirectUri,
1542
+ client_id: clientId,
1543
+ code_verifier: verifier
1544
+ }),
1545
+ signal: AbortSignal.timeout(2e4)
1546
+ });
1547
+ if (!tok.ok) {
1548
+ const body = await tok.text().catch(() => "");
1549
+ throw new Error(`token exchange failed (HTTP ${tok.status})${body ? `: ${body.slice(0, 200)}` : ""}`);
1550
+ }
1551
+ const tj = await tok.json();
1552
+ const bearer2 = tj.id_token ?? tj.access_token;
1553
+ if (!bearer2) throw new Error("token endpoint returned neither an id_token nor an access_token");
1554
+ return await fetchOrgKey(bearer2);
1555
+ } finally {
1556
+ server2.close();
1557
+ }
1558
+ }
1559
+
1560
+ // src/cli/init.ts
1561
+ var API_BASE2 = (process.env.SPEKOAI_API_URL || "https://api.speko.dev").replace(/\/+$/, "");
1384
1562
  var DASHBOARD = "https://platform.speko.dev";
1385
1563
  var PKG = "@spekoai/mcp-calls";
1386
1564
  var SERVER_NAME = "speko-calls";
@@ -1393,7 +1571,7 @@ var c = {
1393
1571
  cyan: (s) => `\x1B[36m${s}\x1B[0m`
1394
1572
  };
1395
1573
  function parseFlags(argv) {
1396
- const f = { scope: "user", yes: false, printConfig: false };
1574
+ const f = { scope: "user", yes: false, printConfig: false, paste: false };
1397
1575
  for (let i = 0; i < argv.length; i++) {
1398
1576
  const a = argv[i];
1399
1577
  if (a === "--token") f.token = argv[++i];
@@ -1401,6 +1579,7 @@ function parseFlags(argv) {
1401
1579
  else if (a === "--scope") f.scope = argv[++i] ?? "user";
1402
1580
  else if (a === "--yes" || a === "-y") f.yes = true;
1403
1581
  else if (a === "--print-config") f.printConfig = true;
1582
+ else if (a === "--paste" || a === "--manual") f.paste = true;
1404
1583
  }
1405
1584
  return f;
1406
1585
  }
@@ -1451,12 +1630,12 @@ function askSecret(query) {
1451
1630
  stdin.on("data", onData);
1452
1631
  });
1453
1632
  }
1454
- function openBrowser(url) {
1633
+ function openBrowser2(url) {
1455
1634
  try {
1456
- const p = platform();
1635
+ const p = platform2();
1457
1636
  const cmd2 = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
1458
1637
  const args = p === "win32" ? ["/c", "start", "", url] : [url];
1459
- const child = spawn(cmd2, args, { stdio: "ignore", detached: true });
1638
+ const child = spawn2(cmd2, args, { stdio: "ignore", detached: true });
1460
1639
  child.on("error", () => {
1461
1640
  });
1462
1641
  child.unref();
@@ -1465,7 +1644,7 @@ function openBrowser(url) {
1465
1644
  }
1466
1645
  async function verifyKey(key) {
1467
1646
  try {
1468
- const r = await fetch(`${API_BASE}/v1/organization`, {
1647
+ const r = await fetch(`${API_BASE2}/v1/organization`, {
1469
1648
  headers: { authorization: `Bearer ${key}` },
1470
1649
  signal: AbortSignal.timeout(15e3)
1471
1650
  });
@@ -1485,13 +1664,12 @@ function claudeCliPresent() {
1485
1664
  }
1486
1665
  function desktopConfigPath() {
1487
1666
  const home = homedir();
1488
- if (platform() === "darwin") return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1489
- if (platform() === "win32") return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
1667
+ if (platform2() === "darwin") return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1668
+ if (platform2() === "win32") return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
1490
1669
  return join(home, ".config", "Claude", "claude_desktop_config.json");
1491
1670
  }
1492
- function configureClaudeCode(key, scope, extraEnv = {}) {
1671
+ function configureClaudeCode(key, scope) {
1493
1672
  const envArgs = ["--env", `SPEKO_API_KEY=${key}`];
1494
- for (const [k, v] of Object.entries(extraEnv)) envArgs.push("--env", `${k}=${v}`);
1495
1673
  const manual = `claude mcp add ${SERVER_NAME} --scope ${scope} --env SPEKO_API_KEY=<your-key> -- npx -y ${PKG}`;
1496
1674
  if (!claudeCliPresent()) {
1497
1675
  console.log(c.yellow(" \u2022 Claude Code CLI not found on PATH. Run this yourself once installed:"));
@@ -1512,7 +1690,7 @@ function configureClaudeCode(key, scope, extraEnv = {}) {
1512
1690
  console.log(" " + c.cyan(manual));
1513
1691
  return false;
1514
1692
  }
1515
- function configureClaudeDesktop(key, extraEnv = {}) {
1693
+ function configureClaudeDesktop(key) {
1516
1694
  const path = desktopConfigPath();
1517
1695
  try {
1518
1696
  let cfg = {};
@@ -1529,7 +1707,7 @@ function configureClaudeDesktop(key, extraEnv = {}) {
1529
1707
  mkdirSync(dirname(path), { recursive: true });
1530
1708
  }
1531
1709
  const servers = cfg.mcpServers && typeof cfg.mcpServers === "object" ? cfg.mcpServers : {};
1532
- servers[SERVER_NAME] = { command: "npx", args: ["-y", PKG], env: { SPEKO_API_KEY: key, ...extraEnv } };
1710
+ servers[SERVER_NAME] = { command: "npx", args: ["-y", PKG], env: { SPEKO_API_KEY: key } };
1533
1711
  cfg.mcpServers = servers;
1534
1712
  writeFileSync(path, `${JSON.stringify(cfg, null, 2)}
1535
1713
  `);
@@ -1544,8 +1722,11 @@ function configureClaudeDesktop(key, extraEnv = {}) {
1544
1722
  function installSkill() {
1545
1723
  try {
1546
1724
  const here = dirname(fileURLToPath(import.meta.url));
1547
- const src = resolve(here, "..", "..", "skills", SERVER_NAME, "SKILL.md");
1548
- if (!existsSync(src)) {
1725
+ const src = [
1726
+ resolve(here, "..", "skills", SERVER_NAME, "SKILL.md"),
1727
+ resolve(here, "..", "..", "skills", SERVER_NAME, "SKILL.md")
1728
+ ].find((p) => existsSync(p));
1729
+ if (!src) {
1549
1730
  console.log(c.yellow(" \u2022 Bundled skill not found in package; skipping skill install."));
1550
1731
  return false;
1551
1732
  }
@@ -1563,27 +1744,33 @@ function installSkill() {
1563
1744
  return false;
1564
1745
  }
1565
1746
  }
1566
- async function runInit(argv) {
1747
+ async function runInit(argv, mode = "init") {
1567
1748
  const f = parseFlags(argv);
1568
- console.log(c.bold("\n Speko Calls \u2014 setup\n"));
1569
- console.log(" This MCP places " + c.bold("real, disclosed") + " outbound phone calls to " + c.bold("businesses") + ",");
1570
- console.log(" straight from your coding agent. Every call opens with an AI disclosure;");
1571
- console.log(" business lines only; quiet hours 08:00\u201321:00 in the destination's local time.\n");
1572
- if (!f.yes) {
1573
- const ok = (await ask(" Continue? [Y/n] ")).toLowerCase();
1574
- if (ok === "n" || ok === "no") {
1575
- console.log(" Aborted.");
1576
- return;
1577
- }
1749
+ const quick = mode === "login";
1750
+ console.log(c.bold(quick ? "\n Speko Calls \u2014 sign in\n" : "\n Speko Calls \u2014 setup\n"));
1751
+ if (!quick) {
1752
+ console.log(" This MCP places " + c.bold("real, disclosed") + " outbound phone calls to " + c.bold("businesses") + ",");
1753
+ console.log(" straight from your coding agent. Every call opens with an AI disclosure;");
1754
+ console.log(" business lines only; quiet hours 08:00\u201321:00 in the destination's local time.\n");
1578
1755
  }
1579
1756
  let key = (f.token ?? process.env.SPEKO_API_KEY ?? "").trim();
1757
+ if (!key && !f.paste) {
1758
+ console.log("\n Sign in to connect \u2014 this opens your browser. " + c.dim("No key to copy or paste."));
1759
+ try {
1760
+ key = await browserLogin((m) => console.log(c.dim(" " + m)));
1761
+ console.log(c.green(" \u2713 Signed in \u2014 fetched your API key automatically."));
1762
+ } catch (e) {
1763
+ console.log(c.yellow(` \u2022 Browser sign-in didn't complete (${e.message}).`));
1764
+ console.log(" Falling back to manual key entry. " + c.dim("(Use --paste to skip the browser next time.)"));
1765
+ }
1766
+ }
1580
1767
  if (!key) {
1581
1768
  console.log(`
1582
1769
  Opening ${c.cyan(DASHBOARD)} \u2014 sign in and create an API key (starts with "sk_").`);
1583
1770
  console.log(c.dim(` (If it doesn't open: visit ${DASHBOARD} and copy your key.)
1584
1771
  `));
1585
1772
  if (!f.yes) await ask(" Press Enter to open your browser\u2026 ");
1586
- openBrowser(DASHBOARD);
1773
+ openBrowser2(DASHBOARD);
1587
1774
  key = await askSecret(" Paste your Speko API key: ");
1588
1775
  }
1589
1776
  if (!key) {
@@ -1609,33 +1796,10 @@ async function runInit(argv) {
1609
1796
  console.log(" " + c.cyan(JSON.stringify({ [SERVER_NAME]: { command: "npx", args: ["-y", PKG], env: { SPEKO_API_KEY: key } } })));
1610
1797
  return;
1611
1798
  }
1612
- let target = (f.client ?? "").toLowerCase();
1613
- if (!target) {
1614
- const hasCode = claudeCliPresent();
1615
- const def = hasCode ? "code" : "desktop";
1616
- const ans = (await ask(`
1617
- Configure which client? [code/desktop/both] (${def}) `)).toLowerCase();
1618
- target = ans || def;
1619
- }
1620
- const extraEnv = {};
1621
- if (!f.yes) {
1622
- const demo = (await ask('\n Set up a quick DEMO so "call <a business>" works right away \u2014 rings a number you control? [y/N] ')).toLowerCase();
1623
- if (demo === "y" || demo === "yes") {
1624
- const num = (await ask(" Number to ring, E.164 (e.g. +15551234567): ")).replace(/\s/g, "");
1625
- if (/^\+?[1-9]\d{6,14}$/.test(num)) {
1626
- const biz = (await ask(" Business name to say on the call (default: Sakura Sushi): ")).trim() || "Sakura Sushi";
1627
- extraEnv.SPEKO_DEMO = "1";
1628
- extraEnv.SPEKO_DEMO_E164 = num.startsWith("+") ? num : `+${num}`;
1629
- extraEnv.SPEKO_DEMO_BUSINESS = biz;
1630
- console.log(c.dim(` Demo on: "call ${biz}" will ring ${extraEnv.SPEKO_DEMO_E164}.`));
1631
- } else {
1632
- console.log(c.yellow(" \u2022 Skipping demo \u2014 that didn't look like an E.164 number."));
1633
- }
1634
- }
1635
- }
1799
+ const target = (f.client || "both").toLowerCase();
1636
1800
  console.log("");
1637
- if (target === "code" || target === "both") configureClaudeCode(key, f.scope, extraEnv);
1638
- if (target === "desktop" || target === "both") configureClaudeDesktop(key, extraEnv);
1801
+ if (target === "code" || target === "both") configureClaudeCode(key, f.scope);
1802
+ if (target === "desktop" || target === "both") configureClaudeDesktop(key);
1639
1803
  installSkill();
1640
1804
  console.log(c.bold("\n \u2705 Done.\n"));
1641
1805
  console.log(" Try it: open your agent and say");
@@ -1705,7 +1869,7 @@ import { MCPTool as MCPTool2 } from "mcp-framework";
1705
1869
  import { z as z2 } from "zod";
1706
1870
 
1707
1871
  // src/http/serverClient.ts
1708
- import { randomBytes as randomBytes2 } from "crypto";
1872
+ import { randomBytes as randomBytes3 } from "crypto";
1709
1873
  var DemoServerError = class extends Error {
1710
1874
  name = "DemoServerError";
1711
1875
  };
@@ -1728,7 +1892,7 @@ var InProcessBackend = class {
1728
1892
  if (!this.ready) {
1729
1893
  this.ready = (async () => {
1730
1894
  if (!(process.env.SPEKO_DIAL_TOKEN_SECRET ?? "").trim()) {
1731
- process.env.SPEKO_DIAL_TOKEN_SECRET = randomBytes2(32).toString("hex");
1895
+ process.env.SPEKO_DIAL_TOKEN_SECRET = randomBytes3(32).toString("hex");
1732
1896
  }
1733
1897
  const core = await Promise.resolve().then(() => (init_core(), core_exports));
1734
1898
  const cfg = core.loadConfig();
@@ -1865,9 +2029,9 @@ function getServerClient() {
1865
2029
  // src/tools/CallNumberTool.ts
1866
2030
  var schema2 = z2.object({
1867
2031
  phone_number: z2.string().describe("Number to call, E.164 (e.g. +77011234567). A real number the user has consent to call."),
1868
- objective: z2.string().describe("What to say / accomplish, e.g. 'Tell Karim that Amirlan says happy birthday and misses him.'"),
2032
+ objective: z2.string().describe("What to say / accomplish, e.g. 'Tell Sam that John says happy birthday and misses him.'"),
1869
2033
  caller_name: z2.string().describe("Name of the human the call is on behalf of (1-80 chars); spoken in the AI-disclosure opening."),
1870
- recipient_name: z2.string().optional().describe("Who you're calling, used in the greeting (e.g. 'Karim')."),
2034
+ recipient_name: z2.string().optional().describe("Who you're calling, used in the greeting (e.g. 'Sam')."),
1871
2035
  context: z2.string().optional().describe("Optional extra context for the message."),
1872
2036
  utc_offset_minutes: z2.number().int().optional().describe("Callee UTC offset in minutes for quiet hours (e.g. 300 = UTC+5). Auto-derived from the number; pass it only if a call is blocked for unknown timezone."),
1873
2037
  max_duration_seconds: z2.number().int().optional().describe("Max seconds to wait for the call to finish; clamped 30-300.")
@@ -2095,13 +2259,13 @@ var MakeCallTool = class extends MCPTool6 {
2095
2259
  // src/index.ts
2096
2260
  var cmd = process.argv[2];
2097
2261
  if (cmd === "init" || cmd === "setup" || cmd === "login") {
2098
- await runInit(process.argv.slice(3));
2262
+ await runInit(process.argv.slice(3), cmd);
2099
2263
  process.exit(0);
2100
2264
  }
2101
2265
  loadEnv();
2102
2266
  var server = new MCPServer({
2103
2267
  name: "speko-calls",
2104
- version: "0.2.1",
2268
+ version: "0.3.1",
2105
2269
  transport: { type: "stdio" }
2106
2270
  });
2107
2271
  server.addTool(LookupBusinessTool);