@oodarun/cli 0.1.11 → 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.
- package/dist/cli.js +126 -29
- 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 (
|
|
1054
|
-
choices2.push({ name: `Open published site ${c.gray}${
|
|
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" }
|
|
@@ -2026,7 +2008,7 @@ if (files.length === 0) {
|
|
|
2026
2008
|
process.exit(1);
|
|
2027
2009
|
}
|
|
2028
2010
|
|
|
2029
|
-
const body = JSON.stringify({ slug, files });
|
|
2011
|
+
const body = JSON.stringify({ slug, projectName: orgProjectName || undefined, files });
|
|
2030
2012
|
console.log('Uploading (' + (body.length / 1024 / 1024).toFixed(1) + 'MB)...');
|
|
2031
2013
|
|
|
2032
2014
|
const res = await fetch(PUBLISH_URL, {
|
|
@@ -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}
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
5570
|
-
await openUrl(publishUrl);
|
|
5667
|
+
await openUrl(publishedUrl ?? `https://${project.name}-p.ooda.run`);
|
|
5571
5668
|
break;
|
|
5572
5669
|
}
|
|
5573
5670
|
case "reupload": {
|