@clawcard/cli 1.0.3 → 1.0.5
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 +1 -1
- package/src/commands/help.js +1 -0
- package/src/commands/login.js +46 -11
- package/src/commands/signup.js +47 -13
- package/src/index.js +32 -2
- package/src/splash.js +23 -1
package/package.json
CHANGED
package/src/commands/help.js
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import open from "open";
|
|
3
|
-
import
|
|
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,64 @@ export async function loginCommand() {
|
|
|
11
11
|
if (p.isCancel(cont) || !cont) return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Generate a random code for this CLI session
|
|
15
|
+
const code = crypto.randomBytes(16).toString("hex");
|
|
16
16
|
|
|
17
|
-
const
|
|
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.
|
|
20
|
+
const s = p.spinner();
|
|
21
|
+
s.start("Waiting for login in browser...");
|
|
23
22
|
|
|
24
23
|
try {
|
|
25
|
-
const
|
|
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
|
+
// Fetch email using the token
|
|
53
|
+
let email = "";
|
|
54
|
+
try {
|
|
55
|
+
const meRes = await fetch(`${BASE_URL}/api/me`, {
|
|
56
|
+
headers: { Authorization: `Bearer ${data.token}` },
|
|
57
|
+
});
|
|
58
|
+
const me = await meRes.json();
|
|
59
|
+
email = me.email || "";
|
|
60
|
+
} catch {
|
|
61
|
+
// non-critical
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { token: data.token, email };
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// network error, keep polling
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error("Login timed out — no response within 2 minutes");
|
|
74
|
+
}
|
package/src/commands/signup.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import open from "open";
|
|
3
|
-
import
|
|
3
|
+
import crypto from "crypto";
|
|
4
4
|
import { saveConfig, BASE_URL } from "../config.js";
|
|
5
5
|
|
|
6
6
|
export async function signupCommand() {
|
|
7
|
-
const
|
|
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,68 @@ export async function signupCommand() {
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
if (p.isCancel(
|
|
17
|
+
if (p.isCancel(inviteCode)) {
|
|
18
18
|
p.cancel("Signup cancelled");
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Generate a random code for this CLI session
|
|
23
|
+
const cliCode = crypto.randomBytes(16).toString("hex");
|
|
24
24
|
|
|
25
|
-
const
|
|
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.
|
|
28
|
+
const s = p.spinner();
|
|
29
|
+
s.start("Waiting for signup in browser...");
|
|
31
30
|
|
|
32
31
|
try {
|
|
33
|
-
const
|
|
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
|
+
let email = "";
|
|
61
|
+
try {
|
|
62
|
+
const meRes = await fetch(`${BASE_URL}/api/me`, {
|
|
63
|
+
headers: { Authorization: `Bearer ${data.token}` },
|
|
64
|
+
});
|
|
65
|
+
const me = await meRes.json();
|
|
66
|
+
email = me.email || "";
|
|
67
|
+
} catch {
|
|
68
|
+
// non-critical
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { token: data.token, email };
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// network error, keep polling
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error("Signup timed out — no response within 2 minutes");
|
|
81
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
|
-
import
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { showSplash, checkForUpdate, VERSION } from "./splash.js";
|
|
4
5
|
import { isLoggedIn } from "./config.js";
|
|
5
6
|
|
|
7
|
+
const orange = chalk.hex("#FF6B35");
|
|
8
|
+
|
|
6
9
|
// Show splash on every invocation
|
|
7
10
|
showSplash();
|
|
8
11
|
|
|
12
|
+
// Check for updates in background (non-blocking)
|
|
13
|
+
const updatePromise = checkForUpdate();
|
|
14
|
+
|
|
9
15
|
const program = new Command();
|
|
10
|
-
program.name("clawcard").description("The ClawCard CLI").version(
|
|
16
|
+
program.name("clawcard").description("The ClawCard CLI").version(VERSION);
|
|
11
17
|
|
|
12
18
|
// Auth commands
|
|
13
19
|
program
|
|
@@ -142,6 +148,22 @@ program
|
|
|
142
148
|
await referralCommand();
|
|
143
149
|
});
|
|
144
150
|
|
|
151
|
+
// Update
|
|
152
|
+
program
|
|
153
|
+
.command("update")
|
|
154
|
+
.description("Update to the latest version")
|
|
155
|
+
.action(async () => {
|
|
156
|
+
const s = p.spinner();
|
|
157
|
+
s.start("Checking for updates...");
|
|
158
|
+
const latest = await checkForUpdate();
|
|
159
|
+
if (latest) {
|
|
160
|
+
s.stop(`Update available: ${chalk.dim(VERSION)} → ${orange.bold(latest)}`);
|
|
161
|
+
p.log.info(`Run: ${orange("npm i -g @clawcard/cli@latest")}`);
|
|
162
|
+
} else {
|
|
163
|
+
s.stop("You're on the latest version!");
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
145
167
|
// Help
|
|
146
168
|
program
|
|
147
169
|
.command("help")
|
|
@@ -155,6 +177,14 @@ program
|
|
|
155
177
|
if (process.argv.length <= 2) {
|
|
156
178
|
const loggedIn = isLoggedIn();
|
|
157
179
|
|
|
180
|
+
// Show update notice if available (non-blocking, already fetched)
|
|
181
|
+
const latest = await updatePromise;
|
|
182
|
+
if (latest) {
|
|
183
|
+
p.log.warn(
|
|
184
|
+
`Update available: ${chalk.dim(VERSION)} → ${orange.bold(latest)} Run ${orange("npm i -g @clawcard/cli@latest")}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
158
188
|
const options = loggedIn
|
|
159
189
|
? [
|
|
160
190
|
{
|
package/src/splash.js
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
2
5
|
|
|
3
6
|
const o = chalk.hex("#FF6B35");
|
|
4
7
|
const d = chalk.dim;
|
|
5
8
|
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
11
|
+
export const VERSION = pkg.version;
|
|
12
|
+
|
|
13
|
+
export async function checkForUpdate() {
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch("https://registry.npmjs.org/@clawcard/cli/latest", {
|
|
16
|
+
signal: AbortSignal.timeout(3000),
|
|
17
|
+
});
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
if (data.version && data.version !== VERSION) {
|
|
20
|
+
return data.version;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// offline or timeout — skip silently
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
6
28
|
export function showSplash() {
|
|
7
29
|
console.log();
|
|
8
|
-
console.log(` 🦞 ${o.bold("clawcard.sh")}`);
|
|
30
|
+
console.log(` 🦞 ${o.bold("clawcard.sh")} ${d("v" + VERSION)}`);
|
|
9
31
|
console.log(` ${o("on-demand credit cards, email and phone for your agents")}`);
|
|
10
32
|
console.log();
|
|
11
33
|
}
|