agent-companion 0.1.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/README.md +401 -0
- package/bridge/defaultState.mjs +504 -0
- package/bridge/directIngest.mjs +712 -0
- package/bridge/server.mjs +2812 -0
- package/package.json +86 -0
- package/relay/server.mjs +2056 -0
- package/scripts/add-pending.mjs +51 -0
- package/scripts/agent-runner.mjs +1475 -0
- package/scripts/background-service.mjs +122 -0
- package/scripts/banner.mjs +36 -0
- package/scripts/cli.mjs +77 -0
- package/scripts/dev-stack.mjs +64 -0
- package/scripts/laptop-companion.mjs +1179 -0
- package/scripts/laptop-service.mjs +282 -0
- package/scripts/repair-codex-resume.mjs +300 -0
- package/scripts/reset-bridge.mjs +19 -0
- package/scripts/start-task.mjs +108 -0
- package/scripts/ui-claude-delegate.mjs +220 -0
- package/wake-proxy/server.mjs +162 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
|
|
5
|
+
const argv = process.argv.slice(2);
|
|
6
|
+
const dividerIndex = argv.indexOf("--");
|
|
7
|
+
const optionArgs = dividerIndex >= 0 ? argv.slice(0, dividerIndex) : argv;
|
|
8
|
+
const commandArgs = dividerIndex >= 0 ? argv.slice(dividerIndex + 1).filter(Boolean) : [];
|
|
9
|
+
while (commandArgs[0] === "--") {
|
|
10
|
+
commandArgs.shift();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const options = parseOptions(optionArgs);
|
|
14
|
+
const bridgeUrl = trimTrailingSlash(
|
|
15
|
+
options.bridge || process.env.AGENT_COMPANION_BRIDGE_URL || process.env.AGENT_BRIDGE_URL || "http://localhost:8787"
|
|
16
|
+
);
|
|
17
|
+
const workspacePath = path.resolve(options.workspace || process.env.AGENT_WORKSPACE_PATH || process.cwd());
|
|
18
|
+
const sessionId = trim(options.session || process.env.AGENT_SESSION_ID || "");
|
|
19
|
+
const label = trim(options.label || "");
|
|
20
|
+
const commandText = trim(options.command || "");
|
|
21
|
+
const token = trim(options.token || process.env.AGENT_BRIDGE_TOKEN || "");
|
|
22
|
+
|
|
23
|
+
const portRaw = Number.parseInt(String(options.port || "").trim(), 10);
|
|
24
|
+
const port = Number.isFinite(portRaw) && portRaw > 0 && portRaw <= 65535 ? portRaw : null;
|
|
25
|
+
|
|
26
|
+
const payload = {
|
|
27
|
+
workspacePath,
|
|
28
|
+
};
|
|
29
|
+
if (sessionId) payload.sessionId = sessionId;
|
|
30
|
+
if (label) payload.label = label;
|
|
31
|
+
if (port) payload.port = port;
|
|
32
|
+
|
|
33
|
+
if (commandArgs.length > 0) {
|
|
34
|
+
payload.command = commandArgs;
|
|
35
|
+
} else if (commandText) {
|
|
36
|
+
payload.command = commandText;
|
|
37
|
+
} else {
|
|
38
|
+
printUsageAndExit(1, "missing command");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const headers = {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
Accept: "application/json",
|
|
44
|
+
};
|
|
45
|
+
if (token) {
|
|
46
|
+
headers["x-bridge-token"] = token;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const response = await fetch(`${bridgeUrl}/api/launcher/services/start`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify(payload),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const text = await response.text();
|
|
56
|
+
let body;
|
|
57
|
+
try {
|
|
58
|
+
body = text ? JSON.parse(text) : {};
|
|
59
|
+
} catch {
|
|
60
|
+
body = { raw: text };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!response.ok || !body?.ok || !body?.service) {
|
|
64
|
+
const errorText = typeof body?.error === "string" ? body.error : `request failed (${response.status})`;
|
|
65
|
+
console.error(`[background-service] ${errorText}`);
|
|
66
|
+
if (body && typeof body === "object") {
|
|
67
|
+
console.error(JSON.stringify(body, null, 2));
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const service = body.service;
|
|
73
|
+
console.log(`[background-service] started ${service.id}`);
|
|
74
|
+
console.log(`[background-service] status=${service.status} pid=${service.pid ?? "pending"}`);
|
|
75
|
+
if (service.localhostUrl) {
|
|
76
|
+
console.log(`[background-service] localhost=${service.localhostUrl}`);
|
|
77
|
+
}
|
|
78
|
+
console.log(JSON.stringify({ ok: true, service }, null, 2));
|
|
79
|
+
|
|
80
|
+
function parseOptions(tokens) {
|
|
81
|
+
const out = {};
|
|
82
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
83
|
+
const token = tokens[index];
|
|
84
|
+
if (!token.startsWith("--")) continue;
|
|
85
|
+
const key = token.slice(2);
|
|
86
|
+
const next = tokens[index + 1];
|
|
87
|
+
if (!next || next.startsWith("--")) {
|
|
88
|
+
out[key] = "true";
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
out[key] = next;
|
|
92
|
+
index += 1;
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function trim(value) {
|
|
98
|
+
return String(value || "").trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function trimTrailingSlash(value) {
|
|
102
|
+
return trim(value).replace(/\/+$/, "");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function printUsageAndExit(code, error = "") {
|
|
106
|
+
if (error) {
|
|
107
|
+
console.error(`[background-service] ${error}`);
|
|
108
|
+
}
|
|
109
|
+
console.log(`Usage:
|
|
110
|
+
node scripts/background-service.mjs [options] -- <command> [args...]
|
|
111
|
+
node scripts/background-service.mjs --command "<shell command>" [options]
|
|
112
|
+
|
|
113
|
+
Options:
|
|
114
|
+
--bridge <url> Bridge URL (default: http://localhost:8787)
|
|
115
|
+
--workspace <path> Workspace path (default: current directory)
|
|
116
|
+
--session <id> Optional session id for timeline events
|
|
117
|
+
--label <text> Optional display label
|
|
118
|
+
--port <n> Optional localhost port hint
|
|
119
|
+
--token <token> Optional bridge token
|
|
120
|
+
`);
|
|
121
|
+
process.exit(code);
|
|
122
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
export function printAgentCompanionBanner() {
|
|
4
|
+
const useColor = shouldUseColor();
|
|
5
|
+
const reset = useColor ? "\x1b[0m" : "";
|
|
6
|
+
const brand = colorRgb(209, 145, 99, useColor, true);
|
|
7
|
+
const dot = colorRgb(34, 182, 155, useColor, true);
|
|
8
|
+
const text = colorRgb(181, 187, 200, useColor, true);
|
|
9
|
+
const hint = colorRgb(121, 123, 130, useColor);
|
|
10
|
+
|
|
11
|
+
console.log("");
|
|
12
|
+
console.log(` ${brand}agent${reset}${text}.${reset}${text}companion${reset} ${dot}•${reset}`);
|
|
13
|
+
console.log(` ${hint}computer service${reset}`);
|
|
14
|
+
console.log("");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function printInfoLine(label, value) {
|
|
18
|
+
const useColor = shouldUseColor();
|
|
19
|
+
const labelColor = colorRgb(121, 123, 130, useColor);
|
|
20
|
+
const valueColor = colorRgb(236, 236, 239, useColor, true);
|
|
21
|
+
const reset = useColor ? "\x1b[0m" : "";
|
|
22
|
+
|
|
23
|
+
console.log(` ${labelColor}${label.padEnd(10)}${reset}${valueColor}${value}${reset}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function shouldUseColor() {
|
|
27
|
+
if (process.env.NO_COLOR) return false;
|
|
28
|
+
if (process.env.FORCE_COLOR === "0") return false;
|
|
29
|
+
return process.stdout.isTTY;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function colorRgb(r, g, b, enabled, bold = false) {
|
|
33
|
+
if (!enabled) return "";
|
|
34
|
+
const boldPart = bold ? "1;" : "";
|
|
35
|
+
return `\x1b[${boldPart}38;2;${r};${g};${b}m`;
|
|
36
|
+
}
|
package/scripts/cli.mjs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const projectRoot = path.resolve(__dirname, "..");
|
|
10
|
+
const computerServiceScript = path.resolve(projectRoot, "scripts", "laptop-service.mjs");
|
|
11
|
+
|
|
12
|
+
const argv = process.argv.slice(2);
|
|
13
|
+
const command = String(argv[0] || "").trim().toLowerCase();
|
|
14
|
+
const rest = argv.slice(1);
|
|
15
|
+
|
|
16
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
17
|
+
printHelp(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!command || command.startsWith("--")) {
|
|
21
|
+
runNodeScript(computerServiceScript, argv);
|
|
22
|
+
} else if (command === "laptop" || command === "computer" || command === "desktop") {
|
|
23
|
+
runNodeScript(computerServiceScript, rest);
|
|
24
|
+
} else if (command === "relay") {
|
|
25
|
+
runNodeScript(path.resolve(projectRoot, "relay", "server.mjs"), rest);
|
|
26
|
+
} else if (command === "bridge") {
|
|
27
|
+
runNodeScript(path.resolve(projectRoot, "bridge", "server.mjs"), rest);
|
|
28
|
+
} else if (command === "background" || command === "bg") {
|
|
29
|
+
runNodeScript(path.resolve(projectRoot, "scripts", "background-service.mjs"), rest);
|
|
30
|
+
} else if (command === "wake-proxy") {
|
|
31
|
+
runNodeScript(path.resolve(projectRoot, "wake-proxy", "server.mjs"), rest);
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`[agent-companion] unknown command "${command}"`);
|
|
34
|
+
printHelp(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function runNodeScript(scriptPath, args) {
|
|
38
|
+
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
39
|
+
cwd: projectRoot,
|
|
40
|
+
env: process.env,
|
|
41
|
+
stdio: "inherit"
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on("exit", (code, signal) => {
|
|
45
|
+
if (signal) {
|
|
46
|
+
process.kill(process.pid, signal);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
process.exit(Number.isInteger(code) ? code : 1);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printHelp(exitCode) {
|
|
54
|
+
console.log(`Usage:
|
|
55
|
+
agent-companion
|
|
56
|
+
agent-companion --relay <url>
|
|
57
|
+
agent-companion --with-local-relay
|
|
58
|
+
agent-companion computer
|
|
59
|
+
agent-companion computer --relay <url>
|
|
60
|
+
agent-companion computer --with-local-relay
|
|
61
|
+
agent-companion relay
|
|
62
|
+
agent-companion bridge
|
|
63
|
+
agent-companion background --workspace <path> -- <command>
|
|
64
|
+
agent-companion wake-proxy
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
agent-companion
|
|
68
|
+
agent-companion --relay https://agent-companion-relay.onrender.com
|
|
69
|
+
agent-companion --with-local-relay
|
|
70
|
+
agent-companion computer
|
|
71
|
+
agent-companion computer --relay https://agent-companion-relay.onrender.com
|
|
72
|
+
agent-companion computer --with-local-relay
|
|
73
|
+
agent-companion background --workspace ~/Desktop/test --port 5173 -- npm run dev
|
|
74
|
+
agent-companion wake-proxy
|
|
75
|
+
`);
|
|
76
|
+
process.exit(exitCode);
|
|
77
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
5
|
+
|
|
6
|
+
const runners = [
|
|
7
|
+
{
|
|
8
|
+
name: "bridge",
|
|
9
|
+
cmd: npmCmd,
|
|
10
|
+
args: ["run", "bridge"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "web",
|
|
14
|
+
cmd: npmCmd,
|
|
15
|
+
args: ["run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const children = runners.map((runner) => {
|
|
20
|
+
const child = spawn(runner.cmd, runner.args, {
|
|
21
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
22
|
+
shell: false,
|
|
23
|
+
env: process.env
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
child.stdout.on("data", (chunk) => {
|
|
27
|
+
process.stdout.write(`[${runner.name}] ${chunk}`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
child.stderr.on("data", (chunk) => {
|
|
31
|
+
process.stderr.write(`[${runner.name}] ${chunk}`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
child.on("close", (code) => {
|
|
35
|
+
if (code === 0) return;
|
|
36
|
+
process.stderr.write(`[${runner.name}] exited with code ${code}\n`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return child;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let shuttingDown = false;
|
|
43
|
+
|
|
44
|
+
function shutdown(signal) {
|
|
45
|
+
if (shuttingDown) return;
|
|
46
|
+
shuttingDown = true;
|
|
47
|
+
process.stderr.write(`\n[stack] received ${signal}, shutting down...\n`);
|
|
48
|
+
|
|
49
|
+
for (const child of children) {
|
|
50
|
+
if (!child.killed) {
|
|
51
|
+
child.kill("SIGTERM");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
for (const child of children) {
|
|
57
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
58
|
+
}
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}, 1200);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
64
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|