@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.
Files changed (2) hide show
  1. package/dist/index.js +75 -27
  2. 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
- // Pull template updates (git pull + conditional npm install)
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
- // Run git pull from the repo root
478
- const pullOutput = runCommand("git pull", gitRoot);
479
- console.log(`git pull output: ${pullOutput}`);
480
- // Check if already up to date
481
- if (pullOutput.includes("Already up to date")) {
482
- res.json({ updated: false, files_changed: [], npm_installed: false });
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
- // Get list of changed files from the pull
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: true, files_changed: filesChanged, npm_installed: npmInstalled });
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.14",
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",