@companyhelm/cli 0.1.2 → 0.1.5
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/LICENSE +21 -0
- package/README.md +40 -33
- package/dist/cli.js +11 -1
- package/dist/commands/dependencies.d.ts +22 -3
- package/dist/commands/dependencies.js +218 -23
- package/dist/commands/interactive.d.ts +6 -0
- package/dist/commands/interactive.js +22 -0
- package/dist/commands/logs.js +6 -1
- package/dist/commands/register-commands.js +4 -0
- package/dist/commands/reset.d.ts +4 -0
- package/dist/commands/reset.js +43 -4
- package/dist/commands/set-image-version.d.ts +31 -0
- package/dist/commands/set-image-version.js +87 -0
- package/dist/commands/setup-github-app.d.ts +10 -0
- package/dist/commands/setup-github-app.js +211 -0
- package/dist/commands/status.js +3 -1
- package/dist/commands/up.js +36 -2
- package/dist/core/bootstrap/DeploymentBootstrapper.d.ts +7 -2
- package/dist/core/bootstrap/DeploymentBootstrapper.js +13 -11
- package/dist/core/bootstrap/SeedSqlRenderer.js +23 -5
- package/dist/core/config/ApiEnvFileWriter.d.ts +6 -0
- package/dist/core/config/ApiEnvFileWriter.js +26 -0
- package/dist/core/config/GithubAppConfig.d.ts +6 -0
- package/dist/core/config/GithubAppConfig.js +26 -0
- package/dist/core/config/GithubAppConfigStore.d.ts +11 -0
- package/dist/core/config/GithubAppConfigStore.js +65 -0
- package/dist/core/docker/ComposeTemplateRenderer.d.ts +9 -1
- package/dist/core/docker/ComposeTemplateRenderer.js +48 -5
- package/dist/core/docker/DockerStackManager.d.ts +18 -3
- package/dist/core/docker/DockerStackManager.js +70 -8
- package/dist/core/local/ApiLocalService.d.ts +22 -0
- package/dist/core/local/ApiLocalService.js +65 -0
- package/dist/core/local/LocalRepoSourceResolver.d.ts +24 -0
- package/dist/core/local/LocalRepoSourceResolver.js +33 -0
- package/dist/core/local/LocalServiceProcessManager.d.ts +18 -0
- package/dist/core/local/LocalServiceProcessManager.js +83 -0
- package/dist/core/local/WebLocalService.d.ts +23 -0
- package/dist/core/local/WebLocalService.js +101 -0
- package/dist/core/process/CommandRunner.d.ts +2 -2
- package/dist/core/process/CommandRunner.js +10 -2
- package/dist/core/runner/RunnerSupervisor.d.ts +6 -0
- package/dist/core/runner/RunnerSupervisor.js +31 -3
- package/dist/core/runtime/ImageCatalog.js +5 -2
- package/dist/core/runtime/LocalConfigStore.d.ts +16 -0
- package/dist/core/runtime/LocalConfigStore.js +59 -0
- package/dist/core/runtime/ManagedImages.d.ts +10 -0
- package/dist/core/runtime/ManagedImages.js +27 -0
- package/dist/core/runtime/ProjectPaths.d.ts +7 -0
- package/dist/core/runtime/ProjectPaths.js +16 -0
- package/dist/core/runtime/PublicImageTagRegistry.d.ts +16 -0
- package/dist/core/runtime/PublicImageTagRegistry.js +148 -0
- package/dist/core/runtime/RuntimePaths.d.ts +2 -0
- package/dist/core/runtime/RuntimePaths.js +7 -1
- package/dist/core/runtime/RuntimeState.d.ts +15 -1
- package/dist/core/runtime/RuntimeStateStore.d.ts +2 -0
- package/dist/core/runtime/RuntimeStateStore.js +33 -4
- package/dist/core/runtime/VersionCatalog.d.ts +10 -0
- package/dist/core/runtime/VersionCatalog.js +21 -0
- package/dist/core/status/StatusService.d.ts +8 -1
- package/dist/core/status/StatusService.js +16 -4
- package/dist/core/ui/TerminalRenderer.d.ts +10 -0
- package/dist/core/ui/TerminalRenderer.js +48 -0
- package/dist/templates/docker-compose.yaml.tpl +3 -27
- package/dist/templates/seed.sql.tpl +32 -13
- package/package.json +7 -3
- package/src/templates/docker-compose.yaml.tpl +3 -27
- package/src/templates/seed.sql.tpl +32 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CompanyHelm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,54 +1,61 @@
|
|
|
1
|
-
# CompanyHelm
|
|
1
|
+
# CompanyHelm - Distributed AI Agent Orchestration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CompanyHelm is an open-source control plane for running AI-agent companies in your own infrastructure.
|
|
4
|
+
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.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
[Website](https://www.companyhelm.com/)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
Dependecies:
|
|
11
|
+
- Docker
|
|
12
|
+
- Node.js `>=24`
|
|
13
|
+
- Codex subscription or api key
|
|
14
|
+
- Github account
|
|
8
15
|
|
|
9
16
|
```bash
|
|
10
17
|
npx @companyhelm/cli up
|
|
11
18
|
```
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
After startup, the CLI prints:
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```
|
|
22
|
+
- the local dashboard UI URL
|
|
23
|
+
- the generated username and password
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
## What CompanyHelm is
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
npx @companyhelm/cli status
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Show logs for one managed service:
|
|
27
|
+
From the product perspective, CompanyHelm is built around a few core ideas:
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
- Your infrastructure, not a vendor-controlled runtime
|
|
30
|
+
- Model-agnostic agent execution through open runners and protocols
|
|
31
|
+
- Easy agent customization: add skills, MCP servers, and custom instructions to agents from the UI
|
|
32
|
+
- Human-in-the-loop workflows: tasks can be steered at any moment through the built-in chat; approvals and questions are still a work in progress
|
|
33
|
+
- Isolated execution so agents can work in parallel with minimal interference. Each runner can spin up the full app infrastructure within Docker and test in isolation
|
|
34
|
+
- YOLO mode by default: agents run commands without pausing for trivial confirmations
|
|
35
|
+
- Remote repository as the source of truth: agents clone the repo and submit PRs automatically
|
|
36
|
+
- Parallel task execution: multiple agents can execute tasks independently in isolated environments
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
## What the CLI boots locally
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
- `api`
|
|
35
|
-
- `frontend`
|
|
36
|
-
- `runner`
|
|
40
|
+
`npx @companyhelm/cli up` brings up a local CompanyHelm stack with:
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
- CompanyHelm API
|
|
43
|
+
- Postgres
|
|
44
|
+
- CompanyHelm frontend
|
|
45
|
+
- CompanyHelm agent runner
|
|
39
46
|
|
|
40
|
-
```bash
|
|
41
|
-
npx @companyhelm/cli reset --force
|
|
42
|
-
```
|
|
43
47
|
|
|
44
|
-
##
|
|
48
|
+
## Command reference
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
For the full CLI help:
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
```bash
|
|
53
|
+
npx @companyhelm/cli --help
|
|
54
|
+
```
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
Common commands:
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
```bash
|
|
59
|
+
npx @companyhelm/cli logs {service]
|
|
60
|
+
npx @companyhelm/cli reset
|
|
61
|
+
```
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { buildProgram } from "./commands/register-commands.js";
|
|
2
|
+
import { InteractiveCommandCancelledError } from "./commands/interactive.js";
|
|
2
3
|
export async function main(argv = process.argv) {
|
|
3
4
|
const program = buildProgram();
|
|
4
|
-
|
|
5
|
+
try {
|
|
6
|
+
await program.parseAsync(argv);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
if (error instanceof InteractiveCommandCancelledError) {
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
5
15
|
}
|
|
6
16
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
7
17
|
void main();
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
import { type RuntimeVersions } from "../core/runtime/VersionCatalog.js";
|
|
1
2
|
import { type StatusSnapshot } from "../core/status/StatusService.js";
|
|
3
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
4
|
+
export type LocalRepoOptionValue = string | true | undefined;
|
|
5
|
+
export interface UpOptions {
|
|
6
|
+
logLevel?: LogLevel;
|
|
7
|
+
useHostDockerRuntime?: boolean;
|
|
8
|
+
apiRepoPath?: LocalRepoOptionValue;
|
|
9
|
+
webRepoPath?: LocalRepoOptionValue;
|
|
10
|
+
}
|
|
11
|
+
export interface ResetOptions {
|
|
12
|
+
removeGithubAppConfig?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface StatusReport {
|
|
15
|
+
services: StatusSnapshot;
|
|
16
|
+
apiUrl?: string;
|
|
17
|
+
uiUrl?: string;
|
|
18
|
+
username?: string;
|
|
19
|
+
versions?: RuntimeVersions;
|
|
20
|
+
}
|
|
2
21
|
export interface CommandDependencies {
|
|
3
|
-
up(): Promise<void>;
|
|
22
|
+
up(options?: UpOptions): Promise<void>;
|
|
4
23
|
down(): Promise<void>;
|
|
5
|
-
status(): Promise<
|
|
24
|
+
status(): Promise<StatusReport>;
|
|
6
25
|
logs(service: string): Promise<void>;
|
|
7
|
-
reset(): Promise<void>;
|
|
26
|
+
reset(options?: ResetOptions): Promise<void>;
|
|
8
27
|
}
|
|
9
28
|
export declare function createDefaultDependencies(): CommandDependencies;
|
|
@@ -2,15 +2,24 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { DeploymentBootstrapper } from "../core/bootstrap/DeploymentBootstrapper.js";
|
|
5
|
+
import { ApiEnvFileWriter } from "../core/config/ApiEnvFileWriter.js";
|
|
6
|
+
import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
|
|
5
7
|
import { DockerStackManager } from "../core/docker/DockerStackManager.js";
|
|
8
|
+
import { ApiLocalService } from "../core/local/ApiLocalService.js";
|
|
9
|
+
import { LocalRepoSourceResolver } from "../core/local/LocalRepoSourceResolver.js";
|
|
10
|
+
import { LocalServiceProcessManager } from "../core/local/LocalServiceProcessManager.js";
|
|
11
|
+
import { WebLocalService } from "../core/local/WebLocalService.js";
|
|
6
12
|
import { LogsService } from "../core/logs/LogsService.js";
|
|
7
13
|
import { CommandRunner } from "../core/process/CommandRunner.js";
|
|
14
|
+
import { ProjectPaths } from "../core/runtime/ProjectPaths.js";
|
|
8
15
|
import { RunnerSupervisor } from "../core/runner/RunnerSupervisor.js";
|
|
9
16
|
import { createPasswordHash } from "../core/runtime/Secrets.js";
|
|
10
17
|
import { RuntimePaths } from "../core/runtime/RuntimePaths.js";
|
|
11
18
|
import { RuntimeStateStore } from "../core/runtime/RuntimeStateStore.js";
|
|
19
|
+
import { VersionCatalog } from "../core/runtime/VersionCatalog.js";
|
|
12
20
|
import { StatusService } from "../core/status/StatusService.js";
|
|
13
21
|
import { TerminalRenderer } from "../core/ui/TerminalRenderer.js";
|
|
22
|
+
import { ensureGithubAppConfig } from "./setup-github-app.js";
|
|
14
23
|
function runtimeRoot() {
|
|
15
24
|
return process.env.COMPANYHELM_HOME || path.join(os.homedir(), ".companyhelm");
|
|
16
25
|
}
|
|
@@ -23,7 +32,38 @@ export function createDefaultDependencies() {
|
|
|
23
32
|
const dockerStackManager = new DockerStackManager(root, commandRunner);
|
|
24
33
|
const runnerSupervisor = new RunnerSupervisor(runtimePaths.runnerConfigPath());
|
|
25
34
|
const bootstrapper = new DeploymentBootstrapper();
|
|
26
|
-
const
|
|
35
|
+
const githubAppConfigStore = new GithubAppConfigStore();
|
|
36
|
+
const apiEnvFileWriter = new ApiEnvFileWriter(process.cwd());
|
|
37
|
+
const projectPaths = new ProjectPaths(process.cwd());
|
|
38
|
+
const localRepoSourceResolver = new LocalRepoSourceResolver(process.cwd());
|
|
39
|
+
const localServiceProcessManager = new LocalServiceProcessManager();
|
|
40
|
+
const apiLocalService = new ApiLocalService(localServiceProcessManager, commandRunner);
|
|
41
|
+
const webLocalService = new WebLocalService(localServiceProcessManager, commandRunner);
|
|
42
|
+
const versionCatalog = new VersionCatalog();
|
|
43
|
+
const statusService = new StatusService(() => dockerStackManager.runningServices(), {
|
|
44
|
+
api: () => {
|
|
45
|
+
const state = stateStore.load();
|
|
46
|
+
return state?.services.api.source === "local"
|
|
47
|
+
? localServiceProcessManager.isRunning(state.services.api)
|
|
48
|
+
: undefined;
|
|
49
|
+
},
|
|
50
|
+
frontend: () => {
|
|
51
|
+
const state = stateStore.load();
|
|
52
|
+
return state?.services.frontend.source === "local"
|
|
53
|
+
? localServiceProcessManager.isRunning(state.services.frontend)
|
|
54
|
+
: undefined;
|
|
55
|
+
},
|
|
56
|
+
runner: async () => {
|
|
57
|
+
try {
|
|
58
|
+
const statusCommand = runnerSupervisor.buildStatusArgs();
|
|
59
|
+
const output = await commandRunner.capture(statusCommand.command, statusCommand.args);
|
|
60
|
+
return output.includes("Daemon: running");
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
27
67
|
const logsService = new LogsService(async (service) => {
|
|
28
68
|
if (service === "runner") {
|
|
29
69
|
if (fs.existsSync(runtimePaths.runnerLogPath())) {
|
|
@@ -31,34 +71,140 @@ export function createDefaultDependencies() {
|
|
|
31
71
|
}
|
|
32
72
|
return;
|
|
33
73
|
}
|
|
74
|
+
const state = stateStore.load();
|
|
75
|
+
if (service === "api" && state?.services.api.source === "local") {
|
|
76
|
+
localServiceProcessManager.printLogs(state.services.api);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (service === "frontend" && state?.services.frontend.source === "local") {
|
|
80
|
+
localServiceProcessManager.printLogs(state.services.frontend);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
34
83
|
await dockerStackManager.logs(service);
|
|
35
84
|
});
|
|
36
85
|
return {
|
|
37
|
-
async up() {
|
|
86
|
+
async up(options = {}) {
|
|
87
|
+
const logLevel = options.logLevel ?? "info";
|
|
88
|
+
const useHostDockerRuntime = options.useHostDockerRuntime ?? false;
|
|
89
|
+
const githubAppConfig = await ensureGithubAppConfig(githubAppConfigStore, process.stdin, process.stdout);
|
|
38
90
|
const state = stateStore.initialize();
|
|
91
|
+
process.stdout.write(`${renderer.renderBanner()}\n`);
|
|
92
|
+
const runnerAlreadyRunning = await isRunnerRunning(commandRunner, runnerSupervisor);
|
|
93
|
+
const desiredSources = localRepoSourceResolver.resolve(options);
|
|
94
|
+
const versions = versionCatalog.resolve();
|
|
39
95
|
const passwordRecord = createPasswordHash(state.auth.password);
|
|
96
|
+
const startedLocalServices = [];
|
|
97
|
+
let runnerStarted = false;
|
|
40
98
|
fs.mkdirSync(root, { recursive: true });
|
|
99
|
+
fs.mkdirSync(runtimePaths.serviceRuntimePath(), { recursive: true });
|
|
100
|
+
apiEnvFileWriter.write(githubAppConfig);
|
|
41
101
|
bootstrapper.writeSeedSql(root, state, passwordRecord.passwordHash, passwordRecord.passwordSalt);
|
|
42
|
-
bootstrapper.writeApiConfig(root, state
|
|
43
|
-
|
|
44
|
-
process.stdout.write(`${renderer.renderBanner()}\n`);
|
|
45
|
-
await dockerStackManager.up(state);
|
|
46
|
-
await dockerStackManager.applySeedSql();
|
|
47
|
-
const startCommand = runnerSupervisor.buildStartArgs({
|
|
48
|
-
serverUrl: `127.0.0.1:${state.ports.runnerGrpc}`,
|
|
49
|
-
agentApiUrl: `127.0.0.1:${state.ports.agentCliGrpc}`,
|
|
50
|
-
logPath: runtimePaths.runnerLogPath(),
|
|
51
|
-
secret: state.runner.secret
|
|
102
|
+
bootstrapper.writeApiConfig(root, state, logLevel, {
|
|
103
|
+
databaseHost: desiredSources.api.source === "local" ? "127.0.0.1" : "postgres"
|
|
52
104
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
105
|
+
bootstrapper.writeFrontendConfig(root, state);
|
|
106
|
+
await stopLocalServicesFromState(stateStore.load(), localServiceProcessManager);
|
|
107
|
+
try {
|
|
108
|
+
await dockerStackManager.up(state, {
|
|
109
|
+
frontendLogLevel: logLevel,
|
|
110
|
+
includeApi: desiredSources.api.source === "docker",
|
|
111
|
+
includeFrontend: desiredSources.frontend.source === "docker",
|
|
112
|
+
exposePostgresPort: desiredSources.api.source === "local"
|
|
113
|
+
});
|
|
114
|
+
const apiUrl = `http://127.0.0.1:${state.ports.apiHttp}/graphql`;
|
|
115
|
+
const uiUrl = `http://127.0.0.1:${state.ports.ui}`;
|
|
116
|
+
if (desiredSources.api.source === "local") {
|
|
117
|
+
process.stdout.write(`${renderer.progress(`Starting companyhelm-api from ${desiredSources.api.repoPath}...`)}\n`);
|
|
118
|
+
state.services.api = await apiLocalService.start({
|
|
119
|
+
repoPath: desiredSources.api.repoPath,
|
|
120
|
+
configPath: runtimePaths.apiConfigPath(),
|
|
121
|
+
graphqlUrl: apiUrl,
|
|
122
|
+
logPath: runtimePaths.serviceLogPath("api"),
|
|
123
|
+
githubAppConfig,
|
|
124
|
+
state,
|
|
125
|
+
logLevel
|
|
126
|
+
});
|
|
127
|
+
startedLocalServices.push("api");
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
state.services.api = { source: "docker" };
|
|
131
|
+
}
|
|
132
|
+
process.stdout.write(`${renderer.progress("Initializing the database...")}\n`);
|
|
133
|
+
process.stdout.write(`${renderer.progress("Waiting for database migrations...")}\n`);
|
|
134
|
+
await dockerStackManager.applySeedSql(state.auth.username);
|
|
135
|
+
if (runnerAlreadyRunning) {
|
|
136
|
+
process.stdout.write(`${renderer.progress("Runner already running; skipping startup.")}\n`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const configureSdkCommand = runnerSupervisor.buildUseHostAuthArgs();
|
|
140
|
+
process.stdout.write(`${renderer.progress("Configuring runner authentication...")}\n`);
|
|
141
|
+
await commandRunner.run(configureSdkCommand.command, configureSdkCommand.args);
|
|
142
|
+
const startCommand = runnerSupervisor.buildStartArgs({
|
|
143
|
+
serverUrl: `127.0.0.1:${state.ports.runnerGrpc}`,
|
|
144
|
+
agentApiUrl: `127.0.0.1:${state.ports.agentCliGrpc}`,
|
|
145
|
+
logPath: runtimePaths.runnerLogPath(),
|
|
146
|
+
secret: state.runner.secret,
|
|
147
|
+
logLevel,
|
|
148
|
+
useHostDockerRuntime
|
|
149
|
+
});
|
|
150
|
+
process.stdout.write(`${renderer.progress("Starting the runner...")}\n`);
|
|
151
|
+
await commandRunner.run(startCommand.command, startCommand.args);
|
|
152
|
+
runnerStarted = true;
|
|
153
|
+
}
|
|
154
|
+
if (desiredSources.frontend.source === "local") {
|
|
155
|
+
process.stdout.write(`${renderer.progress(`Starting companyhelm-web from ${desiredSources.frontend.repoPath}...`)}\n`);
|
|
156
|
+
state.services.frontend = await webLocalService.start({
|
|
157
|
+
repoPath: desiredSources.frontend.repoPath,
|
|
158
|
+
configPath: runtimePaths.frontendConfigPath(),
|
|
159
|
+
url: uiUrl,
|
|
160
|
+
uiPort: state.ports.ui,
|
|
161
|
+
logPath: runtimePaths.serviceLogPath("frontend"),
|
|
162
|
+
logLevel
|
|
163
|
+
});
|
|
164
|
+
startedLocalServices.push("frontend");
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
state.services.frontend = { source: "docker" };
|
|
168
|
+
}
|
|
169
|
+
stateStore.persist(state);
|
|
170
|
+
process.stdout.write(`${renderer.success(`API ready: ${apiUrl}`)}\n`);
|
|
171
|
+
process.stdout.write(`CompanyHelm CLI: ${versions.cliPackage}\n`);
|
|
172
|
+
process.stdout.write(`Runner package: ${versions.runnerPackage}\n`);
|
|
173
|
+
process.stdout.write(desiredSources.api.source === "local"
|
|
174
|
+
? `API repo: ${desiredSources.api.repoPath}\n`
|
|
175
|
+
: `API image: ${versions.images.api}\n`);
|
|
176
|
+
process.stdout.write(desiredSources.frontend.source === "local"
|
|
177
|
+
? `companyhelm-web repo: ${desiredSources.frontend.repoPath}\n`
|
|
178
|
+
: `companyhelm-web image: ${versions.images.frontend}\n`);
|
|
179
|
+
process.stdout.write(`Postgres image: ${versions.images.postgres}\n`);
|
|
180
|
+
process.stdout.write(`\n${renderer.success("CompanyHelm started successfully.")}\n`);
|
|
181
|
+
process.stdout.write(`${renderer.successHighlight("UI URL")}\n`);
|
|
182
|
+
process.stdout.write(`${renderer.clickableUrl(uiUrl)}\n`);
|
|
183
|
+
process.stdout.write(`${renderer.successHighlight("Login credentials")}\n`);
|
|
184
|
+
process.stdout.write(`username: ${state.auth.username}\n`);
|
|
185
|
+
process.stdout.write(`password: ${state.auth.password}\n`);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (runnerStarted) {
|
|
189
|
+
const stopCommand = runnerSupervisor.buildStopArgs();
|
|
190
|
+
try {
|
|
191
|
+
await commandRunner.run(stopCommand.command, stopCommand.args);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// Ignore runner stop failures during cleanup.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const service of startedLocalServices.reverse()) {
|
|
198
|
+
const runtime = service === "api" ? state.services.api : state.services.frontend;
|
|
199
|
+
if (runtime.source === "local") {
|
|
200
|
+
await localServiceProcessManager.stop(runtime);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
await dockerStackManager.down();
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
57
206
|
},
|
|
58
207
|
async down() {
|
|
59
|
-
if (!stateStore.load()) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
208
|
const stopCommand = runnerSupervisor.buildStopArgs();
|
|
63
209
|
try {
|
|
64
210
|
await commandRunner.run(stopCommand.command, stopCommand.args);
|
|
@@ -66,17 +212,66 @@ export function createDefaultDependencies() {
|
|
|
66
212
|
catch {
|
|
67
213
|
// Ignore runner stop failures during teardown so container cleanup still happens.
|
|
68
214
|
}
|
|
215
|
+
const state = stateStore.load();
|
|
216
|
+
if (!state) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
await stopLocalServicesFromState(state, localServiceProcessManager);
|
|
69
220
|
await dockerStackManager.down();
|
|
70
221
|
},
|
|
71
|
-
status() {
|
|
72
|
-
|
|
222
|
+
async status() {
|
|
223
|
+
const services = await statusService.read();
|
|
224
|
+
const state = stateStore.load();
|
|
225
|
+
return {
|
|
226
|
+
services,
|
|
227
|
+
apiUrl: state ? `http://127.0.0.1:${state.ports.apiHttp}/graphql` : undefined,
|
|
228
|
+
uiUrl: state ? `http://127.0.0.1:${state.ports.ui}` : undefined,
|
|
229
|
+
username: state?.auth.username,
|
|
230
|
+
versions: versionCatalog.resolve()
|
|
231
|
+
};
|
|
73
232
|
},
|
|
74
233
|
logs(service) {
|
|
75
234
|
return logsService.stream(service);
|
|
76
235
|
},
|
|
77
|
-
async reset() {
|
|
78
|
-
|
|
236
|
+
async reset(options = {}) {
|
|
237
|
+
const state = stateStore.load();
|
|
238
|
+
if (state) {
|
|
239
|
+
const stopCommand = runnerSupervisor.buildStopArgs();
|
|
240
|
+
try {
|
|
241
|
+
await commandRunner.run(stopCommand.command, stopCommand.args);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// Ignore runner stop failures during teardown so docker cleanup still runs.
|
|
245
|
+
}
|
|
246
|
+
await stopLocalServicesFromState(state, localServiceProcessManager);
|
|
247
|
+
}
|
|
248
|
+
await dockerStackManager.down({ removeVolumes: true });
|
|
249
|
+
fs.rmSync(projectPaths.apiEnvPath(), { force: true });
|
|
79
250
|
fs.rmSync(root, { recursive: true, force: true });
|
|
251
|
+
if (options.removeGithubAppConfig) {
|
|
252
|
+
githubAppConfigStore.delete();
|
|
253
|
+
}
|
|
80
254
|
}
|
|
81
255
|
};
|
|
82
256
|
}
|
|
257
|
+
async function stopLocalServicesFromState(state, processManager) {
|
|
258
|
+
if (!state) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (state.services.api.source === "local") {
|
|
262
|
+
await processManager.stop(state.services.api);
|
|
263
|
+
}
|
|
264
|
+
if (state.services.frontend.source === "local") {
|
|
265
|
+
await processManager.stop(state.services.frontend);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function isRunnerRunning(commandRunner, runnerSupervisor) {
|
|
269
|
+
try {
|
|
270
|
+
const statusCommand = runnerSupervisor.buildStatusArgs();
|
|
271
|
+
const output = await commandRunner.capture(statusCommand.command, statusCommand.args);
|
|
272
|
+
return output.includes("Daemon: running");
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
export declare class InteractiveCommandCancelledError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function requireInteractiveTerminal(input: Readable, output: Writable, message: string): void;
|
|
6
|
+
export declare function unwrapPromptResult<T>(value: T | symbol, message: string, output: Writable): T;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts";
|
|
2
|
+
export class InteractiveCommandCancelledError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "InteractiveCommandCancelledError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function isReadableTty(input) {
|
|
9
|
+
return "isTTY" in input && Boolean(input.isTTY);
|
|
10
|
+
}
|
|
11
|
+
export function requireInteractiveTerminal(input, output, message) {
|
|
12
|
+
if (!isReadableTty(input) || !clack.isTTY(output)) {
|
|
13
|
+
throw new Error(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function unwrapPromptResult(value, message, output) {
|
|
17
|
+
if (clack.isCancel(value)) {
|
|
18
|
+
clack.cancel(message, { output });
|
|
19
|
+
throw new InteractiveCommandCancelledError(message);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
package/dist/commands/logs.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
const AVAILABLE_LOG_SERVICES = ["postgres", "api", "frontend", "runner"];
|
|
1
2
|
export function registerLogsCommand(program, dependencies) {
|
|
2
3
|
program
|
|
3
4
|
.command("logs")
|
|
4
5
|
.description("Show logs for a managed service.")
|
|
5
|
-
.argument("
|
|
6
|
+
.argument("[service]")
|
|
6
7
|
.action(async (service) => {
|
|
8
|
+
if (!service) {
|
|
9
|
+
process.stdout.write(`Available services:\n${AVAILABLE_LOG_SERVICES.map((name) => `- ${name}`).join("\n")}\n`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
7
12
|
await dependencies.logs(service);
|
|
8
13
|
});
|
|
9
14
|
}
|
|
@@ -3,14 +3,18 @@ import { createDefaultDependencies } from "./dependencies.js";
|
|
|
3
3
|
import { registerDownCommand } from "./down.js";
|
|
4
4
|
import { registerLogsCommand } from "./logs.js";
|
|
5
5
|
import { registerResetCommand } from "./reset.js";
|
|
6
|
+
import { registerSetupGithubAppCommand } from "./setup-github-app.js";
|
|
7
|
+
import { registerSetImageVersionCommand } from "./set-image-version.js";
|
|
6
8
|
import { registerStatusCommand } from "./status.js";
|
|
7
9
|
import { registerUpCommand } from "./up.js";
|
|
8
10
|
export function buildProgram(dependencies = createDefaultDependencies()) {
|
|
9
11
|
const program = new Command().name("companyhelm");
|
|
12
|
+
registerSetupGithubAppCommand(program);
|
|
10
13
|
registerUpCommand(program, dependencies);
|
|
11
14
|
registerDownCommand(program, dependencies);
|
|
12
15
|
registerStatusCommand(program, dependencies);
|
|
13
16
|
registerLogsCommand(program, dependencies);
|
|
17
|
+
registerSetImageVersionCommand(program);
|
|
14
18
|
registerResetCommand(program, dependencies);
|
|
15
19
|
return program;
|
|
16
20
|
}
|
package/dist/commands/reset.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
1
2
|
import type { Command } from "commander";
|
|
3
|
+
import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
|
|
2
4
|
import type { CommandDependencies } from "./dependencies.js";
|
|
5
|
+
export declare function confirmReset(input?: Readable, output?: Writable): Promise<boolean>;
|
|
6
|
+
export declare function confirmRemoveGithubAppConfig(store?: GithubAppConfigStore, input?: Readable, output?: Writable): Promise<boolean>;
|
|
3
7
|
export declare function registerResetCommand(program: Command, dependencies: CommandDependencies): void;
|
package/dist/commands/reset.js
CHANGED
|
@@ -1,12 +1,51 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts";
|
|
2
|
+
import { GithubAppConfigStore } from "../core/config/GithubAppConfigStore.js";
|
|
3
|
+
import { requireInteractiveTerminal, unwrapPromptResult } from "./interactive.js";
|
|
4
|
+
export async function confirmReset(input = process.stdin, output = process.stdout) {
|
|
5
|
+
requireInteractiveTerminal(input, output, "reset requires confirmation from a TTY. Re-run with --yes to skip the prompt.");
|
|
6
|
+
const confirmed = await clack.confirm({
|
|
7
|
+
message: "This will remove CompanyHelm containers, Postgres data, local runtime state, and generated .companyhelm/api/.env. Continue?",
|
|
8
|
+
active: "Yes",
|
|
9
|
+
inactive: "No",
|
|
10
|
+
initialValue: false,
|
|
11
|
+
input,
|
|
12
|
+
output
|
|
13
|
+
});
|
|
14
|
+
return unwrapPromptResult(confirmed, "Reset cancelled.", output);
|
|
15
|
+
}
|
|
16
|
+
export async function confirmRemoveGithubAppConfig(store = new GithubAppConfigStore(), input = process.stdin, output = process.stdout) {
|
|
17
|
+
if (!store.hasConfig()) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
requireInteractiveTerminal(input, output, "reset requires confirmation from a TTY. Re-run with --yes to skip the prompt.");
|
|
21
|
+
const confirmed = await clack.confirm({
|
|
22
|
+
message: `Also remove the machine GitHub App config at ${store.configPath()}?`,
|
|
23
|
+
active: "Remove it",
|
|
24
|
+
inactive: "Keep it",
|
|
25
|
+
initialValue: false,
|
|
26
|
+
input,
|
|
27
|
+
output
|
|
28
|
+
});
|
|
29
|
+
return unwrapPromptResult(confirmed, "Reset cancelled.", output);
|
|
30
|
+
}
|
|
1
31
|
export function registerResetCommand(program, dependencies) {
|
|
2
32
|
program
|
|
3
33
|
.command("reset")
|
|
4
34
|
.description("Destroy the local deployment state.")
|
|
5
|
-
.option("--
|
|
35
|
+
.option("-y, --yes", "Skip the confirmation prompt.")
|
|
36
|
+
.option("--remove-github-app-config", "Also remove the machine GitHub App config.")
|
|
6
37
|
.action(async (options) => {
|
|
7
|
-
|
|
8
|
-
|
|
38
|
+
let removeGithubAppConfig = Boolean(options.removeGithubAppConfig);
|
|
39
|
+
if (!options.yes) {
|
|
40
|
+
const confirmed = await confirmReset();
|
|
41
|
+
if (!confirmed) {
|
|
42
|
+
clack.cancel("Reset cancelled.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!removeGithubAppConfig) {
|
|
46
|
+
removeGithubAppConfig = await confirmRemoveGithubAppConfig();
|
|
47
|
+
}
|
|
9
48
|
}
|
|
10
|
-
await dependencies.reset();
|
|
49
|
+
await dependencies.reset({ removeGithubAppConfig });
|
|
11
50
|
});
|
|
12
51
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { type ManagedImageService } from "../core/runtime/ManagedImages.js";
|
|
4
|
+
export interface SetImageVersionOptions {
|
|
5
|
+
service?: string;
|
|
6
|
+
limit: number;
|
|
7
|
+
}
|
|
8
|
+
export interface AvailableImageTag {
|
|
9
|
+
tag: string;
|
|
10
|
+
createdAt?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface InteractiveImageSelector {
|
|
13
|
+
listAvailableTags(service: ManagedImageService, limit: number): Promise<AvailableImageTag[]>;
|
|
14
|
+
buildImageReference(service: ManagedImageService, tag: string): string;
|
|
15
|
+
}
|
|
16
|
+
export interface ImageConfigStore {
|
|
17
|
+
load(): {
|
|
18
|
+
images: Partial<Record<ManagedImageService, string>>;
|
|
19
|
+
};
|
|
20
|
+
setImage(service: ManagedImageService, image: string): {
|
|
21
|
+
configPath: string;
|
|
22
|
+
image: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare function runSetImageVersion(options: SetImageVersionOptions, dependencies?: {
|
|
26
|
+
input?: Readable;
|
|
27
|
+
output?: Writable;
|
|
28
|
+
registry?: InteractiveImageSelector;
|
|
29
|
+
configStore?: ImageConfigStore;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
export declare function registerSetImageVersionCommand(program: Command): void;
|