@punkcode/cli 0.1.14 → 0.1.16

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 (2) hide show
  1. package/dist/cli.js +179 -17
  2. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -169,7 +169,7 @@ function runClaude(options, callbacks) {
169
169
  systemPrompt: opts.systemPrompt ?? { type: "preset", preset: "claude_code" },
170
170
  ...options.workingDirectory && { cwd: options.workingDirectory },
171
171
  ...options.sessionId && { resume: options.sessionId },
172
- maxThinkingTokens: 1e4,
172
+ thinking: { type: "adaptive" },
173
173
  includePartialMessages: true,
174
174
  canUseTool: async (toolName, input, toolOpts) => {
175
175
  if (!callbacks.onPermissionRequest) {
@@ -1173,7 +1173,8 @@ async function connect(server, options) {
1173
1173
  reconnection: true,
1174
1174
  reconnectionAttempts: Infinity,
1175
1175
  reconnectionDelay: 1e3,
1176
- reconnectionDelayMax: 5e3
1176
+ reconnectionDelayMax: 5e3,
1177
+ perMessageDeflate: { threshold: 1024 }
1177
1178
  });
1178
1179
  const activeSessions = /* @__PURE__ */ new Map();
1179
1180
  const sleepLock = preventIdleSleep();
@@ -1435,13 +1436,35 @@ async function handleListSessions(socket, msg, defaultCwd) {
1435
1436
  logger.info({ count: sessions.length }, "Listed sessions");
1436
1437
  }
1437
1438
  var DEFAULT_HISTORY_LIMIT = 30;
1439
+ var MAX_PAYLOAD_BYTES = 19.5 * 1024 * 1024;
1440
+ function fitToPayloadLimit(messages) {
1441
+ if (Buffer.byteLength(JSON.stringify(messages), "utf8") <= MAX_PAYLOAD_BYTES) {
1442
+ return messages;
1443
+ }
1444
+ let lo = 0;
1445
+ let hi = messages.length;
1446
+ while (lo < hi) {
1447
+ const mid = Math.floor((lo + hi + 1) / 2);
1448
+ const slice = messages.slice(messages.length - mid);
1449
+ if (Buffer.byteLength(JSON.stringify(slice), "utf8") <= MAX_PAYLOAD_BYTES) {
1450
+ lo = mid;
1451
+ } else {
1452
+ hi = mid - 1;
1453
+ }
1454
+ }
1455
+ return messages.slice(messages.length - lo);
1456
+ }
1438
1457
  async function handleLoadSession(socket, msg) {
1439
1458
  const { id, sessionId, limit = DEFAULT_HISTORY_LIMIT } = msg;
1440
1459
  const log2 = createChildLogger({ sessionId });
1441
1460
  log2.info("Loading session...");
1442
1461
  const all = await loadSession(sessionId);
1443
1462
  if (all) {
1444
- const messages = limit > 0 && all.length > limit ? all.slice(-limit) : all;
1463
+ const sliced = limit > 0 && all.length > limit ? all.slice(-limit) : all;
1464
+ const messages = fitToPayloadLimit(sliced);
1465
+ if (messages.length < sliced.length) {
1466
+ log2.warn({ requested: sliced.length, sent: messages.length }, "Session payload trimmed to fit size limit");
1467
+ }
1445
1468
  send(socket, "response", { type: "history", messages, total: all.length, requestId: id });
1446
1469
  log2.info({ count: messages.length, total: all.length }, "Session loaded");
1447
1470
  } else {
@@ -1581,6 +1604,135 @@ async function handleGetContext(socket, msg, defaultCwd) {
1581
1604
 
1582
1605
  // src/commands/login.ts
1583
1606
  import readline from "readline";
1607
+
1608
+ // src/lib/qr-login.ts
1609
+ import qrcode from "qrcode-terminal";
1610
+ var BACKEND_URL = process.env.BACKEND_URL || "https://api.punkcode.dev";
1611
+ var POLL_INTERVAL_MS = 3e3;
1612
+ var MAX_POLL_ATTEMPTS = 100;
1613
+ var QR_COLORS = [
1614
+ "\x1B[36m",
1615
+ // cyan
1616
+ "\x1B[35m",
1617
+ // magenta
1618
+ "\x1B[34m",
1619
+ // blue
1620
+ "\x1B[32m",
1621
+ // green
1622
+ "\x1B[33m",
1623
+ // yellow
1624
+ "\x1B[91m",
1625
+ // bright red
1626
+ "\x1B[96m",
1627
+ // bright cyan
1628
+ "\x1B[95m"
1629
+ // bright magenta
1630
+ ];
1631
+ var RESET = "\x1B[0m";
1632
+ async function loginWithQr() {
1633
+ const res = await fetch(`${BACKEND_URL}/auth/qr/init`, {
1634
+ method: "POST",
1635
+ headers: { "Content-Type": "application/json" }
1636
+ });
1637
+ if (!res.ok) {
1638
+ const body = await res.json().catch(() => ({}));
1639
+ throw new Error(body?.message ?? `Failed to init QR login: HTTP ${res.status}`);
1640
+ }
1641
+ const { pairingCode, secret, qrPayload } = await res.json();
1642
+ let qrLines = [];
1643
+ qrcode.generate(qrPayload, { small: true }, (code) => {
1644
+ qrLines = code.split("\n").filter(Boolean);
1645
+ });
1646
+ console.log();
1647
+ console.log(" Scan this QR code with the Punk app:");
1648
+ console.log();
1649
+ let colorIdx = 0;
1650
+ for (const line of qrLines) {
1651
+ console.log(` ${QR_COLORS[0]}${line}${RESET}`);
1652
+ }
1653
+ console.log();
1654
+ const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1655
+ let spinIdx = 0;
1656
+ let attempts = 0;
1657
+ let backoffMs = POLL_INTERVAL_MS;
1658
+ const recolorQr = () => {
1659
+ colorIdx = (colorIdx + 1) % QR_COLORS.length;
1660
+ const color = QR_COLORS[colorIdx];
1661
+ const moveUp = qrLines.length + 1;
1662
+ process.stdout.write(`\x1B[${moveUp}A`);
1663
+ for (const line of qrLines) {
1664
+ process.stdout.write(`\r ${color}${line}${RESET}
1665
+ `);
1666
+ }
1667
+ process.stdout.write("\n");
1668
+ };
1669
+ const colorTimer = setInterval(recolorQr, 800);
1670
+ const abort = new AbortController();
1671
+ const onSigint = () => {
1672
+ abort.abort();
1673
+ clearInterval(colorTimer);
1674
+ process.stdout.write("\r\x1B[K");
1675
+ console.log(" Login cancelled.");
1676
+ process.exit(0);
1677
+ };
1678
+ process.on("SIGINT", onSigint);
1679
+ try {
1680
+ while (attempts < MAX_POLL_ATTEMPTS) {
1681
+ process.stdout.write(`\r Waiting for confirmation... ${spinner[spinIdx++ % spinner.length]} `);
1682
+ await sleep(backoffMs);
1683
+ let poll;
1684
+ try {
1685
+ const pollRes = await fetch(
1686
+ `${BACKEND_URL}/auth/qr/poll?pairingCode=${pairingCode}&secret=${secret}`,
1687
+ { signal: abort.signal }
1688
+ );
1689
+ if (!pollRes.ok) {
1690
+ if (pollRes.status === 429) {
1691
+ backoffMs = Math.min(backoffMs * 2, 3e4);
1692
+ continue;
1693
+ }
1694
+ throw new Error(`Poll failed: HTTP ${pollRes.status}`);
1695
+ }
1696
+ poll = await pollRes.json();
1697
+ backoffMs = POLL_INTERVAL_MS;
1698
+ } catch {
1699
+ if (abort.signal.aborted) return;
1700
+ backoffMs = Math.min(backoffMs * 2, 3e4);
1701
+ continue;
1702
+ }
1703
+ attempts++;
1704
+ if (poll.status === "pending") {
1705
+ continue;
1706
+ }
1707
+ if (poll.status === "expired") {
1708
+ process.stdout.write("\r\x1B[K");
1709
+ throw new Error("QR code expired. Run `punk login` to try again.");
1710
+ }
1711
+ if (poll.status === "confirmed") {
1712
+ process.stdout.write("\r\x1B[K");
1713
+ saveAuth({
1714
+ idToken: poll.idToken,
1715
+ refreshToken: poll.refreshToken,
1716
+ expiresAt: Date.now() + parseInt(poll.expiresIn, 10) * 1e3,
1717
+ email: poll.email,
1718
+ uid: poll.uid
1719
+ });
1720
+ logger.info({ success: true, email: poll.email }, "Logged in");
1721
+ return;
1722
+ }
1723
+ }
1724
+ process.stdout.write("\r\x1B[K");
1725
+ throw new Error("QR code expired. Run `punk login` to try again.");
1726
+ } finally {
1727
+ clearInterval(colorTimer);
1728
+ process.removeListener("SIGINT", onSigint);
1729
+ }
1730
+ }
1731
+ function sleep(ms) {
1732
+ return new Promise((resolve) => setTimeout(resolve, ms));
1733
+ }
1734
+
1735
+ // src/commands/login.ts
1584
1736
  function prompt(question, hidden = false) {
1585
1737
  if (!hidden) {
1586
1738
  const rl = readline.createInterface({
@@ -1624,19 +1776,29 @@ function prompt(question, hidden = false) {
1624
1776
  stdin.on("data", onData);
1625
1777
  });
1626
1778
  }
1627
- async function login() {
1628
- const email = await prompt("Email: ");
1629
- const password = await prompt("Password: ", true);
1630
- if (!email || !password) {
1631
- logger.error("Email and password are required.");
1632
- process.exit(1);
1633
- }
1634
- try {
1635
- const auth = await signIn(email, password);
1636
- logger.info({ success: true, email: auth.email }, "Logged in");
1637
- } catch (err) {
1638
- logger.error({ err }, "Login failed");
1639
- process.exit(1);
1779
+ async function login(options) {
1780
+ if (options.email) {
1781
+ const email = await prompt("Email: ");
1782
+ const password = await prompt("Password: ", true);
1783
+ if (!email || !password) {
1784
+ logger.error("Email and password are required.");
1785
+ process.exit(1);
1786
+ }
1787
+ try {
1788
+ const auth = await signIn(email, password);
1789
+ logger.info({ success: true, email: auth.email }, "Logged in");
1790
+ } catch (err) {
1791
+ logger.error({ err }, "Login failed");
1792
+ process.exit(1);
1793
+ }
1794
+ } else {
1795
+ try {
1796
+ await loginWithQr();
1797
+ } catch (err) {
1798
+ logger.error({ err }, "QR login failed");
1799
+ console.log("\n Try email/password login: punk login --email");
1800
+ process.exit(1);
1801
+ }
1640
1802
  }
1641
1803
  }
1642
1804
  function logout() {
@@ -1647,7 +1809,7 @@ function logout() {
1647
1809
  // src/commands/index.ts
1648
1810
  function registerCommands(program2) {
1649
1811
  program2.command("connect").argument("[server]", "Backend server URL", "https://api.punkcode.dev").description("Connect to backend server").option("-t, --token <token>", "Authentication token").option("-d, --device-id <deviceId>", "Device identifier (defaults to hostname)").option("-n, --name <name>", "Custom device display name").option("--tag <tag>", "Device tag (repeatable, e.g. --tag home --tag mac --tag docker)", (val, acc) => [...acc, val], []).option("--cwd <directory>", "Working directory for sessions (default: ~/punk)").action(connect);
1650
- program2.command("login").description("Log in with your email and password").action(login);
1812
+ program2.command("login").description("Log in to your account").option("--email", "Use email/password login instead of QR code").action(login);
1651
1813
  program2.command("logout").description("Log out and clear stored credentials").action(logout);
1652
1814
  }
1653
1815
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punkcode/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Control Claude Code from your phone",
5
5
  "type": "module",
6
6
  "bin": {
@@ -59,6 +59,7 @@
59
59
  "execa": "^9.6.1",
60
60
  "pino": "^10.3.1",
61
61
  "pino-pretty": "^13.1.3",
62
+ "qrcode-terminal": "^0.12.0",
62
63
  "socket.io-client": "^4.8.3",
63
64
  "zod": "^4.3.6"
64
65
  }