@companyhelm/runner 0.0.15 → 0.0.18

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,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerCommands = registerCommands;
4
+ const common_js_1 = require("./runner/common.js");
4
5
  const register_runner_commands_js_1 = require("./runner/register-runner-commands.js");
6
+ const start_js_1 = require("./runner/start.js");
5
7
  const shell_js_1 = require("./shell.js");
6
8
  const register_sdk_commands_js_1 = require("./sdk/register-sdk-commands.js");
7
9
  const status_js_1 = require("./status.js");
8
10
  const register_thread_commands_js_1 = require("./thread/register-thread-commands.js");
9
11
  function registerCommands(program) {
12
+ (0, common_js_1.addRunnerStartOptions)(program
13
+ .command("companyhelm-runner")
14
+ .description("Alias for starting the local CompanyHelm runner.")).action(start_js_1.runRunnerStartCommand);
10
15
  (0, register_runner_commands_js_1.registerRunnerCommands)(program);
11
16
  (0, status_js_1.registerStatusCommand)(program);
12
17
  (0, register_thread_commands_js_1.registerThreadCommands)(program);
@@ -1661,15 +1661,17 @@ async function countSdkModels(cfg, sdkName) {
1661
1661
  client.close();
1662
1662
  }
1663
1663
  }
1664
- async function refreshCodexModelsForRegistration(cfg, logger) {
1664
+ async function loadCodexSdkState(cfg) {
1665
1665
  const { db, client } = await (0, db_js_1.initDb)(cfg.state_db_path);
1666
- let codexSdk;
1667
1666
  try {
1668
- codexSdk = await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex")).get() ?? undefined;
1667
+ return await db.select().from(schema_js_1.agentSdks).where((0, drizzle_orm_1.eq)(schema_js_1.agentSdks.name, "codex")).get() ?? undefined;
1669
1668
  }
1670
1669
  finally {
1671
1670
  client.close();
1672
1671
  }
1672
+ }
1673
+ async function refreshCodexModelsForRegistration(cfg, logger) {
1674
+ const codexSdk = await loadCodexSdkState(cfg);
1673
1675
  if (!codexSdk || codexSdk.status !== "configured" || codexSdk.authentication === "unauthenticated") {
1674
1676
  logger.info("Codex is not configured; registering runner with unconfigured Codex SDK state.");
1675
1677
  return null;
@@ -2004,18 +2006,18 @@ async function deleteThreadWithCleanup(cfg, request) {
2004
2006
  }
2005
2007
  return { kind: "deleted" };
2006
2008
  }
2007
- async function handleDeleteThreadRequest(cfg, commandChannel, request, logger) {
2009
+ async function handleDeleteThreadRequest(cfg, commandChannel, request, requestId, logger) {
2008
2010
  const deleteResult = await deleteThreadWithCleanup(cfg, request);
2009
2011
  if (deleteResult.kind === "not_found") {
2010
2012
  logger.warn(`Delete requested for missing thread '${request.threadId}'. Treating as deleted.`);
2011
- await sendThreadUpdate(commandChannel, request.threadId, protos_1.ThreadStatus.DELETED);
2013
+ await sendThreadUpdate(commandChannel, request.threadId, protos_1.ThreadStatus.DELETED, requestId);
2012
2014
  return;
2013
2015
  }
2014
2016
  if (deleteResult.kind === "error") {
2015
- await sendRequestError(commandChannel, deleteResult.message);
2017
+ await sendRequestError(commandChannel, deleteResult.message, requestId);
2016
2018
  return;
2017
2019
  }
2018
- await sendThreadUpdate(commandChannel, request.threadId, protos_1.ThreadStatus.DELETED);
2020
+ await sendThreadUpdate(commandChannel, request.threadId, protos_1.ThreadStatus.DELETED, requestId);
2019
2021
  }
2020
2022
  async function reportNoRunningInterruptAsReady(cfg, commandChannel, request, threadState, logger, logMessage) {
2021
2023
  try {
@@ -2394,6 +2396,12 @@ async function handleCodexConfigurationRequest(cfg, commandChannel, request, req
2394
2396
  await sendRequestError(commandChannel, "Unsupported Codex auth type.", requestId);
2395
2397
  return;
2396
2398
  }
2399
+ const codexSdk = await loadCodexSdkState(cfg);
2400
+ if (!codexSdk || codexSdk.status !== "configured" || codexSdk.authentication === "unauthenticated") {
2401
+ const sdkUpdate = await buildCodexAgentSdkUpdate(cfg, logger, protos_1.AgentSdkStatus.UNCONFIGURED);
2402
+ await sendAgentSdkUpdate(commandChannel, sdkUpdate, requestId);
2403
+ return;
2404
+ }
2397
2405
  const codexRefreshErrorMessage = await refreshCodexModelsForRegistration(cfg, logger);
2398
2406
  const sdkUpdate = await buildCodexAgentSdkUpdate(cfg, logger, codexRefreshErrorMessage ? protos_1.AgentSdkStatus.ERROR : protos_1.AgentSdkStatus.READY, codexRefreshErrorMessage ?? undefined);
2399
2407
  await sendAgentSdkUpdate(commandChannel, sdkUpdate, requestId);
@@ -2413,7 +2421,7 @@ async function runCommandLoop(cfg, commandChannel, commandMessageSink, apiClient
2413
2421
  await handleCreateThreadRequest(cfg, commandMessageSink, serverMessage.request.value, requestId, apiClient, apiCallOptions, logger);
2414
2422
  break;
2415
2423
  case "deleteThreadRequest":
2416
- await handleDeleteThreadRequest(cfg, commandMessageSink, serverMessage.request.value, logger);
2424
+ await handleDeleteThreadRequest(cfg, commandMessageSink, serverMessage.request.value, requestId, logger);
2417
2425
  break;
2418
2426
  case "createUserMessageRequest":
2419
2427
  void handleCreateUserMessageRequest(cfg, commandMessageSink, serverMessage.request.value, requestId, logger).catch((error) => {
@@ -1,30 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRunnerStartCommand = runRunnerStartCommand;
3
4
  exports.registerRunnerStartCommand = registerRunnerStartCommand;
4
5
  const root_js_1 = require("../root.js");
5
6
  const common_js_1 = require("./common.js");
7
+ async function runRunnerStartCommand(options) {
8
+ if (options.daemon && !(0, root_js_1.isInternalDaemonChildProcess)()) {
9
+ await (0, root_js_1.runDetachedDaemonProcess)(options);
10
+ return;
11
+ }
12
+ try {
13
+ await (0, root_js_1.runRootCommand)(options, (0, root_js_1.isInternalDaemonChildProcess)()
14
+ ? {
15
+ onDaemonReady: () => {
16
+ (0, root_js_1.sendDaemonParentMessage)({ type: "daemon-ready" });
17
+ },
18
+ }
19
+ : undefined);
20
+ }
21
+ catch (error) {
22
+ if ((0, root_js_1.isInternalDaemonChildProcess)()) {
23
+ (0, root_js_1.sendDaemonParentMessage)({ type: "daemon-error", message: (0, root_js_1.toErrorMessage)(error) });
24
+ }
25
+ throw error;
26
+ }
27
+ }
6
28
  function registerRunnerStartCommand(runnerCommand) {
7
29
  (0, common_js_1.addRunnerStartOptions)(runnerCommand
8
30
  .command("start")
9
- .description("Start the local CompanyHelm runner daemon.")).action(async (options) => {
10
- if (options.daemon && !(0, root_js_1.isInternalDaemonChildProcess)()) {
11
- await (0, root_js_1.runDetachedDaemonProcess)(options);
12
- return;
13
- }
14
- try {
15
- await (0, root_js_1.runRootCommand)(options, (0, root_js_1.isInternalDaemonChildProcess)()
16
- ? {
17
- onDaemonReady: () => {
18
- (0, root_js_1.sendDaemonParentMessage)({ type: "daemon-ready" });
19
- },
20
- }
21
- : undefined);
22
- }
23
- catch (error) {
24
- if ((0, root_js_1.isInternalDaemonChildProcess)()) {
25
- (0, root_js_1.sendDaemonParentMessage)({ type: "daemon-error", message: (0, root_js_1.toErrorMessage)(error) });
26
- }
27
- throw error;
28
- }
29
- });
31
+ .description("Start the local CompanyHelm runner daemon.")).action(runRunnerStartCommand);
30
32
  }
@@ -40,12 +40,90 @@ exports.defaultUseDedicatedCodexAuthDependencies = {
40
40
  spawnCommand: node_child_process_1.spawn,
41
41
  spawnSyncCommand: node_child_process_1.spawnSync,
42
42
  };
43
+ function resolveContainerPath(pathValue, containerHome) {
44
+ if (pathValue === "~") {
45
+ return containerHome;
46
+ }
47
+ if (pathValue.startsWith("~/")) {
48
+ return `${containerHome}${pathValue.slice(1)}`;
49
+ }
50
+ return pathValue;
51
+ }
52
+ function shellQuote(value) {
53
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
54
+ }
55
+ function buildCodexLoginShellCommand(cfg, loginCommand) {
56
+ const hostInfo = (0, host_js_1.getHostInfo)(cfg.codex.codex_auth_path);
57
+ const containerAuthPath = resolveContainerPath(cfg.codex.codex_auth_path, cfg.agent_home_directory);
58
+ return [
59
+ `AGENT_USER=${shellQuote(cfg.agent_user)}`,
60
+ `AGENT_HOME=${shellQuote(cfg.agent_home_directory)}`,
61
+ `AGENT_UID=${shellQuote(String(hostInfo.uid))}`,
62
+ `AGENT_GID=${shellQuote(String(hostInfo.gid))}`,
63
+ `CODEX_AUTH_PATH=${shellQuote(containerAuthPath)}`,
64
+ `CODEX_LOGIN_COMMAND=${shellQuote(`source "$NVM_DIR/nvm.sh"; ${loginCommand}`)}`,
65
+ 'EXISTING_UID_USER=""',
66
+ 'if getent passwd "$AGENT_UID" >/dev/null 2>&1; then',
67
+ ' EXISTING_UID_USER="$(getent passwd "$AGENT_UID" | cut -d: -f1)"',
68
+ ' AGENT_USER="$EXISTING_UID_USER"',
69
+ 'fi',
70
+ 'AGENT_GROUP="$AGENT_USER"',
71
+ 'if getent group "$AGENT_GID" >/dev/null 2>&1; then',
72
+ ' AGENT_GROUP="$(getent group "$AGENT_GID" | cut -d: -f1)"',
73
+ 'elif getent group "$AGENT_USER" >/dev/null 2>&1; then',
74
+ ' groupmod -g "$AGENT_GID" "$AGENT_USER"',
75
+ ' AGENT_GROUP="$AGENT_USER"',
76
+ 'else',
77
+ ' groupadd -g "$AGENT_GID" "$AGENT_USER"',
78
+ ' AGENT_GROUP="$AGENT_USER"',
79
+ 'fi',
80
+ 'if id -u "$AGENT_USER" >/dev/null 2>&1; then',
81
+ ' usermod -u "$AGENT_UID" -g "$AGENT_GROUP" -d "$AGENT_HOME" -s /bin/bash "$AGENT_USER" || true',
82
+ 'else',
83
+ ' useradd -m -d "$AGENT_HOME" -u "$AGENT_UID" -g "$AGENT_GROUP" -s /bin/bash "$AGENT_USER"',
84
+ 'fi',
85
+ 'mkdir -p "$AGENT_HOME" "$(dirname "$CODEX_AUTH_PATH")"',
86
+ 'chown -R "$AGENT_UID:$AGENT_GID" "$AGENT_HOME" "$(dirname "$CODEX_AUTH_PATH")" || true',
87
+ 'export HOME="$AGENT_HOME"',
88
+ 'exec sudo -n -E -H -u "$AGENT_USER" bash -lc "$CODEX_LOGIN_COMMAND"',
89
+ ].join("\n");
90
+ }
43
91
  function isErrnoException(error) {
44
92
  return error instanceof Error && "code" in error;
45
93
  }
46
94
  function buildDockerMissingError() {
47
95
  return new Error("Docker is not installed or not available on PATH. Install Docker and retry.");
48
96
  }
97
+ function readSpawnSyncText(output) {
98
+ if (typeof output === "string") {
99
+ return output.trim();
100
+ }
101
+ if (Buffer.isBuffer(output)) {
102
+ return output.toString("utf8").trim();
103
+ }
104
+ return "";
105
+ }
106
+ function describeSpawnSyncFailure(command, args, result) {
107
+ if (isErrnoException(result.error)) {
108
+ return `${command} ${args.join(" ")} failed to start: ${result.error.message}`;
109
+ }
110
+ const parts = [`${command} ${args.join(" ")}`];
111
+ if (typeof result.status === "number") {
112
+ parts.push(`exit code ${result.status}`);
113
+ }
114
+ if (result.signal) {
115
+ parts.push(`signal ${result.signal}`);
116
+ }
117
+ const stderr = readSpawnSyncText(result.stderr);
118
+ if (stderr.length > 0) {
119
+ parts.push(`stderr: ${stderr}`);
120
+ }
121
+ const stdout = readSpawnSyncText(result.stdout);
122
+ if (stdout.length > 0) {
123
+ parts.push(`stdout: ${stdout}`);
124
+ }
125
+ return parts.join(", ");
126
+ }
49
127
  function ensureDockerAvailable(spawnSyncCommand) {
50
128
  const result = spawnSyncCommand("docker", ["--version"], { stdio: "ignore" });
51
129
  if (isErrnoException(result.error) && result.error.code === "ENOENT") {
@@ -64,9 +142,10 @@ async function runContainerizedCodexLogin(cfg, deps, options) {
64
142
  (0, node_fs_1.mkdirSync)(configDir, { recursive: true });
65
143
  }
66
144
  const destPath = (0, node_path_1.join)(configDir, cfg.codex.codex_auth_file_path);
67
- const containerAuthPath = cfg.codex.codex_auth_path;
145
+ const containerAuthPath = resolveContainerPath(cfg.codex.codex_auth_path, cfg.agent_home_directory);
68
146
  let authCopied = false;
69
147
  let combinedOutput = "";
148
+ let lastCopyFailure = null;
70
149
  await new Promise((resolve, reject) => {
71
150
  let settled = false;
72
151
  let poll;
@@ -77,19 +156,17 @@ async function runContainerizedCodexLogin(cfg, deps, options) {
77
156
  deps.spawnSyncCommand("docker", ["rm", "-f", options.containerName], { stdio: "ignore" });
78
157
  };
79
158
  const tryCopyAuthFile = () => {
80
- const check = deps.spawnSyncCommand("docker", ["exec", options.containerName, "sh", "-c", `test -f ${containerAuthPath}`], {
81
- stdio: "ignore",
82
- });
83
- if (check.status !== 0) {
84
- return false;
85
- }
86
- const cpResult = deps.spawnSyncCommand("docker", ["cp", `${options.containerName}:${containerAuthPath}`, destPath], {
87
- stdio: "ignore",
159
+ const cpArgs = ["cp", `${options.containerName}:${containerAuthPath}`, destPath];
160
+ const cpResult = deps.spawnSyncCommand("docker", cpArgs, {
161
+ stdio: ["ignore", "pipe", "pipe"],
162
+ encoding: "utf8",
88
163
  });
89
164
  if (cpResult.status !== 0) {
165
+ lastCopyFailure = describeSpawnSyncFailure("docker", cpArgs, cpResult);
90
166
  return false;
91
167
  }
92
168
  authCopied = true;
169
+ lastCopyFailure = null;
93
170
  cleanup();
94
171
  return true;
95
172
  };
@@ -141,7 +218,14 @@ async function runContainerizedCodexLogin(cfg, deps, options) {
141
218
  resolveOnce();
142
219
  return;
143
220
  }
144
- rejectOnce(new Error(`Codex login failed or was cancelled.${combinedOutput.trim().length > 0 ? ` Output: ${combinedOutput.trim()}` : ""}`));
221
+ const details = [];
222
+ if (lastCopyFailure) {
223
+ details.push(`Auth file copy failed from ${containerAuthPath}: ${lastCopyFailure}`);
224
+ }
225
+ if (combinedOutput.trim().length > 0) {
226
+ details.push(`Output: ${combinedOutput.trim()}`);
227
+ }
228
+ rejectOnce(new Error(`Codex login failed or was cancelled.${details.length > 0 ? ` ${details.join(" ")}` : ""}`));
145
229
  });
146
230
  });
147
231
  return destPath;
@@ -232,6 +316,8 @@ async function runSetCodexHostAuth(cfg, overrides = {}) {
232
316
  }
233
317
  async function runDedicatedCodexAuth(cfg, db, deps) {
234
318
  const containerName = `companyhelm-codex-auth-${Date.now()}`;
319
+ ensureDockerAvailable(deps.spawnSyncCommand);
320
+ const loginCommand = buildCodexLoginShellCommand(cfg, "codex login --device-auth");
235
321
  deps.logInfo("Starting Codex login inside a container...");
236
322
  deps.logInfo("A browser URL and device code will appear -- open it to complete authentication.");
237
323
  const destPath = await runContainerizedCodexLogin(cfg, deps, {
@@ -244,7 +330,7 @@ async function runDedicatedCodexAuth(cfg, db, deps) {
244
330
  "bash",
245
331
  cfg.runtime_image,
246
332
  "-lc",
247
- 'source "$NVM_DIR/nvm.sh"; codex login --device-auth',
333
+ loginCommand,
248
334
  ],
249
335
  });
250
336
  await setCodexDedicatedAuthInDb(db);
@@ -257,6 +343,8 @@ async function runCodexApiKeyAuth(cfg, apiKey, overrides = {}) {
257
343
  try {
258
344
  deps.logInfo("Starting Codex API key login inside a container...");
259
345
  const containerName = `companyhelm-codex-auth-${Date.now()}`;
346
+ ensureDockerAvailable(deps.spawnSyncCommand);
347
+ const loginCommand = buildCodexLoginShellCommand(cfg, 'printf \'%s\\n\' "$CODEX_API_KEY" | codex login --with-api-key');
260
348
  const destPath = await runContainerizedCodexLogin(cfg, deps, {
261
349
  containerName,
262
350
  dockerArgs: [
@@ -269,7 +357,7 @@ async function runCodexApiKeyAuth(cfg, apiKey, overrides = {}) {
269
357
  "bash",
270
358
  cfg.runtime_image,
271
359
  "-lc",
272
- 'source "$NVM_DIR/nvm.sh"; printf \'%s\\n\' "$CODEX_API_KEY" | codex login --with-api-key',
360
+ loginCommand,
273
361
  ],
274
362
  });
275
363
  await setCodexApiKeyAuthInDb(db);
@@ -288,6 +376,8 @@ async function runCodexDeviceCodeAuth(cfg, onDeviceCode, overrides = {}) {
288
376
  let onDeviceCodePromise = Promise.resolve();
289
377
  deps.logInfo("Starting Codex device login inside a container...");
290
378
  const containerName = `companyhelm-codex-auth-${Date.now()}`;
379
+ ensureDockerAvailable(deps.spawnSyncCommand);
380
+ const loginCommand = buildCodexLoginShellCommand(cfg, "codex login --device-auth");
291
381
  const destPath = await runContainerizedCodexLogin(cfg, deps, {
292
382
  containerName,
293
383
  dockerArgs: [
@@ -298,7 +388,7 @@ async function runCodexDeviceCodeAuth(cfg, onDeviceCode, overrides = {}) {
298
388
  "bash",
299
389
  cfg.runtime_image,
300
390
  "-lc",
301
- 'source "$NVM_DIR/nvm.sh"; codex login --device-auth',
391
+ loginCommand,
302
392
  ],
303
393
  onOutput: (output) => {
304
394
  const deviceCode = extractCodexDeviceCodeFromOutput(output);
@@ -5,6 +5,7 @@ exports.registerSdkCodexUseDedicatedAuthCommand = registerSdkCodexUseDedicatedAu
5
5
  const config_js_1 = require("../../../config.js");
6
6
  const auth_js_1 = require("./auth.js");
7
7
  async function runSdkCodexUseDedicatedAuthCommand(cfg = config_js_1.config.parse({}), overrides = {}) {
8
+ cfg = config_js_1.config.parse(cfg);
8
9
  const deps = { ...auth_js_1.defaultUseDedicatedCodexAuthDependencies, ...overrides };
9
10
  await (0, auth_js_1.runUseDedicatedCodexAuth)(cfg, deps);
10
11
  }
@@ -5,6 +5,7 @@ exports.registerSdkCodexUseHostAuthCommand = registerSdkCodexUseHostAuthCommand;
5
5
  const config_js_1 = require("../../../config.js");
6
6
  const auth_js_1 = require("./auth.js");
7
7
  async function runSdkCodexUseHostAuthCommand(cfg = config_js_1.config.parse({}), overrides = {}) {
8
+ cfg = config_js_1.config.parse(cfg);
8
9
  const deps = { ...auth_js_1.defaultSetCodexHostAuthDependencies, ...overrides };
9
10
  const authPath = await (0, auth_js_1.runSetCodexHostAuth)(cfg, deps);
10
11
  console.log(`Codex SDK configured with host authentication using ${authPath}.`);
@@ -96,6 +96,7 @@ async function selectStartupAuthMode(options, deps) {
96
96
  return authMode;
97
97
  }
98
98
  async function startup(cfg = config_js_1.config.parse({}), overrides = {}) {
99
+ cfg = config_js_1.config.parse(cfg);
99
100
  const deps = { ...defaultStartupDependencies, ...overrides };
100
101
  banner();
101
102
  const s = deps.promptApi.spinner();
@@ -216,6 +216,7 @@ class AppServerContainerService {
216
216
  const hostInfo = (0, host_js_1.getHostInfo)(cfg.codex.codex_auth_path);
217
217
  const containerHome = cfg.agent_home_directory;
218
218
  const containerAuthPath = resolveContainerPath(cfg.codex.codex_auth_path, containerHome);
219
+ const hostAuthPath = (0, path_js_1.expandHome)(cfg.codex.codex_auth_path);
219
220
  const hostDedicatedAuthPath = `${(0, path_js_1.expandHome)(cfg.config_directory)}/${cfg.codex.codex_auth_file_path}`;
220
221
  const mountArgs = [];
221
222
  if (codexAuthMode === "dedicated") {
@@ -224,6 +225,12 @@ class AppServerContainerService {
224
225
  }
225
226
  mountArgs.push("--mount", `type=bind,src=${hostDedicatedAuthPath},dst=${containerAuthPath}`);
226
227
  }
228
+ else if (codexAuthMode === "host") {
229
+ if (!hostInfo.codexAuthExists) {
230
+ throw new Error(`Codex host auth file was not found at ${hostAuthPath}`);
231
+ }
232
+ mountArgs.push("--mount", `type=bind,src=${hostAuthPath},dst=${containerAuthPath}`);
233
+ }
227
234
  this.containerName = `companyhelm-codex-app-server-${Date.now()}`;
228
235
  const bootstrapTemplate = (0, node_fs_1.readFileSync)(resolveTemplatePath(), "utf8");
229
236
  const bootstrapScript = renderJinjaTemplate(bootstrapTemplate, {
@@ -108,7 +108,7 @@ function buildSharedThreadMounts(options) {
108
108
  mounts.push({
109
109
  Type: "bind",
110
110
  Source: hostAuthPath,
111
- Target: hostAuthPath,
111
+ Target: resolveContainerPath(options.codexAuthPath, options.containerHomeDirectory),
112
112
  });
113
113
  return mounts;
114
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companyhelm/runner",
3
- "version": "0.0.15",
3
+ "version": "0.0.18",
4
4
  "description": "Run the CompanyHelm runner in fully isolated Docker sandboxes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,6 +24,7 @@
24
24
  "test": "npm run build && vitest run",
25
25
  "test:unit": "npm run build && vitest run tests/unit",
26
26
  "test:integration": "npm run build && vitest run tests/integration",
27
+ "test:system:device-auth": "npm run build && COMPANYHELM_RUN_MANUAL_DEVICE_AUTH_TEST=1 vitest run --reporter=verbose tests/system/codex-device-auth.system.test.ts",
27
28
  "db:generate": "drizzle-kit generate",
28
29
  "db:migrate": "drizzle-kit migrate",
29
30
  "generate:codex-app-server": "docker run --rm --user \"$(id -u):$(id -g)\" -e CODEX_HOME=/workspace/node_modules/.cache/codex -v \"$PWD:/workspace\" -w /workspace companyhelm/runner:$(tr -d '[:space:]' < RUNTIME_IMAGE_VERSION) bash -lc 'mkdir -p /workspace/node_modules/.cache/codex; source \"$NVM_DIR/nvm.sh\"; codex app-server generate-ts --out src/generated/codex-app-server'"