@papercraneai/sandbox-agent 0.1.16 → 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.
- package/dist/index.js +73 -37
- 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>
|
|
99
|
-
--agent-only
|
|
100
|
-
--port <port>
|
|
101
|
-
--dev-port <port>
|
|
102
|
-
--host-mode <mode>
|
|
103
|
-
--register
|
|
104
|
-
--token <token>
|
|
105
|
-
--papercrane-url <url>
|
|
106
|
-
--
|
|
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
|
-
|
|
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
|
-
|
|
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 ${
|
|
1501
|
+
console.log(`Registering with Papercrane server at ${papercraneUrl}...`);
|
|
1493
1502
|
try {
|
|
1494
|
-
const
|
|
1495
|
-
const
|
|
1496
|
-
const
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
}
|
|
@@ -1704,6 +1722,24 @@ async function start() {
|
|
|
1704
1722
|
console.log(` Connected to Papercrane at ${cliArgs.papercraneUrl}`);
|
|
1705
1723
|
}
|
|
1706
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
|
+
}
|
|
1707
1743
|
});
|
|
1708
1744
|
}
|
|
1709
1745
|
catch (error) {
|