@clawcard/cli 1.0.4 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawcard/cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "The ClawCard CLI — manage your agent keys, billing, and setup from the terminal",
5
5
  "bin": {
6
6
  "clawcard": "./bin/clawcard.mjs"
@@ -1,6 +1,6 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import open from "open";
3
- import { startCallbackServer } from "../auth-server.js";
3
+ import crypto from "crypto";
4
4
  import { saveConfig, isLoggedIn, getConfig, BASE_URL } from "../config.js";
5
5
 
6
6
  export async function loginCommand() {
@@ -11,29 +11,52 @@ export async function loginCommand() {
11
11
  if (p.isCancel(cont) || !cont) return;
12
12
  }
13
13
 
14
- const s = p.spinner();
15
- s.start("Opening browser...");
14
+ // Generate a random code for this CLI session
15
+ const code = crypto.randomBytes(16).toString("hex");
16
16
 
17
- const { promise, port } = startCallbackServer();
18
- const actualPort = await port;
19
- const loginUrl = `${BASE_URL}/login?cli=true&port=${actualPort}`;
17
+ const loginUrl = `${BASE_URL}/login?cli=true&cli_code=${code}`;
20
18
  await open(loginUrl);
21
19
 
22
- s.message("Waiting for login in browser...");
20
+ const s = p.spinner();
21
+ s.start("Waiting for login in browser...");
23
22
 
24
23
  try {
25
- const { token, email } = await promise;
24
+ const result = await pollForToken(code);
26
25
  s.stop("Logged in!");
27
26
 
28
27
  saveConfig({
29
- token,
30
- email,
28
+ token: result.token,
29
+ email: result.email,
31
30
  loggedInAt: new Date().toISOString(),
32
31
  });
33
32
 
34
- p.log.success(`Logged in as ${email}`);
33
+ p.log.success(`Logged in as ${result.email}`);
35
34
  } catch (err) {
36
35
  s.stop("Login failed");
37
36
  p.log.error(err.message);
38
37
  }
39
38
  }
39
+
40
+ async function pollForToken(code, timeout = 120_000) {
41
+ const start = Date.now();
42
+ const interval = 2000;
43
+
44
+ while (Date.now() - start < timeout) {
45
+ try {
46
+ const res = await fetch(
47
+ `${BASE_URL}/api/auth/cli-callback?code=${code}`
48
+ );
49
+ const data = await res.json();
50
+
51
+ if (data.status === "complete" && data.token) {
52
+ return { token: data.token, email: data.email || "" };
53
+ }
54
+ } catch {
55
+ // network error, keep polling
56
+ }
57
+
58
+ await new Promise((r) => setTimeout(r, interval));
59
+ }
60
+
61
+ throw new Error("Login timed out — no response within 2 minutes");
62
+ }
@@ -1,10 +1,10 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import open from "open";
3
- import { startCallbackServer } from "../auth-server.js";
3
+ import crypto from "crypto";
4
4
  import { saveConfig, BASE_URL } from "../config.js";
5
5
 
6
6
  export async function signupCommand() {
7
- const code = await p.text({
7
+ const inviteCode = await p.text({
8
8
  message: "Enter your invite code",
9
9
  placeholder: "CLAW-XXXX",
10
10
  validate: (val) => {
@@ -14,34 +14,57 @@ export async function signupCommand() {
14
14
  },
15
15
  });
16
16
 
17
- if (p.isCancel(code)) {
17
+ if (p.isCancel(inviteCode)) {
18
18
  p.cancel("Signup cancelled");
19
19
  return;
20
20
  }
21
21
 
22
- const s = p.spinner();
23
- s.start("Opening browser...");
22
+ // Generate a random code for this CLI session
23
+ const cliCode = crypto.randomBytes(16).toString("hex");
24
24
 
25
- const { promise, port } = startCallbackServer();
26
- const actualPort = await port;
27
- const signupUrl = `${BASE_URL}/login?cli=true&port=${actualPort}&signup=true&code=${code}`;
25
+ const signupUrl = `${BASE_URL}/login?cli=true&cli_code=${cliCode}&signup=true&invite=${inviteCode}`;
28
26
  await open(signupUrl);
29
27
 
30
- s.message("Waiting for signup in browser...");
28
+ const s = p.spinner();
29
+ s.start("Waiting for signup in browser...");
31
30
 
32
31
  try {
33
- const { token, email } = await promise;
32
+ const result = await pollForToken(cliCode);
34
33
  s.stop("Account created!");
35
34
 
36
35
  saveConfig({
37
- token,
38
- email,
36
+ token: result.token,
37
+ email: result.email,
39
38
  loggedInAt: new Date().toISOString(),
40
39
  });
41
40
 
42
- p.log.success(`Welcome to ClawCard, ${email}!`);
41
+ p.log.success(`Welcome to ClawCard, ${result.email}!`);
43
42
  } catch (err) {
44
43
  s.stop("Signup failed");
45
44
  p.log.error(err.message);
46
45
  }
47
46
  }
47
+
48
+ async function pollForToken(code, timeout = 120_000) {
49
+ const start = Date.now();
50
+ const interval = 2000;
51
+
52
+ while (Date.now() - start < timeout) {
53
+ try {
54
+ const res = await fetch(
55
+ `${BASE_URL}/api/auth/cli-callback?code=${code}`
56
+ );
57
+ const data = await res.json();
58
+
59
+ if (data.status === "complete" && data.token) {
60
+ return { token: data.token, email: data.email || "" };
61
+ }
62
+ } catch {
63
+ // network error, keep polling
64
+ }
65
+
66
+ await new Promise((r) => setTimeout(r, interval));
67
+ }
68
+
69
+ throw new Error("Signup timed out — no response within 2 minutes");
70
+ }