@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.
@@ -1 +1 @@
1
- 0.0.8
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
+ }
@@ -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 = (0, daemon_js_1.resolveDaemonLogPath)(cfg.state_db_path);
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>", "Config directory override (defaults to ~/.config/companyhelm).")
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 ~/.local/share/companyhelm/state.db).")
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 ~/.local/share/companyhelm/state.db).")
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
  }
@@ -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 path_js_1 = require("../utils/path.js");
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: host_js_1.getHostInfo,
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 hostInfo = deps.getHostInfoFn(cfg.codex.codex_auth_path);
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 db.insert(schema_js_1.agentSdks).values({ name: "codex", authentication: "host" });
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 dedicatedAuth(cfg, db, deps);
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
  }
@@ -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 ~/.local/share/companyhelm/state.db).")
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("~/.config/companyhelm"),
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("~/.local/share/companyhelm/state.db"),
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
+ }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companyhelm/cli",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Run coding agents in fully isolated Docker sandboxes, locally.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {