openpouch 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -10,6 +10,13 @@ npx openpouch deploy
10
10
 
11
11
  You get a live `https://<slug>.openpouch.sh` URL plus a claim link. The agent deploys autonomously; a human claims it via the link to keep it. openpouch writes the deployment truth (`deploy.manifest.json`, `deploy.evidence.json`, `DEPLOYMENT.md`) back into your repo, so any agent can resume after context loss.
12
12
 
13
+ > **Framework frontends (React/Vite/Next/Svelte…): deploy the _built output_, not the source folder.** Build first, then point `deploy` at the build directory:
14
+ > ```bash
15
+ > npm run build
16
+ > npx openpouch deploy dist # Vite → dist/ · CRA → build/ · Next static export → out/
17
+ > ```
18
+ > If you run `openpouch deploy` in an unbuilt frontend root, it **stops and tells you which folder to deploy** instead of silently shipping source that won't run (use `openpouch deploy .` to force the current folder). Server-side build-on-deploy is on the roadmap.
19
+
13
20
  ## Accounts (optional)
14
21
 
15
22
  Start anonymous, or create a free account for higher limits — entirely from the agent, no dashboard:
package/openpouch.js CHANGED
@@ -5129,7 +5129,7 @@ async function approveCommand(ctx, requestId) {
5129
5129
  }
5130
5130
 
5131
5131
  // packages/cli/src/deploy-engine.ts
5132
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5132
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
5133
5133
  var TERMINAL = /* @__PURE__ */ new Set(["live", "failed", "canceled"]);
