@companyhelm/cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/cli.js +12 -1
- package/dist/commands/dependencies.js +29 -14
- package/dist/commands/interactive.d.ts +1 -0
- package/dist/commands/interactive.js +4 -1
- package/dist/commands/logs.js +2 -2
- package/dist/commands/register-commands.js +4 -1
- package/dist/commands/reset.js +1 -1
- package/dist/commands/set-image-version.js +3 -3
- package/dist/commands/setup-github-app.d.ts +4 -1
- package/dist/commands/setup-github-app.js +30 -8
- package/dist/commands/startup-preferences.d.ts +3 -0
- package/dist/commands/startup-preferences.js +39 -0
- package/dist/core/bootstrap/DeploymentBootstrapper.d.ts +2 -1
- package/dist/core/bootstrap/DeploymentBootstrapper.js +23 -8
- package/dist/core/config/ApiEnvFileWriter.d.ts +5 -3
- package/dist/core/config/ApiEnvFileWriter.js +20 -13
- package/dist/core/config/GithubAppConfigStore.js +2 -10
- package/dist/core/docker/ComposeTemplateRenderer.js +0 -1
- package/dist/core/docker/DockerStackManager.js +1 -2
- package/dist/core/local/ApiLocalService.d.ts +1 -1
- package/dist/core/local/ApiLocalService.js +3 -3
- package/dist/core/logs/LogsService.d.ts +2 -1
- package/dist/core/logs/LogsService.js +5 -4
- package/dist/core/runner/RunnerSupervisor.d.ts +6 -0
- package/dist/core/runner/RunnerSupervisor.js +20 -3
- package/dist/core/runner/runner-bootstrap.d.ts +2 -0
- package/dist/core/runner/runner-bootstrap.js +48 -0
- package/dist/core/runtime/CliPackageMetadata.d.ts +3 -0
- package/dist/core/runtime/CliPackageMetadata.js +8 -0
- package/dist/core/runtime/CliRoot.d.ts +2 -0
- package/dist/core/runtime/CliRoot.js +23 -0
- package/dist/core/runtime/ImageCatalog.js +2 -2
- package/dist/core/runtime/LocalConfigStore.d.ts +4 -4
- package/dist/core/runtime/LocalConfigStore.js +18 -27
- package/dist/core/runtime/PublicImageTagRegistry.d.ts +1 -0
- package/dist/core/runtime/PublicImageTagRegistry.js +34 -14
- package/dist/core/runtime/RepoConfigStore.d.ts +16 -0
- package/dist/core/runtime/RepoConfigStore.js +63 -0
- package/dist/core/runtime/RuntimePaths.d.ts +2 -0
- package/dist/core/runtime/RuntimePaths.js +6 -0
- package/dist/core/services/ManagedServiceNames.d.ts +5 -0
- package/dist/core/services/ManagedServiceNames.js +12 -0
- package/dist/preflight/ApiPortPreflightCheck.d.ts +6 -0
- package/dist/preflight/ApiPortPreflightCheck.js +10 -0
- package/dist/preflight/DockerInstalledPreflightCheck.d.ts +7 -0
- package/dist/preflight/DockerInstalledPreflightCheck.js +15 -0
- package/dist/preflight/PortAvailabilityPreflightCheck.d.ts +7 -0
- package/dist/preflight/PortAvailabilityPreflightCheck.js +31 -0
- package/dist/preflight/PostgresPortPreflightCheck.d.ts +6 -0
- package/dist/preflight/PostgresPortPreflightCheck.js +10 -0
- package/dist/preflight/PreflightCheck.d.ts +3 -0
- package/dist/preflight/PreflightCheck.js +1 -0
- package/dist/preflight/WebPortPreflightCheck.d.ts +6 -0
- package/dist/preflight/WebPortPreflightCheck.js +10 -0
- package/dist/preflight/runStartupPreflightChecks.d.ts +18 -0
- package/dist/preflight/runStartupPreflightChecks.js +42 -0
- package/dist/templates/api.env.tpl +3 -0
- package/package.json +2 -2
- package/src/templates/api.env.tpl +3 -0
- package/dist/core/runtime/ProjectPaths.d.ts +0 -7
- package/dist/core/runtime/ProjectPaths.js +0 -16
package/README.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# CompanyHelm - Distributed AI Agent Orchestration
|
|
2
2
|
|
|
3
|
+
<a href="https://discord.gg/YueY3dQM9Q"><img src="https://img.shields.io/discord/1456350064065904867?label=Discord&logo=discord&logoColor=white&color=5865F2&style=for-the-badge" alt="Discord"></a>
|
|
4
|
+
|
|
3
5
|
CompanyHelm is an open-source control plane for running AI-agent companies in your own infrastructure.
|
|
4
6
|
It gives teams a way to organize agents by role, keep humans in the loop for approvals and clarification, and run agent workloads in isolated environments instead of opaque hosted black boxes. Each agent can run its own app infrastructure for testing and create PRs autonomously. Spin up container-isolated agent threads with a click.
|
|
5
7
|
|
|
6
|
-
[Website](https://www.companyhelm.com/)
|
|
8
|
+
[Website](https://www.companyhelm.com/) · [Discord](https://discord.gg/YueY3dQM9Q)
|
|
9
|
+
|
|
10
|
+
<img width="1843" height="973" alt="Task management" src="https://github.com/user-attachments/assets/c0faa139-06ed-4299-a76f-63c186204038" />
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
## Quick start
|
|
9
14
|
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,16 @@ import { realpathSync } from "node:fs";
|
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { buildProgram } from "./commands/register-commands.js";
|
|
5
5
|
import { InteractiveCommandCancelledError } from "./commands/interactive.js";
|
|
6
|
+
import { CliPackageMetadata } from "./core/runtime/CliPackageMetadata.js";
|
|
7
|
+
function shouldPrintVersion(argv = process.argv) {
|
|
8
|
+
const args = argv.slice(2);
|
|
9
|
+
return args.length === 1 && (args[0] === "--version" || args[0] === "-V");
|
|
10
|
+
}
|
|
6
11
|
export async function main(argv = process.argv) {
|
|
12
|
+
if (shouldPrintVersion(argv)) {
|
|
13
|
+
process.stdout.write(`${new CliPackageMetadata().version()}\n`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
7
16
|
const program = buildProgram();
|
|
8
17
|
try {
|
|
9
18
|
await program.parseAsync(argv);
|
|
@@ -13,7 +22,9 @@ export async function main(argv = process.argv) {
|
|
|
13
22
|
process.exitCode = 1;
|
|
14
23
|
return;
|
|
15
24
|
}
|
|
16
|
-
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
process.stderr.write(`${message}\n`);
|
|
27
|
+
process.exitCode = 1;
|
|
17
28
|
}
|
|
18
29
|
}
|
|
19
30
|
function isCliEntrypoint(argv = process.argv) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import { DeploymentBootstrapper } from "../core/bootstrap/DeploymentBootstrapper.js";
|
|
5
3
|
import { ApiEnvFileWriter } from "../core/config/ApiEnvFileWriter.js";
|
|
6
4
|
import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
|
|
@@ -11,17 +9,21 @@ import { LocalServiceProcessManager } from "../core/local/LocalServiceProcessMan
|
|
|
11
9
|
import { WebLocalService } from "../core/local/WebLocalService.js";
|
|
12
10
|
import { LogsService } from "../core/logs/LogsService.js";
|
|
13
11
|
import { CommandRunner } from "../core/process/CommandRunner.js";
|
|
14
|
-
import { ProjectPaths } from "../core/runtime/ProjectPaths.js";
|
|
15
12
|
import { RunnerSupervisor } from "../core/runner/RunnerSupervisor.js";
|
|
13
|
+
import { defaultCliRoot } from "../core/runtime/CliRoot.js";
|
|
16
14
|
import { createPasswordHash } from "../core/runtime/Secrets.js";
|
|
17
15
|
import { RuntimePaths } from "../core/runtime/RuntimePaths.js";
|
|
18
16
|
import { RuntimeStateStore } from "../core/runtime/RuntimeStateStore.js";
|
|
19
17
|
import { VersionCatalog } from "../core/runtime/VersionCatalog.js";
|
|
20
18
|
import { StatusService } from "../core/status/StatusService.js";
|
|
21
19
|
import { TerminalRenderer } from "../core/ui/TerminalRenderer.js";
|
|
20
|
+
import { LocalConfigStore } from "../core/runtime/LocalConfigStore.js";
|
|
21
|
+
import { PortAllocator } from "../core/runtime/PortAllocator.js";
|
|
22
|
+
import { runStartupPreflightChecks } from "../preflight/runStartupPreflightChecks.js";
|
|
22
23
|
import { ensureGithubAppConfig } from "./setup-github-app.js";
|
|
24
|
+
import { ensureAgentWorkspaceMode } from "./startup-preferences.js";
|
|
23
25
|
function runtimeRoot() {
|
|
24
|
-
return
|
|
26
|
+
return defaultCliRoot();
|
|
25
27
|
}
|
|
26
28
|
export function createDefaultDependencies() {
|
|
27
29
|
const root = runtimeRoot();
|
|
@@ -33,9 +35,9 @@ export function createDefaultDependencies() {
|
|
|
33
35
|
const runnerSupervisor = new RunnerSupervisor(runtimePaths.runnerConfigPath());
|
|
34
36
|
const bootstrapper = new DeploymentBootstrapper();
|
|
35
37
|
const githubAppConfigStore = new GithubAppConfigStore();
|
|
36
|
-
const apiEnvFileWriter = new ApiEnvFileWriter(
|
|
37
|
-
const projectPaths = new ProjectPaths(process.cwd());
|
|
38
|
+
const apiEnvFileWriter = new ApiEnvFileWriter(root);
|
|
38
39
|
const localRepoSourceResolver = new LocalRepoSourceResolver(process.cwd());
|
|
40
|
+
const localConfigStore = new LocalConfigStore();
|
|
39
41
|
const localServiceProcessManager = new LocalServiceProcessManager();
|
|
40
42
|
const apiLocalService = new ApiLocalService(localServiceProcessManager, commandRunner);
|
|
41
43
|
const webLocalService = new WebLocalService(localServiceProcessManager, commandRunner);
|
|
@@ -86,11 +88,21 @@ export function createDefaultDependencies() {
|
|
|
86
88
|
async up(options = {}) {
|
|
87
89
|
const logLevel = options.logLevel ?? "info";
|
|
88
90
|
const useHostDockerRuntime = options.useHostDockerRuntime ?? false;
|
|
89
|
-
const githubAppConfig = await ensureGithubAppConfig(githubAppConfigStore, process.stdin, process.stdout);
|
|
90
|
-
const state = stateStore.initialize();
|
|
91
91
|
process.stdout.write(`${renderer.renderBanner()}\n`);
|
|
92
|
-
const runnerAlreadyRunning = await isRunnerRunning(commandRunner, runnerSupervisor);
|
|
93
92
|
const desiredSources = localRepoSourceResolver.resolve(options);
|
|
93
|
+
const currentState = stateStore.load();
|
|
94
|
+
const allocatedPorts = currentState?.ports ?? new PortAllocator().allocate();
|
|
95
|
+
await runStartupPreflightChecks({
|
|
96
|
+
commandRunner,
|
|
97
|
+
currentState,
|
|
98
|
+
desiredSources,
|
|
99
|
+
ports: allocatedPorts,
|
|
100
|
+
readStatus: () => statusService.read()
|
|
101
|
+
});
|
|
102
|
+
const workspaceMode = await ensureAgentWorkspaceMode(localConfigStore, process.stdin, process.stdout);
|
|
103
|
+
const githubAppConfig = await ensureGithubAppConfig(githubAppConfigStore, process.stdin, process.stdout, { workspaceMode });
|
|
104
|
+
const state = currentState ?? stateStore.initialize();
|
|
105
|
+
const runnerAlreadyRunning = await isRunnerRunning(commandRunner, runnerSupervisor);
|
|
94
106
|
const versions = versionCatalog.resolve();
|
|
95
107
|
const passwordRecord = createPasswordHash(state.auth.password);
|
|
96
108
|
const startedLocalServices = [];
|
|
@@ -100,7 +112,8 @@ export function createDefaultDependencies() {
|
|
|
100
112
|
apiEnvFileWriter.write(githubAppConfig);
|
|
101
113
|
bootstrapper.writeSeedSql(root, state, passwordRecord.passwordHash, passwordRecord.passwordSalt);
|
|
102
114
|
bootstrapper.writeApiConfig(root, state, logLevel, {
|
|
103
|
-
databaseHost: desiredSources.api.source === "local" ? "127.0.0.1" : "postgres"
|
|
115
|
+
databaseHost: desiredSources.api.source === "local" ? "127.0.0.1" : "postgres",
|
|
116
|
+
githubAppConfig
|
|
104
117
|
});
|
|
105
118
|
bootstrapper.writeFrontendConfig(root, state);
|
|
106
119
|
await stopLocalServicesFromState(stateStore.load(), localServiceProcessManager);
|
|
@@ -141,14 +154,16 @@ export function createDefaultDependencies() {
|
|
|
141
154
|
await commandRunner.run(configureSdkCommand.command, configureSdkCommand.args);
|
|
142
155
|
const startCommand = runnerSupervisor.buildStartArgs({
|
|
143
156
|
serverUrl: `127.0.0.1:${state.ports.runnerGrpc}`,
|
|
144
|
-
agentApiUrl: `127.0.0.1:${state.ports.
|
|
157
|
+
agentApiUrl: `http://127.0.0.1:${state.ports.apiHttp}/agent/v1`,
|
|
145
158
|
logPath: runtimePaths.runnerLogPath(),
|
|
146
159
|
secret: state.runner.secret,
|
|
147
160
|
logLevel,
|
|
148
|
-
useHostDockerRuntime
|
|
161
|
+
useHostDockerRuntime,
|
|
162
|
+
workspaceMode,
|
|
163
|
+
projectRoot: process.cwd()
|
|
149
164
|
});
|
|
150
165
|
process.stdout.write(`${renderer.progress("Starting the runner...")}\n`);
|
|
151
|
-
await commandRunner.run(startCommand.command, startCommand.args);
|
|
166
|
+
await commandRunner.run(startCommand.command, startCommand.args, undefined, startCommand.env);
|
|
152
167
|
runnerStarted = true;
|
|
153
168
|
}
|
|
154
169
|
if (desiredSources.frontend.source === "local") {
|
|
@@ -246,7 +261,7 @@ export function createDefaultDependencies() {
|
|
|
246
261
|
await stopLocalServicesFromState(state, localServiceProcessManager);
|
|
247
262
|
}
|
|
248
263
|
await dockerStackManager.down({ removeVolumes: true });
|
|
249
|
-
fs.rmSync(
|
|
264
|
+
fs.rmSync(localConfigStore.configPath(), { force: true });
|
|
250
265
|
fs.rmSync(root, { recursive: true, force: true });
|
|
251
266
|
if (options.removeGithubAppConfig) {
|
|
252
267
|
githubAppConfigStore.delete();
|
|
@@ -2,5 +2,6 @@ import type { Readable, Writable } from "node:stream";
|
|
|
2
2
|
export declare class InteractiveCommandCancelledError extends Error {
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
5
|
+
export declare function hasInteractiveTerminal(input: Readable, output: Writable): boolean;
|
|
5
6
|
export declare function requireInteractiveTerminal(input: Readable, output: Writable, message: string): void;
|
|
6
7
|
export declare function unwrapPromptResult<T>(value: T | symbol, message: string, output: Writable): T;
|
|
@@ -8,8 +8,11 @@ export class InteractiveCommandCancelledError extends Error {
|
|
|
8
8
|
function isReadableTty(input) {
|
|
9
9
|
return "isTTY" in input && Boolean(input.isTTY);
|
|
10
10
|
}
|
|
11
|
+
export function hasInteractiveTerminal(input, output) {
|
|
12
|
+
return isReadableTty(input) && clack.isTTY(output);
|
|
13
|
+
}
|
|
11
14
|
export function requireInteractiveTerminal(input, output, message) {
|
|
12
|
-
if (!
|
|
15
|
+
if (!hasInteractiveTerminal(input, output)) {
|
|
13
16
|
throw new Error(message);
|
|
14
17
|
}
|
|
15
18
|
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { AVAILABLE_MANAGED_SERVICE_NAMES } from "../core/services/ManagedServiceNames.js";
|
|
2
2
|
export function registerLogsCommand(program, dependencies) {
|
|
3
3
|
program
|
|
4
4
|
.command("logs")
|
|
@@ -6,7 +6,7 @@ export function registerLogsCommand(program, dependencies) {
|
|
|
6
6
|
.argument("[service]")
|
|
7
7
|
.action(async (service) => {
|
|
8
8
|
if (!service) {
|
|
9
|
-
process.stdout.write(`Available services:\n${
|
|
9
|
+
process.stdout.write(`Available services:\n${AVAILABLE_MANAGED_SERVICE_NAMES.map((name) => `- ${name}`).join("\n")}\n`);
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
await dependencies.logs(service);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { createDefaultDependencies } from "./dependencies.js";
|
|
3
|
+
import { CliPackageMetadata } from "../core/runtime/CliPackageMetadata.js";
|
|
3
4
|
import { registerDownCommand } from "./down.js";
|
|
4
5
|
import { registerLogsCommand } from "./logs.js";
|
|
5
6
|
import { registerResetCommand } from "./reset.js";
|
|
@@ -8,7 +9,9 @@ import { registerSetImageVersionCommand } from "./set-image-version.js";
|
|
|
8
9
|
import { registerStatusCommand } from "./status.js";
|
|
9
10
|
import { registerUpCommand } from "./up.js";
|
|
10
11
|
export function buildProgram(dependencies = createDefaultDependencies()) {
|
|
11
|
-
const program = new Command()
|
|
12
|
+
const program = new Command()
|
|
13
|
+
.name("companyhelm")
|
|
14
|
+
.version(new CliPackageMetadata().version());
|
|
12
15
|
registerSetupGithubAppCommand(program);
|
|
13
16
|
registerUpCommand(program, dependencies);
|
|
14
17
|
registerDownCommand(program, dependencies);
|
package/dist/commands/reset.js
CHANGED
|
@@ -4,7 +4,7 @@ import { requireInteractiveTerminal, unwrapPromptResult } from "./interactive.js
|
|
|
4
4
|
export async function confirmReset(input = process.stdin, output = process.stdout) {
|
|
5
5
|
requireInteractiveTerminal(input, output, "reset requires confirmation from a TTY. Re-run with --yes to skip the prompt.");
|
|
6
6
|
const confirmed = await clack.confirm({
|
|
7
|
-
message: "This will remove CompanyHelm containers, Postgres data,
|
|
7
|
+
message: "This will remove CompanyHelm containers, Postgres data, generated runtime files under ~/.companyhelm/cli/runtime, and the CLI workspace config under ~/.companyhelm/cli/config.yaml. Continue?",
|
|
8
8
|
active: "Yes",
|
|
9
9
|
inactive: "No",
|
|
10
10
|
initialValue: false,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as clack from "@clack/prompts";
|
|
2
|
-
import { LocalConfigStore } from "../core/runtime/LocalConfigStore.js";
|
|
3
2
|
import { MANAGED_IMAGE_SERVICES, requireManagedImageService } from "../core/runtime/ManagedImages.js";
|
|
4
3
|
import { PublicImageTagRegistry } from "../core/runtime/PublicImageTagRegistry.js";
|
|
4
|
+
import { RepoConfigStore } from "../core/runtime/RepoConfigStore.js";
|
|
5
5
|
import { requireInteractiveTerminal, unwrapPromptResult } from "./interactive.js";
|
|
6
6
|
function parsePositiveInteger(value) {
|
|
7
7
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -59,7 +59,7 @@ export async function runSetImageVersion(options, dependencies = {}) {
|
|
|
59
59
|
const input = dependencies.input ?? process.stdin;
|
|
60
60
|
const output = dependencies.output ?? process.stdout;
|
|
61
61
|
const registry = dependencies.registry ?? new PublicImageTagRegistry();
|
|
62
|
-
const configStore = dependencies.configStore ?? new
|
|
62
|
+
const configStore = dependencies.configStore ?? new RepoConfigStore();
|
|
63
63
|
clack.intro("CompanyHelm image selection", { output });
|
|
64
64
|
const selectedService = options.service
|
|
65
65
|
? requireManagedImageService(options.service)
|
|
@@ -78,7 +78,7 @@ export async function runSetImageVersion(options, dependencies = {}) {
|
|
|
78
78
|
export function registerSetImageVersionCommand(program) {
|
|
79
79
|
program
|
|
80
80
|
.command("set-image-version")
|
|
81
|
-
.description("Interactively choose an API or frontend image tag and store it in
|
|
81
|
+
.description("Interactively choose an API or frontend image tag and store it in the project config.")
|
|
82
82
|
.option("-s, --service <service>", "Prefill the service to update (api or frontend)")
|
|
83
83
|
.option("-l, --limit <count>", "How many image tags to show", parsePositiveInteger, 20)
|
|
84
84
|
.action(async (options) => {
|
|
@@ -2,9 +2,12 @@ import { Writable, type Readable } from "node:stream";
|
|
|
2
2
|
import type { Command } from "commander";
|
|
3
3
|
import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
|
|
4
4
|
import { type GithubAppConfig } from "../core/config/GithubAppConfig.js";
|
|
5
|
+
import type { AgentWorkspaceMode } from "../core/runtime/LocalConfigStore.js";
|
|
5
6
|
type BrowserUrlOpener = (url: string) => Promise<void>;
|
|
6
7
|
export declare function readPemFromTerminal(input?: Readable, output?: Writable): Promise<string>;
|
|
7
8
|
export declare function promptGithubAppConfig(input?: Readable, output?: Writable, openBrowser?: BrowserUrlOpener): Promise<GithubAppConfig>;
|
|
8
|
-
export declare function ensureGithubAppConfig(store?: GithubAppConfigStore, input?: Readable, output?: Writable
|
|
9
|
+
export declare function ensureGithubAppConfig(store?: GithubAppConfigStore, input?: Readable, output?: Writable, options?: {
|
|
10
|
+
workspaceMode?: AgentWorkspaceMode;
|
|
11
|
+
}): Promise<GithubAppConfig | null>;
|
|
9
12
|
export declare function registerSetupGithubAppCommand(program: Command, store?: GithubAppConfigStore): void;
|
|
10
13
|
export {};
|
|
@@ -3,7 +3,7 @@ import chalk from "chalk";
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { Writable } from "node:stream";
|
|
6
|
-
import { unwrapPromptResult, requireInteractiveTerminal, InteractiveCommandCancelledError } from "./interactive.js";
|
|
6
|
+
import { unwrapPromptResult, requireInteractiveTerminal, InteractiveCommandCancelledError, hasInteractiveTerminal } from "./interactive.js";
|
|
7
7
|
import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
|
|
8
8
|
import { normalizeGithubAppConfig } from "../core/config/GithubAppConfig.js";
|
|
9
9
|
const GITHUB_NEW_APP_URL = "https://github.com/settings/apps/new";
|
|
@@ -176,17 +176,39 @@ export async function promptGithubAppConfig(input = process.stdin, output = proc
|
|
|
176
176
|
appPrivateKeyPem,
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
|
-
export async function ensureGithubAppConfig(store = new GithubAppConfigStore(), input = process.stdin, output = process.stdout) {
|
|
179
|
+
export async function ensureGithubAppConfig(store = new GithubAppConfigStore(), input = process.stdin, output = process.stdout, options = {}) {
|
|
180
180
|
const existingConfig = store.load();
|
|
181
181
|
if (existingConfig) {
|
|
182
182
|
return existingConfig;
|
|
183
183
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
if (!hasInteractiveTerminal(input, output)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const workspaceMode = options.workspaceMode ?? "dedicated";
|
|
188
|
+
clack.intro("CompanyHelm GitHub auth", { output });
|
|
189
|
+
clack.note(workspaceMode === "dedicated"
|
|
190
|
+
? [
|
|
191
|
+
"No machine GitHub App config was found.",
|
|
192
|
+
"Dedicated workspaces cannot access files from your host system.",
|
|
193
|
+
"GitHub auth is recommended so agents can clone and work in your repositories.",
|
|
194
|
+
].join("\n")
|
|
195
|
+
: [
|
|
196
|
+
"No machine GitHub App config was found.",
|
|
197
|
+
"GitHub auth is optional in current working directory mode.",
|
|
198
|
+
"Set it up now if you want agents to access GitHub directly from this deployment.",
|
|
199
|
+
].join("\n"), "Optional setup", { output });
|
|
200
|
+
const shouldSetup = unwrapPromptResult(await clack.confirm({
|
|
201
|
+
message: "Set up GitHub auth now?",
|
|
202
|
+
active: "Set it up",
|
|
203
|
+
inactive: "Skip for now",
|
|
204
|
+
initialValue: false,
|
|
205
|
+
input,
|
|
206
|
+
output,
|
|
207
|
+
}), "GitHub App setup cancelled.", output);
|
|
208
|
+
if (!shouldSetup) {
|
|
209
|
+
clack.outro("Skipping GitHub App setup. Continuing startup without GitHub access.", { output });
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
190
212
|
const config = await promptGithubAppConfig(input, output);
|
|
191
213
|
const spinner = clack.spinner({ output });
|
|
192
214
|
spinner.start("Saving machine GitHub App config");
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import { LocalConfigStore, type AgentWorkspaceMode } from "../core/runtime/LocalConfigStore.js";
|
|
3
|
+
export declare function ensureAgentWorkspaceMode(store?: LocalConfigStore, input?: Readable, output?: Writable): Promise<AgentWorkspaceMode>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts";
|
|
2
|
+
import { unwrapPromptResult, hasInteractiveTerminal } from "./interactive.js";
|
|
3
|
+
import { LocalConfigStore } from "../core/runtime/LocalConfigStore.js";
|
|
4
|
+
const DEFAULT_WORKSPACE_MODE = "current-working-directory";
|
|
5
|
+
export async function ensureAgentWorkspaceMode(store = new LocalConfigStore(), input = process.stdin, output = process.stdout) {
|
|
6
|
+
const existingMode = store.load().agentWorkspaceMode;
|
|
7
|
+
if (existingMode) {
|
|
8
|
+
return existingMode;
|
|
9
|
+
}
|
|
10
|
+
if (!hasInteractiveTerminal(input, output)) {
|
|
11
|
+
return DEFAULT_WORKSPACE_MODE;
|
|
12
|
+
}
|
|
13
|
+
clack.note([
|
|
14
|
+
"Choose where agent threads should run.",
|
|
15
|
+
"Dedicated workspaces keep agents isolated from your host filesystem.",
|
|
16
|
+
"In dedicated mode agents will not have access to files on your system, so GitHub auth is recommended if you want them to clone your repositories.",
|
|
17
|
+
"Current working directory mode mounts this checkout directly into agent threads.",
|
|
18
|
+
].join("\n"), "Agent workspace", { output });
|
|
19
|
+
const selectedMode = unwrapPromptResult(await clack.select({
|
|
20
|
+
message: "Where should agents operate?",
|
|
21
|
+
options: [
|
|
22
|
+
{
|
|
23
|
+
value: "current-working-directory",
|
|
24
|
+
label: "Current working directory",
|
|
25
|
+
hint: "recommended: agents work directly in this checkout"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
value: "dedicated",
|
|
29
|
+
label: "Dedicated workspaces directory",
|
|
30
|
+
hint: "isolated thread workspaces"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
initialValue: DEFAULT_WORKSPACE_MODE,
|
|
34
|
+
input,
|
|
35
|
+
output
|
|
36
|
+
}), "Workspace selection cancelled.", output);
|
|
37
|
+
store.setAgentWorkspaceMode(selectedMode);
|
|
38
|
+
return selectedMode;
|
|
39
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LogLevel } from "../../commands/dependencies.js";
|
|
2
|
+
import type { GithubAppConfig } from "../config/GithubAppConfig.js";
|
|
2
3
|
import type { RuntimeState } from "../runtime/RuntimeState.js";
|
|
3
4
|
export declare class DeploymentBootstrapper {
|
|
4
5
|
private readonly renderer;
|
|
@@ -7,7 +8,7 @@ export declare class DeploymentBootstrapper {
|
|
|
7
8
|
databaseHost?: string;
|
|
8
9
|
appPort?: number;
|
|
9
10
|
runnerGrpcPort?: number;
|
|
10
|
-
|
|
11
|
+
githubAppConfig?: GithubAppConfig | null;
|
|
11
12
|
}): string;
|
|
12
13
|
writeFrontendConfig(root: string, state: RuntimeState): string;
|
|
13
14
|
private indentBlock;
|
|
@@ -23,8 +23,18 @@ export class DeploymentBootstrapper {
|
|
|
23
23
|
const outputPath = runtimePaths.apiConfigPath();
|
|
24
24
|
const appPort = options.appPort ?? state.ports.apiHttp;
|
|
25
25
|
const runnerGrpcPort = options.runnerGrpcPort ?? state.ports.runnerGrpc;
|
|
26
|
-
const agentGrpcPort = options.agentGrpcPort ?? state.ports.agentCliGrpc;
|
|
27
26
|
const databaseHost = options.databaseHost ?? "postgres";
|
|
27
|
+
const githubConfigLines = options.githubAppConfig
|
|
28
|
+
? [
|
|
29
|
+
' app_client_id: "${GITHUB_APP_CLIENT_ID}"',
|
|
30
|
+
' app_private_key_pem: "${GITHUB_APP_PRIVATE_KEY_PEM}"',
|
|
31
|
+
' app_link: "${GITHUB_APP_URL}"'
|
|
32
|
+
]
|
|
33
|
+
: [
|
|
34
|
+
' app_client_id: "companyhelm-local-github-disabled"',
|
|
35
|
+
' app_private_key_pem: "companyhelm-local-github-disabled"',
|
|
36
|
+
' app_link: "https://github.com/apps/companyhelm-local-disabled"'
|
|
37
|
+
];
|
|
28
38
|
const yaml = [
|
|
29
39
|
"app:",
|
|
30
40
|
' host: "0.0.0.0"',
|
|
@@ -37,10 +47,17 @@ export class DeploymentBootstrapper {
|
|
|
37
47
|
" heartbeat:",
|
|
38
48
|
" intervalMs: 20000",
|
|
39
49
|
" jitterMs: 10000",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
" workers:",
|
|
51
|
+
" agentHeartbeats:",
|
|
52
|
+
" intervalSeconds: 60",
|
|
53
|
+
" jitterSeconds: 60",
|
|
54
|
+
" batchSize: 10",
|
|
55
|
+
" leaseSeconds: 120",
|
|
56
|
+
" taskWorker:",
|
|
57
|
+
" intervalSeconds: 60",
|
|
58
|
+
" jitterSeconds: 60",
|
|
59
|
+
" batchSize: 10",
|
|
60
|
+
" leaseSeconds: 120",
|
|
44
61
|
"database:",
|
|
45
62
|
' name: "companyhelm"',
|
|
46
63
|
` host: "${databaseHost}"`,
|
|
@@ -53,9 +70,7 @@ export class DeploymentBootstrapper {
|
|
|
53
70
|
' username: "postgres"',
|
|
54
71
|
' password: "postgres"',
|
|
55
72
|
"github:",
|
|
56
|
-
|
|
57
|
-
' app_private_key_pem: "${GITHUB_APP_PRIVATE_KEY_PEM}"',
|
|
58
|
-
' app_link: "${GITHUB_APP_URL}"',
|
|
73
|
+
...githubConfigLines,
|
|
59
74
|
'authProvider: "companyhelm"',
|
|
60
75
|
"auth:",
|
|
61
76
|
" companyhelm:",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { GithubAppConfig } from "./GithubAppConfig.js";
|
|
2
2
|
export declare class ApiEnvFileWriter {
|
|
3
|
-
private readonly
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
private readonly runtimePaths;
|
|
4
|
+
private readonly templatePath;
|
|
5
|
+
constructor(root: string);
|
|
6
|
+
write(config: GithubAppConfig | null): string;
|
|
7
|
+
private render;
|
|
6
8
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { RuntimePaths } from "../runtime/RuntimePaths.js";
|
|
3
5
|
function escapeEnvValue(value) {
|
|
4
6
|
return String(value || "")
|
|
5
7
|
.replace(/\\/g, "\\\\")
|
|
@@ -8,19 +10,24 @@ function escapeEnvValue(value) {
|
|
|
8
10
|
.replace(/\n/g, "\\n");
|
|
9
11
|
}
|
|
10
12
|
export class ApiEnvFileWriter {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
runtimePaths;
|
|
14
|
+
templatePath;
|
|
15
|
+
constructor(root) {
|
|
16
|
+
this.runtimePaths = new RuntimePaths(root);
|
|
17
|
+
this.templatePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../templates/api.env.tpl");
|
|
14
18
|
}
|
|
15
19
|
write(config) {
|
|
16
|
-
fs.mkdirSync(this.
|
|
17
|
-
const contents =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
fs.mkdirSync(this.runtimePaths.apiDirectoryPath(), { recursive: true });
|
|
21
|
+
const contents = this.render(config);
|
|
22
|
+
fs.writeFileSync(this.runtimePaths.apiEnvPath(), contents, "utf8");
|
|
23
|
+
return this.runtimePaths.apiEnvPath();
|
|
24
|
+
}
|
|
25
|
+
render(config) {
|
|
26
|
+
const template = fs.readFileSync(this.templatePath, "utf8");
|
|
27
|
+
const rendered = template
|
|
28
|
+
.replace("{{GITHUB_APP_URL}}", escapeEnvValue(config?.appUrl ?? ""))
|
|
29
|
+
.replace("{{GITHUB_APP_CLIENT_ID}}", escapeEnvValue(config?.appClientId ?? ""))
|
|
30
|
+
.replace("{{GITHUB_APP_PRIVATE_KEY_PEM}}", escapeEnvValue(config?.appPrivateKeyPem ?? ""));
|
|
31
|
+
return rendered.endsWith("\n") ? rendered : `${rendered}\n`;
|
|
25
32
|
}
|
|
26
33
|
}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { parse, stringify } from "yaml";
|
|
5
4
|
import { normalizeGithubAppConfig } from "./GithubAppConfig.js";
|
|
5
|
+
import { defaultCliConfigRoot } from "../runtime/CliRoot.js";
|
|
6
6
|
function defaultConfigRoot() {
|
|
7
|
-
|
|
8
|
-
if (explicitRoot) {
|
|
9
|
-
return path.resolve(explicitRoot);
|
|
10
|
-
}
|
|
11
|
-
const xdgRoot = String(process.env.XDG_CONFIG_HOME || "").trim();
|
|
12
|
-
if (xdgRoot) {
|
|
13
|
-
return path.resolve(xdgRoot, "companyhelm");
|
|
14
|
-
}
|
|
15
|
-
return path.join(os.homedir(), ".config", "companyhelm");
|
|
7
|
+
return defaultCliConfigRoot();
|
|
16
8
|
}
|
|
17
9
|
export class GithubAppConfigStore {
|
|
18
10
|
configRoot;
|
|
@@ -29,7 +29,6 @@ export class ComposeTemplateRenderer {
|
|
|
29
29
|
" ports:",
|
|
30
30
|
` - "${ports.apiHttpPort}:4000"`,
|
|
31
31
|
` - "${ports.runnerGrpcPort}:${ports.runnerGrpcPort}"`,
|
|
32
|
-
` - "${ports.agentCliGrpcPort}:${ports.agentCliGrpcPort}"`,
|
|
33
32
|
" volumes:",
|
|
34
33
|
` - "${paths.apiConfigPath}:/run/companyhelm/config.yaml:ro"`,
|
|
35
34
|
" networks:",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { CommandRunner } from "../process/CommandRunner.js";
|
|
3
|
-
import { ProjectPaths } from "../runtime/ProjectPaths.js";
|
|
4
3
|
import { RuntimePaths } from "../runtime/RuntimePaths.js";
|
|
5
4
|
import { ComposeTemplateRenderer } from "./ComposeTemplateRenderer.js";
|
|
6
5
|
export class DockerStackManager {
|
|
@@ -23,7 +22,7 @@ export class DockerStackManager {
|
|
|
23
22
|
agentCliGrpcPort: state.ports.agentCliGrpc
|
|
24
23
|
}, {
|
|
25
24
|
apiConfigPath: this.runtimePaths.apiConfigPath(),
|
|
26
|
-
apiEnvPath:
|
|
25
|
+
apiEnvPath: this.runtimePaths.apiEnvPath(),
|
|
27
26
|
frontendConfigPath: this.runtimePaths.frontendConfigPath(),
|
|
28
27
|
seedFilePath: this.runtimePaths.seedFilePath()
|
|
29
28
|
}, {
|
|
@@ -24,9 +24,9 @@ export class ApiLocalService {
|
|
|
24
24
|
logPath: input.logPath,
|
|
25
25
|
env: {
|
|
26
26
|
APP_ENV: "local",
|
|
27
|
-
GITHUB_APP_CLIENT_ID: input.githubAppConfig
|
|
28
|
-
GITHUB_APP_URL: input.githubAppConfig
|
|
29
|
-
GITHUB_APP_PRIVATE_KEY_PEM: input.githubAppConfig
|
|
27
|
+
GITHUB_APP_CLIENT_ID: input.githubAppConfig?.appClientId ?? "",
|
|
28
|
+
GITHUB_APP_URL: input.githubAppConfig?.appUrl ?? "",
|
|
29
|
+
GITHUB_APP_PRIVATE_KEY_PEM: input.githubAppConfig?.appPrivateKeyPem ?? "",
|
|
30
30
|
COMPANYHELM_JWT_PRIVATE_KEY_PEM: input.state.auth.jwtPrivateKeyPem,
|
|
31
31
|
COMPANYHELM_JWT_PUBLIC_KEY_PEM: input.state.auth.jwtPublicKeyPem,
|
|
32
32
|
COMPANYHELM_LOG_LEVEL: input.logLevel
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type ManagedServiceKey } from "../services/ManagedServiceNames.js";
|
|
1
2
|
export declare class LogsService {
|
|
2
3
|
private readonly streamServiceLogs;
|
|
3
|
-
constructor(streamServiceLogs: (service:
|
|
4
|
+
constructor(streamServiceLogs: (service: ManagedServiceKey) => Promise<void>);
|
|
4
5
|
stream(service: string): Promise<void>;
|
|
5
6
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { AVAILABLE_MANAGED_SERVICE_NAMES, resolveManagedServiceKey } from "../services/ManagedServiceNames.js";
|
|
2
2
|
export class LogsService {
|
|
3
3
|
streamServiceLogs;
|
|
4
4
|
constructor(streamServiceLogs) {
|
|
5
5
|
this.streamServiceLogs = streamServiceLogs;
|
|
6
6
|
}
|
|
7
7
|
async stream(service) {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const resolvedService = resolveManagedServiceKey(service);
|
|
9
|
+
if (!resolvedService) {
|
|
10
|
+
throw new Error(`Unknown service '${service}'. Expected one of: ${AVAILABLE_MANAGED_SERVICE_NAMES.join(", ")}`);
|
|
10
11
|
}
|
|
11
|
-
await this.streamServiceLogs(
|
|
12
|
+
await this.streamServiceLogs(resolvedService);
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { LogLevel } from "../../commands/dependencies.js";
|
|
2
|
+
import type { AgentWorkspaceMode } from "../runtime/LocalConfigStore.js";
|
|
2
3
|
export interface RunnerStartInput {
|
|
3
4
|
serverUrl: string;
|
|
4
5
|
agentApiUrl: string;
|
|
@@ -6,10 +7,13 @@ export interface RunnerStartInput {
|
|
|
6
7
|
secret: string;
|
|
7
8
|
logLevel?: LogLevel;
|
|
8
9
|
useHostDockerRuntime?: boolean;
|
|
10
|
+
workspaceMode?: AgentWorkspaceMode;
|
|
11
|
+
projectRoot?: string;
|
|
9
12
|
}
|
|
10
13
|
export interface RunnerStartCommand {
|
|
11
14
|
command: string;
|
|
12
15
|
args: string[];
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
13
17
|
}
|
|
14
18
|
export declare class RunnerSupervisor {
|
|
15
19
|
private readonly configPath;
|
|
@@ -19,5 +23,7 @@ export declare class RunnerSupervisor {
|
|
|
19
23
|
buildStopArgs(): RunnerStartCommand;
|
|
20
24
|
buildStatusArgs(): RunnerStartCommand;
|
|
21
25
|
private resolveRunnerCliPath;
|
|
26
|
+
private resolveRunnerCliOverridePath;
|
|
27
|
+
private resolveRunnerEntrypointPath;
|
|
22
28
|
private resolveHostDockerPath;
|
|
23
29
|
}
|