@oodarun/cli 0.1.14 → 0.1.15

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 +213 -41
  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,126 @@ 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
+ }
1192
1328
  async function completeJwtLogin(result) {
1193
1329
  let orgId;
1194
1330
  let orgName;
@@ -5767,6 +5903,8 @@ Usage:
5767
5903
 
5768
5904
  Commands:
5769
5905
  (no command) Open the interactive project menu
5906
+ login Sign in with an email code (password fallback)
5907
+ whoami Show the current session (exits non-zero if none)
5770
5908
  list, ls List your projects
5771
5909
  connect <project> Connect to a project and run Claude (interactive)
5772
5910
  deploy [path|github-url] Deploy a local folder or GitHub repo as a project
@@ -5777,6 +5915,14 @@ Commands:
5777
5915
  sites delete <slug> Unpublish a site
5778
5916
  help, --help, -h Show this help
5779
5917
 
5918
+ login
5919
+ Passwordless sign-in via a 6-digit email code. No flags \u2192 interactive (with a
5920
+ password fallback). Flag mode (for agents/CI):
5921
+ --email <e> Send a login code to this email
5922
+ --email <e> --code <c> Verify the code and save the session
5923
+ --org <id> Choose the org when the account has several
5924
+ --json Machine-readable JSON output
5925
+
5780
5926
  publish [path]
5781
5927
  Publishes an already-built static site (no build is run). Looks for a build
5782
5928
  output dir (dist, build, out, .output/public, .next/static) in [path] (default:
@@ -5813,7 +5959,7 @@ Examples:
5813
5959
  }
5814
5960
 
5815
5961
  // src/cli/index.ts
5816
- var CLI_VERSION = "0.1.14";
5962
+ var CLI_VERSION = "0.1.15";
5817
5963
  function formatMutationError(result) {
5818
5964
  const parts = [];
5819
5965
  if (result.status !== void 0) parts.push(String(result.status));
@@ -5859,6 +6005,8 @@ function parseArgs(argv) {
5859
6005
  else if (first === "deploy") command = "deploy";
5860
6006
  else if (first === "publish") command = "publish";
5861
6007
  else if (first === "sites") command = "sites";
6008
+ else if (first === "login") command = "login";
6009
+ else if (first === "whoami") command = "whoami";
5862
6010
  else if (first === "help") command = "help";
5863
6011
  if (rest.includes("--help") || rest.includes("-h")) command = "help";
5864
6012
  const positionals = [];
@@ -5873,6 +6021,15 @@ function parseArgs(argv) {
5873
6021
  } else if (arg === "--slug" && rest[i + 1]) {
5874
6022
  flags.slug = rest[i + 1];
5875
6023
  i++;
6024
+ } else if (arg === "--email" && rest[i + 1]) {
6025
+ flags.email = rest[i + 1];
6026
+ i++;
6027
+ } else if (arg === "--code" && rest[i + 1]) {
6028
+ flags.code = rest[i + 1];
6029
+ i++;
6030
+ } else if (arg === "--org" && rest[i + 1]) {
6031
+ flags.org = rest[i + 1];
6032
+ i++;
5876
6033
  } else if (arg === "--mode" && rest[i + 1]) {
5877
6034
  flags.mode = rest[i + 1];
5878
6035
  i++;
@@ -5902,6 +6059,13 @@ function parseArgs(argv) {
5902
6059
  clearMode: Boolean(flags.clearMode),
5903
6060
  json: Boolean(flags.json)
5904
6061
  };
6062
+ } else if (command === "login") {
6063
+ args.login = {
6064
+ email: flags.email,
6065
+ code: flags.code,
6066
+ org: flags.org,
6067
+ json: Boolean(flags.json)
6068
+ };
5905
6069
  }
5906
6070
  return args;
5907
6071
  }
@@ -6353,6 +6517,14 @@ async function main() {
6353
6517
  await runSitesCommand(args.sites ?? {});
6354
6518
  process.exit(process.exitCode ?? 0);
6355
6519
  }
6520
+ if (args.command === "login") {
6521
+ await loginCmd(args.login ?? {});
6522
+ process.exit(process.exitCode ?? 0);
6523
+ }
6524
+ if (args.command === "whoami") {
6525
+ whoamiCmd({ json: args.json });
6526
+ process.exit(process.exitCode ?? 0);
6527
+ }
6356
6528
  if (args.command === "connect") {
6357
6529
  if (!args.connectTarget) {
6358
6530
  console.log(` ${c.red}Usage: ooda connect <project-name>${c.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oodarun/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Launch Claude Code on cloud dev environments",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",