5134
5134
  async function runSmokeChecks(url, healthcheck) {
5135
5135
  const check = healthcheck ?? { path: "/", expectStatus: 200, timeoutMs: 1e4 };
@@ -5363,6 +5363,7 @@ function renderAgentsSection(projectName) {
5363
5363
  "",
5364
5364
  `${projectName} deploys via openpouch \u2014 an agent-native deployment control plane. This works the same from any agent harness.`,
5365
5365
  "",
5366
+ "- **Framework frontends (React/Vite/Next/Svelte\u2026): deploy the _built output_, not the source folder.** Build first, then point deploy at the build directory: `npm run build`, then `openpouch deploy dist` (Vite\u2192`dist/`, CRA\u2192`build/`, Next static export\u2192`out/`). Running `openpouch deploy` in an unbuilt frontend root is refused with the right folder to use (`openpouch deploy .` forces the current folder); server-side build-on-deploy is on the roadmap.",
5366
5367
  "- **Deployment truth** (what is live, where, which commit, rollback anchor): read `DEPLOYMENT.md` or `deploy.evidence.json`. Config lives in `deploy.manifest.json`, agent permissions in `deploy.policy.json`.",
5367
5368
  "- **CLI:** `openpouch inspect` \xB7 `plan` \xB7 `preview` (autonomous if policy allows) \xB7 `prod` \xB7 `verify` \xB7 `logs` \xB7 `rollback`. Add `--json` for a single machine-readable JSON object; errors come as `{category, message, fix}` \u2014 act on the fix. Exit codes are documented API.",
5368
5369
  "- **Relay the `summary` to your human:** every result (CLI `--json` and MCP) carries a top-level `summary` \u2014 plain-language, jargon-free text written for a non-technical operator. Pass it on verbatim: it says what happened, whether the app is live and healthy, the live link, and what (if anything) the human needs to do. The **live URL is the primary result** \u2014 on a successful deploy it's the top-level `url` field, ready to share.",
@@ -5627,8 +5628,8 @@ async function initCommand(ctx, flags) {
5627
5628
 
5628
5629
  // packages/cli/src/commands/instant.ts
5629
5630
  import { spawn } from "node:child_process";
5630
- import { readFile as readFile7 } from "node:fs/promises";
5631
- import { basename as basename2, join as join7 } from "node:path";
5631
+ import { readFile as readFile7, stat } from "node:fs/promises";
5632
+ import { basename as basename2, join as join7, resolve } from "node:path";
5632
5633
  var TAR_EXCLUDES = [
5633
5634
  "./node_modules",
5634
5635
  "./.git",
@@ -5641,7 +5642,7 @@ var TAR_EXCLUDES = [
5641
5642
  "./.openpouch"
5642
5643
  ];
5643
5644
  function buildTarball(cwd) {
5644
- return new Promise((resolve, reject) => {
5645
+ return new Promise((resolve2, reject) => {
5645
5646
  const args = ["-czf", "-", ...TAR_EXCLUDES.flatMap((e) => ["--exclude", e]), "-C", cwd, "."];
5646
5647
  const tar = spawn("tar", args, { stdio: ["ignore", "pipe", "pipe"] });
5647
5648
  const chunks = [];
@@ -5650,7 +5651,7 @@ function buildTarball(cwd) {
5650
5651
  tar.stderr.on("data", (c) => stderr += String(c));
5651
5652
  tar.on("error", reject);
5652
5653
  tar.on("close", (code) => {
5653
- if (code === 0) resolve(Buffer.concat(chunks));
5654
+ if (code === 0) resolve2(Buffer.concat(chunks));
5654
5655
  else reject(new Error(`tar exited ${code}: ${stderr.trim().slice(0, 300)}`));
5655
5656
  });
5656
5657
  });
@@ -5663,7 +5664,49 @@ async function projectHint(cwd) {
5663
5664
  }
5664
5665
  return basename2(cwd) || "joey";
5665
5666
  }
5666
- async function instantCommand(ctx) {
5667
+ async function unbuiltFrontendHint(dir) {
5668
+ const buildDirs = [];
5669
+ for (const c of ["dist", "build", "out"]) {
5670
+ try {
5671
+ if ((await stat(join7(dir, c))).isDirectory()) buildDirs.push(c);
5672
+ } catch {
5673
+ }
5674
+ }
5675
+ let html;
5676
+ try {
5677
+ html = await readFile7(join7(dir, "index.html"), "utf8");
5678
+ } catch {
5679
+ html = void 0;
5680
+ }
5681
+ const devEntry = html !== void 0 && (/src=["']\/?src\//.test(html) || /\.(jsx|tsx|ts|vue|svelte)["']/.test(html));
5682
+ let noEntrySpa = false;
5683
+ if (html === void 0) {
5684
+ try {
5685
+ const pkg = JSON.parse(await readFile7(join7(dir, "package.json"), "utf8"));
5686
+ if (pkg.scripts?.["build"]) {
5687
+ try {
5688
+ noEntrySpa = (await stat(join7(dir, "src"))).isDirectory();
5689
+ } catch {
5690
+ noEntrySpa = false;
5691
+ }
5692
+ }
5693
+ } catch {
5694
+ }
5695
+ }
5696
+ return { buildDirs, isUnbuilt: devEntry || noEntrySpa };
5697
+ }
5698
+ async function instantCommand(ctx, dir) {
5699
+ const explicitDir = dir !== void 0 && dir !== "";
5700
+ const targetDir = explicitDir ? resolve(ctx.cwd, dir) : ctx.cwd;
5701
+ if (explicitDir) {
5702
+ try {
5703
+ if (!(await stat(targetDir)).isDirectory()) {
5704
+ return errorResult(EXIT.USAGE, "usage", `not a directory: ${dir}`, "Pass a folder to deploy, e.g. `openpouch deploy dist`.");
5705
+ }
5706
+ } catch {
5707
+ return errorResult(EXIT.USAGE, "usage", `folder not found: ${dir}`, "Pass an existing folder, e.g. `openpouch deploy dist`.");
5708
+ }
5709
+ }
5667
5710
  const loaded = await loadManifest(ctx.cwd);
5668
5711
  if (loaded.status === "ok") {
5669
5712
  const envs = Object.values(loaded.manifest.environments ?? {});
@@ -5677,9 +5720,16 @@ async function instantCommand(ctx) {
5677
5720
  );
5678
5721
  }
5679
5722
  }
5723
+ if (!explicitDir) {
5724
+ const { buildDirs, isUnbuilt } = await unbuiltFrontendHint(targetDir);
5725
+ if (isUnbuilt) {
5726
+ const fix = buildDirs.length > 0 ? `This looks like an unbuilt frontend. Deploy the build output instead: \`openpouch deploy ${buildDirs[0]}\` \u2014 or \`openpouch deploy .\` to ship this folder as-is.` : "This looks like an unbuilt frontend (index.html references source modules). Build first, then deploy the build folder: `npm run build`, then `openpouch deploy dist` \u2014 or `openpouch deploy .` to ship this folder as-is.";
5727
+ return errorResult(EXIT.USAGE, "usage", "refusing to deploy what looks like unbuilt frontend source", fix);
5728
+ }
5729
+ }
5680
5730
  let tarball;
5681
5731
  try {
5682
- tarball = await buildTarball(ctx.cwd);
5732
+ tarball = await buildTarball(targetDir);
5683
5733
  } catch (e) {
5684
5734
  return errorResult(EXIT.UNEXPECTED, "provider", `could not package the project: ${e.message}`, "Ensure `tar` is available and the directory is readable.");
5685
5735
  }
@@ -6222,7 +6272,7 @@ var USAGE = [
6222
6272
  "usage: openpouch <command> [--json]",
6223
6273
  "",
6224
6274
  "commands:",
6225
- " deploy zero-config instant preview on openpouch's own infra (anonymous, or under your account if a key is set)",
6275
+ " deploy [dir] zero-config instant preview on openpouch's own infra \u2014 deploys ./<dir> (e.g. `deploy dist`) or the current folder (anonymous, or under your account if a key is set)",
6226
6276
  " signup create an openpouch account: --email <addr> or --github (gives you an API key)",
6227
6277
  " activate finish an email signup: --account <id> --token <token> (saves your API key)",
6228
6278
  " whoami show the account behind your API key: tier + current usage",
@@ -6296,7 +6346,7 @@ async function run(argv, ctx) {
6296
6346
  try {
6297
6347
  switch (command) {
6298
6348
  case "deploy":
6299
- return render(await instantCommand(ctx), json);
6349
+ return render(await instantCommand(ctx, parsed.positionals[1]), json);
6300
6350
  case "signup":
6301
6351
  return render(await signupCommand(ctx, { email: parsed.values.email, github: parsed.values.github === true }), json);
6302
6352
  case "activate":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openpouch",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "openpouch 🦘 — agent-native hosting, built for coding agents. Deploy any folder to a live URL in one command: npx openpouch deploy",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",