@orgloop/agentctl 1.0.0
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/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/adapters/claude-code.d.ts +83 -0
- package/dist/adapters/claude-code.js +783 -0
- package/dist/adapters/openclaw.d.ts +88 -0
- package/dist/adapters/openclaw.js +297 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +808 -0
- package/dist/client/daemon-client.d.ts +6 -0
- package/dist/client/daemon-client.js +81 -0
- package/dist/compat-shim.d.ts +2 -0
- package/dist/compat-shim.js +15 -0
- package/dist/core/types.d.ts +68 -0
- package/dist/core/types.js +2 -0
- package/dist/daemon/fuse-engine.d.ts +30 -0
- package/dist/daemon/fuse-engine.js +118 -0
- package/dist/daemon/launchagent.d.ts +7 -0
- package/dist/daemon/launchagent.js +49 -0
- package/dist/daemon/lock-manager.d.ts +16 -0
- package/dist/daemon/lock-manager.js +71 -0
- package/dist/daemon/metrics.d.ts +20 -0
- package/dist/daemon/metrics.js +72 -0
- package/dist/daemon/server.d.ts +33 -0
- package/dist/daemon/server.js +283 -0
- package/dist/daemon/session-tracker.d.ts +28 -0
- package/dist/daemon/session-tracker.js +121 -0
- package/dist/daemon/state.d.ts +61 -0
- package/dist/daemon/state.js +126 -0
- package/dist/daemon/supervisor.d.ts +24 -0
- package/dist/daemon/supervisor.js +79 -0
- package/dist/hooks.d.ts +19 -0
- package/dist/hooks.js +39 -0
- package/dist/merge.d.ts +24 -0
- package/dist/merge.js +65 -0
- package/dist/migration/migrate-locks.d.ts +5 -0
- package/dist/migration/migrate-locks.js +41 -0
- package/dist/worktree.d.ts +24 -0
- package/dist/worktree.js +65 -0
- package/package.json +60 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
/**
|
|
6
|
+
* Daemon supervisor — launches the daemon in foreground mode and
|
|
7
|
+
* restarts it on crash with exponential backoff (1s, 2s, 4s... cap 5min).
|
|
8
|
+
* Resets backoff after stable uptime.
|
|
9
|
+
*/
|
|
10
|
+
export async function runSupervisor(opts) {
|
|
11
|
+
const minBackoff = opts.minBackoffMs ?? 1000;
|
|
12
|
+
const maxBackoff = opts.maxBackoffMs ?? 300_000;
|
|
13
|
+
const stableUptime = opts.stableUptimeMs ?? 60_000;
|
|
14
|
+
let currentBackoff = minBackoff;
|
|
15
|
+
let running = true;
|
|
16
|
+
// Write supervisor PID file
|
|
17
|
+
const supervisorPidPath = path.join(opts.configDir, "supervisor.pid");
|
|
18
|
+
await fs.writeFile(supervisorPidPath, String(process.pid));
|
|
19
|
+
const cleanup = async () => {
|
|
20
|
+
running = false;
|
|
21
|
+
await fs.rm(supervisorPidPath, { force: true }).catch(() => { });
|
|
22
|
+
};
|
|
23
|
+
process.on("SIGTERM", async () => {
|
|
24
|
+
await cleanup();
|
|
25
|
+
process.exit(0);
|
|
26
|
+
});
|
|
27
|
+
process.on("SIGINT", async () => {
|
|
28
|
+
await cleanup();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
|
31
|
+
const logDir = opts.configDir;
|
|
32
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
33
|
+
while (running) {
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
const stdoutFd = await fs.open(path.join(logDir, "daemon.stdout.log"), "a");
|
|
36
|
+
const stderrFd = await fs.open(path.join(logDir, "daemon.stderr.log"), "a");
|
|
37
|
+
const child = spawn(opts.nodePath, [
|
|
38
|
+
opts.cliPath,
|
|
39
|
+
"daemon",
|
|
40
|
+
"start",
|
|
41
|
+
"--foreground",
|
|
42
|
+
"--metrics-port",
|
|
43
|
+
String(opts.metricsPort),
|
|
44
|
+
], {
|
|
45
|
+
stdio: ["ignore", stdoutFd.fd, stderrFd.fd],
|
|
46
|
+
});
|
|
47
|
+
// Wait for child to exit
|
|
48
|
+
const exitCode = await new Promise((resolve) => {
|
|
49
|
+
child.on("exit", (code) => resolve(code));
|
|
50
|
+
child.on("error", () => resolve(1));
|
|
51
|
+
});
|
|
52
|
+
await stdoutFd.close();
|
|
53
|
+
await stderrFd.close();
|
|
54
|
+
if (!running)
|
|
55
|
+
break;
|
|
56
|
+
const uptime = Date.now() - startTime;
|
|
57
|
+
// Reset backoff if daemon ran long enough to be considered stable
|
|
58
|
+
if (uptime >= stableUptime) {
|
|
59
|
+
currentBackoff = minBackoff;
|
|
60
|
+
}
|
|
61
|
+
console.error(`Daemon exited (code ${exitCode}) after ${Math.round(uptime / 1000)}s. Restarting in ${Math.round(currentBackoff / 1000)}s...`);
|
|
62
|
+
await new Promise((r) => setTimeout(r, currentBackoff));
|
|
63
|
+
// Exponential backoff (double, capped)
|
|
64
|
+
currentBackoff = Math.min(currentBackoff * 2, maxBackoff);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Read the supervisor PID from disk and check if it's alive */
|
|
68
|
+
export async function getSupervisorPid(configDir) {
|
|
69
|
+
const dir = configDir || path.join(os.homedir(), ".agentctl");
|
|
70
|
+
try {
|
|
71
|
+
const raw = await fs.readFile(path.join(dir, "supervisor.pid"), "utf-8");
|
|
72
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
73
|
+
process.kill(pid, 0); // Check liveness
|
|
74
|
+
return pid;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LifecycleHooks } from "./core/types.js";
|
|
2
|
+
export type HookPhase = "onCreate" | "onComplete" | "preMerge" | "postMerge";
|
|
3
|
+
export interface HookContext {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
adapter: string;
|
|
7
|
+
branch?: string;
|
|
8
|
+
exitCode?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Run a lifecycle hook script if defined.
|
|
12
|
+
* Hook scripts receive context via environment variables:
|
|
13
|
+
* AGENTCTL_SESSION_ID, AGENTCTL_CWD, AGENTCTL_ADAPTER,
|
|
14
|
+
* AGENTCTL_BRANCH, AGENTCTL_EXIT_CODE
|
|
15
|
+
*/
|
|
16
|
+
export declare function runHook(hooks: LifecycleHooks | undefined, phase: HookPhase, ctx: HookContext): Promise<{
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
} | null>;
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
/**
|
|
5
|
+
* Run a lifecycle hook script if defined.
|
|
6
|
+
* Hook scripts receive context via environment variables:
|
|
7
|
+
* AGENTCTL_SESSION_ID, AGENTCTL_CWD, AGENTCTL_ADAPTER,
|
|
8
|
+
* AGENTCTL_BRANCH, AGENTCTL_EXIT_CODE
|
|
9
|
+
*/
|
|
10
|
+
export async function runHook(hooks, phase, ctx) {
|
|
11
|
+
if (!hooks)
|
|
12
|
+
return null;
|
|
13
|
+
const script = hooks[phase];
|
|
14
|
+
if (!script)
|
|
15
|
+
return null;
|
|
16
|
+
const env = {
|
|
17
|
+
...process.env,
|
|
18
|
+
AGENTCTL_SESSION_ID: ctx.sessionId,
|
|
19
|
+
AGENTCTL_CWD: ctx.cwd,
|
|
20
|
+
AGENTCTL_ADAPTER: ctx.adapter,
|
|
21
|
+
};
|
|
22
|
+
if (ctx.branch)
|
|
23
|
+
env.AGENTCTL_BRANCH = ctx.branch;
|
|
24
|
+
if (ctx.exitCode != null)
|
|
25
|
+
env.AGENTCTL_EXIT_CODE = String(ctx.exitCode);
|
|
26
|
+
try {
|
|
27
|
+
const result = await execAsync(script, {
|
|
28
|
+
cwd: ctx.cwd,
|
|
29
|
+
env,
|
|
30
|
+
timeout: 60_000,
|
|
31
|
+
});
|
|
32
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const e = err;
|
|
36
|
+
console.error(`Hook ${phase} failed:`, e.message);
|
|
37
|
+
return { stdout: e.stdout || "", stderr: e.stderr || "" };
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/merge.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface MergeOpts {
|
|
2
|
+
/** Working directory of the session */
|
|
3
|
+
cwd: string;
|
|
4
|
+
/** Commit message (auto-generated if omitted) */
|
|
5
|
+
message?: string;
|
|
6
|
+
/** Whether to remove worktree after push */
|
|
7
|
+
removeWorktree?: boolean;
|
|
8
|
+
/** The main repo path (needed for worktree removal) */
|
|
9
|
+
repoPath?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MergeResult {
|
|
12
|
+
committed: boolean;
|
|
13
|
+
pushed: boolean;
|
|
14
|
+
prUrl?: string;
|
|
15
|
+
worktreeRemoved: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Merge + cleanup workflow:
|
|
19
|
+
* 1. Commit uncommitted changes
|
|
20
|
+
* 2. Push to remote
|
|
21
|
+
* 3. Open PR via `gh`
|
|
22
|
+
* 4. Optionally remove worktree
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeSession(opts: MergeOpts): Promise<MergeResult>;
|
package/dist/merge.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { exec, execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
/**
|
|
6
|
+
* Merge + cleanup workflow:
|
|
7
|
+
* 1. Commit uncommitted changes
|
|
8
|
+
* 2. Push to remote
|
|
9
|
+
* 3. Open PR via `gh`
|
|
10
|
+
* 4. Optionally remove worktree
|
|
11
|
+
*/
|
|
12
|
+
export async function mergeSession(opts) {
|
|
13
|
+
const { cwd } = opts;
|
|
14
|
+
const result = {
|
|
15
|
+
committed: false,
|
|
16
|
+
pushed: false,
|
|
17
|
+
worktreeRemoved: false,
|
|
18
|
+
};
|
|
19
|
+
// 1. Check for uncommitted changes
|
|
20
|
+
const { stdout: status } = await execFileAsync("git", ["status", "--porcelain"], { cwd });
|
|
21
|
+
if (status.trim()) {
|
|
22
|
+
// Stage all changes and commit
|
|
23
|
+
await execFileAsync("git", ["add", "-A"], { cwd });
|
|
24
|
+
const message = opts.message || "chore: commit agent session work (via agentctl merge)";
|
|
25
|
+
await execFileAsync("git", ["commit", "-m", message], { cwd });
|
|
26
|
+
result.committed = true;
|
|
27
|
+
}
|
|
28
|
+
// 2. Get current branch name
|
|
29
|
+
const { stdout: branchRaw } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd });
|
|
30
|
+
const branch = branchRaw.trim();
|
|
31
|
+
// 3. Push to remote
|
|
32
|
+
try {
|
|
33
|
+
await execFileAsync("git", ["push", "-u", "origin", branch], { cwd });
|
|
34
|
+
result.pushed = true;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.error("Push failed:", err.message);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
// 4. Open PR via gh (best effort)
|
|
41
|
+
try {
|
|
42
|
+
const { stdout: prOut } = await execAsync(`gh pr create --fill --head ${branch} 2>&1 || gh pr view --json url -q .url 2>&1`, { cwd });
|
|
43
|
+
// Extract URL from output
|
|
44
|
+
const urlMatch = prOut.match(/https:\/\/github\.com\/[^\s]+/);
|
|
45
|
+
if (urlMatch) {
|
|
46
|
+
result.prUrl = urlMatch[0];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// gh not available or PR already exists
|
|
51
|
+
}
|
|
52
|
+
// 5. Optionally remove worktree
|
|
53
|
+
if (opts.removeWorktree && opts.repoPath) {
|
|
54
|
+
try {
|
|
55
|
+
await execFileAsync("git", ["worktree", "remove", "--force", cwd], {
|
|
56
|
+
cwd: opts.repoPath,
|
|
57
|
+
});
|
|
58
|
+
result.worktreeRemoved = true;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.error("Worktree removal failed:", err.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* One-time migration from ~/.openclaw/locks/locks.json to ~/.agentctl/locks.json.
|
|
6
|
+
* Idempotent — skips if target already exists or source is missing.
|
|
7
|
+
*/
|
|
8
|
+
export async function migrateLocks(configDir) {
|
|
9
|
+
const targetDir = configDir || path.join(os.homedir(), ".agentctl");
|
|
10
|
+
const oldPath = path.join(os.homedir(), ".openclaw", "locks", "locks.json");
|
|
11
|
+
const newPath = path.join(targetDir, "locks.json");
|
|
12
|
+
// Skip if already migrated
|
|
13
|
+
if (await fileExists(newPath))
|
|
14
|
+
return 0;
|
|
15
|
+
// Skip if no old file
|
|
16
|
+
if (!(await fileExists(oldPath)))
|
|
17
|
+
return 0;
|
|
18
|
+
const oldData = JSON.parse(await fs.readFile(oldPath, "utf-8"));
|
|
19
|
+
// Transform old format → new format
|
|
20
|
+
// Old format: array of { directory, lockedBy, reason, lockedAt }
|
|
21
|
+
const newLocks = (Array.isArray(oldData) ? oldData : []).map((old) => ({
|
|
22
|
+
directory: old.directory,
|
|
23
|
+
type: "manual",
|
|
24
|
+
lockedBy: (old.lockedBy || old.by || "unknown"),
|
|
25
|
+
reason: (old.reason || ""),
|
|
26
|
+
lockedAt: old.lockedAt || new Date().toISOString(),
|
|
27
|
+
}));
|
|
28
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
29
|
+
await fs.writeFile(newPath, JSON.stringify(newLocks, null, 2));
|
|
30
|
+
console.log(`Migrated ${newLocks.length} locks from ${oldPath}`);
|
|
31
|
+
return newLocks.length;
|
|
32
|
+
}
|
|
33
|
+
async function fileExists(p) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(p);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface WorktreeCreateOpts {
|
|
2
|
+
/** Path to the main repo (e.g. ~/code/myproject) */
|
|
3
|
+
repo: string;
|
|
4
|
+
/** Branch name (e.g. charlie/feature-name) */
|
|
5
|
+
branch: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WorktreeInfo {
|
|
8
|
+
/** Absolute path to the worktree directory */
|
|
9
|
+
path: string;
|
|
10
|
+
/** Branch name */
|
|
11
|
+
branch: string;
|
|
12
|
+
/** The base repo path */
|
|
13
|
+
repo: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a git worktree for the given repo + branch.
|
|
17
|
+
* Worktree is placed at `<repo>-<branch-slug>` (sibling directory).
|
|
18
|
+
* If the worktree already exists, returns its info without creating a new one.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createWorktree(opts: WorktreeCreateOpts): Promise<WorktreeInfo>;
|
|
21
|
+
/**
|
|
22
|
+
* Remove a git worktree.
|
|
23
|
+
*/
|
|
24
|
+
export declare function removeWorktree(repo: string, worktreePath: string): Promise<void>;
|
package/dist/worktree.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
/**
|
|
7
|
+
* Create a git worktree for the given repo + branch.
|
|
8
|
+
* Worktree is placed at `<repo>-<branch-slug>` (sibling directory).
|
|
9
|
+
* If the worktree already exists, returns its info without creating a new one.
|
|
10
|
+
*/
|
|
11
|
+
export async function createWorktree(opts) {
|
|
12
|
+
const repoResolved = path.resolve(opts.repo);
|
|
13
|
+
const slug = opts.branch.replace(/\//g, "-");
|
|
14
|
+
const worktreePath = `${repoResolved}-${slug}`;
|
|
15
|
+
// Check if worktree already exists (check filesystem + git)
|
|
16
|
+
try {
|
|
17
|
+
// Quick filesystem check — if the .git file exists in the worktree dir, it's a worktree
|
|
18
|
+
const gitFile = path.join(worktreePath, ".git");
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(gitFile);
|
|
21
|
+
return { path: worktreePath, branch: opts.branch, repo: repoResolved };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Not on disk yet — check git's worktree list (handles symlink/realpath differences)
|
|
25
|
+
}
|
|
26
|
+
// Validate this is actually a git repo
|
|
27
|
+
await execFileAsync("git", ["rev-parse", "--git-dir"], {
|
|
28
|
+
cwd: repoResolved,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error(`Not a git repository: ${repoResolved}`);
|
|
33
|
+
}
|
|
34
|
+
// Check if branch already exists
|
|
35
|
+
let branchExists = false;
|
|
36
|
+
try {
|
|
37
|
+
await execFileAsync("git", ["rev-parse", "--verify", opts.branch], {
|
|
38
|
+
cwd: repoResolved,
|
|
39
|
+
});
|
|
40
|
+
branchExists = true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Branch doesn't exist yet
|
|
44
|
+
}
|
|
45
|
+
if (branchExists) {
|
|
46
|
+
// Branch exists — create worktree checking it out
|
|
47
|
+
await execFileAsync("git", ["worktree", "add", worktreePath, opts.branch], {
|
|
48
|
+
cwd: repoResolved,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Branch doesn't exist — create new branch from HEAD
|
|
53
|
+
await execFileAsync("git", ["worktree", "add", "-b", opts.branch, worktreePath], { cwd: repoResolved });
|
|
54
|
+
}
|
|
55
|
+
return { path: worktreePath, branch: opts.branch, repo: repoResolved };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Remove a git worktree.
|
|
59
|
+
*/
|
|
60
|
+
export async function removeWorktree(repo, worktreePath) {
|
|
61
|
+
const repoResolved = path.resolve(repo);
|
|
62
|
+
await execFileAsync("git", ["worktree", "remove", "--force", worktreePath], {
|
|
63
|
+
cwd: repoResolved,
|
|
64
|
+
});
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@orgloop/agentctl",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal agent supervision interface — monitor and control AI coding agents from a single CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agentctl": "./dist/cli.js",
|
|
8
|
+
"agent-ctl": "./dist/compat-shim.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsx src/cli.ts",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"lint": "biome check src/",
|
|
22
|
+
"lint:fix": "biome check --write src/",
|
|
23
|
+
"format": "biome format --write src/",
|
|
24
|
+
"prepare": "git config core.hooksPath .githooks"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/OrgLoop/agentctl.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/c-h-/agentctl#readme",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/c-h-/agentctl/issues"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"cli",
|
|
36
|
+
"agent",
|
|
37
|
+
"ai",
|
|
38
|
+
"supervisor",
|
|
39
|
+
"claude",
|
|
40
|
+
"coding-agent"
|
|
41
|
+
],
|
|
42
|
+
"author": "Charlie Hulcher <charlie.hulcher@gmail.com>",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@types/node": "^25.2.3",
|
|
49
|
+
"commander": "^14.0.3",
|
|
50
|
+
"tsx": "^4.21.0",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vitest": "^4.0.18"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "^2.4.2"
|
|
56
|
+
},
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"access": "public"
|
|
59
|
+
}
|
|
60
|
+
}
|