@kurtel/cli 0.1.1 → 0.1.4
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/run.js +34 -20
- package/dist/commands/runs.js +54 -0
- package/dist/commands/runtime.js +66 -44
- package/dist/index.js +14 -7
- package/dist/lib/api.js +49 -0
- package/dist/lib/config.js +1 -1
- package/dist/lib/engines.js +4 -0
- package/package.json +1 -1
package/dist/commands/run.js
CHANGED
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
import { c, symbols } from "../ui/colors.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createRun, AuthError } from "../lib/api.js";
|
|
3
|
+
import { isEngineName } from "../lib/engines.js";
|
|
4
4
|
export async function runCommand(task, opts) {
|
|
5
5
|
if (!task || task.trim() === "") {
|
|
6
|
-
console.log(`${c.red(symbols.cross)} Provide a task, e.g. ${c.indigo('kurtel run "fix flaky auth test"')}`);
|
|
6
|
+
console.log(`${c.red(symbols.cross)} Provide a task, e.g. ${c.indigo('kurtel run "fix the flaky auth test"')}`);
|
|
7
7
|
process.exitCode = 1;
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
if (opts.engine && !isEngineName(opts.engine)) {
|
|
11
|
+
console.log(`${c.red(symbols.cross)} Unknown engine ${c.white(opts.engine)}. Use ${c.indigo("claude-code")} or ${c.indigo("codex")}.`);
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const run = await createRun({
|
|
17
|
+
task,
|
|
18
|
+
repo: opts.repo,
|
|
19
|
+
branch: opts.branch,
|
|
20
|
+
engine: opts.engine,
|
|
21
|
+
});
|
|
22
|
+
console.log("");
|
|
23
|
+
console.log(`${symbols.check} Launched ${c.indigo(run.id)}`);
|
|
24
|
+
console.log(`${c.gray("task")} ${c.white(run.task)}`);
|
|
25
|
+
console.log(`${c.gray("engine")} ${c.white(run.engine)}`);
|
|
26
|
+
if (run.repo)
|
|
27
|
+
console.log(`${c.gray("repo")} ${c.white(run.repo)}`);
|
|
28
|
+
console.log(`${c.gray("status")} ${c.indigo(run.status)}`);
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(`${c.dim("Follow it with")} ${c.indigo(`kurtel logs ${run.id} --follow`)}`);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
if (e instanceof AuthError) {
|
|
34
|
+
console.log(`${c.red(symbols.cross)} ${e.message}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(`${c.red(symbols.cross)} Failed to launch: ${e instanceof Error ? e.message : String(e)}`);
|
|
38
|
+
}
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
27
41
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { c } from "../ui/colors.js";
|
|
2
|
+
import { listRuns, AuthError } from "../lib/api.js";
|
|
3
|
+
function pad(s, width) {
|
|
4
|
+
// eslint-disable-next-line no-control-regex
|
|
5
|
+
const len = s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
6
|
+
return s + " ".repeat(Math.max(0, width - len));
|
|
7
|
+
}
|
|
8
|
+
function statusLabel(status) {
|
|
9
|
+
switch (status) {
|
|
10
|
+
case "running": return c.indigo("● running");
|
|
11
|
+
case "queued": return c.gray("○ queued");
|
|
12
|
+
case "succeeded": return c.green("✔ done");
|
|
13
|
+
case "failed": return c.red("✖ failed");
|
|
14
|
+
case "canceled": return c.yellow("• canceled");
|
|
15
|
+
default: return status;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function ago(iso) {
|
|
19
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
20
|
+
const m = Math.floor(diff / 60000);
|
|
21
|
+
if (m < 1)
|
|
22
|
+
return "just now";
|
|
23
|
+
if (m < 60)
|
|
24
|
+
return `${m}m ago`;
|
|
25
|
+
const h = Math.floor(m / 60);
|
|
26
|
+
if (h < 24)
|
|
27
|
+
return `${h}h ago`;
|
|
28
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
29
|
+
}
|
|
30
|
+
export async function runsCommand() {
|
|
31
|
+
try {
|
|
32
|
+
const { runs } = await listRuns();
|
|
33
|
+
if (!runs.length) {
|
|
34
|
+
console.log(c.dim("No runs yet. Launch one with `kurtel run \"…\"`."));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(pad(c.gray("ID"), 14) + pad(c.gray("STATUS"), 14) +
|
|
39
|
+
pad(c.gray("ENGINE"), 14) + pad(c.gray("AGE"), 10) + c.gray("TASK"));
|
|
40
|
+
for (const r of runs) {
|
|
41
|
+
console.log(pad(c.indigo(r.id), 14) + pad(statusLabel(r.status), 14) +
|
|
42
|
+
pad(c.dim(r.engine), 14) + pad(c.dim(ago(r.created_at)), 10) +
|
|
43
|
+
c.white(r.task.length > 50 ? r.task.slice(0, 49) + "…" : r.task));
|
|
44
|
+
}
|
|
45
|
+
console.log("");
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
if (e instanceof AuthError)
|
|
49
|
+
console.log(`${c.red("✖")} ${e.message}`);
|
|
50
|
+
else
|
|
51
|
+
console.log(`${c.red("✖")} ${e instanceof Error ? e.message : String(e)}`);
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/commands/runtime.js
CHANGED
|
@@ -1,50 +1,72 @@
|
|
|
1
|
-
import { c } from "../ui/colors.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { c, symbols } from "../ui/colors.js";
|
|
2
|
+
import { sleep } from "../ui/spinner.js";
|
|
3
|
+
import { getRun, getRunLogs, cancelRun, AuthError } from "../lib/api.js";
|
|
4
|
+
function handleErr(e) {
|
|
5
|
+
if (e instanceof AuthError)
|
|
6
|
+
console.log(`${c.red(symbols.cross)} ${e.message}`);
|
|
7
|
+
else
|
|
8
|
+
console.log(`${c.red(symbols.cross)} ${e instanceof Error ? e.message : String(e)}`);
|
|
9
|
+
process.exitCode = 1;
|
|
7
10
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export async function logsCommand(id) {
|
|
17
|
-
const agent = findAgent(id);
|
|
18
|
-
console.log(`\n${c.dim("Streaming logs for")} ${c.indigo(agent.id)} ${c.dim(`· ${agent.repo}`)}\n`);
|
|
19
|
-
for (const [tag, msg] of FAKE_LOG_LINES) {
|
|
20
|
-
const ts = c.dim(new Date().toLocaleTimeString());
|
|
21
|
-
const label = tag === "learn"
|
|
22
|
-
? c.cyan(`[${tag}]`)
|
|
23
|
-
: tag === "done"
|
|
24
|
-
? c.green(`[${tag}]`)
|
|
25
|
-
: c.indigo(`[${tag}]`);
|
|
26
|
-
console.log(`${ts} ${label} ${c.white(msg)}`);
|
|
27
|
-
await sleep(450);
|
|
11
|
+
function statusLabel(status) {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case "running": return c.indigo("● running");
|
|
14
|
+
case "queued": return c.gray("○ queued");
|
|
15
|
+
case "succeeded": return c.green("✔ succeeded");
|
|
16
|
+
case "failed": return c.red("✖ failed");
|
|
17
|
+
case "canceled": return c.yellow("• canceled");
|
|
18
|
+
default: return status;
|
|
28
19
|
}
|
|
29
|
-
console.log("");
|
|
30
|
-
previewNotice("Live logs");
|
|
31
20
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
const TERMINAL = ["succeeded", "failed", "canceled"];
|
|
22
|
+
export async function logsCommand(id, opts = {}) {
|
|
23
|
+
try {
|
|
24
|
+
let printed = 0;
|
|
25
|
+
for (;;) {
|
|
26
|
+
const { status, logs } = await getRunLogs(id);
|
|
27
|
+
if (logs.length > printed) {
|
|
28
|
+
process.stdout.write(logs.slice(printed));
|
|
29
|
+
printed = logs.length;
|
|
30
|
+
}
|
|
31
|
+
if (!opts.follow || TERMINAL.includes(status)) {
|
|
32
|
+
console.log(`\n${statusLabel(status)}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await sleep(1500);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
handleErr(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function statusCommand(id) {
|
|
43
|
+
try {
|
|
44
|
+
const run = await getRun(id);
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(`${c.gray("id")} ${c.indigo(run.id)}`);
|
|
47
|
+
console.log(`${c.gray("task")} ${c.white(run.task)}`);
|
|
48
|
+
if (run.repo)
|
|
49
|
+
console.log(`${c.gray("repo")} ${c.white(run.repo)}`);
|
|
50
|
+
console.log(`${c.gray("engine")} ${c.white(run.engine)}`);
|
|
51
|
+
if (run.model)
|
|
52
|
+
console.log(`${c.gray("model")} ${c.white(run.model)}`);
|
|
53
|
+
console.log(`${c.gray("status")} ${statusLabel(run.status)}`);
|
|
54
|
+
if (run.result?.summary)
|
|
55
|
+
console.log(`${c.gray("summary")} ${c.white(run.result.summary)}`);
|
|
56
|
+
if (run.error)
|
|
57
|
+
console.log(`${c.gray("error")} ${c.red(run.error)}`);
|
|
58
|
+
console.log("");
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
handleErr(e);
|
|
62
|
+
}
|
|
43
63
|
}
|
|
44
64
|
export async function stopCommand(id) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
try {
|
|
66
|
+
await cancelRun(id);
|
|
67
|
+
console.log(`${symbols.check} Canceled ${c.indigo(id)} ${c.dim("· sandbox torn down")}`);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
handleErr(e);
|
|
71
|
+
}
|
|
50
72
|
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { startSession } from "./session/repl.js";
|
|
|
7
7
|
import { runCommand } from "./commands/run.js";
|
|
8
8
|
import { loginCommand, logoutCommand, whoamiCommand } from "./commands/auth.js";
|
|
9
9
|
import { agentsCommand } from "./commands/agents.js";
|
|
10
|
+
import { runsCommand } from "./commands/runs.js";
|
|
10
11
|
import { logsCommand, statusCommand, stopCommand } from "./commands/runtime.js";
|
|
11
12
|
import { configCommand } from "./commands/config.js";
|
|
12
13
|
import { initCommand, doctorCommand } from "./commands/project.js";
|
|
@@ -26,12 +27,17 @@ program
|
|
|
26
27
|
.command("run")
|
|
27
28
|
.description("Launch a cloud agent on a task")
|
|
28
29
|
.argument("[task...]", "What you want the agent to do")
|
|
29
|
-
.option("-r, --repo <repo>", "Target repository")
|
|
30
|
+
.option("-r, --repo <repo>", "Target repository (owner/name or URL)")
|
|
30
31
|
.option("-b, --branch <branch>", "Base branch", "main")
|
|
31
|
-
.option("-
|
|
32
|
+
.option("-e, --engine <engine>", "Engine: claude-code | codex")
|
|
33
|
+
.option("-m, --model <model>", "Model to use (engine-specific, optional)")
|
|
32
34
|
.action(async (taskParts, opts) => {
|
|
33
35
|
await runCommand((taskParts ?? []).join(" "), opts);
|
|
34
36
|
});
|
|
37
|
+
program
|
|
38
|
+
.command("runs")
|
|
39
|
+
.description("List your recent runs")
|
|
40
|
+
.action(() => runsCommand());
|
|
35
41
|
program
|
|
36
42
|
.command("agents")
|
|
37
43
|
.alias("ps")
|
|
@@ -39,13 +45,14 @@ program
|
|
|
39
45
|
.action(() => agentsCommand());
|
|
40
46
|
program
|
|
41
47
|
.command("logs")
|
|
42
|
-
.description("Stream
|
|
43
|
-
.argument("<id>", "
|
|
44
|
-
.
|
|
48
|
+
.description("Stream a run's logs")
|
|
49
|
+
.argument("<id>", "Run id")
|
|
50
|
+
.option("-f, --follow", "Keep streaming until the run finishes", false)
|
|
51
|
+
.action(async (id, opts) => logsCommand(id, opts));
|
|
45
52
|
program
|
|
46
53
|
.command("status")
|
|
47
|
-
.description("Show
|
|
48
|
-
.argument("<id>", "
|
|
54
|
+
.description("Show a run's status")
|
|
55
|
+
.argument("<id>", "Run id")
|
|
49
56
|
.action((id) => statusCommand(id));
|
|
50
57
|
program
|
|
51
58
|
.command("stop")
|
package/dist/lib/api.js
CHANGED
|
@@ -27,3 +27,52 @@ export function pollDeviceAuth(deviceCode) {
|
|
|
27
27
|
device_code: deviceCode,
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
+
// ── Authenticated API (uses the stored CLI token) ──────────────────────────
|
|
31
|
+
import { loadConfig } from "./config.js";
|
|
32
|
+
export class AuthError extends Error {
|
|
33
|
+
}
|
|
34
|
+
async function authed(method, path, body) {
|
|
35
|
+
const token = loadConfig().token;
|
|
36
|
+
if (!token)
|
|
37
|
+
throw new AuthError("Not signed in. Run `kurtel login`.");
|
|
38
|
+
const res = await fetch(`${apiUrl()}${path}`, {
|
|
39
|
+
method,
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
Authorization: `Bearer ${token}`,
|
|
43
|
+
},
|
|
44
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
const text = await res.text();
|
|
47
|
+
let data = {};
|
|
48
|
+
try {
|
|
49
|
+
data = text ? JSON.parse(text) : {};
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
if (res.status === 401) {
|
|
53
|
+
throw new AuthError("Your session is invalid or expired. Run `kurtel login`.");
|
|
54
|
+
}
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const msg = data?.error ?? `error ${res.status}`;
|
|
57
|
+
throw new Error(msg);
|
|
58
|
+
}
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
export function verifyToken() {
|
|
62
|
+
return authed("GET", "/api/cli/me");
|
|
63
|
+
}
|
|
64
|
+
export function createRun(input) {
|
|
65
|
+
return authed("POST", "/api/runs", input);
|
|
66
|
+
}
|
|
67
|
+
export function listRuns() {
|
|
68
|
+
return authed("GET", "/api/runs");
|
|
69
|
+
}
|
|
70
|
+
export function getRun(id) {
|
|
71
|
+
return authed("GET", `/api/runs/${id}`);
|
|
72
|
+
}
|
|
73
|
+
export function getRunLogs(id) {
|
|
74
|
+
return authed("GET", `/api/runs/${id}/logs`);
|
|
75
|
+
}
|
|
76
|
+
export function cancelRun(id) {
|
|
77
|
+
return authed("DELETE", `/api/runs/${id}`);
|
|
78
|
+
}
|
package/dist/lib/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
|
6
6
|
export function apiUrl() {
|
|
7
7
|
return (process.env.KURTEL_API_URL ??
|
|
8
8
|
loadConfig().apiUrl ??
|
|
9
|
-
"
|
|
9
|
+
"htt");
|
|
10
10
|
}
|
|
11
11
|
export function saveSession(session) {
|
|
12
12
|
const config = loadConfig();
|