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.
@@ -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
+ }
@@ -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"));