@papercraneai/sandbox-agent 0.1.15 → 0.1.17-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +72 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -46,7 +46,9 @@ function parseArgs() {
46
46
  devPort: parseInt(process.env.DEV_PORT || "3000"),
47
47
  workdir: join(homedir(), ".papercrane", "sandbox"),
48
48
  agentOnly: false,
49
- hostMode: (process.env.HOST_MODE || "localhost")
49
+ hostMode: (process.env.HOST_MODE || "localhost"),
50
+ agentEndpoint: null,
51
+ dashboardsEndpoint: null,
50
52
  };
51
53
  for (let i = 0; i < args.length; i++) {
52
54
  const arg = args[i];
@@ -79,6 +81,17 @@ function parseArgs() {
79
81
  }
80
82
  result.hostMode = mode;
81
83
  }
84
+ else if (arg === "--agent-endpoint" && args[i + 1]) {
85
+ // URL papercrane should use to reach this agent. Required for BYOC
86
+ // deployments where localhost / LAN auto-detection isn't reachable
87
+ // by papercrane (e.g., a Tailscale MagicDNS URL).
88
+ result.agentEndpoint = args[++i];
89
+ }
90
+ else if (arg === "--dashboards-endpoint" && args[i + 1]) {
91
+ // URL papercrane (and ultimately the browser iframe) should use to
92
+ // reach this sandbox's dev server. Same rationale as --agent-endpoint.
93
+ result.dashboardsEndpoint = args[++i];
94
+ }
82
95
  else if (arg === "--version" || arg === "-v") {
83
96
  console.log(pkg.version);
84
97
  process.exit(0);
@@ -95,15 +108,18 @@ Modes:
95
108
  Agent-only: Just runs the agent API (use with --agent-only)
96
109
 
97
110
  Options:
98
- --workdir <path> Project directory (default: ~/.papercrane/sandbox)
99
- --agent-only Only run agent API (no template setup, no dev server)
100
- --port <port> Agent API port (default: 3001)
101
- --dev-port <port> Dev server port, standalone only (default: 3000)
102
- --host-mode <mode> Host for advertised URLs: localhost or lan (default: localhost)
103
- --register Register with Papercrane server (requires --token)
104
- --token <token> Connection token from Papercrane UI
105
- --papercrane-url <url> Papercrane server URL (default: https://fly.papercrane.ai)
106
- --help, -h Show this help message
111
+ --workdir <path> Project directory (default: ~/.papercrane/sandbox)
112
+ --agent-only Only run agent API (no template setup, no dev server)
113
+ --port <port> Agent API port (default: 3001)
114
+ --dev-port <port> Dev server port, standalone only (default: 3000)
115
+ --host-mode <mode> Host for advertised URLs: localhost or lan (default: localhost)
116
+ --register Register with Papercrane server (requires --token)
117
+ --token <token> Connection token from Papercrane UI
118
+ --papercrane-url <url> Papercrane server URL (default: https://fly.papercrane.ai)
119
+ --agent-endpoint <url> URL Papercrane uses to reach this agent (overrides default
120
+ localhost/LAN auto-detection; required for BYOC deployments)
121
+ --dashboards-endpoint <url> URL Papercrane / iframe uses for the dev server (same purpose)
122
+ --help, -h Show this help message
107
123
 
108
124
  Environment Variables:
109
125
  PORT Agent API port (same as --port)
@@ -115,6 +131,9 @@ Examples:
115
131
  sandbox-agent # Standalone: full setup
116
132
  sandbox-agent --workdir ~/my-project # Standalone: custom directory
117
133
  sandbox-agent --agent-only --workdir /tmp/app # Agent-only: just the API
134
+ sandbox-agent --register --token tok_abc \\
135
+ --agent-endpoint http://sandbox-abc.tail-x.ts.net:3001 \\
136
+ --dashboards-endpoint http://sandbox-abc.tail-x.ts.net:3000 # BYOC self-register
118
137
  `);
119
138
  process.exit(0);
120
139
  }
@@ -380,6 +399,7 @@ const sessionContext = {};
380
399
  // Registration state
381
400
  let environmentId = null;
382
401
  let connectionToken = null;
402
+ let registeredPapercraneUrl = null;
383
403
  let heartbeatInterval = null;
384
404
  // Client-side tool: ShowPreview
385
405
  const showPreviewTool = tool("ShowPreview", "Shows the preview iframe for a specific route. Call this after creating or modifying a visualization so the user can see the result immediately. The preview will appear in the user's browser.", {
@@ -1471,21 +1491,22 @@ app.post("/chat", async (req, res) => {
1471
1491
  // =============================================================================
1472
1492
  // Registration & Heartbeat
1473
1493
  // =============================================================================
1474
- async function registerWithPapercrane() {
1475
- if (!cliArgs.token) {
1494
+ async function registerWithPapercrane(overrides) {
1495
+ const token = overrides?.token ?? cliArgs.token;
1496
+ const papercraneUrl = overrides?.papercraneUrl ?? cliArgs.papercraneUrl;
1497
+ if (!token) {
1476
1498
  console.error("Error: --token is required when using --register");
1477
1499
  return false;
1478
1500
  }
1479
- console.log(`Registering with Papercrane server at ${cliArgs.papercraneUrl}...`);
1501
+ console.log(`Registering with Papercrane server at ${papercraneUrl}...`);
1480
1502
  try {
1481
- const host = cliArgs.hostMode === "lan" ? getLocalNetworkIP() : "localhost";
1482
- const agentEndpoint = `http://${host}:${cliArgs.agentPort}`;
1483
- const previewEndpoint = `http://${host}:${cliArgs.devPort}`;
1484
- const res = await fetch(`${cliArgs.papercraneUrl}/api/environments/register`, {
1503
+ const agentEndpoint = overrides?.agentEndpoint ?? `http://${cliArgs.hostMode === "lan" ? getLocalNetworkIP() : "localhost"}:${cliArgs.agentPort}`;
1504
+ const previewEndpoint = overrides?.previewEndpoint ?? `http://${cliArgs.hostMode === "lan" ? getLocalNetworkIP() : "localhost"}:${cliArgs.devPort}`;
1505
+ const res = await fetch(`${papercraneUrl}/api/environments/register`, {
1485
1506
  method: "POST",
1486
1507
  headers: { "Content-Type": "application/json" },
1487
1508
  body: JSON.stringify({
1488
- connectionToken: cliArgs.token,
1509
+ connectionToken: token,
1489
1510
  agentEndpoint,
1490
1511
  previewEndpoint
1491
1512
  })
@@ -1497,7 +1518,8 @@ async function registerWithPapercrane() {
1497
1518
  }
1498
1519
  const data = await res.json();
1499
1520
  environmentId = data.environmentId;
1500
- connectionToken = cliArgs.token;
1521
+ connectionToken = token;
1522
+ registeredPapercraneUrl = papercraneUrl;
1501
1523
  console.log(`✓ Successfully registered as environment #${environmentId}`);
1502
1524
  console.log(` Agent endpoint: ${agentEndpoint}`);
1503
1525
  console.log(` Preview endpoint: ${previewEndpoint}`);
@@ -1513,7 +1535,7 @@ async function sendHeartbeat() {
1513
1535
  if (!environmentId || !connectionToken)
1514
1536
  return;
1515
1537
  try {
1516
- const res = await fetch(`${cliArgs.papercraneUrl}/api/environments/${environmentId}/heartbeat`, {
1538
+ const res = await fetch(`${registeredPapercraneUrl ?? cliArgs.papercraneUrl}/api/environments/${environmentId}/heartbeat`, {
1517
1539
  method: "POST",
1518
1540
  headers: {
1519
1541
  "Content-Type": "application/json",
@@ -1562,7 +1584,7 @@ function handleShutdown(exitCode = 0) {
1562
1584
  // Notify Papercrane of disconnect
1563
1585
  if (environmentId && connectionToken) {
1564
1586
  console.log("Disconnecting from Papercrane...");
1565
- fetch(`${cliArgs.papercraneUrl}/api/environments/${environmentId}/disconnect`, {
1587
+ fetch(`${registeredPapercraneUrl ?? cliArgs.papercraneUrl}/api/environments/${environmentId}/disconnect`, {
1566
1588
  method: "POST",
1567
1589
  headers: {
1568
1590
  "Content-Type": "application/json",
@@ -1613,9 +1635,18 @@ async function start() {
1613
1635
  log.info({ mode }, "Standalone mode: setting up template and dev server");
1614
1636
  PROJECT_DIR = await setupTemplate();
1615
1637
  }
1616
- // Register with Papercrane if requested
1638
+ // Register with Papercrane if requested. CLI overrides for the agent /
1639
+ // dashboards URLs are required for BYOC deployments where the default
1640
+ // localhost/LAN fallback isn't reachable from papercrane (e.g., the
1641
+ // agent's only ingress is a Tailscale MagicDNS hostname).
1617
1642
  if (cliArgs.register) {
1618
- const success = await registerWithPapercrane();
1643
+ const overrides = cliArgs.agentEndpoint && cliArgs.dashboardsEndpoint
1644
+ ? {
1645
+ agentEndpoint: cliArgs.agentEndpoint,
1646
+ previewEndpoint: cliArgs.dashboardsEndpoint,
1647
+ }
1648
+ : undefined;
1649
+ const success = await registerWithPapercrane(overrides);
1619
1650
  if (!success) {
1620
1651
  process.exit(1);
1621
1652
  }
@@ -1691,6 +1722,24 @@ async function start() {
1691
1722
  console.log(` Connected to Papercrane at ${cliArgs.papercraneUrl}`);
1692
1723
  }
1693
1724
  console.log(`\nReady to accept connections.`);
1725
+ // Auto-register when running in k8s/BYOC — env vars set by KubernetesSandboxProvider
1726
+ if (process.env.CONNECTION_TOKEN && process.env.PAPERCRANE_URL) {
1727
+ // POD_IP is injected via the Kubernetes Downward API — use it so the control plane
1728
+ // gets a real routable IP (reachable via Tailscale subnet routing) rather than
1729
+ // a cluster-internal DNS name that's only resolvable inside the cluster.
1730
+ const podIp = process.env.POD_IP;
1731
+ const agentEndpoint = podIp ? `http://${podIp}:${PORT}` : (process.env.AGENT_ENDPOINT ?? `http://localhost:${PORT}`);
1732
+ const previewEndpoint = podIp ? `http://${podIp}:3000` : agentEndpoint.replace(`:${PORT}`, ":3000");
1733
+ registerWithPapercrane({
1734
+ token: process.env.CONNECTION_TOKEN,
1735
+ papercraneUrl: process.env.PAPERCRANE_URL,
1736
+ agentEndpoint,
1737
+ previewEndpoint,
1738
+ }).then((ok) => {
1739
+ if (!ok)
1740
+ console.error("Auto-registration failed — sandbox will not be reachable by control plane");
1741
+ });
1742
+ }
1694
1743
  });
1695
1744
  }
1696
1745
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papercraneai/sandbox-agent",
3
- "version": "0.1.15",
3
+ "version": "0.1.17-beta.0",
4
4
  "description": "Claude Agent SDK server for sandbox environments",
5
5
  "license": "MIT",
6
6
  "type": "module",