@oodarun/cli 0.1.12 → 0.1.13

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/cli.js +125 -28
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1036,22 +1036,13 @@ function promptToken(question) {
1036
1036
  process.stdin.on("data", onData);
1037
1037
  });
1038
1038
  }
1039
- async function projectSubMenu(project, isOwner = true) {
1039
+ async function projectSubMenu(project, isOwner = true, publishedUrl = null) {
1040
1040
  const icon = statusIcon(project.status);
1041
1041
  const ownerLabel = project.owner ? ` ${c.gray}(${project.owner})${c.reset}` : "";
1042
1042
  if (!isOwner) {
1043
- let publishedUrl2 = null;
1044
- try {
1045
- const res = await fetch(`https://ooda.run/api/publish/${project.name}`);
1046
- if (res.ok) {
1047
- const info = await res.json();
1048
- publishedUrl2 = info.latestUrl;
1049
- }
1050
- } catch {
1051
- }
1052
1043
  const choices2 = [];
1053
- if (publishedUrl2) {
1054
- choices2.push({ name: `Open published site ${c.gray}${publishedUrl2}${c.reset}`, value: "open-published" });
1044
+ if (publishedUrl) {
1045
+ choices2.push({ name: `Open published site ${c.gray}${publishedUrl}${c.reset}`, value: "open-published" });
1055
1046
  }
1056
1047
  choices2.push(
1057
1048
  new Separator(` ${c.gray}You don't have access to manage this project${c.reset}`),
@@ -1068,15 +1059,6 @@ async function projectSubMenu(project, isOwner = true) {
1068
1059
  throw err;
1069
1060
  }
1070
1061
  }
1071
- let publishedUrl = null;
1072
- try {
1073
- const res = await fetch(`https://ooda.run/api/publish/${project.name}`);
1074
- if (res.ok) {
1075
- const info = await res.json();
1076
- publishedUrl = info.latestUrl;
1077
- }
1078
- } catch {
1079
- }
1080
1062
  const choices = [
1081
1063
  { name: "Connect (launch Claude)", value: "connect" },
1082
1064
  { name: "Re-upload files from local folder", value: "reupload" }
@@ -4087,6 +4069,26 @@ function startServer(opts) {
4087
4069
  });
4088
4070
  }
4089
4071
 
4072
+ // src/cli/sites-client.ts
4073
+ function buildSitesUrl(apiBase, orgId) {
4074
+ return `${apiBase}/org/${encodeURIComponent(orgId)}/dashboard/sites`;
4075
+ }
4076
+ async function fetchPublishedSiteUrl(opts) {
4077
+ try {
4078
+ const res = await fetch(buildSitesUrl(opts.apiBase, opts.orgId), {
4079
+ headers: { Authorization: `Bearer ${opts.jwt}` }
4080
+ });
4081
+ if (!res.ok) return null;
4082
+ const data = await res.json();
4083
+ const match = (data.sites ?? []).find(
4084
+ (s) => s.projectName === opts.projectName || s.slug === opts.projectName
4085
+ );
4086
+ return match?.url ?? null;
4087
+ } catch {
4088
+ return null;
4089
+ }
4090
+ }
4091
+
4090
4092
  // src/urls.ts
4091
4093
  function parseLoaderBase() {
4092
4094
  const raw = process.env.OODA_LOADER_BASE;
@@ -4135,10 +4137,9 @@ function buildSelectorChoices(state) {
4135
4137
  } else {
4136
4138
  projectChoices = state.projects.map((p) => {
4137
4139
  const icon = statusIcon(p.status);
4138
- const url = p.url ? ` ${c.gray}${toPublicUrl(p.url)}${c.reset}` : "";
4139
4140
  const owner = p.owner ? ` ${c.gray}(${p.owner})${c.reset}` : "";
4140
4141
  return {
4141
- name: ` ${p.name}${owner} ${icon} ${p.status}${url}`,
4142
+ name: ` ${p.name}${owner} ${icon} ${p.status}`,
4142
4143
  value: { kind: "project", project: p }
4143
4144
  };
4144
4145
  });
@@ -4644,8 +4645,42 @@ function mergeOnboardingConfig(existing, projectRoot) {
4644
4645
  return d;
4645
4646
  }
4646
4647
 
4648
+ // src/cli/backup-client.ts
4649
+ function buildBackupUrl(apiBase, orgId, projectName) {
4650
+ return `${apiBase}/org/${encodeURIComponent(orgId)}/project/${encodeURIComponent(projectName)}/backup`;
4651
+ }
4652
+ function buildRestoreUrl(apiBase, orgId, projectName) {
4653
+ return `${apiBase}/org/${encodeURIComponent(orgId)}/project/${encodeURIComponent(projectName)}/restore`;
4654
+ }
4655
+ async function restoreProject(opts) {
4656
+ try {
4657
+ const res = await fetch(buildRestoreUrl(opts.apiBase, opts.orgId, opts.projectName), {
4658
+ method: "POST",
4659
+ headers: { Authorization: `Bearer ${opts.jwt}` }
4660
+ });
4661
+ if (!res.ok) return { restored: false, reason: `http-${res.status}` };
4662
+ const data = await res.json();
4663
+ return { restored: Boolean(data.restored), reason: data.reason, dir: data.dir };
4664
+ } catch {
4665
+ return { restored: false, reason: "network-error" };
4666
+ }
4667
+ }
4668
+ async function backupProject(opts) {
4669
+ try {
4670
+ const res = await fetch(buildBackupUrl(opts.apiBase, opts.orgId, opts.projectName), {
4671
+ method: "POST",
4672
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${opts.jwt}` },
4673
+ body: JSON.stringify({ projectRoot: opts.projectRoot })
4674
+ });
4675
+ return res.ok;
4676
+ } catch {
4677
+ return false;
4678
+ }
4679
+ }
4680
+
4647
4681
  // src/cli/session.ts
4648
4682
  var IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
4683
+ var BACKUP_INTERVAL_MS = 3 * 60 * 1e3;
4649
4684
  var IDLE_WARNING_MS = 25 * 60 * 1e3;
4650
4685
  var IDLE_CHECK_INTERVAL_MS = 60 * 1e3;
4651
4686
  var _activeProject = null;
@@ -4715,8 +4750,27 @@ async function connectAndRunClaude(projectName, apiToken, claudeToken, projectUr
4715
4750
  `);
4716
4751
  return;
4717
4752
  }
4718
- const projectRoot = await findProjectRoot(projectName, apiToken);
4719
4753
  console.log(` ${c.green}${c.bold}\u2713${c.reset} Connected ${c.gray}${elapsed()}${c.reset}`);
4754
+ const backupApiBase = process.env.OODA_API_BASE || "https://api.ooda.run";
4755
+ const backupOrgId = isOrgMode() ? getOrgId() : null;
4756
+ if (backupOrgId) {
4757
+ const r = await restoreProject({
4758
+ apiBase: backupApiBase,
4759
+ orgId: backupOrgId,
4760
+ projectName,
4761
+ jwt: getAccessToken() ?? ""
4762
+ });
4763
+ if (r.restored) {
4764
+ const where = r.dir ? ` ${c.gray}(${r.dir})${c.reset}` : "";
4765
+ console.log(` ${c.green}${c.bold}\u2713${c.reset} ${c.gray}Restored project files from last snapshot${c.reset}${where}`);
4766
+ } else if (r.reason === "no-backup") {
4767
+ console.log(` ${c.gray}\xB7 No snapshot to restore yet${c.reset}`);
4768
+ } else {
4769
+ console.log(` ${c.yellow}!${c.reset} ${c.gray}Snapshot restore failed (${r.reason ?? "unknown"})${c.reset}`);
4770
+ }
4771
+ }
4772
+ const projectRoot = await findProjectRoot(projectName, apiToken);
4773
+ const canBackup = !!backupOrgId && projectRoot !== "/home/user" && projectRoot !== "/root";
4720
4774
  pushPhase("setup", "Writing config files");
4721
4775
  try {
4722
4776
  const memRaw = await client.exec("free -m 2>/dev/null || cat /proc/meminfo 2>/dev/null | head -3");
@@ -4996,6 +5050,23 @@ ${text}`;
4996
5050
  claudeExec
4997
5051
  ].join("; ");
4998
5052
  }
5053
+ let backupTimer = null;
5054
+ let backupInFlight = false;
5055
+ if (canBackup) {
5056
+ backupTimer = setInterval(() => {
5057
+ if (backupInFlight) return;
5058
+ backupInFlight = true;
5059
+ void backupProject({
5060
+ apiBase: backupApiBase,
5061
+ orgId: backupOrgId,
5062
+ projectName,
5063
+ projectRoot,
5064
+ jwt: getAccessToken() ?? ""
5065
+ }).finally(() => {
5066
+ backupInFlight = false;
5067
+ });
5068
+ }, BACKUP_INTERVAL_MS);
5069
+ }
4999
5070
  let tokenRefreshTimer = null;
5000
5071
  const orgApiKey = claudeEnv?.ANTHROPIC_API_KEY;
5001
5072
  const useServerRefresh = !apiKeyHelper && orgApiKey && isOrgMode();
@@ -5144,6 +5215,22 @@ ${text}`;
5144
5215
  currentCmdAlive = false;
5145
5216
  _activeProject = null;
5146
5217
  if (tokenRefreshTimer) clearInterval(tokenRefreshTimer);
5218
+ if (backupTimer) clearInterval(backupTimer);
5219
+ if (canBackup) {
5220
+ console.log(` ${c.gray}Saving project snapshot...${c.reset}`);
5221
+ const saved = await backupProject({
5222
+ apiBase: backupApiBase,
5223
+ orgId: backupOrgId,
5224
+ projectName,
5225
+ projectRoot,
5226
+ jwt: getAccessToken() ?? ""
5227
+ });
5228
+ if (saved) {
5229
+ console.log(` ${c.green}${c.bold}\u2713${c.reset} ${c.gray}Snapshot saved${c.reset}`);
5230
+ } else {
5231
+ console.log(` ${c.yellow}!${c.reset} ${c.gray}Snapshot save failed (project not backed up)${c.reset}`);
5232
+ }
5233
+ }
5147
5234
  console.log(` ${c.gray}Stopping project processes...${c.reset}`);
5148
5235
  const result = await stopProject(projectName, apiToken);
5149
5236
  if (result.ok) {
@@ -5373,7 +5460,7 @@ async function deployFromGitHubFlow(target, apiToken, claudeToken) {
5373
5460
  }
5374
5461
 
5375
5462
  // src/cli/index.ts
5376
- var CLI_VERSION = "0.1.11";
5463
+ var CLI_VERSION = "0.1.13";
5377
5464
  function formatMutationError(result) {
5378
5465
  const parts = [];
5379
5466
  if (result.status !== void 0) parts.push(String(result.status));
@@ -5560,14 +5647,24 @@ async function selectorLoop(apiToken, claudeToken, dashboardUrl, cwd) {
5560
5647
  const project = action.project;
5561
5648
  const currentUserName = isOrgMode() ? getOrgDisplayName() : null;
5562
5649
  const isOwner = !isOrgMode() || !project.owner || project.owner === currentUserName;
5563
- const subAction = await projectSubMenu(project, isOwner);
5650
+ let publishedUrl = null;
5651
+ const subOrgId = isOrgMode() ? getOrgId() : null;
5652
+ if (subOrgId) {
5653
+ const apiBase = process.env.OODA_API_BASE || "https://api.ooda.run";
5654
+ publishedUrl = await fetchPublishedSiteUrl({
5655
+ apiBase,
5656
+ orgId: subOrgId,
5657
+ projectName: project.name,
5658
+ jwt: getAccessToken() ?? ""
5659
+ });
5660
+ }
5661
+ const subAction = await projectSubMenu(project, isOwner, publishedUrl);
5564
5662
  switch (subAction) {
5565
5663
  case "connect":
5566
5664
  await connectAndRunClaude(project.name, apiToken, claudeToken, project.url, void 0, project.previewUrl);
5567
5665
  break;
5568
5666
  case "open-published": {
5569
- const publishUrl = `https://${project.name}-p.ooda.run`;
5570
- await openUrl(publishUrl);
5667
+ await openUrl(publishedUrl ?? `https://${project.name}-p.ooda.run`);
5571
5668
  break;
5572
5669
  }
5573
5670
  case "reupload": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oodarun/cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Launch Claude Code on cloud dev environments",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",