@suronai/cli 2.0.2 → 2.0.3

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 (3) hide show
  1. package/cli.js +51 -40
  2. package/package.json +4 -4
  3. package/suron +49 -24
package/cli.js CHANGED
@@ -46,8 +46,8 @@ const c = {
46
46
 
47
47
  // ── Logo ──────────────────────────────────────────────────────────────────────
48
48
  const logo = `
49
- ${c.orange("▄▀ █ █ █▀▄ █▀█ █▄ █")}
50
- ${c.orange("▀▄█ ▀▄█ █▀▄ █▄█ █ ▀█")} ${c.gray("secrets manager · v2")}
49
+ ${c.orange("▄▀▀ █ █ █▀▄ █▀█ █▄ █")}
50
+ ${c.orange("▀▀▄ ▀▄█ █▀▄ █▄█ █ ▀█")} ${c.gray("secrets manager · v2")}
51
51
  `;
52
52
 
53
53
  // ── Config ────────────────────────────────────────────────────────────────────
@@ -75,7 +75,7 @@ function getConfig() {
75
75
  function requireConfig() {
76
76
  const cfg = getConfig();
77
77
  if (!cfg.url) {
78
- console.error(c.red("✗ SURON_URL not set — run: suron init"));
78
+ console.error(c.red("✗ SURON_URL not set — run: suron login"));
79
79
  process.exit(1);
80
80
  }
81
81
  return cfg;
@@ -154,10 +154,10 @@ function printHelp() {
154
154
  console.log(logo);
155
155
  const sections = [
156
156
  { heading: "Setup", cmds: [
157
- ["suron init", "", "Interactive setup wizard saves ~/.suron/config"],
158
- ["suron login", "", "Authenticate CLI via browser (opens dashboard)"],
157
+ ["suron login", "", "Authenticate CLIasks for URLs + opens approval page in browser"],
159
158
  ["suron logout", "", "Remove saved credentials"],
160
159
  ["suron whoami", "", "Show current auth status"],
160
+ ["suron init", "", "(legacy) — just runs: suron login"],
161
161
  ]},
162
162
  { heading: "Scaffold", cmds: [
163
163
  ["suron new <directory>", "", "Scaffold a new Node.js project with SURON pre-wired"],
@@ -196,57 +196,65 @@ function printHelp() {
196
196
  const cfg = getConfig();
197
197
  console.log(` ${c.gray("Config:")} ${cfg.url ? c.cyan(cfg.url) : c.red("not set — run: suron init")}`);
198
198
  console.log(` ${c.gray("Version:")} ${c.gray(VERSION)}`);
199
- console.log(` ${c.gray("Docs: ")} ${c.cyan("https://github.com/your-org/suron")}\n`);
199
+ console.log(` ${c.gray("Docs: ")} ${c.cyan("https://github.com/Cryptoistaken/Suron")}\n`);
200
200
  }
201
201
 
202
202
  // ── INIT ──────────────────────────────────────────────────────────────────────
203
203
  async function cmdInit() {
204
204
  console.log(logo);
205
- console.log(`${c.bold("Setup Wizard")} — saves config to ${c.cyan(CONFIG_FILE)}\n`);
206
- const ex = loadConfig();
207
-
208
- const url = await prompt(`Convex URL ${ex.SURON_URL ? c.gray("("+ex.SURON_URL+")") : c.gray("e.g. https://xxx.convex.cloud")}: `);
209
- const dashUrl = await prompt(`Dashboard URL ${ex.SURON_DASHBOARD_URL ? c.gray("("+ex.SURON_DASHBOARD_URL+")") : c.gray("e.g. https://suron.vercel.app")}: `);
210
-
211
- const cfg = {
212
- ...ex,
213
- ...(url ? { SURON_URL: url } : {}),
214
- ...(dashUrl ? { SURON_DASHBOARD_URL: dashUrl } : {}),
215
- };
216
- saveConfig(cfg);
217
- console.log(c.green(`\n✓ Config saved`));
218
- console.log(c.gray(` SURON_URL = ${cfg.SURON_URL}`));
219
- if (cfg.SURON_DASHBOARD_URL) console.log(c.gray(` SURON_DASHBOARD_URL = ${cfg.SURON_DASHBOARD_URL}`));
220
- console.log(`\n${c.dim(" Next: suron login or suron apps create --name my-app")}\n`);
205
+ console.log(`${c.bold("Setup Wizard")}\n`);
206
+ console.log(c.gray(" suron init is no longer needed for URL setup."));
207
+ console.log(c.gray(" Run ") + c.cyan("suron login") + c.gray(" — it will ask for your Convex URL and Dashboard URL automatically.\n"));
208
+ console.log(c.dim(" Or run: suron whoami to check current status\n"));
221
209
  }
222
210
 
223
211
  // ── LOGIN ─────────────────────────────────────────────────────────────────────
224
212
  async function cmdLogin() {
225
- const { url } = requireConfig();
226
- const client = getClient();
227
213
  console.log(logo);
228
214
  console.log(`${c.bold("CLI Authentication")}\n`);
229
215
 
216
+ // ── Step 1: ensure Convex URL is set
217
+ let cfg = loadConfig();
218
+ let convexUrl = process.env.SURON_URL ?? cfg.SURON_URL;
219
+ if (!convexUrl) {
220
+ console.log(c.gray(" No Convex URL found. Let's set it up.\n"));
221
+ convexUrl = await prompt(` Convex URL ${c.gray("(e.g. https://xxx.eu-west-1.convex.cloud)")}: `);
222
+ if (!convexUrl) { console.error(c.red("✗ Convex URL is required")); await new Promise(r => setTimeout(r, 50)); process.exit(1); }
223
+ cfg = { ...cfg, SURON_URL: convexUrl };
224
+ saveConfig(cfg);
225
+ console.log(c.green(" ✓ Convex URL saved\n"));
226
+ }
227
+
228
+ // ── Step 2: ensure Dashboard URL is set
229
+ let dashUrl = process.env.SURON_DASHBOARD_URL ?? cfg.SURON_DASHBOARD_URL;
230
+ if (!dashUrl) {
231
+ console.log(c.gray(" No Dashboard URL found. Where is your SURON dashboard deployed?\n"));
232
+ dashUrl = await prompt(` Dashboard URL ${c.gray("(e.g. https://suron.vercel.app)")}: `);
233
+ if (!dashUrl) { console.error(c.red("✗ Dashboard URL is required")); await new Promise(r => setTimeout(r, 50)); process.exit(1); }
234
+ cfg = { ...cfg, SURON_DASHBOARD_URL: dashUrl };
235
+ saveConfig(cfg);
236
+ console.log(c.green(" ✓ Dashboard URL saved\n"));
237
+ }
238
+
239
+ // ── Step 3: create session and open browser
240
+ const client = new ConvexHttpClient(convexUrl);
230
241
  let sessionId, code;
231
242
  try {
232
243
  const r = await client.mutation("auth:createCliSession", {});
233
244
  sessionId = r.sessionId; code = r.code;
234
- } catch(e) { console.error(c.red(`✗ ${e.message}`)); process.exit(1); }
245
+ } catch(e) { console.error(c.red(`✗ ${e.message}`)); await new Promise(r => setTimeout(r, 50)); process.exit(1); }
235
246
 
236
- const dashUrl = process.env.SURON_DASHBOARD_URL ?? loadConfig().SURON_DASHBOARD_URL;
237
- if (!dashUrl) {
238
- console.error(c.red("✗ SURON_DASHBOARD_URL not set — run: suron init"));
239
- process.exit(1);
240
- }
247
+ const approvalUrl = `${dashUrl.replace(/\/$/, "")}/approve?code=${code}`;
241
248
 
242
- const approvalUrl = `${dashUrl}/cli-auth?code=${code}`;
243
- console.log(` ${c.bold("Login code")}: ${c.orange(c.bold(code))}\n`);
244
- console.log(` ${c.gray("Approval URL")}: ${c.cyan(approvalUrl)}\n`);
245
- console.log(c.dim(" Press Enter to open in browser"));
249
+ console.log(` ${c.bold("Your code")}: ${c.orange(c.bold(code))}\n`);
250
+ console.log(` ${c.gray("Opening approval page in browser…")}`);
251
+ console.log(` ${c.dim(approvalUrl)}\n`);
252
+ console.log(c.dim(" Press Enter to open in browser, or visit the URL above manually."));
246
253
  await prompt("");
247
254
  openBrowser(approvalUrl);
248
- console.log(c.gray("\n Waiting for dashboard approval… (Ctrl+C to cancel)\n"));
255
+ console.log(c.gray("\n Waiting for approval… (Ctrl+C to cancel)\n"));
249
256
 
257
+ // ── Step 4: poll
250
258
  const deadline = Date.now() + 10 * 60 * 1000;
251
259
  let dots = 0;
252
260
  while (Date.now() < deadline) {
@@ -257,20 +265,23 @@ async function cmdLogin() {
257
265
  process.stdout.write(`\r ${c.gray("Waiting" + ".".repeat(dots).padEnd(3))}`);
258
266
  if (result.status === "approved" && result.adminToken) {
259
267
  process.stdout.write("\n");
260
- const cfg = loadConfig();
261
268
  cfg.SURON_ADMIN_TOKEN = result.adminToken;
262
269
  saveConfig(cfg);
263
270
  console.log(`\n${c.green("✓ Authenticated!")} Token saved to ${c.cyan(CONFIG_FILE)}\n`);
271
+ await new Promise(r => setTimeout(r, 50));
264
272
  process.exit(0);
265
273
  }
266
274
  if (result.status === "denied") {
267
- console.log("\n" + c.red("✗ Denied by dashboard.")); process.exit(1);
275
+ process.stdout.write("\n"); console.log(c.red("✗ Denied."));
276
+ await new Promise(r => setTimeout(r, 50)); process.exit(1);
268
277
  }
269
278
  if (["expired","not_found"].includes(result.status)) {
270
- console.log("\n" + c.red("✗ Session expired. Run: suron login")); process.exit(1);
279
+ process.stdout.write("\n"); console.log(c.red("✗ Session expired. Run: suron login"));
280
+ await new Promise(r => setTimeout(r, 50)); process.exit(1);
271
281
  }
272
282
  }
273
- console.log("\n" + c.red("✗ Timed out.")); process.exit(1);
283
+ process.stdout.write("\n"); console.log(c.red("✗ Timed out."));
284
+ await new Promise(r => setTimeout(r, 50)); process.exit(1);
274
285
  }
275
286
 
276
287
  // ── WHOAMI ────────────────────────────────────────────────────────────────────
@@ -731,7 +742,7 @@ try {
731
742
  case "--help":
732
743
  case "-h": printHelp(); break;
733
744
  case "version": console.log(`suron v${VERSION}`); break;
734
- case "init": await cmdInit(); break;
745
+ case "init": await cmdLogin(); break; // init now just runs login
735
746
  case "login": await cmdLogin(); break;
736
747
  case "logout": cmdLogout(); break;
737
748
  case "whoami": await cmdWhoami(); break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suronai/cli",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "SURON CLI — manage secrets, push .env files, scaffold Node.js projects with Telegram approval",
5
5
  "main": "cli.js",
6
6
  "type": "module",
@@ -26,14 +26,14 @@
26
26
  ],
27
27
  "license": "MIT",
28
28
  "author": "SuronAI",
29
- "homepage": "https://github.com/your-org/suron",
29
+ "homepage": "https://github.com/Cryptoistaken/Suron",
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "git+https://github.com/your-org/suron.git",
32
+ "url": "git+https://github.com/Cryptoistaken/Suron.git",
33
33
  "directory": "SDK/CLI"
34
34
  },
35
35
  "bugs": {
36
- "url": "https://github.com/your-org/suron/issues"
36
+ "url": "https://github.com/Cryptoistaken/Suron/issues"
37
37
  },
38
38
  "files": [
39
39
  "suron",
package/suron CHANGED
@@ -203,50 +203,69 @@ function printHelp() {
203
203
  async function cmdInit() {
204
204
  console.log(logo);
205
205
  console.log(`${c.bold("Setup Wizard")} — saves config to ${c.cyan(CONFIG_FILE)}\n`);
206
+ console.log(c.dim(" Tip: suron login will also ask for these URLs if not set.\n"));
206
207
  const ex = loadConfig();
207
208
 
208
- const url = await prompt(`Convex URL ${ex.SURON_URL ? c.gray("("+ex.SURON_URL+")") : c.gray("e.g. https://xxx.convex.cloud")}: `);
209
- const dashUrl = await prompt(`Dashboard URL ${ex.SURON_DASHBOARD_URL ? c.gray("("+ex.SURON_DASHBOARD_URL+")") : c.gray("e.g. https://suron.vercel.app")}: `);
209
+ const url = await prompt(`Convex URL ${ex.SURON_URL ? c.gray("("+ex.SURON_URL+")") : c.gray("e.g. https://xxx.convex.cloud")}: `);
210
210
 
211
211
  const cfg = {
212
212
  ...ex,
213
- ...(url ? { SURON_URL: url } : {}),
214
- ...(dashUrl ? { SURON_DASHBOARD_URL: dashUrl } : {}),
213
+ ...(url ? { SURON_URL: url } : {}),
215
214
  };
216
215
  saveConfig(cfg);
217
216
  console.log(c.green(`\n✓ Config saved`));
218
- console.log(c.gray(` SURON_URL = ${cfg.SURON_URL}`));
219
- if (cfg.SURON_DASHBOARD_URL) console.log(c.gray(` SURON_DASHBOARD_URL = ${cfg.SURON_DASHBOARD_URL}`));
220
- console.log(`\n${c.dim(" Next: suron login or suron apps create --name my-app")}\n`);
217
+ console.log(c.gray(` SURON_URL = ${cfg.SURON_URL ?? c.red("(not set)")}`));
218
+ console.log(`\n${c.dim(" Next: suron login")}\n`);
221
219
  }
222
220
 
223
221
  // ── LOGIN ─────────────────────────────────────────────────────────────────────
224
222
  async function cmdLogin() {
225
- const { url } = requireConfig();
226
- const client = getClient();
227
223
  console.log(logo);
228
224
  console.log(`${c.bold("CLI Authentication")}\n`);
229
225
 
226
+ // ── Step 1: ensure Convex URL is set ────────────────────────────────────────
227
+ let cfg = loadConfig();
228
+ let convexUrl = process.env.SURON_URL ?? cfg.SURON_URL;
229
+ if (!convexUrl) {
230
+ console.log(c.gray(" No Convex URL found. Let's set it up.\n"));
231
+ convexUrl = await prompt(` Convex URL ${c.gray("(e.g. https://xxx.eu-west-1.convex.cloud)")}: `);
232
+ if (!convexUrl) { console.error(c.red("✗ Convex URL is required")); await new Promise(r => setTimeout(r, 50)); process.exit(1); }
233
+ cfg = { ...cfg, SURON_URL: convexUrl };
234
+ saveConfig(cfg);
235
+ console.log(c.green(" ✓ Convex URL saved\n"));
236
+ }
237
+
238
+ // ── Step 2: ensure Dashboard URL is set ─────────────────────────────────────
239
+ let dashUrl = process.env.SURON_DASHBOARD_URL ?? cfg.SURON_DASHBOARD_URL;
240
+ if (!dashUrl) {
241
+ console.log(c.gray(" No Dashboard URL found. Where is your SURON dashboard deployed?\n"));
242
+ dashUrl = await prompt(` Dashboard URL ${c.gray("(e.g. https://suron.vercel.app)")}: `);
243
+ if (!dashUrl) { console.error(c.red("✗ Dashboard URL is required")); await new Promise(r => setTimeout(r, 50)); process.exit(1); }
244
+ cfg = { ...cfg, SURON_DASHBOARD_URL: dashUrl };
245
+ saveConfig(cfg);
246
+ console.log(c.green(" ✓ Dashboard URL saved\n"));
247
+ }
248
+
249
+ // ── Step 3: create session and open browser ──────────────────────────────────
250
+ const client = new ConvexHttpClient(convexUrl);
230
251
  let sessionId, code;
231
252
  try {
232
253
  const r = await client.mutation("auth:createCliSession", {});
233
254
  sessionId = r.sessionId; code = r.code;
234
- } catch(e) { console.error(c.red(`✗ ${e.message}`)); process.exit(1); }
255
+ } catch(e) { console.error(c.red(`✗ ${e.message}`)); await new Promise(r => setTimeout(r, 50)); process.exit(1); }
235
256
 
236
- const dashUrl = process.env.SURON_DASHBOARD_URL ?? loadConfig().SURON_DASHBOARD_URL;
237
- if (!dashUrl) {
238
- console.error(c.red("✗ SURON_DASHBOARD_URL not set — run: suron init"));
239
- process.exit(1);
240
- }
257
+ // Open a clean standalone approval page (not the full dashboard)
258
+ const approvalUrl = `${dashUrl.replace(/\/$/, "")}/approve?code=${code}`;
241
259
 
242
- const approvalUrl = `${dashUrl}/cli-auth?code=${code}`;
243
- console.log(` ${c.bold("Login code")}: ${c.orange(c.bold(code))}\n`);
244
- console.log(` ${c.gray("Approval URL")}: ${c.cyan(approvalUrl)}\n`);
245
- console.log(c.dim(" Press Enter to open in browser"));
260
+ console.log(` ${c.bold("Your code")}: ${c.orange(c.bold(code))}\n`);
261
+ console.log(` ${c.gray("Opening approval page in browser…")}`);
262
+ console.log(` ${c.dim(approvalUrl)}\n`);
263
+ console.log(c.dim(" Press Enter to open in browser, or visit the URL above manually."));
246
264
  await prompt("");
247
265
  openBrowser(approvalUrl);
248
- console.log(c.gray("\n Waiting for dashboard approval… (Ctrl+C to cancel)\n"));
266
+ console.log(c.gray("\n Waiting for approval… (Ctrl+C to cancel)\n"));
249
267
 
268
+ // ── Step 4: poll until approved/denied/expired ───────────────────────────────
250
269
  const deadline = Date.now() + 10 * 60 * 1000;
251
270
  let dots = 0;
252
271
  while (Date.now() < deadline) {
@@ -257,20 +276,26 @@ async function cmdLogin() {
257
276
  process.stdout.write(`\r ${c.gray("Waiting" + ".".repeat(dots).padEnd(3))}`);
258
277
  if (result.status === "approved" && result.adminToken) {
259
278
  process.stdout.write("\n");
260
- const cfg = loadConfig();
261
279
  cfg.SURON_ADMIN_TOKEN = result.adminToken;
262
280
  saveConfig(cfg);
263
281
  console.log(`\n${c.green("✓ Authenticated!")} Token saved to ${c.cyan(CONFIG_FILE)}\n`);
282
+ await new Promise(r => setTimeout(r, 50));
264
283
  process.exit(0);
265
284
  }
266
285
  if (result.status === "denied") {
267
- console.log("\n" + c.red("✗ Denied by dashboard.")); process.exit(1);
286
+ process.stdout.write("\n");
287
+ console.log(c.red("✗ Denied."));
288
+ await new Promise(r => setTimeout(r, 50)); process.exit(1);
268
289
  }
269
290
  if (["expired","not_found"].includes(result.status)) {
270
- console.log("\n" + c.red("✗ Session expired. Run: suron login")); process.exit(1);
291
+ process.stdout.write("\n");
292
+ console.log(c.red("✗ Session expired. Run: suron login"));
293
+ await new Promise(r => setTimeout(r, 50)); process.exit(1);
271
294
  }
272
295
  }
273
- console.log("\n" + c.red("✗ Timed out.")); process.exit(1);
296
+ process.stdout.write("\n");
297
+ console.log(c.red("✗ Timed out."));
298
+ await new Promise(r => setTimeout(r, 50)); process.exit(1);
274
299
  }
275
300
 
276
301
  // ── WHOAMI ────────────────────────────────────────────────────────────────────