@papercraneai/sandbox-agent 0.1.16 → 0.1.17-beta.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.
Files changed (2) hide show
  1. package/dist/index.js +55 -37
  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.", {
@@ -524,20 +544,7 @@ app.post("/cli/refresh", async (_req, res) => {
524
544
  if (!gitRoot)
525
545
  return;
526
546
  const { workspaceRefresh } = await import("@papercraneai/cli/lib/workspace-ops.js");
527
- const result = await workspaceRefresh(gitRoot);
528
- // If we start seeing publishes that change npm deps frequently, enable this
529
- // block so refresh re-installs node_modules and bounces the dev-server PM2
530
- // process (sandbox-agent stays up). Today most refreshes are dashboard source
531
- // only, so paying the install + restart cost (and brief 502s on any open
532
- // preview URL) on every push isn't worth it.
533
- //
534
- // const packageJsonChanged = result.filesChanged?.some((f: string) => f === "package.json" || f === "package-lock.json")
535
- // if (packageJsonChanged) {
536
- // await runNpmInstall(gitRoot)
537
- // execSync("pm2 restart dev-server")
538
- // }
539
- // await ensureAppScaffold(gitRoot)
540
- res.json(result);
547
+ res.json(await workspaceRefresh(gitRoot));
541
548
  });
542
549
  app.post("/cli/publish", async (req, res) => {
543
550
  const gitRoot = await resolveGitRoot(res);
@@ -1484,21 +1491,22 @@ app.post("/chat", async (req, res) => {
1484
1491
  // =============================================================================
1485
1492
  // Registration & Heartbeat
1486
1493
  // =============================================================================
1487
- async function registerWithPapercrane() {
1488
- 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) {
1489
1498
  console.error("Error: --token is required when using --register");
1490
1499
  return false;
1491
1500
  }
1492
- console.log(`Registering with Papercrane server at ${cliArgs.papercraneUrl}...`);
1501
+ console.log(`Registering with Papercrane server at ${papercraneUrl}...`);
1493
1502
  try {
1494
- const host = cliArgs.hostMode === "lan" ? getLocalNetworkIP() : "localhost";
1495
- const agentEndpoint = `http://${host}:${cliArgs.agentPort}`;
1496
- const previewEndpoint = `http://${host}:${cliArgs.devPort}`;
1497
- 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`, {
1498
1506
  method: "POST",
1499
1507
  headers: { "Content-Type": "application/json" },
1500
1508
  body: JSON.stringify({
1501
- connectionToken: cliArgs.token,
1509
+ connectionToken: token,
1502
1510
  agentEndpoint,
1503
1511
  previewEndpoint
1504
1512
  })
@@ -1510,7 +1518,8 @@ async function registerWithPapercrane() {
1510
1518
  }
1511
1519
  const data = await res.json();
1512
1520
  environmentId = data.environmentId;
1513
- connectionToken = cliArgs.token;
1521
+ connectionToken = token;
1522
+ registeredPapercraneUrl = papercraneUrl;
1514
1523
  console.log(`✓ Successfully registered as environment #${environmentId}`);
1515
1524
  console.log(` Agent endpoint: ${agentEndpoint}`);
1516
1525
  console.log(` Preview endpoint: ${previewEndpoint}`);
@@ -1526,7 +1535,7 @@ async function sendHeartbeat() {
1526
1535
  if (!environmentId || !connectionToken)
1527
1536
  return;
1528
1537
  try {
1529
- const res = await fetch(`${cliArgs.papercraneUrl}/api/environments/${environmentId}/heartbeat`, {
1538
+ const res = await fetch(`${registeredPapercraneUrl ?? cliArgs.papercraneUrl}/api/environments/${environmentId}/heartbeat`, {
1530
1539
  method: "POST",
1531
1540
  headers: {
1532
1541
  "Content-Type": "application/json",
@@ -1575,7 +1584,7 @@ function handleShutdown(exitCode = 0) {
1575
1584
  // Notify Papercrane of disconnect
1576
1585
  if (environmentId && connectionToken) {
1577
1586
  console.log("Disconnecting from Papercrane...");
1578
- fetch(`${cliArgs.papercraneUrl}/api/environments/${environmentId}/disconnect`, {
1587
+ fetch(`${registeredPapercraneUrl ?? cliArgs.papercraneUrl}/api/environments/${environmentId}/disconnect`, {
1579
1588
  method: "POST",
1580
1589
  headers: {
1581
1590
  "Content-Type": "application/json",
@@ -1626,9 +1635,18 @@ async function start() {
1626
1635
  log.info({ mode }, "Standalone mode: setting up template and dev server");
1627
1636
  PROJECT_DIR = await setupTemplate();
1628
1637
  }
1629
- // 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).
1630
1642
  if (cliArgs.register) {
1631
- 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);
1632
1650
  if (!success) {
1633
1651
  process.exit(1);
1634
1652
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papercraneai/sandbox-agent",
3
- "version": "0.1.16",
3
+ "version": "0.1.17-beta.1",
4
4
  "description": "Claude Agent SDK server for sandbox environments",
5
5
  "license": "MIT",
6
6
  "type": "module",