@oodarun/cli 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/cli.js +235 -42
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -846,6 +846,31 @@ async function loginWithPassword(email, password) {
846
846
  return null;
847
847
  }
848
848
  }
849
+ async function requestLoginCode(email) {
850
+ try {
851
+ const res = await fetch(`${ORG_API_BASE}/auth/request-code`, {
852
+ method: "POST",
853
+ headers: { "Content-Type": "application/json" },
854
+ body: JSON.stringify({ email })
855
+ });
856
+ return res.ok;
857
+ } catch {
858
+ return false;
859
+ }
860
+ }
861
+ async function verifyLoginCode(email, code) {
862
+ try {
863
+ const res = await fetch(`${ORG_API_BASE}/auth/verify-code`, {
864
+ method: "POST",
865
+ headers: { "Content-Type": "application/json" },
866
+ body: JSON.stringify({ email, code })
867
+ });
868
+ if (!res.ok) return null;
869
+ return await res.json();
870
+ } catch {
871
+ return null;
872
+ }
873
+ }
849
874
  async function signupWithPassword(email, password, name) {
850
875
  try {
851
876
  const res = await fetch(`${ORG_API_BASE}/auth/signup`, {
@@ -1129,58 +1154,49 @@ function patchFetchForOrg(orgId) {
1129
1154
  });
1130
1155
  }
1131
1156
  async function handleEmailPasswordAuth() {
1132
- const action = await select2({
1133
- message: "Do you have an account?",
1157
+ const method = await select2({
1158
+ message: "How would you like to sign in?",
1134
1159
  choices: [
1135
- { name: "Yes, log me in", value: "login" },
1136
- { name: "No, I need to sign up (requires invite)", value: "signup" }
1160
+ { name: "Email me a login code (recommended)", value: "code" },
1161
+ { name: "Use my password", value: "password" },
1162
+ { name: "Sign up (requires invite)", value: "signup" }
1137
1163
  ]
1138
1164
  });
1139
1165
  console.log("");
1140
- if (action === "signup") {
1141
- const email2 = await prompt(` ${c.blue}${c.bold}Email:${c.reset} `);
1142
- if (!email2) process.exit(1);
1143
- const name = await prompt(` ${c.blue}${c.bold}Your name:${c.reset} `);
1144
- if (!name) process.exit(1);
1145
- for (let attempt = 0; attempt < 3; attempt++) {
1146
- const password = await promptPassword(` ${c.blue}${c.bold}Password:${c.reset} `);
1147
- if (!password) process.exit(1);
1148
- const result = await signupWithPassword(email2, password, name);
1149
- if (!result) {
1150
- console.log(` ${c.red}Could not reach the server. Check your connection.${c.reset}`);
1151
- process.exit(1);
1152
- }
1153
- if (result.ok) {
1154
- const loginResult = await loginWithPassword(email2, password);
1155
- if (!loginResult) {
1156
- console.log(` ${c.red}Account created but login failed. Try again.${c.reset}`);
1157
- process.exit(1);
1158
- }
1159
- return completeJwtLogin(loginResult);
1160
- }
1161
- const remaining = 2 - attempt;
1162
- console.log(` ${c.red}${result.error || "Signup failed"}${c.reset}`);
1163
- if (result.errors?.length) {
1164
- for (const err of result.errors) {
1165
- console.log(` ${c.gray}\u2022 ${err}${c.reset}`);
1166
- }
1167
- }
1168
- if (remaining > 0) {
1169
- console.log(` ${c.gray}${remaining} attempt${remaining > 1 ? "s" : ""} remaining${c.reset}`);
1170
- }
1171
- }
1172
- console.log(` ${c.red}Too many failed attempts.${c.reset}`);
1166
+ if (method === "code") return emailCodeLoginInteractive();
1167
+ if (method === "signup") return signupInteractive();
1168
+ return passwordLoginInteractive();
1169
+ }
1170
+ async function emailCodeLoginInteractive(prefillEmail) {
1171
+ const email = prefillEmail || await prompt(` ${c.blue}${c.bold}Email:${c.reset} `);
1172
+ if (!email) process.exit(1);
1173
+ if (!await requestLoginCode(email)) {
1174
+ console.log(` ${c.red}Couldn't send a login code. Check the email address and try again.${c.reset}`);
1173
1175
  process.exit(1);
1174
1176
  }
1177
+ console.log(` ${c.gray}We emailed a 6-digit code to ${email} (expires in 10 minutes).${c.reset}`);
1178
+ console.log("");
1179
+ for (let attempt = 0; attempt < 3; attempt++) {
1180
+ const code = await promptToken(` ${c.blue}${c.bold}Code:${c.reset} `);
1181
+ if (!code) process.exit(1);
1182
+ const result = await verifyLoginCode(email, code);
1183
+ if (result) return completeJwtLogin(result);
1184
+ const remaining = 2 - attempt;
1185
+ if (remaining > 0) {
1186
+ console.log(` ${c.red}Invalid or expired code.${c.reset} ${c.gray}${remaining} attempt${remaining > 1 ? "s" : ""} remaining${c.reset}`);
1187
+ }
1188
+ }
1189
+ console.log(` ${c.red}Too many failed attempts.${c.reset}`);
1190
+ process.exit(1);
1191
+ }
1192
+ async function passwordLoginInteractive() {
1175
1193
  const email = await prompt(` ${c.blue}${c.bold}Email:${c.reset} `);
1176
1194
  if (!email) process.exit(1);
1177
1195
  for (let attempt = 0; attempt < 3; attempt++) {
1178
1196
  const password = await promptPassword(` ${c.blue}${c.bold}Password:${c.reset} `);
1179
1197
  if (!password) process.exit(1);
1180
1198
  const result = await loginWithPassword(email, password);
1181
- if (result) {
1182
- return completeJwtLogin(result);
1183
- }
1199
+ if (result) return completeJwtLogin(result);
1184
1200
  const remaining = 2 - attempt;
1185
1201
  if (remaining > 0) {
1186
1202
  console.log(` ${c.red}Invalid password.${c.reset} ${c.gray}${remaining} attempt${remaining > 1 ? "s" : ""} remaining${c.reset}`);
@@ -1189,6 +1205,141 @@ async function handleEmailPasswordAuth() {
1189
1205
  console.log(` ${c.red}Too many failed attempts.${c.reset}`);
1190
1206
  process.exit(1);
1191
1207
  }
1208
+ async function signupInteractive() {
1209
+ const email = await prompt(` ${c.blue}${c.bold}Email:${c.reset} `);
1210
+ if (!email) process.exit(1);
1211
+ const name = await prompt(` ${c.blue}${c.bold}Your name:${c.reset} `);
1212
+ if (!name) process.exit(1);
1213
+ for (let attempt = 0; attempt < 3; attempt++) {
1214
+ const password = await promptPassword(` ${c.blue}${c.bold}Password:${c.reset} `);
1215
+ if (!password) process.exit(1);
1216
+ const result = await signupWithPassword(email, password, name);
1217
+ if (!result) {
1218
+ console.log(` ${c.red}Could not reach the server. Check your connection.${c.reset}`);
1219
+ process.exit(1);
1220
+ }
1221
+ if (result.ok) {
1222
+ const loginResult = await loginWithPassword(email, password);
1223
+ if (!loginResult) {
1224
+ console.log(` ${c.red}Account created but login failed. Try again.${c.reset}`);
1225
+ process.exit(1);
1226
+ }
1227
+ return completeJwtLogin(loginResult);
1228
+ }
1229
+ const remaining = 2 - attempt;
1230
+ console.log(` ${c.red}${result.error || "Signup failed"}${c.reset}`);
1231
+ if (result.errors?.length) {
1232
+ for (const err of result.errors) {
1233
+ console.log(` ${c.gray}\u2022 ${err}${c.reset}`);
1234
+ }
1235
+ }
1236
+ if (remaining > 0) {
1237
+ console.log(` ${c.gray}${remaining} attempt${remaining > 1 ? "s" : ""} remaining${c.reset}`);
1238
+ }
1239
+ }
1240
+ console.log(` ${c.red}Too many failed attempts.${c.reset}`);
1241
+ process.exit(1);
1242
+ }
1243
+ async function loginCmd(opts) {
1244
+ if (opts.email && opts.code) {
1245
+ const result = await verifyLoginCode(opts.email, opts.code);
1246
+ if (!result) return failLogin(opts.json, "Invalid or expired code.");
1247
+ finalizeLogin(result, opts.org, opts.json);
1248
+ return;
1249
+ }
1250
+ if (opts.email) {
1251
+ if (!await requestLoginCode(opts.email)) return failLogin(opts.json, "Couldn't send a login code.");
1252
+ if (opts.json) {
1253
+ console.log(JSON.stringify({ ok: true, sent: true, email: opts.email }));
1254
+ } else {
1255
+ console.log(`
1256
+ ${c.green}${c.bold}\u2713${c.reset} Code sent to ${c.bold}${opts.email}${c.reset}`);
1257
+ console.log(` ${c.gray}Check your email, then run:${c.reset}`);
1258
+ console.log(` ${c.cyan}ooda login --email ${opts.email} --code <code>${c.reset}
1259
+ `);
1260
+ }
1261
+ return;
1262
+ }
1263
+ await emailCodeLoginInteractive();
1264
+ }
1265
+ function finalizeLogin(result, orgFlag, json) {
1266
+ if (result.orgs.length === 0) return failLogin(json, "No organizations for this account.");
1267
+ let orgId;
1268
+ let orgName;
1269
+ if (orgFlag) {
1270
+ const match = result.orgs.find((o) => o.orgId === orgFlag);
1271
+ if (!match) return failLogin(json, `You're not a member of org "${orgFlag}".`);
1272
+ orgId = match.orgId;
1273
+ orgName = match.orgName;
1274
+ } else if (result.orgs.length === 1) {
1275
+ orgId = result.orgs[0].orgId;
1276
+ orgName = result.orgs[0].orgName;
1277
+ } else {
1278
+ return failLogin(json, `Multiple organizations \u2014 pass --org <id>. Options: ${result.orgs.map((o) => o.orgId).join(", ")}`);
1279
+ }
1280
+ saveJwtTokens(result.accessToken, result.refreshToken, result.user.id, orgId);
1281
+ const selected = result.orgs.find((o) => o.orgId === orgId);
1282
+ setOrgMode(orgId, result.user.email, result.user.name, orgName, selected?.claudeConfig ?? void 0);
1283
+ patchFetchForOrg(orgId);
1284
+ if (json) {
1285
+ console.log(JSON.stringify({ ok: true, email: result.user.email, orgId, orgName }));
1286
+ } else {
1287
+ console.log(`
1288
+ ${c.green}${c.bold}\u2713${c.reset} Logged in as ${c.bold}${result.user.name}${c.reset} (${orgName})
1289
+ `);
1290
+ }
1291
+ }
1292
+ function failLogin(json, message) {
1293
+ if (json) {
1294
+ console.log(JSON.stringify({ ok: false, error: message }));
1295
+ } else {
1296
+ console.log(`
1297
+ ${c.red}${message}${c.reset}
1298
+ `);
1299
+ }
1300
+ process.exitCode = 1;
1301
+ }
1302
+ function whoamiCmd(opts) {
1303
+ const orgId = getOrgId();
1304
+ const token = getAccessToken();
1305
+ if (!orgId || !token) {
1306
+ if (opts.json) {
1307
+ console.log(JSON.stringify({ ok: false, authenticated: false }));
1308
+ } else {
1309
+ console.log(`
1310
+ ${c.yellow}Not signed in.${c.reset} ${c.gray}Run ${c.bold}ooda login${c.reset}${c.gray} to authenticate.${c.reset}
1311
+ `);
1312
+ }
1313
+ process.exitCode = 1;
1314
+ return;
1315
+ }
1316
+ const orgName = getOrgName() || orgId;
1317
+ const email = getOrgEmail();
1318
+ const name = getOrgDisplayName();
1319
+ if (opts.json) {
1320
+ console.log(JSON.stringify({ ok: true, authenticated: true, orgId, orgName, email: email ?? null, name: name ?? null }));
1321
+ } else {
1322
+ const who = name || email;
1323
+ console.log(`
1324
+ ${c.green}${c.bold}\u2713${c.reset} Signed in${who ? ` as ${c.bold}${who}${c.reset}` : ""} ${c.gray}(${orgName})${c.reset}
1325
+ `);
1326
+ }
1327
+ }
1328
+ function logoutCmd(opts) {
1329
+ const envSession = Boolean(process.env.OODA_ACCESS_TOKEN || process.env.OODA_ORG_ID);
1330
+ clearJwtTokens();
1331
+ clearOrgMode();
1332
+ if (opts.json) {
1333
+ console.log(JSON.stringify({ ok: true, loggedOut: true, envSession }));
1334
+ return;
1335
+ }
1336
+ console.log(`
1337
+ ${c.green}${c.bold}\u2713${c.reset} Logged out${c.gray} (cleared ~/.ooda/auth.json).${c.reset}`);
1338
+ if (envSession) {
1339
+ console.log(` ${c.yellow}!${c.reset} ${c.gray}A session is still set via ${c.bold}OODA_ACCESS_TOKEN${c.reset}${c.gray}/${c.bold}OODA_ORG_ID${c.reset}${c.gray} \u2014 unset those to fully log out.${c.reset}`);
1340
+ }
1341
+ console.log("");
1342
+ }
1192
1343
  async function completeJwtLogin(result) {
1193
1344
  let orgId;
1194
1345
  let orgName;
@@ -5767,6 +5918,9 @@ Usage:
5767
5918
 
5768
5919
  Commands:
5769
5920
  (no command) Open the interactive project menu
5921
+ login Sign in with an email code (password fallback)
5922
+ logout Sign out (clears the saved session)
5923
+ whoami Show the current session (exits non-zero if none)
5770
5924
  list, ls List your projects
5771
5925
  connect <project> Connect to a project and run Claude (interactive)
5772
5926
  deploy [path|github-url] Deploy a local folder or GitHub repo as a project
@@ -5777,6 +5931,14 @@ Commands:
5777
5931
  sites delete <slug> Unpublish a site
5778
5932
  help, --help, -h Show this help
5779
5933
 
5934
+ login
5935
+ Passwordless sign-in via a 6-digit email code. No flags \u2192 interactive (with a
5936
+ password fallback). Flag mode (for agents/CI):
5937
+ --email <e> Send a login code to this email
5938
+ --email <e> --code <c> Verify the code and save the session
5939
+ --org <id> Choose the org when the account has several
5940
+ --json Machine-readable JSON output
5941
+
5780
5942
  publish [path]
5781
5943
  Publishes an already-built static site (no build is run). Looks for a build
5782
5944
  output dir (dist, build, out, .output/public, .next/static) in [path] (default:
@@ -5813,7 +5975,7 @@ Examples:
5813
5975
  }
5814
5976
 
5815
5977
  // src/cli/index.ts
5816
- var CLI_VERSION = "0.1.14";
5978
+ var CLI_VERSION = "0.1.16";
5817
5979
  function formatMutationError(result) {
5818
5980
  const parts = [];
5819
5981
  if (result.status !== void 0) parts.push(String(result.status));
@@ -5859,6 +6021,9 @@ function parseArgs(argv) {
5859
6021
  else if (first === "deploy") command = "deploy";
5860
6022
  else if (first === "publish") command = "publish";
5861
6023
  else if (first === "sites") command = "sites";
6024
+ else if (first === "login") command = "login";
6025
+ else if (first === "logout") command = "logout";
6026
+ else if (first === "whoami") command = "whoami";
5862
6027
  else if (first === "help") command = "help";
5863
6028
  if (rest.includes("--help") || rest.includes("-h")) command = "help";
5864
6029
  const positionals = [];
@@ -5873,6 +6038,15 @@ function parseArgs(argv) {
5873
6038
  } else if (arg === "--slug" && rest[i + 1]) {
5874
6039
  flags.slug = rest[i + 1];
5875
6040
  i++;
6041
+ } else if (arg === "--email" && rest[i + 1]) {
6042
+ flags.email = rest[i + 1];
6043
+ i++;
6044
+ } else if (arg === "--code" && rest[i + 1]) {
6045
+ flags.code = rest[i + 1];
6046
+ i++;
6047
+ } else if (arg === "--org" && rest[i + 1]) {
6048
+ flags.org = rest[i + 1];
6049
+ i++;
5876
6050
  } else if (arg === "--mode" && rest[i + 1]) {
5877
6051
  flags.mode = rest[i + 1];
5878
6052
  i++;
@@ -5902,6 +6076,13 @@ function parseArgs(argv) {
5902
6076
  clearMode: Boolean(flags.clearMode),
5903
6077
  json: Boolean(flags.json)
5904
6078
  };
6079
+ } else if (command === "login") {
6080
+ args.login = {
6081
+ email: flags.email,
6082
+ code: flags.code,
6083
+ org: flags.org,
6084
+ json: Boolean(flags.json)
6085
+ };
5905
6086
  }
5906
6087
  return args;
5907
6088
  }
@@ -6322,7 +6503,6 @@ async function directConnect(projectName) {
6322
6503
  async function main() {
6323
6504
  const args = parseArgs(process.argv);
6324
6505
  const cwd = process.cwd();
6325
- printLogo(CLI_VERSION);
6326
6506
  if (args.command === "help") {
6327
6507
  console.log(buildHelpText(CLI_VERSION));
6328
6508
  process.exit(0);
@@ -6353,6 +6533,18 @@ async function main() {
6353
6533
  await runSitesCommand(args.sites ?? {});
6354
6534
  process.exit(process.exitCode ?? 0);
6355
6535
  }
6536
+ if (args.command === "login") {
6537
+ await loginCmd(args.login ?? {});
6538
+ process.exit(process.exitCode ?? 0);
6539
+ }
6540
+ if (args.command === "whoami") {
6541
+ whoamiCmd({ json: args.json });
6542
+ process.exit(process.exitCode ?? 0);
6543
+ }
6544
+ if (args.command === "logout") {
6545
+ logoutCmd({ json: args.json });
6546
+ process.exit(process.exitCode ?? 0);
6547
+ }
6356
6548
  if (args.command === "connect") {
6357
6549
  if (!args.connectTarget) {
6358
6550
  console.log(` ${c.red}Usage: ooda connect <project-name>${c.reset}`);
@@ -6364,6 +6556,7 @@ async function main() {
6364
6556
  await directConnect(args.connectTarget);
6365
6557
  process.exit(0);
6366
6558
  }
6559
+ printLogo(CLI_VERSION);
6367
6560
  const { port } = await startServer({ port: args.port, cwd });
6368
6561
  const dashboardUrl = process.env.OODA_DASHBOARD_BASE || `http://localhost:${port}`;
6369
6562
  while (true) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oodarun/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Launch Claude Code on cloud dev environments",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",