@companyhelm/cli 0.0.5 → 0.0.7
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/dist/commands/register-commands.js +2 -2
- package/dist/commands/root.js +72 -43
- package/dist/commands/runner/common.js +16 -0
- package/dist/commands/runner/register-runner-commands.js +12 -0
- package/dist/commands/runner/start.js +30 -0
- package/dist/commands/runner/stop.js +55 -0
- package/dist/commands/shell.js +3 -1
- package/package.json +4 -3
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerCommands = registerCommands;
|
|
4
|
-
const
|
|
4
|
+
const register_runner_commands_js_1 = require("./runner/register-runner-commands.js");
|
|
5
5
|
const shell_js_1 = require("./shell.js");
|
|
6
6
|
const register_sdk_commands_js_1 = require("./sdk/register-sdk-commands.js");
|
|
7
7
|
const status_js_1 = require("./status.js");
|
|
8
8
|
const register_thread_commands_js_1 = require("./thread/register-thread-commands.js");
|
|
9
9
|
function registerCommands(program) {
|
|
10
|
-
(0,
|
|
10
|
+
(0, register_runner_commands_js_1.registerRunnerCommands)(program);
|
|
11
11
|
(0, status_js_1.registerStatusCommand)(program);
|
|
12
12
|
(0, register_thread_commands_js_1.registerThreadCommands)(program);
|
|
13
13
|
(0, shell_js_1.registerShellCommand)(program);
|
package/dist/commands/root.js
CHANGED
|
@@ -33,16 +33,21 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.toErrorMessage = toErrorMessage;
|
|
36
37
|
exports.isRetryableApiConnectionError = isRetryableApiConnectionError;
|
|
38
|
+
exports.formatApiConnectionFailureMessage = formatApiConnectionFailureMessage;
|
|
39
|
+
exports.formatApiConnectionFailureDiagnostics = formatApiConnectionFailureDiagnostics;
|
|
37
40
|
exports.shouldUseTurnSteer = shouldUseTurnSteer;
|
|
38
41
|
exports.isNoActiveTurnSteerError = isNoActiveTurnSteerError;
|
|
39
42
|
exports.isNoRunningTurnInterruptError = isNoRunningTurnInterruptError;
|
|
40
43
|
exports.normalizeThreadAgentApiUrlForRuntime = normalizeThreadAgentApiUrlForRuntime;
|
|
41
44
|
exports.extractThreadNameUpdateFromNotification = extractThreadNameUpdateFromNotification;
|
|
42
45
|
exports.extractServerMessageRequestId = extractServerMessageRequestId;
|
|
46
|
+
exports.isInternalDaemonChildProcess = isInternalDaemonChildProcess;
|
|
47
|
+
exports.runDetachedDaemonProcess = runDetachedDaemonProcess;
|
|
48
|
+
exports.sendDaemonParentMessage = sendDaemonParentMessage;
|
|
43
49
|
exports.runRootCommand = runRootCommand;
|
|
44
50
|
exports.buildRootConfig = buildRootConfig;
|
|
45
|
-
exports.registerRootCommand = registerRootCommand;
|
|
46
51
|
const protobuf_1 = require("@bufbuild/protobuf");
|
|
47
52
|
const protos_1 = require("@companyhelm/protos");
|
|
48
53
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
@@ -206,19 +211,74 @@ function getGrpcStatusCode(error) {
|
|
|
206
211
|
const { code } = error;
|
|
207
212
|
return typeof code === "number" ? code : undefined;
|
|
208
213
|
}
|
|
214
|
+
function getGrpcStatusName(error) {
|
|
215
|
+
const code = getGrpcStatusCode(error);
|
|
216
|
+
if (code === undefined) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const statusName = grpc.status[code];
|
|
220
|
+
return typeof statusName === "string" ? statusName : undefined;
|
|
221
|
+
}
|
|
209
222
|
function isRetryableApiConnectionError(error) {
|
|
210
223
|
return getGrpcStatusCode(error) !== grpc.status.UNAUTHENTICATED;
|
|
211
224
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return message;
|
|
225
|
+
function formatGrpcMetadataForLog(metadata) {
|
|
226
|
+
if (!metadata) {
|
|
227
|
+
return undefined;
|
|
216
228
|
}
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
const rawEntries = metadata.getMap();
|
|
230
|
+
const entries = Object.entries(rawEntries);
|
|
231
|
+
if (entries.length === 0) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
const normalizedEntries = Object.fromEntries(entries.map(([key, value]) => [
|
|
235
|
+
key,
|
|
236
|
+
Buffer.isBuffer(value) ? value.toString("base64") : String(value),
|
|
237
|
+
]));
|
|
238
|
+
return JSON.stringify(normalizedEntries);
|
|
239
|
+
}
|
|
240
|
+
function formatApiConnectionFailureMessage(error, apiUrl, secret) {
|
|
241
|
+
const statusCode = getGrpcStatusCode(error);
|
|
242
|
+
const statusName = getGrpcStatusName(error);
|
|
243
|
+
const serviceError = isGrpcServiceError(error) ? error : undefined;
|
|
244
|
+
const baseMessage = serviceError && typeof serviceError.details === "string" && serviceError.details.trim().length > 0
|
|
245
|
+
? serviceError.details.trim()
|
|
246
|
+
: toErrorMessage(error);
|
|
247
|
+
let message = baseMessage;
|
|
248
|
+
if (statusCode !== undefined) {
|
|
249
|
+
message = `gRPC ${statusName ?? "UNKNOWN"} (${statusCode}): ${baseMessage}`;
|
|
250
|
+
}
|
|
251
|
+
message += ` [endpoint=${apiUrl}]`;
|
|
252
|
+
if (statusCode === grpc.status.UNAUTHENTICATED && (!secret || secret.trim().length === 0)) {
|
|
253
|
+
message += " Provide --secret <secret> to authenticate.";
|
|
219
254
|
}
|
|
220
255
|
return message;
|
|
221
256
|
}
|
|
257
|
+
function formatApiConnectionFailureDiagnostics(error) {
|
|
258
|
+
if (!isGrpcServiceError(error)) {
|
|
259
|
+
return error instanceof Error && typeof error.stack === "string" ? error.stack : undefined;
|
|
260
|
+
}
|
|
261
|
+
const diagnostics = [];
|
|
262
|
+
const statusCode = getGrpcStatusCode(error);
|
|
263
|
+
const statusName = getGrpcStatusName(error);
|
|
264
|
+
if (statusCode !== undefined) {
|
|
265
|
+
diagnostics.push(`code=${statusCode}`);
|
|
266
|
+
}
|
|
267
|
+
if (statusName) {
|
|
268
|
+
diagnostics.push(`status=${statusName}`);
|
|
269
|
+
}
|
|
270
|
+
if (typeof error.details === "string" && error.details.trim().length > 0) {
|
|
271
|
+
diagnostics.push(`details=${JSON.stringify(error.details.trim())}`);
|
|
272
|
+
}
|
|
273
|
+
const metadata = formatGrpcMetadataForLog(error.metadata);
|
|
274
|
+
if (metadata) {
|
|
275
|
+
diagnostics.push(`metadata=${metadata}`);
|
|
276
|
+
}
|
|
277
|
+
if (typeof error.stack === "string" && error.stack.trim().length > 0) {
|
|
278
|
+
diagnostics.push(`stack=${JSON.stringify(error.stack)}`);
|
|
279
|
+
}
|
|
280
|
+
return diagnostics.length > 0 ? diagnostics.join(" ") : undefined;
|
|
281
|
+
}
|
|
222
282
|
function shouldUseTurnSteer(allowSteer, startedFromIdle) {
|
|
223
283
|
return allowSteer && !startedFromIdle;
|
|
224
284
|
}
|
|
@@ -2252,7 +2312,11 @@ async function runRootCommand(options, runtimeOptions) {
|
|
|
2252
2312
|
logger.warn("CompanyHelm API command channel closed. Reconnecting...");
|
|
2253
2313
|
}
|
|
2254
2314
|
catch (error) {
|
|
2255
|
-
const failureMessage =
|
|
2315
|
+
const failureMessage = formatApiConnectionFailureMessage(error, cfg.companyhelm_api_url, options.secret);
|
|
2316
|
+
const diagnostics = formatApiConnectionFailureDiagnostics(error);
|
|
2317
|
+
if (diagnostics) {
|
|
2318
|
+
logger.debug(`CompanyHelm API failure diagnostics: ${diagnostics}`);
|
|
2319
|
+
}
|
|
2256
2320
|
if (!isRetryableApiConnectionError(error)) {
|
|
2257
2321
|
throw new Error(failureMessage);
|
|
2258
2322
|
}
|
|
@@ -2300,38 +2364,3 @@ function buildRootConfig(options) {
|
|
|
2300
2364
|
thread_git_skills_directory: options.threadGitSkillsDirectory,
|
|
2301
2365
|
});
|
|
2302
2366
|
}
|
|
2303
|
-
function registerRootCommand(program) {
|
|
2304
|
-
program
|
|
2305
|
-
.option("--config-path <path>", "Config directory override (defaults to ~/.config/companyhelm).")
|
|
2306
|
-
.option("--server-url <url>", "CompanyHelm gRPC API URL override.")
|
|
2307
|
-
.option("--agent-api-url <url>", "Agent gRPC API URL for companyhelm-agent in runtime containers (localhost is rewritten to http://host.docker.internal).")
|
|
2308
|
-
.option("--secret <secret>", "Bearer secret used as gRPC Authorization header.")
|
|
2309
|
-
.option("--state-db-path <path>", "State database path override (defaults to ~/.local/share/companyhelm/state.db).")
|
|
2310
|
-
.option("--use-host-docker-runtime", "Mount host Docker socket into runtime containers instead of creating DinD sidecars.")
|
|
2311
|
-
.option("--host-docker-path <path>", "Host Docker endpoint when --use-host-docker-runtime is enabled (unix:///<socket-path> or tcp://localhost:<port>).")
|
|
2312
|
-
.option("--thread-git-skills-directory <path>", "Container path where thread git skill repositories are cloned before linking into ~/.codex/skills.")
|
|
2313
|
-
.option("-d, --daemon", "Run in daemon mode and fail fast when no SDK is configured.")
|
|
2314
|
-
.option("--log-level <level>", "Log level (DEBUG, INFO, WARN, ERROR).", "INFO")
|
|
2315
|
-
.action(async () => {
|
|
2316
|
-
const options = program.opts();
|
|
2317
|
-
if (options.daemon && !isInternalDaemonChildProcess()) {
|
|
2318
|
-
await runDetachedDaemonProcess(options);
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
try {
|
|
2322
|
-
await runRootCommand(options, isInternalDaemonChildProcess()
|
|
2323
|
-
? {
|
|
2324
|
-
onDaemonReady: () => {
|
|
2325
|
-
sendDaemonParentMessage({ type: "daemon-ready" });
|
|
2326
|
-
},
|
|
2327
|
-
}
|
|
2328
|
-
: undefined);
|
|
2329
|
-
}
|
|
2330
|
-
catch (error) {
|
|
2331
|
-
if (isInternalDaemonChildProcess()) {
|
|
2332
|
-
sendDaemonParentMessage({ type: "daemon-error", message: toErrorMessage(error) });
|
|
2333
|
-
}
|
|
2334
|
-
throw error;
|
|
2335
|
-
}
|
|
2336
|
-
});
|
|
2337
|
-
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.addRunnerStartOptions = addRunnerStartOptions;
|
|
4
|
+
function addRunnerStartOptions(command) {
|
|
5
|
+
return command
|
|
6
|
+
.option("--config-path <path>", "Config directory override (defaults to ~/.config/companyhelm).")
|
|
7
|
+
.option("--server-url <url>", "CompanyHelm gRPC API URL override.")
|
|
8
|
+
.option("--agent-api-url <url>", "Agent gRPC API URL for companyhelm-agent in runtime containers (localhost is rewritten to http://host.docker.internal).")
|
|
9
|
+
.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("--use-host-docker-runtime", "Mount host Docker socket into runtime containers instead of creating DinD sidecars.")
|
|
12
|
+
.option("--host-docker-path <path>", "Host Docker endpoint when --use-host-docker-runtime is enabled (unix:///<socket-path> or tcp://localhost:<port>).")
|
|
13
|
+
.option("--thread-git-skills-directory <path>", "Container path where thread git skill repositories are cloned before linking into ~/.codex/skills.")
|
|
14
|
+
.option("-d, --daemon", "Run in daemon mode and fail fast when no SDK is configured.")
|
|
15
|
+
.option("--log-level <level>", "Log level (DEBUG, INFO, WARN, ERROR).", "INFO");
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerRunnerCommands = registerRunnerCommands;
|
|
4
|
+
const start_js_1 = require("./start.js");
|
|
5
|
+
const stop_js_1 = require("./stop.js");
|
|
6
|
+
function registerRunnerCommands(program) {
|
|
7
|
+
const runnerCommand = program
|
|
8
|
+
.command("runner")
|
|
9
|
+
.description("Manage the local CompanyHelm runner daemon.");
|
|
10
|
+
(0, start_js_1.registerRunnerStartCommand)(runnerCommand);
|
|
11
|
+
(0, stop_js_1.registerRunnerStopCommand)(runnerCommand);
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerRunnerStartCommand = registerRunnerStartCommand;
|
|
4
|
+
const root_js_1 = require("../root.js");
|
|
5
|
+
const common_js_1 = require("./common.js");
|
|
6
|
+
function registerRunnerStartCommand(runnerCommand) {
|
|
7
|
+
(0, common_js_1.addRunnerStartOptions)(runnerCommand
|
|
8
|
+
.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
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRunnerStopCommand = runRunnerStopCommand;
|
|
4
|
+
exports.registerRunnerStopCommand = registerRunnerStopCommand;
|
|
5
|
+
const config_js_1 = require("../../config.js");
|
|
6
|
+
const daemon_state_js_1 = require("../../state/daemon_state.js");
|
|
7
|
+
const process_js_1 = require("../../utils/process.js");
|
|
8
|
+
const root_js_1 = require("../root.js");
|
|
9
|
+
const RUNNER_STOP_TIMEOUT_MS = 15000;
|
|
10
|
+
async function waitForProcessExit(pid, timeoutMs) {
|
|
11
|
+
const deadline = Date.now() + timeoutMs;
|
|
12
|
+
while (Date.now() < deadline) {
|
|
13
|
+
if (!(0, process_js_1.isProcessRunning)(pid)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
17
|
+
}
|
|
18
|
+
return !(0, process_js_1.isProcessRunning)(pid);
|
|
19
|
+
}
|
|
20
|
+
async function runRunnerStopCommand(options) {
|
|
21
|
+
const cfg = config_js_1.config.parse({
|
|
22
|
+
state_db_path: options.stateDbPath,
|
|
23
|
+
});
|
|
24
|
+
const state = await (0, daemon_state_js_1.readCurrentDaemonState)(cfg.state_db_path);
|
|
25
|
+
if (!state?.pid) {
|
|
26
|
+
console.log("CompanyHelm runner is not running.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!(0, process_js_1.isProcessRunning)(state.pid)) {
|
|
30
|
+
await (0, daemon_state_js_1.clearCurrentDaemonState)(cfg.state_db_path, state.pid);
|
|
31
|
+
console.log(`CompanyHelm runner was not running. Cleared stale pid ${state.pid}.`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
process.kill(state.pid, "SIGTERM");
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`Failed to stop CompanyHelm runner pid ${state.pid}: ${(0, root_js_1.toErrorMessage)(error)}`);
|
|
39
|
+
}
|
|
40
|
+
const stopped = await waitForProcessExit(state.pid, RUNNER_STOP_TIMEOUT_MS);
|
|
41
|
+
if (!stopped) {
|
|
42
|
+
throw new Error(`Timed out waiting for CompanyHelm runner pid ${state.pid} to stop.`);
|
|
43
|
+
}
|
|
44
|
+
await (0, daemon_state_js_1.clearCurrentDaemonState)(cfg.state_db_path, state.pid);
|
|
45
|
+
console.log(`CompanyHelm runner stopped (pid ${state.pid}).`);
|
|
46
|
+
}
|
|
47
|
+
function registerRunnerStopCommand(runnerCommand) {
|
|
48
|
+
runnerCommand
|
|
49
|
+
.command("stop")
|
|
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).")
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
await runRunnerStopCommand(options);
|
|
54
|
+
});
|
|
55
|
+
}
|
package/dist/commands/shell.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.registerShellCommand = registerShellCommand;
|
|
|
7
7
|
const config_js_1 = require("../config.js");
|
|
8
8
|
const db_js_1 = require("../state/db.js");
|
|
9
9
|
const schema_js_1 = require("../state/schema.js");
|
|
10
|
+
const common_js_1 = require("./runner/common.js");
|
|
10
11
|
const NON_OVERRIDABLE_DAEMON_OPTION_NAMES = new Set(["daemon", "serverUrl", "secret", "help"]);
|
|
11
12
|
function resolveDaemonOptionValue(option, values) {
|
|
12
13
|
const explicit = values[option.name];
|
|
@@ -22,7 +23,8 @@ function resolveDaemonOptionValue(option, values) {
|
|
|
22
23
|
return undefined;
|
|
23
24
|
}
|
|
24
25
|
function getShellConfigurableDaemonOptions(program) {
|
|
25
|
-
|
|
26
|
+
const runnerStartCommand = (0, common_js_1.addRunnerStartOptions)(program.createCommand("start"));
|
|
27
|
+
return runnerStartCommand.options
|
|
26
28
|
.filter((option) => {
|
|
27
29
|
if (!option.long || option.hidden) {
|
|
28
30
|
return false;
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@companyhelm/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Run coding agents in fully isolated Docker sandboxes, locally.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/CompanyHelm/companyhelm-cli"
|
|
9
|
-
},
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
10
11
|
"node": ">=24"
|
|
11
12
|
},
|
|
12
13
|
"bin": {
|
|
13
|
-
"companyhelm
|
|
14
|
+
"companyhelm": "dist/cli.js"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"dist",
|