@kurtel/cli 0.1.0 → 0.1.1
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/dist/commands/auth.js +79 -19
- package/dist/index.js +5 -1
- package/dist/lib/api.js +29 -0
- package/dist/lib/browser.js +28 -0
- package/dist/lib/config.js +25 -0
- package/package.json +4 -2
package/dist/commands/auth.js
CHANGED
|
@@ -1,35 +1,95 @@
|
|
|
1
1
|
import { c, symbols } from "../ui/colors.js";
|
|
2
2
|
import { Spinner, sleep } from "../ui/spinner.js";
|
|
3
|
-
import { loadConfig,
|
|
4
|
-
import {
|
|
3
|
+
import { loadConfig, saveSession, clearSession } from "../lib/config.js";
|
|
4
|
+
import { startDeviceAuth, pollDeviceAuth } from "../lib/api.js";
|
|
5
|
+
import { openBrowser } from "../lib/browser.js";
|
|
5
6
|
export async function loginCommand() {
|
|
6
7
|
const config = loadConfig();
|
|
7
|
-
if (config.loggedIn) {
|
|
8
|
-
console.log(`${symbols.check} Already signed in as ${c.indigo(config.account ?? "
|
|
8
|
+
if (config.loggedIn && config.token) {
|
|
9
|
+
console.log(`${symbols.check} Already signed in as ${c.indigo(config.account ?? "your account")}${config.organization ? c.dim(` · ${config.organization}`) : ""}.`);
|
|
10
|
+
console.log(`${c.dim("Run")} ${c.indigo("kurtel logout")} ${c.dim("to switch accounts.")}`);
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
13
|
+
// 1. Begin the device flow.
|
|
14
|
+
let session;
|
|
15
|
+
const startSpin = new Spinner("Requesting a device code…").start();
|
|
16
|
+
try {
|
|
17
|
+
session = await startDeviceAuth();
|
|
18
|
+
startSpin.stop();
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
startSpin.fail(`Couldn't reach Kurtel: ${e instanceof Error ? e.message : String(e)}`);
|
|
22
|
+
console.log(`${c.dim("Check your connection, or set")} ${c.indigo("KURTEL_API_URL")} ${c.dim("for local dev.")}`);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 2. Show the URL + code, try to open the browser.
|
|
11
27
|
console.log("");
|
|
12
|
-
console.log(`${c.indigo(symbols.arrow)}
|
|
13
|
-
|
|
14
|
-
console.log(` ${c.dim(fakeUrl)}`);
|
|
28
|
+
console.log(`${c.indigo(symbols.arrow)} Open this URL to authorize:`);
|
|
29
|
+
console.log(` ${c.bold(session.verification_url)}`);
|
|
15
30
|
console.log("");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
console.log(`${c.dim("Device code:")} ${c.indigo(session.user_code)}`);
|
|
32
|
+
console.log("");
|
|
33
|
+
const opened = openBrowser(session.verification_url);
|
|
34
|
+
if (opened) {
|
|
35
|
+
console.log(c.dim("Opening your browser…"));
|
|
36
|
+
}
|
|
37
|
+
// 3. Poll until authorized / expired / timeout.
|
|
38
|
+
const spin = new Spinner("Waiting for you to authorize in the browser…").start();
|
|
39
|
+
const intervalMs = Math.max(1, session.interval) * 1000;
|
|
40
|
+
const deadline = Date.now() + session.expires_in * 1000;
|
|
41
|
+
while (Date.now() < deadline) {
|
|
42
|
+
await sleep(intervalMs);
|
|
43
|
+
let res;
|
|
44
|
+
try {
|
|
45
|
+
res = await pollDeviceAuth(session.device_code);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue; // transient network error — keep polling
|
|
49
|
+
}
|
|
50
|
+
if (res.status === "authorized") {
|
|
51
|
+
saveSession({
|
|
52
|
+
token: res.token,
|
|
53
|
+
account: res.account,
|
|
54
|
+
organization: res.organization,
|
|
55
|
+
});
|
|
56
|
+
spin.succeed(`Signed in as ${c.indigo(res.account ?? "your account")}${res.organization ? c.dim(` · ${res.organization}`) : ""}`);
|
|
57
|
+
console.log(c.dim("You can close the browser tab."));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (res.status === "denied") {
|
|
61
|
+
spin.fail("Authorization was denied.");
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (res.status === "expired" || res.status === "not_found" || res.status === "used") {
|
|
66
|
+
spin.fail("This login request expired. Run `kurtel login` again.");
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// status === "pending" → keep waiting
|
|
71
|
+
}
|
|
72
|
+
spin.fail("Timed out waiting for authorization. Run `kurtel login` again.");
|
|
73
|
+
process.exitCode = 1;
|
|
24
74
|
}
|
|
25
75
|
export async function logoutCommand() {
|
|
26
76
|
const config = loadConfig();
|
|
27
77
|
if (!config.loggedIn) {
|
|
28
|
-
console.log(
|
|
78
|
+
console.log(c.dim("You're not signed in."));
|
|
29
79
|
return;
|
|
30
80
|
}
|
|
31
|
-
|
|
32
|
-
delete config.account;
|
|
33
|
-
saveConfig(config);
|
|
81
|
+
clearSession();
|
|
34
82
|
console.log(`${symbols.check} Signed out.`);
|
|
35
83
|
}
|
|
84
|
+
export function whoamiCommand() {
|
|
85
|
+
const config = loadConfig();
|
|
86
|
+
if (!config.loggedIn || !config.token) {
|
|
87
|
+
console.log(`${c.dim("Not signed in. Run")} ${c.indigo("kurtel login")}${c.dim(".")}`);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(`${c.indigo(symbols.info)} ${c.white(config.account ?? "unknown account")}`);
|
|
92
|
+
if (config.organization) {
|
|
93
|
+
console.log(`${c.gray("org")} ${c.white(config.organization)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadConfig } from "./lib/config.js";
|
|
|
5
5
|
import { c, symbols } from "./ui/colors.js";
|
|
6
6
|
import { startSession } from "./session/repl.js";
|
|
7
7
|
import { runCommand } from "./commands/run.js";
|
|
8
|
-
import { loginCommand, logoutCommand } from "./commands/auth.js";
|
|
8
|
+
import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
|
|
9
9
|
import { agentsCommand } from "./commands/agents.js";
|
|
10
10
|
import { logsCommand, statusCommand, stopCommand } from "./commands/runtime.js";
|
|
11
11
|
import { configCommand } from "./commands/config.js";
|
|
@@ -60,6 +60,10 @@ program
|
|
|
60
60
|
.command("logout")
|
|
61
61
|
.description("Sign out")
|
|
62
62
|
.action(async () => logoutCommand());
|
|
63
|
+
program
|
|
64
|
+
.command("whoami")
|
|
65
|
+
.description("Show the signed-in account")
|
|
66
|
+
.action(() => whoamiCommand());
|
|
63
67
|
program
|
|
64
68
|
.command("config")
|
|
65
69
|
.description("View or change local configuration")
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { apiUrl } from "./config.js";
|
|
2
|
+
async function postJSON(path, body) {
|
|
3
|
+
const res = await fetch(`${apiUrl()}${path}`, {
|
|
4
|
+
method: "POST",
|
|
5
|
+
headers: { "Content-Type": "application/json" },
|
|
6
|
+
body: JSON.stringify(body),
|
|
7
|
+
});
|
|
8
|
+
const text = await res.text();
|
|
9
|
+
let data = {};
|
|
10
|
+
try {
|
|
11
|
+
data = text ? JSON.parse(text) : {};
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// non-JSON response
|
|
15
|
+
}
|
|
16
|
+
if (!res.ok && res.status >= 500) {
|
|
17
|
+
const msg = data?.error ?? `server error (${res.status})`;
|
|
18
|
+
throw new Error(msg);
|
|
19
|
+
}
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
export function startDeviceAuth() {
|
|
23
|
+
return postJSON("/api/cli/auth/start", {});
|
|
24
|
+
}
|
|
25
|
+
export function pollDeviceAuth(deviceCode) {
|
|
26
|
+
return postJSON("/api/cli/auth/poll", {
|
|
27
|
+
device_code: deviceCode,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
// Best-effort: open a URL in the default browser across macOS / Windows / Linux.
|
|
3
|
+
export function openBrowser(url) {
|
|
4
|
+
try {
|
|
5
|
+
const platform = process.platform;
|
|
6
|
+
let command;
|
|
7
|
+
let args;
|
|
8
|
+
if (platform === "darwin") {
|
|
9
|
+
command = "open";
|
|
10
|
+
args = [url];
|
|
11
|
+
}
|
|
12
|
+
else if (platform === "win32") {
|
|
13
|
+
command = "cmd";
|
|
14
|
+
args = ["/c", "start", "", url];
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
command = "xdg-open";
|
|
18
|
+
args = [url];
|
|
19
|
+
}
|
|
20
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
21
|
+
child.on("error", () => { });
|
|
22
|
+
child.unref();
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/lib/config.js
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
4
|
+
// Where the CLI talks to. Override with KURTEL_API_URL for local dev, e.g.
|
|
5
|
+
// KURTEL_API_URL=http://localhost:3000 kurtel login
|
|
6
|
+
export function apiUrl() {
|
|
7
|
+
return (process.env.KURTEL_API_URL ??
|
|
8
|
+
loadConfig().apiUrl ??
|
|
9
|
+
"https://app.kurtel.ai");
|
|
10
|
+
}
|
|
11
|
+
export function saveSession(session) {
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
config.loggedIn = true;
|
|
14
|
+
config.token = session.token;
|
|
15
|
+
if (session.account)
|
|
16
|
+
config.account = session.account;
|
|
17
|
+
if (session.organization)
|
|
18
|
+
config.organization = session.organization;
|
|
19
|
+
saveConfig(config);
|
|
20
|
+
}
|
|
21
|
+
export function clearSession() {
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
config.loggedIn = false;
|
|
24
|
+
delete config.token;
|
|
25
|
+
delete config.account;
|
|
26
|
+
delete config.organization;
|
|
27
|
+
saveConfig(config);
|
|
28
|
+
}
|
|
4
29
|
const DIR = join(homedir(), ".kurtel");
|
|
5
30
|
const FILE = join(DIR, "config.json");
|
|
6
31
|
const DEFAULTS = {
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurtel/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Launch self-improving coding agents in the cloud — the Kurtel CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"kurtel": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"publishConfig": {
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
10
12
|
"files": [
|
|
11
13
|
"dist"
|
|
12
14
|
],
|