openpouch 0.2.0 → 0.2.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/openpouch.js +91 -53
  2. package/package.json +1 -1
package/openpouch.js CHANGED
@@ -4259,9 +4259,8 @@ function findUsableApproval(file, match, now) {
4259
4259
  }
4260
4260
 
4261
4261
  // packages/cli/src/commands/activate.ts
4262
- import { mkdir, writeFile as writeFile3 } from "node:fs/promises";
4263
- import { homedir as homedir2 } from "node:os";
4264
- import { join as join3 } from "node:path";
4262
+ import { mkdir, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
4263
+ import { dirname } from "node:path";
4265
4264
 
4266
4265
  // packages/cli/src/shared.ts
4267
4266
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
@@ -4832,12 +4831,17 @@ var PROVIDER_CREDENTIALS = {
4832
4831
  };
4833
4832
  var ANONYMOUS_KEY = "anonymous";
4834
4833
  var RUN_KEY = { envVar: "OPENPOUCH_API_KEY", file: "openpouch-run.key" };
4834
+ function runKeyPath(env) {
4835
+ const override = env["OPENPOUCH_KEY_FILE"]?.trim();
4836
+ if (override) return override;
4837
+ const home = env["OPENPOUCH_HOME"] ?? homedir();
4838
+ return join2(home, ".openpouch", RUN_KEY.file);
4839
+ }
4835
4840
  async function resolveRunKey(env) {
4836
4841
  const fromEnv = env[RUN_KEY.envVar]?.trim();
4837
4842
  if (fromEnv) return fromEnv;
4838
4843
  try {
4839
- const home = env["OPENPOUCH_HOME"] ?? homedir();
4840
- const fromFile = (await readFile2(join2(home, ".openpouch", RUN_KEY.file), "utf8")).trim();
4844
+ const fromFile = (await readFile2(runKeyPath(env), "utf8")).trim();
4841
4845
  if (fromFile.length > 0) return fromFile;
4842
4846
  } catch {
4843
4847
  }
@@ -4893,6 +4897,15 @@ function keyPublicPrefix(key) {
4893
4897
  const m = /^(op_live_[0-9a-f]+)_/.exec(key);
4894
4898
  return m ? m[1] : "op_live_\u2026";
4895
4899
  }
4900
+ function looksLikeOpenpouchKey(content) {
4901
+ const t = content.trim();
4902
+ if (t.length === 0) return true;
4903
+ if (t.includes("\n")) return false;
4904
+ return /^op_(?:live|test)_[0-9a-f]{6,}_[A-Za-z0-9_-]+$/.test(t);
4905
+ }
4906
+ function looksLikePrivateKey(content) {
4907
+ return /-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/.test(content);
4908
+ }
4896
4909
  async function activateCommand(ctx, opts) {
4897
4910
  const account = opts.account?.trim();
4898
4911
  const token = opts.token?.trim();
@@ -4911,17 +4924,29 @@ async function activateCommand(ctx, opts) {
4911
4924
  try {
4912
4925
  const res = await adapter.verifyEmail(account, token);
4913
4926
  let saved = false;
4914
- let keyPath = "";
4927
+ let reason;
4928
+ const keyPath = opts.keyFile?.trim() || runKeyPath(ctx.env);
4915
4929
  try {
4916
- const home = ctx.env["OPENPOUCH_HOME"] ?? homedir2();
4917
- const dir = join3(home, ".openpouch");
4918
- await mkdir(dir, { recursive: true });
4919
- keyPath = join3(dir, "openpouch-run.key");
4920
- await writeFile3(keyPath, `${res.key}
4930
+ let existing = null;
4931
+ try {
4932
+ existing = await readFile3(keyPath, "utf8");
4933
+ } catch (e) {
4934
+ if (e.code !== "ENOENT") throw e;
4935
+ }
4936
+ if (existing !== null && existing.trim().length > 0 && !looksLikeOpenpouchKey(existing)) {
4937
+ const how = "Set OPENPOUCH_KEY_FILE (or pass --key-file) to a free path, or move that file.";
4938
+ reason = looksLikePrivateKey(existing) ? `refusing to overwrite ${keyPath} \u2014 it looks like an existing private key. ${how}` : `refusing to overwrite ${keyPath} \u2014 it is not an openpouch key file. ${how}`;
4939
+ } else {
4940
+ await mkdir(dirname(keyPath), { recursive: true });
4941
+ if (existing !== null && existing.trim().length > 0) {
4942
+ await writeFile3(`${keyPath}.bak`, existing, { mode: 384 });
4943
+ }
4944
+ await writeFile3(keyPath, `${res.key}
4921
4945
  `, { mode: 384 });
4922
- saved = true;
4923
- } catch {
4924
- saved = false;
4946
+ saved = true;
4947
+ }
4948
+ } catch (e) {
4949
+ reason = `could not save the key to ${keyPath}: ${e.message}`;
4925
4950
  }
4926
4951
  return summarized(
4927
4952
  EXIT.OK,
@@ -4930,12 +4955,12 @@ async function activateCommand(ctx, opts) {
4930
4955
  account: res.account,
4931
4956
  keyPrefix: keyPublicPrefix(res.key),
4932
4957
  saved,
4933
- ...saved ? { keyPath } : { key: res.key }
4958
+ ...saved ? { keyPath } : { reason, key: res.key }
4934
4959
  },
4935
4960
  activateSummary(saved),
4936
4961
  [
4937
4962
  `openpouch activate \u2713 ${res.account.id} [${res.account.tier}]`,
4938
- saved ? ` saved your key to ${keyPath} (chmod 600) \u2014 deploys now run under your account` : ` \u26A0 could not save the key file \u2014 set it yourself: export OPENPOUCH_API_KEY=${res.key}`,
4963
+ ...saved ? [` saved your key to ${keyPath} (chmod 600) \u2014 deploys now run under your account`] : [` \u26A0 key NOT saved: ${reason}`, ` \u2192 use it now: export OPENPOUCH_API_KEY=${res.key}`],
4939
4964
  ` key: ${keyPublicPrefix(res.key)}\u2026`
4940
4965
  ]
4941
4966
  );
@@ -4952,33 +4977,33 @@ import { createInterface } from "node:readline/promises";
4952
4977
 
4953
4978
  // packages/cli/src/approvals.ts
4954
4979
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
4955
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
4956
- import { homedir as homedir3, userInfo } from "node:os";
4957
- import { join as join4 } from "node:path";
4980
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
4981
+ import { homedir as homedir2, userInfo } from "node:os";
4982
+ import { join as join3 } from "node:path";
4958
4983
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
4959
4984
  async function loadApprovals(cwd) {
4960
4985
  try {
4961
- const raw = await readFile3(join4(cwd, APPROVALS_FILENAME), "utf8");
4986
+ const raw = await readFile4(join3(cwd, APPROVALS_FILENAME), "utf8");
4962
4987
  return approvalsFileSchema.parse(JSON.parse(raw));
4963
4988
  } catch {
4964
4989
  return { version: 0, requests: [] };
4965
4990
  }
4966
4991
  }
4967
4992
  async function saveApprovals(cwd, file) {
4968
- await mkdir2(join4(cwd, APPROVALS_DIR), { recursive: true });
4993
+ await mkdir2(join3(cwd, APPROVALS_DIR), { recursive: true });
4969
4994
  await ensureGitignored(cwd);
4970
4995
  await writeFile4(
4971
- join4(cwd, APPROVALS_FILENAME),
4996
+ join3(cwd, APPROVALS_FILENAME),
4972
4997
  `${JSON.stringify(approvalsFileSchema.parse(file), null, 2)}
4973
4998
  `,
4974
4999
  "utf8"
4975
5000
  );
4976
5001
  }
4977
5002
  async function ensureGitignored(cwd) {
4978
- const path = join4(cwd, ".gitignore");
5003
+ const path = join3(cwd, ".gitignore");
4979
5004
  let content = "";
4980
5005
  try {
4981
- content = await readFile3(path, "utf8");
5006
+ content = await readFile4(path, "utf8");
4982
5007
  } catch {
4983
5008
  }
4984
5009
  if (!content.split("\n").some((line) => line.trim() === ".openpouch/")) {
@@ -4999,10 +5024,10 @@ function newApprovalRequest(match, now) {
4999
5024
  };
5000
5025
  }
5001
5026
  async function getApproverSecret(createIfMissing, home) {
5002
- const dir = join4(home ?? homedir3(), ".openpouch");
5003
- const path = join4(dir, "approver.secret");
5027
+ const dir = join3(home ?? homedir2(), ".openpouch");
5028
+ const path = join3(dir, "approver.secret");
5004
5029
  try {
5005
- const existing = (await readFile3(path, "utf8")).trim();
5030
+ const existing = (await readFile4(path, "utf8")).trim();
5006
5031
  if (existing.length > 0) return existing;
5007
5032
  } catch {
5008
5033
  }
@@ -5322,11 +5347,11 @@ async function deployCommand(ctx, environment) {
5322
5347
  }
5323
5348
 
5324
5349
  // packages/cli/src/commands/init.ts
5325
- import { join as join7 } from "node:path";
5350
+ import { join as join6 } from "node:path";
5326
5351
 
5327
5352
  // packages/cli/src/agentsmd.ts
5328
- import { readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
5329
- import { join as join5 } from "node:path";
5353
+ import { readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
5354
+ import { join as join4 } from "node:path";
5330
5355
  var AGENTS_MD_FILENAME = "AGENTS.md";
5331
5356
  var START = "<!-- openpouch:start -->";
5332
5357
  var END = "<!-- openpouch:end -->";
@@ -5349,11 +5374,11 @@ function renderAgentsSection(projectName) {
5349
5374
  ].join("\n");
5350
5375
  }
5351
5376
  async function upsertAgentsSection(cwd, projectName) {
5352
- const path = join5(cwd, AGENTS_MD_FILENAME);
5377
+ const path = join4(cwd, AGENTS_MD_FILENAME);
5353
5378
  const section = renderAgentsSection(projectName);
5354
5379
  let existing;
5355
5380
  try {
5356
- existing = await readFile4(path, "utf8");
5381
+ existing = await readFile5(path, "utf8");
5357
5382
  } catch {
5358
5383
  existing = void 0;
5359
5384
  }
@@ -5380,8 +5405,8 @@ ${section}
5380
5405
  }
5381
5406
 
5382
5407
  // packages/cli/src/detect.ts
5383
- import { readFile as readFile5, readdir } from "node:fs/promises";
5384
- import { basename, extname, join as join6 } from "node:path";
5408
+ import { readFile as readFile6, readdir } from "node:fs/promises";
5409
+ import { basename, extname, join as join5 } from "node:path";
5385
5410
  var FRAMEWORK_BY_DEP = [
5386
5411
  ["next", "nextjs"],
5387
5412
  ["astro", "astro"],
@@ -5416,7 +5441,7 @@ async function collectSourceFiles(root) {
5416
5441
  }
5417
5442
  for (const entry of entries) {
5418
5443
  if (files.length >= MAX_FILES) break;
5419
- const full = join6(current.dir, entry.name);
5444
+ const full = join5(current.dir, entry.name);
5420
5445
  if (entry.isDirectory()) {
5421
5446
  if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".") && current.depth < MAX_DEPTH) {
5422
5447
  stack.push({ dir: full, depth: current.depth + 1 });
@@ -5433,7 +5458,7 @@ async function scanEnvVarNames(root) {
5433
5458
  for (const file of await collectSourceFiles(root)) {
5434
5459
  let content;
5435
5460
  try {
5436
- const buf = await readFile5(file);
5461
+ const buf = await readFile6(file);
5437
5462
  if (buf.byteLength > MAX_FILE_BYTES) continue;
5438
5463
  content = buf.toString("utf8");
5439
5464
  } catch {
@@ -5451,7 +5476,7 @@ async function scanEnvVarNames(root) {
5451
5476
  async function detectProject(cwd) {
5452
5477
  let pkg = {};
5453
5478
  try {
5454
- pkg = JSON.parse(await readFile5(join6(cwd, "package.json"), "utf8"));
5479
+ pkg = JSON.parse(await readFile6(join5(cwd, "package.json"), "utf8"));
5455
5480
  } catch {
5456
5481
  }
5457
5482
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
@@ -5561,8 +5586,8 @@ async function initCommand(ctx, flags) {
5561
5586
  "This is an openpouch bug \u2014 please report it."
5562
5587
  );
5563
5588
  }
5564
- await writeJsonFile(join7(ctx.cwd, MANIFEST_FILENAME), validated.value);
5565
- await writeJsonFile(join7(ctx.cwd, POLICY_FILENAME), defaultPolicy());
5589
+ await writeJsonFile(join6(ctx.cwd, MANIFEST_FILENAME), validated.value);
5590
+ await writeJsonFile(join6(ctx.cwd, POLICY_FILENAME), defaultPolicy());
5566
5591
  const agentsMd = await upsertAgentsSection(ctx.cwd, detected.name);
5567
5592
  hints.push("policy default: previews autonomous, production requires approval \u2014 edit deploy.policy.json to change");
5568
5593
  return summarized(
@@ -5602,8 +5627,8 @@ async function initCommand(ctx, flags) {
5602
5627
 
5603
5628
  // packages/cli/src/commands/instant.ts
5604
5629
  import { spawn } from "node:child_process";
5605
- import { readFile as readFile6 } from "node:fs/promises";
5606
- import { basename as basename2, join as join8 } from "node:path";
5630
+ import { readFile as readFile7 } from "node:fs/promises";
5631
+ import { basename as basename2, join as join7 } from "node:path";
5607
5632
  var TAR_EXCLUDES = [
5608
5633
  "./node_modules",
5609
5634
  "./.git",
@@ -5632,7 +5657,7 @@ function buildTarball(cwd) {
5632
5657
  }
5633
5658
  async function projectHint(cwd) {
5634
5659
  try {
5635
- const pkg = JSON.parse(await readFile6(join8(cwd, "package.json"), "utf8"));
5660
+ const pkg = JSON.parse(await readFile7(join7(cwd, "package.json"), "utf8"));
5636
5661
  if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
5637
5662
  } catch {
5638
5663
  }
@@ -5641,12 +5666,16 @@ async function projectHint(cwd) {
5641
5666
  async function instantCommand(ctx) {
5642
5667
  const loaded = await loadManifest(ctx.cwd);
5643
5668
  if (loaded.status === "ok") {
5644
- return errorResult(
5645
- EXIT.USAGE,
5646
- "usage",
5647
- "this project already has deploy.manifest.json",
5648
- "Use `openpouch preview` / `openpouch prod` for a configured project. `openpouch deploy` is the zero-config instant lane."
5649
- );
5669
+ const envs = Object.values(loaded.manifest.environments ?? {});
5670
+ const hasGovernedEnv = envs.some((e) => e.provider !== "openpouch-run");
5671
+ if (hasGovernedEnv) {
5672
+ return errorResult(
5673
+ EXIT.USAGE,
5674
+ "usage",
5675
+ "this project already has deploy.manifest.json",
5676
+ "Use `openpouch preview` / `openpouch prod` for a configured project. `openpouch deploy` is the zero-config instant lane."
5677
+ );
5678
+ }
5650
5679
  }
5651
5680
  let tarball;
5652
5681
  try {
@@ -5673,8 +5702,8 @@ async function instantCommand(ctx) {
5673
5702
  preview: { provider: "openpouch-run", serviceId: result2.slug, url: result2.url }
5674
5703
  }
5675
5704
  };
5676
- await writeJsonFile(join8(ctx.cwd, MANIFEST_FILENAME), manifest);
5677
- await writeJsonFile(join8(ctx.cwd, POLICY_FILENAME), defaultPolicy());
5705
+ await writeJsonFile(join7(ctx.cwd, MANIFEST_FILENAME), manifest);
5706
+ await writeJsonFile(join7(ctx.cwd, POLICY_FILENAME), defaultPolicy());
5678
5707
  await appendDeployment(ctx.cwd, {
5679
5708
  id: result2.slug,
5680
5709
  environment: "preview",
@@ -6216,8 +6245,9 @@ var USAGE = [
6216
6245
  " --github (signup) create an account via GitHub (prints the authorize URL)",
6217
6246
  " --account <id> (activate) the account id from signup",
6218
6247
  " --token <tok> (activate) the verification token from the signup email",
6248
+ " --key-file <p> (activate) where to save the issued key (default ~/.openpouch/openpouch-run.key; never overwrites a non-openpouch file)",
6219
6249
  "",
6220
- "account key: deploys send Authorization: Bearer from OPENPOUCH_API_KEY or ~/.openpouch/openpouch-run.key (optional \u2014 no key = anonymous)",
6250
+ "account key: deploys send Authorization: Bearer from OPENPOUCH_API_KEY or ~/.openpouch/openpouch-run.key (override the path with OPENPOUCH_KEY_FILE; optional \u2014 no key = anonymous)",
6221
6251
  "exit codes: 0 ok \xB7 1 unexpected \xB7 2 usage \xB7 3 manifest/policy missing or invalid \xB7 4 auth \xB7 5 provider \xB7 6 policy gate (approval required/denied/human required) \xB7 7 deploy failed \xB7 8 verify failed"
6222
6252
  ].join("\n");
6223
6253
  function render(result2, json) {
@@ -6240,7 +6270,8 @@ async function run(argv, ctx) {
6240
6270
  limit: { type: "string" },
6241
6271
  email: { type: "string" },
6242
6272
  account: { type: "string" },
6243
- token: { type: "string" }
6273
+ token: { type: "string" },
6274
+ "key-file": { type: "string" }
6244
6275
  },
6245
6276
  allowPositionals: true
6246
6277
  });
@@ -6269,7 +6300,14 @@ async function run(argv, ctx) {
6269
6300
  case "signup":
6270
6301
  return render(await signupCommand(ctx, { email: parsed.values.email, github: parsed.values.github === true }), json);
6271
6302
  case "activate":
6272
- return render(await activateCommand(ctx, { account: parsed.values.account, token: parsed.values.token }), json);
6303
+ return render(
6304
+ await activateCommand(ctx, {
6305
+ account: parsed.values.account,
6306
+ token: parsed.values.token,
6307
+ keyFile: parsed.values["key-file"]
6308
+ }),
6309
+ json
6310
+ );
6273
6311
  case "whoami":
6274
6312
  return render(await whoamiCommand(ctx), json);
6275
6313
  case "init":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openpouch",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",