@papercraneai/sandbox-agent 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.
- package/dist/index.js +75 -27
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -464,54 +464,102 @@ async function findGitRoot(startDir) {
|
|
|
464
464
|
dir = parent;
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
|
-
//
|
|
467
|
+
// Refresh: sync with remote using rebase. Auto-commits any local changes first
|
|
468
|
+
// so the repo is always in a clean committed state before reconciling. If rebase
|
|
469
|
+
// conflicts occur, aborts immediately and returns conflict info — the repo is
|
|
470
|
+
// always left clean. The AI agent should call `papercrane workspace merge` to
|
|
471
|
+
// enter deliberate conflict resolution.
|
|
468
472
|
app.post("/pull-template", async (_req, res) => {
|
|
469
473
|
const workdir = getWorkingDirectory();
|
|
470
474
|
try {
|
|
471
|
-
// Find the git repo root (may be a parent of the working directory)
|
|
472
475
|
const gitRoot = await findGitRoot(workdir);
|
|
473
476
|
if (!gitRoot) {
|
|
474
477
|
res.status(400).json({ error: "Working directory is not inside a git repository" });
|
|
475
478
|
return;
|
|
476
479
|
}
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
480
|
+
const { workspaceRefresh } = await import("@papercraneai/cli/lib/workspace-ops.js");
|
|
481
|
+
const result = await workspaceRefresh(gitRoot);
|
|
482
|
+
if (!result.upToDate) {
|
|
483
|
+
res.status(409).json({
|
|
484
|
+
error: "conflicts_detected",
|
|
485
|
+
conflicting_files: result.conflictedFiles,
|
|
486
|
+
message: `Refresh blocked — conflicts in ${result.conflictedFiles?.length} file(s). Run 'papercrane workspace merge' to resolve.`,
|
|
487
|
+
});
|
|
483
488
|
return;
|
|
484
489
|
}
|
|
485
|
-
|
|
486
|
-
// git diff HEAD@{1} --name-only shows what changed in the last pull
|
|
487
|
-
let filesChanged = [];
|
|
488
|
-
try {
|
|
489
|
-
const diffOutput = runCommand("git diff HEAD@{1} --name-only", gitRoot);
|
|
490
|
-
filesChanged = diffOutput.split("\n").filter(f => f.trim());
|
|
491
|
-
}
|
|
492
|
-
catch {
|
|
493
|
-
// If HEAD@{1} doesn't exist, just report the pull succeeded
|
|
494
|
-
filesChanged = ["(unable to determine changed files)"];
|
|
495
|
-
}
|
|
496
|
-
// Check if package.json changed — if so, run npm install
|
|
497
|
-
const packageJsonChanged = filesChanged.some(f => f === "package.json" || f === "package-lock.json");
|
|
490
|
+
const packageJsonChanged = result.filesChanged.some((f) => f === "package.json" || f === "package-lock.json");
|
|
498
491
|
let npmInstalled = false;
|
|
499
492
|
if (packageJsonChanged) {
|
|
500
|
-
console.log("package.json changed, running npm install...");
|
|
501
493
|
await runNpmInstall(gitRoot);
|
|
502
494
|
npmInstalled = true;
|
|
503
495
|
}
|
|
504
|
-
// Ensure scaffold files still exist after pull
|
|
505
496
|
await ensureAppScaffold(gitRoot);
|
|
506
|
-
res.json({ updated:
|
|
497
|
+
res.json({ updated: result.upToDate, files_changed: result.filesChanged, npm_installed: npmInstalled, auto_committed: result.autoCommitted });
|
|
507
498
|
}
|
|
508
499
|
catch (error) {
|
|
509
500
|
log.error({ error: error instanceof Error ? error.message : String(error) }, "Pull template failed");
|
|
510
|
-
res.status(500).json({
|
|
511
|
-
error: error instanceof Error ? error.message : "Failed to pull template"
|
|
512
|
-
});
|
|
501
|
+
res.status(500).json({ error: error instanceof Error ? error.message : "Failed to pull template" });
|
|
513
502
|
}
|
|
514
503
|
});
|
|
504
|
+
// ============================================================================
|
|
505
|
+
// CLI PROXY ENDPOINTS
|
|
506
|
+
// ============================================================================
|
|
507
|
+
async function resolveGitRoot(res) {
|
|
508
|
+
const gitRoot = await findGitRoot(getWorkingDirectory());
|
|
509
|
+
if (!gitRoot) {
|
|
510
|
+
res.status(400).json({ error: "Not a git repository" });
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
return gitRoot;
|
|
514
|
+
}
|
|
515
|
+
app.get("/cli/status", async (_req, res) => {
|
|
516
|
+
const gitRoot = await resolveGitRoot(res);
|
|
517
|
+
if (!gitRoot)
|
|
518
|
+
return;
|
|
519
|
+
const { workspaceStatus } = await import("@papercraneai/cli/lib/workspace-ops.js");
|
|
520
|
+
res.json(await workspaceStatus(gitRoot));
|
|
521
|
+
});
|
|
522
|
+
app.post("/cli/refresh", async (_req, res) => {
|
|
523
|
+
const gitRoot = await resolveGitRoot(res);
|
|
524
|
+
if (!gitRoot)
|
|
525
|
+
return;
|
|
526
|
+
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);
|
|
541
|
+
});
|
|
542
|
+
app.post("/cli/publish", async (req, res) => {
|
|
543
|
+
const gitRoot = await resolveGitRoot(res);
|
|
544
|
+
if (!gitRoot)
|
|
545
|
+
return;
|
|
546
|
+
const { workspacePublish } = await import("@papercraneai/cli/lib/workspace-ops.js");
|
|
547
|
+
res.json(await workspacePublish(gitRoot, { message: req.body?.message }));
|
|
548
|
+
});
|
|
549
|
+
app.post("/cli/merge", async (_req, res) => {
|
|
550
|
+
const gitRoot = await resolveGitRoot(res);
|
|
551
|
+
if (!gitRoot)
|
|
552
|
+
return;
|
|
553
|
+
const { workspaceMerge } = await import("@papercraneai/cli/lib/workspace-ops.js");
|
|
554
|
+
res.json(await workspaceMerge(gitRoot));
|
|
555
|
+
});
|
|
556
|
+
app.post("/cli/unmerge", async (_req, res) => {
|
|
557
|
+
const gitRoot = await resolveGitRoot(res);
|
|
558
|
+
if (!gitRoot)
|
|
559
|
+
return;
|
|
560
|
+
const { workspaceUnmerge } = await import("@papercraneai/cli/lib/workspace-ops.js");
|
|
561
|
+
res.json(await workspaceUnmerge(gitRoot));
|
|
562
|
+
});
|
|
515
563
|
// File tree endpoint
|
|
516
564
|
app.get("/files/tree", async (req, res) => {
|
|
517
565
|
const subPath = req.query.path || "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@papercraneai/sandbox-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Claude Agent SDK server for sandbox environments",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/claude-agent-sdk": "^0.2.128",
|
|
27
|
+
"@papercraneai/cli": "*",
|
|
27
28
|
"diff": "^5.2.0",
|
|
28
29
|
"express": "^4.21.1",
|
|
29
30
|
"multer": "^2.0.2",
|