@companyhelm/cli 0.0.8 → 0.0.9
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/RUNTIME_IMAGE_VERSION +1 -1
- package/dist/cli.js +5 -0
- package/dist/commands/global-options.js +20 -0
- package/dist/commands/root.js +5 -2
- package/dist/commands/runner/common.js +4 -2
- package/dist/commands/runner/stop.js +1 -1
- package/dist/commands/sdk/codex/auth.js +200 -0
- package/dist/commands/sdk/codex/register-codex-sdk-commands.js +12 -0
- package/dist/commands/sdk/codex/use-dedicated-auth.js +18 -0
- package/dist/commands/sdk/codex/use-host-auth.js +19 -0
- package/dist/commands/sdk/register-sdk-commands.js +2 -0
- package/dist/commands/startup.js +11 -125
- package/dist/commands/status.js +1 -1
- package/dist/config.js +24 -5
- package/package.json +1 -1
package/RUNTIME_IMAGE_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.9
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const commander_1 = require("commander");
|
|
7
|
+
const global_options_js_1 = require("./commands/global-options.js");
|
|
7
8
|
const register_commands_js_1 = require("./commands/register-commands.js");
|
|
8
9
|
function getVersion() {
|
|
9
10
|
try {
|
|
@@ -20,7 +21,11 @@ program
|
|
|
20
21
|
.name("companyhelm")
|
|
21
22
|
.description("Run coding agents in fully isolated Docker sandboxes, locally.")
|
|
22
23
|
.version(getVersion());
|
|
24
|
+
(0, global_options_js_1.addGlobalOptions)(program);
|
|
23
25
|
(0, register_commands_js_1.registerCommands)(program);
|
|
26
|
+
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
27
|
+
(0, global_options_js_1.applyGlobalOptionEnvironment)(actionCommand);
|
|
28
|
+
});
|
|
24
29
|
function formatCliError(error) {
|
|
25
30
|
if (error instanceof commander_1.CommanderError) {
|
|
26
31
|
return {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_PATH_OPTION_DESCRIPTION = void 0;
|
|
4
|
+
exports.addGlobalOptions = addGlobalOptions;
|
|
5
|
+
exports.applyGlobalOptionEnvironment = applyGlobalOptionEnvironment;
|
|
6
|
+
const config_js_1 = require("../config.js");
|
|
7
|
+
exports.CONFIG_PATH_OPTION_DESCRIPTION = `Config directory override (defaults to $${config_js_1.CONFIG_PATH_ENV} or ${config_js_1.DEFAULT_CONFIG_DIRECTORY}).`;
|
|
8
|
+
function addGlobalOptions(program) {
|
|
9
|
+
return program.option("--config-path <path>", exports.CONFIG_PATH_OPTION_DESCRIPTION);
|
|
10
|
+
}
|
|
11
|
+
function applyGlobalOptionEnvironment(command) {
|
|
12
|
+
const options = command.optsWithGlobals();
|
|
13
|
+
if (typeof options.configPath !== "string") {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const configPath = options.configPath.trim();
|
|
17
|
+
if (configPath.length > 0) {
|
|
18
|
+
process.env[config_js_1.CONFIG_PATH_ENV] = configPath;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/commands/root.js
CHANGED
|
@@ -73,6 +73,7 @@ const db_js_1 = require("../state/db.js");
|
|
|
73
73
|
const schema_js_1 = require("../state/schema.js");
|
|
74
74
|
const daemon_js_1 = require("../utils/daemon.js");
|
|
75
75
|
const logger_js_1 = require("../utils/logger.js");
|
|
76
|
+
const path_js_1 = require("../utils/path.js");
|
|
76
77
|
const workspace_agents_js_1 = require("../service/workspace_agents.js");
|
|
77
78
|
const COMMAND_CHANNEL_CONNECT_RETRY_DELAY_MS = 1000;
|
|
78
79
|
const COMMAND_CHANNEL_OPEN_TIMEOUT_MS = 5000;
|
|
@@ -2185,13 +2186,15 @@ function isInternalDaemonChildProcess() {
|
|
|
2185
2186
|
function resolveEffectiveDaemonLogPath(cfg) {
|
|
2186
2187
|
const envPath = process.env[daemon_js_1.DAEMON_LOG_PATH_ENV];
|
|
2187
2188
|
if (envPath && envPath.trim().length > 0) {
|
|
2188
|
-
return envPath;
|
|
2189
|
+
return (0, path_js_1.expandHome)(envPath);
|
|
2189
2190
|
}
|
|
2190
2191
|
return (0, daemon_js_1.resolveDaemonLogPath)(cfg.state_db_path);
|
|
2191
2192
|
}
|
|
2192
2193
|
async function runDetachedDaemonProcess(options) {
|
|
2193
2194
|
const cfg = buildRootConfig(options);
|
|
2194
|
-
const logPath =
|
|
2195
|
+
const logPath = options.logPath && options.logPath.trim().length > 0
|
|
2196
|
+
? (0, path_js_1.expandHome)(options.logPath)
|
|
2197
|
+
: (0, daemon_js_1.resolveDaemonLogPath)(cfg.state_db_path);
|
|
2195
2198
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(logPath), { recursive: true });
|
|
2196
2199
|
const logFd = (0, node_fs_1.openSync)(logPath, "a");
|
|
2197
2200
|
try {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.addRunnerStartOptions = addRunnerStartOptions;
|
|
4
|
+
const global_options_js_1 = require("../global-options.js");
|
|
4
5
|
function addRunnerStartOptions(command) {
|
|
5
6
|
return command
|
|
6
|
-
.option("--config-path <path>",
|
|
7
|
+
.option("--config-path <path>", global_options_js_1.CONFIG_PATH_OPTION_DESCRIPTION)
|
|
7
8
|
.option("--server-url <url>", "CompanyHelm gRPC API URL override.")
|
|
8
9
|
.option("--agent-api-url <url>", "Agent gRPC API URL for companyhelm-agent in runtime containers (localhost is rewritten to http://host.docker.internal).")
|
|
9
10
|
.option("--secret <secret>", "Bearer secret used as gRPC Authorization header.")
|
|
10
|
-
.option("--state-db-path <path>", "State database path override (defaults to
|
|
11
|
+
.option("--state-db-path <path>", "State database path override (defaults to state.db under the active config directory).")
|
|
12
|
+
.option("--log-path <path>", "Daemon log file override.")
|
|
11
13
|
.option("--use-host-docker-runtime", "Mount host Docker socket into runtime containers instead of creating DinD sidecars.")
|
|
12
14
|
.option("--host-docker-path <path>", "Host Docker endpoint when --use-host-docker-runtime is enabled (unix:///<socket-path> or tcp://localhost:<port>).")
|
|
13
15
|
.option("--thread-git-skills-directory <path>", "Container path where thread git skill repositories are cloned before linking into ~/.codex/skills.")
|
|
@@ -48,7 +48,7 @@ function registerRunnerStopCommand(runnerCommand) {
|
|
|
48
48
|
runnerCommand
|
|
49
49
|
.command("stop")
|
|
50
50
|
.description("Stop the local CompanyHelm runner daemon.")
|
|
51
|
-
.option("--state-db-path <path>", "State database path override (defaults to
|
|
51
|
+
.option("--state-db-path <path>", "State database path override (defaults to state.db under the active config directory).")
|
|
52
52
|
.action(async (options) => {
|
|
53
53
|
await runRunnerStopCommand(options);
|
|
54
54
|
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultUseDedicatedCodexAuthDependencies = exports.defaultSetCodexHostAuthDependencies = void 0;
|
|
4
|
+
exports.isErrnoException = isErrnoException;
|
|
5
|
+
exports.buildDockerMissingError = buildDockerMissingError;
|
|
6
|
+
exports.ensureDockerAvailable = ensureDockerAvailable;
|
|
7
|
+
exports.listCodexStartupAuthOptions = listCodexStartupAuthOptions;
|
|
8
|
+
exports.setCodexHostAuthInDb = setCodexHostAuthInDb;
|
|
9
|
+
exports.setCodexDedicatedAuthInDb = setCodexDedicatedAuthInDb;
|
|
10
|
+
exports.runSetCodexHostAuth = runSetCodexHostAuth;
|
|
11
|
+
exports.runDedicatedCodexAuth = runDedicatedCodexAuth;
|
|
12
|
+
exports.runUseDedicatedCodexAuth = runUseDedicatedCodexAuth;
|
|
13
|
+
const node_child_process_1 = require("node:child_process");
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
17
|
+
const host_js_1 = require("../../../service/host.js");
|
|
18
|
+
const db_js_1 = require("../../../state/db.js");
|
|
19
|
+
const schema_js_1 = require("../../../state/schema.js");
|
|
20
|
+
const path_js_1 = require("../../../utils/path.js");
|
|
21
|
+
exports.defaultSetCodexHostAuthDependencies = {
|
|
22
|
+
getHostInfoFn: host_js_1.getHostInfo,
|
|
23
|
+
initDbFn: db_js_1.initDb,
|
|
24
|
+
};
|
|
25
|
+
exports.defaultUseDedicatedCodexAuthDependencies = {
|
|
26
|
+
initDbFn: db_js_1.initDb,
|
|
27
|
+
logInfo: console.log,
|
|
28
|
+
logSuccess: console.log,
|
|
29
|
+
spawnCommand: node_child_process_1.spawn,
|
|
30
|
+
spawnSyncCommand: node_child_process_1.spawnSync,
|
|
31
|
+
};
|
|
32
|
+
function isErrnoException(error) {
|
|
33
|
+
return error instanceof Error && "code" in error;
|
|
34
|
+
}
|
|
35
|
+
function buildDockerMissingError() {
|
|
36
|
+
return new Error("Docker is not installed or not available on PATH. Install Docker and retry.");
|
|
37
|
+
}
|
|
38
|
+
function ensureDockerAvailable(spawnSyncCommand) {
|
|
39
|
+
const result = spawnSyncCommand("docker", ["--version"], { stdio: "ignore" });
|
|
40
|
+
if (isErrnoException(result.error) && result.error.code === "ENOENT") {
|
|
41
|
+
throw buildDockerMissingError();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function listCodexStartupAuthOptions(cfg, getHostInfoFn = host_js_1.getHostInfo) {
|
|
45
|
+
const options = [
|
|
46
|
+
{
|
|
47
|
+
value: "dedicated",
|
|
48
|
+
label: "Dedicated",
|
|
49
|
+
hint: "recommended -- runs Codex login inside a container",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const hostInfo = getHostInfoFn(cfg.codex.codex_auth_path);
|
|
53
|
+
if (hostInfo.codexAuthExists) {
|
|
54
|
+
options.push({
|
|
55
|
+
value: "host",
|
|
56
|
+
label: "Host",
|
|
57
|
+
hint: `reuse existing credentials from ${cfg.codex.codex_auth_path}`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return options;
|
|
61
|
+
}
|
|
62
|
+
async function setCodexHostAuthInDb(db) {
|
|
63
|
+
const existingSdk = await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex")).get();
|
|
64
|
+
if (existingSdk) {
|
|
65
|
+
await db
|
|
66
|
+
.update(schema_js_1.agentSdks)
|
|
67
|
+
.set({ authentication: "host" })
|
|
68
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex"));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await db.insert(schema_js_1.agentSdks).values({ name: "codex", authentication: "host" });
|
|
72
|
+
}
|
|
73
|
+
async function setCodexDedicatedAuthInDb(db) {
|
|
74
|
+
const existingSdk = await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex")).get();
|
|
75
|
+
if (existingSdk) {
|
|
76
|
+
await db
|
|
77
|
+
.update(schema_js_1.agentSdks)
|
|
78
|
+
.set({ authentication: "dedicated" })
|
|
79
|
+
.where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex"));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
await db.insert(schema_js_1.agentSdks).values({ name: "codex", authentication: "dedicated" });
|
|
83
|
+
}
|
|
84
|
+
async function runSetCodexHostAuth(cfg, overrides = {}) {
|
|
85
|
+
const deps = { ...exports.defaultSetCodexHostAuthDependencies, ...overrides };
|
|
86
|
+
const authPath = (0, path_js_1.expandHome)(cfg.codex.codex_auth_path);
|
|
87
|
+
const hostInfo = deps.getHostInfoFn(cfg.codex.codex_auth_path);
|
|
88
|
+
if (!hostInfo.codexAuthExists) {
|
|
89
|
+
throw new Error(`Codex host auth file not found at ${authPath}.`);
|
|
90
|
+
}
|
|
91
|
+
const { db, client } = await deps.initDbFn(cfg.state_db_path);
|
|
92
|
+
try {
|
|
93
|
+
await setCodexHostAuthInDb(db);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
client.close();
|
|
97
|
+
}
|
|
98
|
+
return authPath;
|
|
99
|
+
}
|
|
100
|
+
async function runDedicatedCodexAuth(cfg, db, deps) {
|
|
101
|
+
ensureDockerAvailable(deps.spawnSyncCommand);
|
|
102
|
+
const port = cfg.codex.codex_auth_port;
|
|
103
|
+
const socatPort = port + 1;
|
|
104
|
+
const containerName = `companyhelm-codex-auth-${Date.now()}`;
|
|
105
|
+
deps.logInfo("Starting Codex login inside a container...");
|
|
106
|
+
deps.logInfo("A browser URL will appear -- open it to complete authentication.");
|
|
107
|
+
const configDir = (0, path_js_1.expandHome)(cfg.config_directory);
|
|
108
|
+
if (!(0, node_fs_1.existsSync)(configDir)) {
|
|
109
|
+
(0, node_fs_1.mkdirSync)(configDir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
const destPath = (0, node_path_1.join)(configDir, cfg.codex.codex_auth_file_path);
|
|
112
|
+
let authCopied = false;
|
|
113
|
+
await new Promise((resolve, reject) => {
|
|
114
|
+
let settled = false;
|
|
115
|
+
let poll;
|
|
116
|
+
const rejectOnce = (error) => {
|
|
117
|
+
if (settled) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
settled = true;
|
|
121
|
+
if (poll) {
|
|
122
|
+
clearInterval(poll);
|
|
123
|
+
}
|
|
124
|
+
deps.spawnSyncCommand("docker", ["rm", "-f", containerName], { stdio: "ignore" });
|
|
125
|
+
reject(error);
|
|
126
|
+
};
|
|
127
|
+
const resolveOnce = () => {
|
|
128
|
+
if (settled) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
settled = true;
|
|
132
|
+
if (poll) {
|
|
133
|
+
clearInterval(poll);
|
|
134
|
+
}
|
|
135
|
+
resolve();
|
|
136
|
+
};
|
|
137
|
+
const child = deps.spawnCommand("docker", [
|
|
138
|
+
"run",
|
|
139
|
+
"-it",
|
|
140
|
+
"--name",
|
|
141
|
+
containerName,
|
|
142
|
+
"-p",
|
|
143
|
+
`${port}:${socatPort}`,
|
|
144
|
+
"--entrypoint",
|
|
145
|
+
"bash",
|
|
146
|
+
cfg.runtime_image,
|
|
147
|
+
"-c",
|
|
148
|
+
`source "$NVM_DIR/nvm.sh"; socat TCP-LISTEN:${socatPort},fork,bind=0.0.0.0,reuseaddr TCP:127.0.0.1:${port} 2>/dev/null & codex`,
|
|
149
|
+
], { stdio: "inherit" });
|
|
150
|
+
child.on("error", (error) => {
|
|
151
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
152
|
+
rejectOnce(buildDockerMissingError());
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
rejectOnce(new Error(`Failed to start Codex login container: ${error.message}`));
|
|
156
|
+
});
|
|
157
|
+
poll = setInterval(() => {
|
|
158
|
+
if (settled) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const check = deps.spawnSyncCommand("docker", ["exec", containerName, "sh", "-c", `test -f ${cfg.codex.codex_auth_path}`], {
|
|
162
|
+
stdio: "ignore",
|
|
163
|
+
});
|
|
164
|
+
if (check.status === 0) {
|
|
165
|
+
const resolveResult = deps.spawnSyncCommand("docker", ["exec", containerName, "sh", "-c", `echo ${cfg.codex.codex_auth_path}`], {
|
|
166
|
+
encoding: "utf-8",
|
|
167
|
+
});
|
|
168
|
+
const containerAuthAbsPath = resolveResult.stdout.trim();
|
|
169
|
+
const cpResult = deps.spawnSyncCommand("docker", ["cp", `${containerName}:${containerAuthAbsPath}`, destPath], {
|
|
170
|
+
stdio: "ignore",
|
|
171
|
+
});
|
|
172
|
+
if (cpResult.status !== 0) {
|
|
173
|
+
rejectOnce(new Error("Failed to extract auth file from container."));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
authCopied = true;
|
|
177
|
+
deps.spawnSyncCommand("docker", ["rm", "-f", containerName], { stdio: "ignore" });
|
|
178
|
+
resolveOnce();
|
|
179
|
+
}
|
|
180
|
+
}, 1000);
|
|
181
|
+
child.on("exit", () => {
|
|
182
|
+
if (!authCopied) {
|
|
183
|
+
rejectOnce(new Error("Codex login failed or was cancelled."));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
await setCodexDedicatedAuthInDb(db);
|
|
188
|
+
deps.logSuccess(`Codex auth saved to ${destPath}`);
|
|
189
|
+
return destPath;
|
|
190
|
+
}
|
|
191
|
+
async function runUseDedicatedCodexAuth(cfg, overrides = {}) {
|
|
192
|
+
const deps = { ...exports.defaultUseDedicatedCodexAuthDependencies, ...overrides };
|
|
193
|
+
const { db, client } = await deps.initDbFn(cfg.state_db_path);
|
|
194
|
+
try {
|
|
195
|
+
return await runDedicatedCodexAuth(cfg, db, deps);
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
client.close();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCodexSdkCommands = registerCodexSdkCommands;
|
|
4
|
+
const use_dedicated_auth_js_1 = require("./use-dedicated-auth.js");
|
|
5
|
+
const use_host_auth_js_1 = require("./use-host-auth.js");
|
|
6
|
+
function registerCodexSdkCommands(sdkCommand) {
|
|
7
|
+
const codexCommand = sdkCommand
|
|
8
|
+
.command("codex")
|
|
9
|
+
.description("Manage Codex SDK authentication.");
|
|
10
|
+
(0, use_host_auth_js_1.registerSdkCodexUseHostAuthCommand)(codexCommand);
|
|
11
|
+
(0, use_dedicated_auth_js_1.registerSdkCodexUseDedicatedAuthCommand)(codexCommand);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSdkCodexUseDedicatedAuthCommand = runSdkCodexUseDedicatedAuthCommand;
|
|
4
|
+
exports.registerSdkCodexUseDedicatedAuthCommand = registerSdkCodexUseDedicatedAuthCommand;
|
|
5
|
+
const config_js_1 = require("../../../config.js");
|
|
6
|
+
const auth_js_1 = require("./auth.js");
|
|
7
|
+
async function runSdkCodexUseDedicatedAuthCommand(cfg = config_js_1.config.parse({}), overrides = {}) {
|
|
8
|
+
const deps = { ...auth_js_1.defaultUseDedicatedCodexAuthDependencies, ...overrides };
|
|
9
|
+
await (0, auth_js_1.runUseDedicatedCodexAuth)(cfg, deps);
|
|
10
|
+
}
|
|
11
|
+
function registerSdkCodexUseDedicatedAuthCommand(codexCommand) {
|
|
12
|
+
codexCommand
|
|
13
|
+
.command("use-dedicated-auth")
|
|
14
|
+
.description("Configure the Codex SDK to use dedicated authentication.")
|
|
15
|
+
.action(async () => {
|
|
16
|
+
await runSdkCodexUseDedicatedAuthCommand();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSdkCodexUseHostAuthCommand = runSdkCodexUseHostAuthCommand;
|
|
4
|
+
exports.registerSdkCodexUseHostAuthCommand = registerSdkCodexUseHostAuthCommand;
|
|
5
|
+
const config_js_1 = require("../../../config.js");
|
|
6
|
+
const auth_js_1 = require("./auth.js");
|
|
7
|
+
async function runSdkCodexUseHostAuthCommand(cfg = config_js_1.config.parse({}), overrides = {}) {
|
|
8
|
+
const deps = { ...auth_js_1.defaultSetCodexHostAuthDependencies, ...overrides };
|
|
9
|
+
const authPath = await (0, auth_js_1.runSetCodexHostAuth)(cfg, deps);
|
|
10
|
+
console.log(`Codex SDK configured with host authentication using ${authPath}.`);
|
|
11
|
+
}
|
|
12
|
+
function registerSdkCodexUseHostAuthCommand(codexCommand) {
|
|
13
|
+
codexCommand
|
|
14
|
+
.command("use-host-auth")
|
|
15
|
+
.description("Configure the Codex SDK to use host authentication.")
|
|
16
|
+
.action(async () => {
|
|
17
|
+
await runSdkCodexUseHostAuthCommand();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerSdkCommands = registerSdkCommands;
|
|
4
|
+
const register_codex_sdk_commands_js_1 = require("./codex/register-codex-sdk-commands.js");
|
|
4
5
|
const list_js_1 = require("./list.js");
|
|
5
6
|
const refresh_models_js_1 = require("./refresh-models.js");
|
|
6
7
|
function registerSdkCommands(program) {
|
|
@@ -9,4 +10,5 @@ function registerSdkCommands(program) {
|
|
|
9
10
|
.description("Manage configured SDKs and their model capabilities.");
|
|
10
11
|
(0, list_js_1.registerSdkListCommand)(sdkCommand);
|
|
11
12
|
(0, refresh_models_js_1.registerSdkRefreshModelsCommand)(sdkCommand);
|
|
13
|
+
(0, register_codex_sdk_commands_js_1.registerCodexSdkCommands)(sdkCommand);
|
|
12
14
|
}
|
package/dist/commands/startup.js
CHANGED
|
@@ -37,42 +37,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.startup = startup;
|
|
40
|
-
const node_fs_1 = require("node:fs");
|
|
41
|
-
const node_path_1 = require("node:path");
|
|
42
40
|
const node_child_process_1 = require("node:child_process");
|
|
43
41
|
const p = __importStar(require("@clack/prompts"));
|
|
44
42
|
const figlet_1 = __importDefault(require("figlet"));
|
|
45
43
|
const config_js_1 = require("../config.js");
|
|
46
44
|
const db_js_1 = require("../state/db.js");
|
|
47
45
|
const schema_js_1 = require("../state/schema.js");
|
|
48
|
-
const host_js_1 = require("../service/host.js");
|
|
49
46
|
const refresh_models_js_1 = require("../service/sdk/refresh_models.js");
|
|
50
|
-
const
|
|
47
|
+
const auth_js_1 = require("./sdk/codex/auth.js");
|
|
51
48
|
function banner() {
|
|
52
49
|
console.log();
|
|
53
50
|
console.log(figlet_1.default.textSync("CompanyHelm", { font: "Small" }));
|
|
54
51
|
console.log();
|
|
55
52
|
}
|
|
56
|
-
function isErrnoException(error) {
|
|
57
|
-
return error instanceof Error && "code" in error;
|
|
58
|
-
}
|
|
59
|
-
function buildDockerMissingError() {
|
|
60
|
-
return new Error("Docker is not installed or not available on PATH. Install Docker and retry.");
|
|
61
|
-
}
|
|
62
53
|
const defaultStartupDependencies = {
|
|
63
|
-
getHostInfoFn:
|
|
54
|
+
getHostInfoFn: auth_js_1.defaultSetCodexHostAuthDependencies.getHostInfoFn,
|
|
64
55
|
initDbFn: db_js_1.initDb,
|
|
65
56
|
promptApi: p,
|
|
66
57
|
refreshSdkModelsFn: refresh_models_js_1.refreshSdkModels,
|
|
67
58
|
spawnCommand: node_child_process_1.spawn,
|
|
68
59
|
spawnSyncCommand: node_child_process_1.spawnSync,
|
|
69
60
|
};
|
|
70
|
-
function ensureDockerAvailable(spawnSyncCommand) {
|
|
71
|
-
const result = spawnSyncCommand("docker", ["--version"], { stdio: "ignore" });
|
|
72
|
-
if (isErrnoException(result.error) && result.error.code === "ENOENT") {
|
|
73
|
-
throw buildDockerMissingError();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
61
|
function restoreInteractiveTerminalState() {
|
|
77
62
|
try {
|
|
78
63
|
if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
|
|
@@ -96,7 +81,7 @@ function exitStartup(code) {
|
|
|
96
81
|
process.exit(code);
|
|
97
82
|
}
|
|
98
83
|
async function refreshCodexModelsInStartup(deps) {
|
|
99
|
-
ensureDockerAvailable(deps.spawnSyncCommand);
|
|
84
|
+
(0, auth_js_1.ensureDockerAvailable)(deps.spawnSyncCommand);
|
|
100
85
|
const spinner = deps.promptApi.spinner();
|
|
101
86
|
const seenStatusMessages = new Set();
|
|
102
87
|
spinner.start("Preparing Codex runtime image and refreshing model catalog via app-server");
|
|
@@ -113,96 +98,6 @@ async function refreshCodexModelsInStartup(deps) {
|
|
|
113
98
|
const count = results[0]?.modelCount ?? 0;
|
|
114
99
|
spinner.stop(`Codex model catalog refreshed (${count} models).`);
|
|
115
100
|
}
|
|
116
|
-
async function dedicatedAuth(cfg, db, deps) {
|
|
117
|
-
ensureDockerAvailable(deps.spawnSyncCommand);
|
|
118
|
-
const port = cfg.codex.codex_auth_port;
|
|
119
|
-
const socatPort = port + 1;
|
|
120
|
-
const containerName = `companyhelm-codex-auth-${Date.now()}`;
|
|
121
|
-
deps.promptApi.log.info("Starting Codex login inside a container...");
|
|
122
|
-
deps.promptApi.log.info("A browser URL will appear -- open it to complete authentication.");
|
|
123
|
-
const configDir = (0, path_js_1.expandHome)(cfg.config_directory);
|
|
124
|
-
if (!(0, node_fs_1.existsSync)(configDir)) {
|
|
125
|
-
(0, node_fs_1.mkdirSync)(configDir, { recursive: true });
|
|
126
|
-
}
|
|
127
|
-
const destPath = (0, node_path_1.join)(configDir, cfg.codex.codex_auth_file_path);
|
|
128
|
-
let authCopied = false;
|
|
129
|
-
await new Promise((resolve, reject) => {
|
|
130
|
-
let settled = false;
|
|
131
|
-
let poll;
|
|
132
|
-
const rejectOnce = (error) => {
|
|
133
|
-
if (settled) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
settled = true;
|
|
137
|
-
if (poll) {
|
|
138
|
-
clearInterval(poll);
|
|
139
|
-
}
|
|
140
|
-
deps.spawnSyncCommand("docker", ["rm", "-f", containerName], { stdio: "ignore" });
|
|
141
|
-
reject(error);
|
|
142
|
-
};
|
|
143
|
-
const resolveOnce = () => {
|
|
144
|
-
if (settled) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
settled = true;
|
|
148
|
-
if (poll) {
|
|
149
|
-
clearInterval(poll);
|
|
150
|
-
}
|
|
151
|
-
resolve();
|
|
152
|
-
};
|
|
153
|
-
const child = deps.spawnCommand("docker", [
|
|
154
|
-
"run",
|
|
155
|
-
"-it",
|
|
156
|
-
"--name",
|
|
157
|
-
containerName,
|
|
158
|
-
"-p",
|
|
159
|
-
`${port}:${socatPort}`,
|
|
160
|
-
"--entrypoint",
|
|
161
|
-
"bash",
|
|
162
|
-
cfg.runtime_image,
|
|
163
|
-
"-c",
|
|
164
|
-
`source "$NVM_DIR/nvm.sh"; socat TCP-LISTEN:${socatPort},fork,bind=0.0.0.0,reuseaddr TCP:127.0.0.1:${port} 2>/dev/null & codex`,
|
|
165
|
-
], { stdio: "inherit" });
|
|
166
|
-
child.on("error", (error) => {
|
|
167
|
-
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
168
|
-
rejectOnce(buildDockerMissingError());
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
rejectOnce(new Error(`Failed to start Codex login container: ${error.message}`));
|
|
172
|
-
});
|
|
173
|
-
poll = setInterval(() => {
|
|
174
|
-
if (settled) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const check = deps.spawnSyncCommand("docker", ["exec", containerName, "sh", "-c", `test -f ${cfg.codex.codex_auth_path}`], {
|
|
178
|
-
stdio: "ignore",
|
|
179
|
-
});
|
|
180
|
-
if (check.status === 0) {
|
|
181
|
-
const resolveResult = deps.spawnSyncCommand("docker", ["exec", containerName, "sh", "-c", `echo ${cfg.codex.codex_auth_path}`], {
|
|
182
|
-
encoding: "utf-8",
|
|
183
|
-
});
|
|
184
|
-
const containerAuthAbsPath = resolveResult.stdout.trim();
|
|
185
|
-
const cpResult = deps.spawnSyncCommand("docker", ["cp", `${containerName}:${containerAuthAbsPath}`, destPath], {
|
|
186
|
-
stdio: "ignore",
|
|
187
|
-
});
|
|
188
|
-
if (cpResult.status !== 0) {
|
|
189
|
-
rejectOnce(new Error("Failed to extract auth file from container."));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
authCopied = true;
|
|
193
|
-
deps.spawnSyncCommand("docker", ["rm", "-f", containerName], { stdio: "ignore" });
|
|
194
|
-
resolveOnce();
|
|
195
|
-
}
|
|
196
|
-
}, 1000);
|
|
197
|
-
child.on("exit", () => {
|
|
198
|
-
if (!authCopied) {
|
|
199
|
-
rejectOnce(new Error("Codex login failed or was cancelled."));
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
await db.insert(schema_js_1.agentSdks).values({ name: "codex", authentication: "dedicated" });
|
|
204
|
-
deps.promptApi.log.success(`Codex auth saved to ${destPath}`);
|
|
205
|
-
}
|
|
206
101
|
async function selectStartupAuthMode(options, deps) {
|
|
207
102
|
if (options.length === 1) {
|
|
208
103
|
return options[0].value;
|
|
@@ -230,30 +125,21 @@ async function startup(cfg = config_js_1.config.parse({}), overrides = {}) {
|
|
|
230
125
|
return;
|
|
231
126
|
}
|
|
232
127
|
deps.promptApi.intro("No agent SDK configured. Let's set up Codex authentication.");
|
|
233
|
-
const
|
|
234
|
-
const options = [
|
|
235
|
-
{
|
|
236
|
-
value: "dedicated",
|
|
237
|
-
label: "Dedicated",
|
|
238
|
-
hint: "recommended -- runs Codex login inside a container",
|
|
239
|
-
},
|
|
240
|
-
];
|
|
241
|
-
if (hostInfo.codexAuthExists) {
|
|
242
|
-
options.push({
|
|
243
|
-
value: "host",
|
|
244
|
-
label: "Host",
|
|
245
|
-
hint: `reuse existing credentials from ${cfg.codex.codex_auth_path}`,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
128
|
+
const options = (0, auth_js_1.listCodexStartupAuthOptions)(cfg, deps.getHostInfoFn);
|
|
248
129
|
try {
|
|
249
130
|
const authMode = await selectStartupAuthMode(options, deps);
|
|
250
131
|
if (authMode === "host") {
|
|
251
|
-
await
|
|
132
|
+
await (0, auth_js_1.setCodexHostAuthInDb)(db);
|
|
252
133
|
await refreshCodexModelsInStartup(deps);
|
|
253
134
|
deps.promptApi.outro("Codex SDK configured with host authentication.");
|
|
254
135
|
return;
|
|
255
136
|
}
|
|
256
|
-
await
|
|
137
|
+
await (0, auth_js_1.runDedicatedCodexAuth)(cfg, db, {
|
|
138
|
+
logInfo: deps.promptApi.log.info,
|
|
139
|
+
logSuccess: deps.promptApi.log.success,
|
|
140
|
+
spawnCommand: deps.spawnCommand,
|
|
141
|
+
spawnSyncCommand: deps.spawnSyncCommand,
|
|
142
|
+
});
|
|
257
143
|
await refreshCodexModelsInStartup(deps);
|
|
258
144
|
deps.promptApi.outro("Codex login successful!");
|
|
259
145
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -10,7 +10,7 @@ function registerStatusCommand(program) {
|
|
|
10
10
|
program
|
|
11
11
|
.command("status")
|
|
12
12
|
.description("Show whether the local CompanyHelm daemon is running.")
|
|
13
|
-
.option("--state-db-path <path>", "State database path override (defaults to
|
|
13
|
+
.option("--state-db-path <path>", "State database path override (defaults to state.db under the active config directory).")
|
|
14
14
|
.action(async (options) => {
|
|
15
15
|
const cfg = config_js_1.config.parse({
|
|
16
16
|
state_db_path: options.stateDbPath,
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.config = exports.codexConfig = void 0;
|
|
3
|
+
exports.config = exports.codexConfig = exports.DEFAULT_CONFIG_DIRECTORY = exports.CONFIG_PATH_ENV = void 0;
|
|
4
4
|
const node_fs_1 = require("node:fs");
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const zod_1 = require("zod");
|
|
7
|
+
const path_js_1 = require("./utils/path.js");
|
|
8
|
+
exports.CONFIG_PATH_ENV = "COMPANYHELM_CONFIG_PATH";
|
|
9
|
+
exports.DEFAULT_CONFIG_DIRECTORY = "~/.config/companyhelm";
|
|
7
10
|
const DEFAULT_RUNTIME_IMAGE_REPOSITORY = "companyhelm/runner";
|
|
8
11
|
const FALLBACK_RUNTIME_IMAGE_VERSION = "latest";
|
|
9
12
|
function loadRuntimeImageVersion() {
|
|
@@ -19,6 +22,19 @@ function loadRuntimeImageVersion() {
|
|
|
19
22
|
return FALLBACK_RUNTIME_IMAGE_VERSION;
|
|
20
23
|
}
|
|
21
24
|
const DEFAULT_RUNTIME_IMAGE = `${DEFAULT_RUNTIME_IMAGE_REPOSITORY}:${loadRuntimeImageVersion()}`;
|
|
25
|
+
function resolveConfigRelativePath(configDirectory, pathValue) {
|
|
26
|
+
if ((0, node_path_1.isAbsolute)((0, path_js_1.expandHome)(pathValue))) {
|
|
27
|
+
return pathValue;
|
|
28
|
+
}
|
|
29
|
+
return (0, node_path_1.join)(configDirectory, pathValue);
|
|
30
|
+
}
|
|
31
|
+
function resolveConfigDirectoryDefault() {
|
|
32
|
+
const envValue = process.env[exports.CONFIG_PATH_ENV]?.trim();
|
|
33
|
+
if (envValue && envValue.length > 0) {
|
|
34
|
+
return envValue;
|
|
35
|
+
}
|
|
36
|
+
return exports.DEFAULT_CONFIG_DIRECTORY;
|
|
37
|
+
}
|
|
22
38
|
exports.codexConfig = zod_1.z.object({
|
|
23
39
|
codex_auth_file_path: zod_1.z.string()
|
|
24
40
|
.describe("The path to the Codex authentication file on the host, relative to config_directory.")
|
|
@@ -36,13 +52,13 @@ exports.codexConfig = zod_1.z.object({
|
|
|
36
52
|
exports.config = zod_1.z.object({
|
|
37
53
|
config_directory: zod_1.z.string()
|
|
38
54
|
.describe("The directory where the config files are stored.")
|
|
39
|
-
.default(
|
|
55
|
+
.default(resolveConfigDirectoryDefault),
|
|
40
56
|
workspaces_directory: zod_1.z.string()
|
|
41
57
|
.describe("The directory where thread workspaces are stored, relative to config_directory when not absolute.")
|
|
42
58
|
.default("workspaces"),
|
|
43
59
|
state_db_path: zod_1.z.string()
|
|
44
|
-
.describe("The path to the state database.")
|
|
45
|
-
.default("
|
|
60
|
+
.describe("The path to the state database, relative to the config directory.")
|
|
61
|
+
.default("state.db"),
|
|
46
62
|
companyhelm_api_url: zod_1.z.string()
|
|
47
63
|
.describe("CompanyHelm control plane gRPC endpoint URL.")
|
|
48
64
|
.default("https://api.companyhelm.com:50051"),
|
|
@@ -83,4 +99,7 @@ exports.config = zod_1.z.object({
|
|
|
83
99
|
.describe("Default git author email used when runtime repositories are missing user.email.")
|
|
84
100
|
.default("agent@companyhelm.com"),
|
|
85
101
|
codex: exports.codexConfig.default(() => exports.codexConfig.parse({})),
|
|
86
|
-
})
|
|
102
|
+
}).transform((value) => ({
|
|
103
|
+
...value,
|
|
104
|
+
state_db_path: resolveConfigRelativePath(value.config_directory, value.state_db_path),
|
|
105
|
+
}));
|