@getforgeai/cli 0.1.0-beta.1
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 +201 -0
- package/README.md +80 -0
- package/dist/bin/forgeai.d.ts +3 -0
- package/dist/bin/forgeai.js +8 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +180 -0
- package/dist/commands/auth-login.d.ts +4 -0
- package/dist/commands/auth-login.js +202 -0
- package/dist/commands/auth-logout.d.ts +2 -0
- package/dist/commands/auth-logout.js +41 -0
- package/dist/commands/auth-status.d.ts +4 -0
- package/dist/commands/auth-status.js +81 -0
- package/dist/commands/cli-list.d.ts +4 -0
- package/dist/commands/cli-list.js +40 -0
- package/dist/commands/cli-unlink.d.ts +4 -0
- package/dist/commands/cli-unlink.js +45 -0
- package/dist/commands/config-set.d.ts +5 -0
- package/dist/commands/config-set.js +63 -0
- package/dist/commands/connect.d.ts +9 -0
- package/dist/commands/connect.js +165 -0
- package/dist/commands/disconnect.d.ts +5 -0
- package/dist/commands/disconnect.js +126 -0
- package/dist/commands/journal-clear.d.ts +2 -0
- package/dist/commands/journal-clear.js +16 -0
- package/dist/commands/journal-list.d.ts +2 -0
- package/dist/commands/journal-list.js +20 -0
- package/dist/commands/journal-status.d.ts +2 -0
- package/dist/commands/journal-status.js +18 -0
- package/dist/commands/mcp-serve.d.ts +13 -0
- package/dist/commands/mcp-serve.js +74 -0
- package/dist/commands/org-list.d.ts +4 -0
- package/dist/commands/org-list.js +56 -0
- package/dist/commands/org-unlink.d.ts +4 -0
- package/dist/commands/org-unlink.js +63 -0
- package/dist/commands/reconnect.d.ts +4 -0
- package/dist/commands/reconnect.js +46 -0
- package/dist/commands/release-open.d.ts +2 -0
- package/dist/commands/release-open.js +43 -0
- package/dist/commands/session-respond.d.ts +2 -0
- package/dist/commands/session-respond.js +29 -0
- package/dist/commands/task-open.d.ts +2 -0
- package/dist/commands/task-open.js +42 -0
- package/dist/commands/vm.d.ts +21 -0
- package/dist/commands/vm.js +122 -0
- package/dist/commands/workflow-open.d.ts +2 -0
- package/dist/commands/workflow-open.js +43 -0
- package/dist/config/config-manager.d.ts +31 -0
- package/dist/config/config-manager.js +70 -0
- package/dist/config/token-manager.d.ts +11 -0
- package/dist/config/token-manager.js +87 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +22 -0
- package/dist/lib/auth-client.d.ts +1670 -0
- package/dist/lib/auth-client.js +10 -0
- package/dist/lib/branch-manager.d.ts +79 -0
- package/dist/lib/branch-manager.js +620 -0
- package/dist/lib/clone-and-checkout.d.ts +6 -0
- package/dist/lib/clone-and-checkout.js +41 -0
- package/dist/lib/connection-manager.d.ts +58 -0
- package/dist/lib/connection-manager.js +436 -0
- package/dist/lib/daemon-entry.d.ts +2 -0
- package/dist/lib/daemon-entry.js +210 -0
- package/dist/lib/daemon.d.ts +38 -0
- package/dist/lib/daemon.js +148 -0
- package/dist/lib/event-journal.d.ts +24 -0
- package/dist/lib/event-journal.js +76 -0
- package/dist/lib/ipc-client.d.ts +10 -0
- package/dist/lib/ipc-client.js +70 -0
- package/dist/lib/ipc-server.d.ts +36 -0
- package/dist/lib/ipc-server.js +70 -0
- package/dist/lib/node-version-guard.d.ts +8 -0
- package/dist/lib/node-version-guard.js +60 -0
- package/dist/lib/open-directory.d.ts +7 -0
- package/dist/lib/open-directory.js +41 -0
- package/dist/lib/repo-manager.d.ts +29 -0
- package/dist/lib/repo-manager.js +103 -0
- package/dist/lib/resilient-emitter.d.ts +53 -0
- package/dist/lib/resilient-emitter.js +160 -0
- package/dist/lib/rest-client.d.ts +164 -0
- package/dist/lib/rest-client.js +139 -0
- package/dist/lib/skill-manager.d.ts +23 -0
- package/dist/lib/skill-manager.js +167 -0
- package/dist/lib/socket-client.d.ts +68 -0
- package/dist/lib/socket-client.js +160 -0
- package/dist/mcp/mcp-probe.d.ts +11 -0
- package/dist/mcp/mcp-probe.js +106 -0
- package/dist/mcp/mcp-response.d.ts +29 -0
- package/dist/mcp/mcp-response.js +67 -0
- package/dist/mcp/mcp-server.d.ts +19 -0
- package/dist/mcp/mcp-server.js +76 -0
- package/dist/mcp/resources/project-conventions.d.ts +4 -0
- package/dist/mcp/resources/project-conventions.js +50 -0
- package/dist/mcp/resources/task-current.d.ts +4 -0
- package/dist/mcp/resources/task-current.js +51 -0
- package/dist/mcp/tools/forge-add-group-dependency.d.ts +4 -0
- package/dist/mcp/tools/forge-add-group-dependency.js +20 -0
- package/dist/mcp/tools/forge-add-task-dependency.d.ts +4 -0
- package/dist/mcp/tools/forge-add-task-dependency.js +20 -0
- package/dist/mcp/tools/forge-ask-human.d.ts +4 -0
- package/dist/mcp/tools/forge-ask-human.js +80 -0
- package/dist/mcp/tools/forge-column-done.d.ts +4 -0
- package/dist/mcp/tools/forge-column-done.js +46 -0
- package/dist/mcp/tools/forge-complete-step.d.ts +4 -0
- package/dist/mcp/tools/forge-complete-step.js +139 -0
- package/dist/mcp/tools/forge-create-task-group.d.ts +4 -0
- package/dist/mcp/tools/forge-create-task-group.js +22 -0
- package/dist/mcp/tools/forge-create-task.d.ts +4 -0
- package/dist/mcp/tools/forge-create-task.js +28 -0
- package/dist/mcp/tools/forge-delete-task-group.d.ts +4 -0
- package/dist/mcp/tools/forge-delete-task-group.js +17 -0
- package/dist/mcp/tools/forge-delete-task.d.ts +4 -0
- package/dist/mcp/tools/forge-delete-task.js +17 -0
- package/dist/mcp/tools/forge-get-task.d.ts +4 -0
- package/dist/mcp/tools/forge-get-task.js +17 -0
- package/dist/mcp/tools/forge-merge-back.d.ts +4 -0
- package/dist/mcp/tools/forge-merge-back.js +179 -0
- package/dist/mcp/tools/forge-release-merge.d.ts +4 -0
- package/dist/mcp/tools/forge-release-merge.js +159 -0
- package/dist/mcp/tools/forge-release-notes.d.ts +4 -0
- package/dist/mcp/tools/forge-release-notes.js +51 -0
- package/dist/mcp/tools/forge-release-tag.d.ts +4 -0
- package/dist/mcp/tools/forge-release-tag.js +125 -0
- package/dist/mcp/tools/forge-remove-group-dependency.d.ts +4 -0
- package/dist/mcp/tools/forge-remove-group-dependency.js +19 -0
- package/dist/mcp/tools/forge-remove-task-dependency.d.ts +4 -0
- package/dist/mcp/tools/forge-remove-task-dependency.js +19 -0
- package/dist/mcp/tools/forge-requeue.d.ts +4 -0
- package/dist/mcp/tools/forge-requeue.js +39 -0
- package/dist/mcp/tools/forge-submit-pr.d.ts +4 -0
- package/dist/mcp/tools/forge-submit-pr.js +68 -0
- package/dist/mcp/tools/forge-update-status.d.ts +4 -0
- package/dist/mcp/tools/forge-update-status.js +77 -0
- package/dist/mcp/tools/forge-update-task-group.d.ts +4 -0
- package/dist/mcp/tools/forge-update-task-group.js +21 -0
- package/dist/mcp/tools/forge-update-task.d.ts +4 -0
- package/dist/mcp/tools/forge-update-task.js +25 -0
- package/dist/mcp/tools/forge-validate-merge.d.ts +4 -0
- package/dist/mcp/tools/forge-validate-merge.js +194 -0
- package/dist/mcp/types.d.ts +39 -0
- package/dist/mcp/types.js +8 -0
- package/dist/methodology/artifact-manager.d.ts +14 -0
- package/dist/methodology/artifact-manager.js +55 -0
- package/dist/orchestrator/agent-registry.d.ts +53 -0
- package/dist/orchestrator/agent-registry.js +96 -0
- package/dist/orchestrator/agent-spawner.d.ts +112 -0
- package/dist/orchestrator/agent-spawner.js +1518 -0
- package/dist/orchestrator/connectors/claude-code-connector.d.ts +3 -0
- package/dist/orchestrator/connectors/claude-code-connector.js +60 -0
- package/dist/orchestrator/connectors/connector.d.ts +95 -0
- package/dist/orchestrator/connectors/connector.js +25 -0
- package/dist/orchestrator/connectors/docker-connector.d.ts +14 -0
- package/dist/orchestrator/connectors/docker-connector.js +337 -0
- package/dist/orchestrator/connectors/native-vm-connector.d.ts +11 -0
- package/dist/orchestrator/connectors/native-vm-connector.js +162 -0
- package/dist/orchestrator/connectors/opencode-connector.d.ts +3 -0
- package/dist/orchestrator/connectors/opencode-connector.js +39 -0
- package/dist/orchestrator/connectors/vm-connector.d.ts +3 -0
- package/dist/orchestrator/connectors/vm-connector.js +139 -0
- package/dist/orchestrator/dispatch-queue.d.ts +43 -0
- package/dist/orchestrator/dispatch-queue.js +93 -0
- package/dist/orchestrator/resume-handler.d.ts +34 -0
- package/dist/orchestrator/resume-handler.js +159 -0
- package/dist/orchestrator/stream-batcher.d.ts +16 -0
- package/dist/orchestrator/stream-batcher.js +50 -0
- package/dist/orchestrator/stream-json-extractor.d.ts +97 -0
- package/dist/orchestrator/stream-json-extractor.js +579 -0
- package/dist/vm/claude-token-manager.d.ts +36 -0
- package/dist/vm/claude-token-manager.js +223 -0
- package/dist/vm/docker-executor.d.ts +131 -0
- package/dist/vm/docker-executor.js +360 -0
- package/dist/vm/docker-process-proxy.d.ts +31 -0
- package/dist/vm/docker-process-proxy.js +105 -0
- package/dist/vm/forge-vm-agent.d.ts +11 -0
- package/dist/vm/forge-vm-agent.js +159 -0
- package/dist/vm/git-credentials.d.ts +43 -0
- package/dist/vm/git-credentials.js +53 -0
- package/dist/vm/image-manager.d.ts +7 -0
- package/dist/vm/image-manager.js +61 -0
- package/dist/vm/log.d.ts +9 -0
- package/dist/vm/log.js +18 -0
- package/dist/vm/mcp-bridge.d.ts +63 -0
- package/dist/vm/mcp-bridge.js +243 -0
- package/dist/vm/mcp-stub.d.ts +15 -0
- package/dist/vm/mcp-stub.js +67 -0
- package/dist/vm/native-addon.d.ts +69 -0
- package/dist/vm/native-addon.js +68 -0
- package/dist/vm/provider-resolver.d.ts +7 -0
- package/dist/vm/provider-resolver.js +27 -0
- package/dist/vm/providers/lima.d.ts +17 -0
- package/dist/vm/providers/lima.js +191 -0
- package/dist/vm/providers/qemu.d.ts +28 -0
- package/dist/vm/providers/qemu.js +260 -0
- package/dist/vm/providers/vfkit.d.ts +40 -0
- package/dist/vm/providers/vfkit.js +371 -0
- package/dist/vm/providers/wsl2.d.ts +27 -0
- package/dist/vm/providers/wsl2.js +226 -0
- package/dist/vm/session-manager.d.ts +130 -0
- package/dist/vm/session-manager.js +494 -0
- package/dist/vm/session-snapshot.d.ts +31 -0
- package/dist/vm/session-snapshot.js +180 -0
- package/dist/vm/types.d.ts +73 -0
- package/dist/vm/types.js +2 -0
- package/dist/vm/vm-helper-client.d.ts +81 -0
- package/dist/vm/vm-helper-client.js +335 -0
- package/dist/vm/vm-helper.d.ts +11 -0
- package/dist/vm/vm-helper.js +156 -0
- package/dist/vm/vm-ipc-protocol.d.ts +84 -0
- package/dist/vm/vm-ipc-protocol.js +9 -0
- package/dist/vm/vm-manager-facade.d.ts +23 -0
- package/dist/vm/vm-manager-facade.js +43 -0
- package/dist/vm/vm-manager.d.ts +74 -0
- package/dist/vm/vm-manager.js +515 -0
- package/dist/vm/vm-process-proxy.d.ts +34 -0
- package/dist/vm/vm-process-proxy.js +64 -0
- package/dist/vm/vsock-bridge.d.ts +44 -0
- package/dist/vm/vsock-bridge.js +140 -0
- package/dist/vm/vsock-protocol.d.ts +98 -0
- package/dist/vm/vsock-protocol.js +57 -0
- package/package.json +65 -0
- package/rootfs/Dockerfile +50 -0
- package/rootfs/forge-mcp-relay.mjs +158 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfig, getServerUrl } from "../config/config-manager.js";
|
|
3
|
+
import { loadToken } from "../config/token-manager.js";
|
|
4
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
5
|
+
import { isDaemonRunning } from "../lib/daemon.js";
|
|
6
|
+
import { sendIpcCommand, DaemonNotRunningError } from "../lib/ipc-client.js";
|
|
7
|
+
export async function disconnectAction(options) {
|
|
8
|
+
// Check if daemon is running — if so, delegate via IPC
|
|
9
|
+
const daemonPid = isDaemonRunning();
|
|
10
|
+
if (daemonPid) {
|
|
11
|
+
return disconnectViaDaemon(options);
|
|
12
|
+
}
|
|
13
|
+
// No daemon running — fallback to REST-only behavior
|
|
14
|
+
return disconnectViaRest(options);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Print disconnect response orgs or generic message.
|
|
18
|
+
*/
|
|
19
|
+
function printDisconnectResponse(response) {
|
|
20
|
+
if ("orgs" in response && response.orgs) {
|
|
21
|
+
for (const org of response.orgs) {
|
|
22
|
+
console.log(chalk.green(`Disconnected from "${org.slug}" (daemon stays alive)`));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log(chalk.green("Disconnected (daemon stays alive)"));
|
|
27
|
+
}
|
|
28
|
+
console.log(chalk.cyan("Run `forge reconnect` to reconnect."));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Disconnect via IPC when daemon is running (AC #8).
|
|
32
|
+
*/
|
|
33
|
+
async function disconnectViaDaemon(options) {
|
|
34
|
+
try {
|
|
35
|
+
const response = await sendIpcCommand({
|
|
36
|
+
cmd: "disconnect",
|
|
37
|
+
org: options.org,
|
|
38
|
+
});
|
|
39
|
+
if (response.ok) {
|
|
40
|
+
printDisconnectResponse(response);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error(chalk.red(response.error));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof DaemonNotRunningError) {
|
|
48
|
+
// Race condition: daemon stopped between check and IPC call
|
|
49
|
+
console.log(chalk.yellow("Daemon stopped. No active connections to disconnect."));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error(chalk.red(`Disconnect failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve target organizations for disconnection.
|
|
58
|
+
*/
|
|
59
|
+
function resolveTargetOrgs(config, orgFlag) {
|
|
60
|
+
if (config.linkedOrgs && config.linkedOrgs.length > 0) {
|
|
61
|
+
if (orgFlag) {
|
|
62
|
+
// --org flag: disconnect from specific org only
|
|
63
|
+
const match = config.linkedOrgs.find((o) => o.orgSlug === orgFlag);
|
|
64
|
+
if (!match) {
|
|
65
|
+
console.error(chalk.red(`Organization "${orgFlag}" not found in linked orgs. Available: ${config.linkedOrgs.map((o) => o.orgSlug).join(", ")}`));
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return [match];
|
|
69
|
+
}
|
|
70
|
+
// No flag: disconnect from ALL linked orgs
|
|
71
|
+
return config.linkedOrgs;
|
|
72
|
+
}
|
|
73
|
+
if (config.defaultOrgId) {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
orgId: config.defaultOrgId,
|
|
77
|
+
orgSlug: config.defaultOrg ?? "default",
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* REST-only disconnect fallback when no daemon is running (AC #9).
|
|
85
|
+
* Preserves the original behavior from story 4-0c.
|
|
86
|
+
*/
|
|
87
|
+
async function disconnectViaRest(options) {
|
|
88
|
+
const token = loadToken();
|
|
89
|
+
if (!token) {
|
|
90
|
+
console.error(chalk.red("Not authenticated. Run `forge auth login` first."));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const config = getConfig();
|
|
94
|
+
const serverUrl = options.server ?? getServerUrl();
|
|
95
|
+
if (!config.cliId) {
|
|
96
|
+
console.error(chalk.red("No CLI ID found. Run `forge connect` first to register this CLI."));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Determine which orgs to disconnect from
|
|
100
|
+
const targetOrgs = resolveTargetOrgs(config, options.org);
|
|
101
|
+
if (!targetOrgs) {
|
|
102
|
+
console.error(chalk.red("No organization configured. Run `forge auth login` to select one."));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let successCount = 0;
|
|
106
|
+
let failCount = 0;
|
|
107
|
+
for (const org of targetOrgs) {
|
|
108
|
+
const restClient = createRestClient(serverUrl, token, org.orgId);
|
|
109
|
+
try {
|
|
110
|
+
await restClient.disconnectCli({ cliId: config.cliId });
|
|
111
|
+
console.log(chalk.green(`Disconnected from "${org.orgSlug}" (CLI remains registered)`));
|
|
112
|
+
successCount++;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(chalk.red(`Failed to disconnect from "${org.orgSlug}": ${error instanceof Error ? error.message : String(error)}`));
|
|
116
|
+
failCount++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (targetOrgs.length > 1) {
|
|
120
|
+
console.log(chalk.cyan(`\nDisconnect summary: ${successCount} succeeded, ${failCount} failed`));
|
|
121
|
+
}
|
|
122
|
+
if (successCount > 0) {
|
|
123
|
+
console.log(chalk.cyan("Run `forge connect` to reconnect."));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=disconnect.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { EventJournal } from "../lib/event-journal.js";
|
|
3
|
+
export function journalClearAction(projectId = "default") {
|
|
4
|
+
const id = projectId;
|
|
5
|
+
const journal = new EventJournal(id);
|
|
6
|
+
const cursor = journal.getCursor();
|
|
7
|
+
if (!cursor) {
|
|
8
|
+
console.log(chalk.gray(`No journal entries to clear for project "${id}"`));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const count = journal.readAll().length;
|
|
12
|
+
console.log(chalk.yellow(`Warning: clearing ${count} journal entries (events not yet synced to Cloud will be lost)`));
|
|
13
|
+
journal.clear(cursor);
|
|
14
|
+
console.log(chalk.green(`Cleared ${count} journal entries for project "${id}"`));
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=journal-clear.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { EventJournal } from "../lib/event-journal.js";
|
|
3
|
+
export function journalListAction(projectId = "default") {
|
|
4
|
+
// Default to orgId-scoped journal when no projectId specified (matches connect.ts)
|
|
5
|
+
const id = projectId;
|
|
6
|
+
const journal = new EventJournal(id);
|
|
7
|
+
const entries = journal.readAll();
|
|
8
|
+
if (entries.length === 0) {
|
|
9
|
+
console.log(chalk.gray(`No journal entries for project "${id}"`));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const pending = entries.filter((e) => !e.synced).length;
|
|
13
|
+
const lastEntry = entries.at(-1);
|
|
14
|
+
console.log(chalk.bold(`Journal: ${id}`));
|
|
15
|
+
console.log(` Total events: ${entries.length}`);
|
|
16
|
+
console.log(` Pending sync: ${pending}`);
|
|
17
|
+
console.log(` Last event: ${lastEntry.timestamp}`);
|
|
18
|
+
console.log(` Last event type: ${lastEntry.type}`);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=journal-list.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfig } from "../config/config-manager.js";
|
|
3
|
+
import { EventJournal } from "../lib/event-journal.js";
|
|
4
|
+
export function journalStatusAction() {
|
|
5
|
+
const config = getConfig();
|
|
6
|
+
// Use orgId-scoped journal (matches connect.ts behavior)
|
|
7
|
+
const journal = new EventJournal(config.defaultOrgId ?? "default");
|
|
8
|
+
const pendingCount = journal.pendingCount();
|
|
9
|
+
const cursor = journal.getCursor();
|
|
10
|
+
const lastTimestamp = journal.lastEventTimestamp();
|
|
11
|
+
console.log(chalk.bold("Sync Status"));
|
|
12
|
+
console.log(` Last sync: ${config.lastSyncTimestamp ?? chalk.gray("never")}`);
|
|
13
|
+
console.log(` Sync cursor: ${config.lastSyncCursor ?? chalk.gray("none")}`);
|
|
14
|
+
console.log(` Pending events: ${pendingCount}`);
|
|
15
|
+
console.log(` Journal cursor: ${cursor || chalk.gray("empty")}`);
|
|
16
|
+
console.log(` Last event at: ${lastTimestamp ?? chalk.gray("no events")}`);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=journal-status.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler for `forge mcp` command.
|
|
3
|
+
* Creates an MCP server with stdio transport, bridging the agent to Cloud.
|
|
4
|
+
* Each invocation is per-task — spawned by the agent process via MCP config.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mcpServeAction(options: {
|
|
7
|
+
taskId: string;
|
|
8
|
+
projectId: string;
|
|
9
|
+
orgId: string;
|
|
10
|
+
workflowId?: string;
|
|
11
|
+
stepId?: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=mcp-serve.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { getServerUrl } from "../config/config-manager.js";
|
|
2
|
+
import { loadToken } from "../config/token-manager.js";
|
|
3
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
4
|
+
import { createSocketClient } from "../lib/socket-client.js";
|
|
5
|
+
import { startMcpServer } from "../mcp/mcp-server.js";
|
|
6
|
+
/**
|
|
7
|
+
* Handler for `forge mcp` command.
|
|
8
|
+
* Creates an MCP server with stdio transport, bridging the agent to Cloud.
|
|
9
|
+
* Each invocation is per-task — spawned by the agent process via MCP config.
|
|
10
|
+
*/
|
|
11
|
+
export async function mcpServeAction(options) {
|
|
12
|
+
const { taskId, projectId, orgId, workflowId, stepId } = options;
|
|
13
|
+
let isShuttingDown = false;
|
|
14
|
+
// Global error handlers — log to stderr so failures are diagnosable.
|
|
15
|
+
// Without these, unhandled errors crash silently and the agent sees no MCP tools.
|
|
16
|
+
process.on("uncaughtException", (err) => {
|
|
17
|
+
if (isShuttingDown)
|
|
18
|
+
return;
|
|
19
|
+
isShuttingDown = true;
|
|
20
|
+
console.error(`[mcp] Uncaught exception: ${err.message}`);
|
|
21
|
+
console.error(err.stack);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
process.on("unhandledRejection", (reason) => {
|
|
25
|
+
if (isShuttingDown)
|
|
26
|
+
return;
|
|
27
|
+
isShuttingDown = true;
|
|
28
|
+
console.error(`[mcp] Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
29
|
+
if (reason instanceof Error && reason.stack)
|
|
30
|
+
console.error(reason.stack);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
const token = loadToken();
|
|
34
|
+
if (!token) {
|
|
35
|
+
console.error("[mcp] Not authenticated. Cannot start MCP bridge.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const serverUrl = getServerUrl();
|
|
39
|
+
const restClient = createRestClient(serverUrl, token, orgId);
|
|
40
|
+
const socketClient = createSocketClient(serverUrl, token, orgId);
|
|
41
|
+
const shutdown = () => {
|
|
42
|
+
if (isShuttingDown)
|
|
43
|
+
return;
|
|
44
|
+
isShuttingDown = true;
|
|
45
|
+
socketClient.disconnect();
|
|
46
|
+
process.exit(0);
|
|
47
|
+
};
|
|
48
|
+
// Handle stdin close (agent disconnected) → clean shutdown
|
|
49
|
+
process.stdin.on("end", shutdown);
|
|
50
|
+
try {
|
|
51
|
+
await startMcpServer({
|
|
52
|
+
taskId,
|
|
53
|
+
projectId,
|
|
54
|
+
orgId,
|
|
55
|
+
restClient,
|
|
56
|
+
socketClient,
|
|
57
|
+
currentTask: null,
|
|
58
|
+
projectRoot: null,
|
|
59
|
+
workflowId,
|
|
60
|
+
stepId,
|
|
61
|
+
}, process.stdin, process.stdout);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`[mcp] Failed to start MCP server: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
if (err instanceof Error && err.stack)
|
|
66
|
+
console.error(err.stack);
|
|
67
|
+
socketClient.disconnect();
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
// Register signal handlers only after server started successfully
|
|
71
|
+
process.on("SIGTERM", shutdown);
|
|
72
|
+
process.on("SIGINT", shutdown);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=mcp-serve.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfig, getServerUrl } from "../config/config-manager.js";
|
|
3
|
+
import { loadToken } from "../config/token-manager.js";
|
|
4
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
5
|
+
async function getOrgConnectionStatus(serverUrl, token, orgId, cliId) {
|
|
6
|
+
try {
|
|
7
|
+
const restClient = createRestClient(serverUrl, token, orgId);
|
|
8
|
+
const state = await restClient.getProjectState();
|
|
9
|
+
const myCli = state.clis.find((c) => c.cliId === cliId);
|
|
10
|
+
if (!myCli)
|
|
11
|
+
return "not registered";
|
|
12
|
+
return myCli.status === "CONNECTED" ? "connected" : "disconnected";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return "unreachable";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function orgListAction(options) {
|
|
19
|
+
const token = loadToken();
|
|
20
|
+
if (!token) {
|
|
21
|
+
console.error(chalk.red("Not authenticated. Run `forge auth login` first."));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const config = getConfig();
|
|
25
|
+
const serverUrl = options.server ?? getServerUrl();
|
|
26
|
+
if (!config.linkedOrgs || config.linkedOrgs.length === 0) {
|
|
27
|
+
console.log(chalk.yellow("No linked organizations. Run `forge auth login` to link."));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log(chalk.bold(`\nLinked Organizations (${config.linkedOrgs.length}):\n`));
|
|
31
|
+
// Parallelize status queries for all orgs
|
|
32
|
+
const statuses = config.cliId
|
|
33
|
+
? await Promise.all(config.linkedOrgs.map((org) => getOrgConnectionStatus(serverUrl, token, org.orgId, config.cliId)))
|
|
34
|
+
: config.linkedOrgs.map(() => "unknown");
|
|
35
|
+
for (const [i, org] of config.linkedOrgs.entries()) {
|
|
36
|
+
const status = statuses[i] ?? "unknown";
|
|
37
|
+
const isDefault = org.orgId === config.defaultOrgId;
|
|
38
|
+
const marker = isDefault ? chalk.green(" (default)") : "";
|
|
39
|
+
let statusColor;
|
|
40
|
+
if (status === "connected") {
|
|
41
|
+
statusColor = chalk.green;
|
|
42
|
+
}
|
|
43
|
+
else if (status === "unreachable") {
|
|
44
|
+
statusColor = chalk.red;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
statusColor = chalk.yellow;
|
|
48
|
+
}
|
|
49
|
+
const displayName = org.orgName ?? org.orgSlug;
|
|
50
|
+
console.log(` ${chalk.bold(displayName)}${marker}`);
|
|
51
|
+
console.log(` Slug: ${org.orgSlug}`);
|
|
52
|
+
console.log(` Status: ${statusColor(status)}`);
|
|
53
|
+
console.log();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=org-list.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { getConfig, getServerUrl, removeLinkedOrg, } from "../config/config-manager.js";
|
|
4
|
+
import { loadToken } from "../config/token-manager.js";
|
|
5
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
6
|
+
function confirm(question) {
|
|
7
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
rl.question(question, (answer) => {
|
|
10
|
+
rl.close();
|
|
11
|
+
resolve(answer.toLowerCase() === "y");
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export async function orgUnlinkAction(slug, options) {
|
|
16
|
+
const token = loadToken();
|
|
17
|
+
if (!token) {
|
|
18
|
+
console.error(chalk.red("Not authenticated. Run `forge auth login` first."));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const config = getConfig();
|
|
22
|
+
const serverUrl = options.server ?? getServerUrl();
|
|
23
|
+
if (!config.linkedOrgs || config.linkedOrgs.length === 0) {
|
|
24
|
+
console.error(chalk.red("No linked organizations. Run `forge auth login` first."));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const match = config.linkedOrgs.find((o) => o.orgSlug === slug);
|
|
28
|
+
if (!match) {
|
|
29
|
+
console.error(chalk.red(`Organization "${slug}" not found in linked orgs. Available: ${config.linkedOrgs.map((o) => o.orgSlug).join(", ")}`));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!config.cliId) {
|
|
33
|
+
console.error(chalk.red("No CLI ID found. Run `forge connect` first to register this CLI."));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Warn if this is the last linked org
|
|
37
|
+
const isLastOrg = config.linkedOrgs.length === 1;
|
|
38
|
+
if (isLastOrg) {
|
|
39
|
+
console.warn(chalk.yellow("This is your last linked org. Run `forge auth logout` to fully disconnect."));
|
|
40
|
+
}
|
|
41
|
+
const confirmed = await confirm(`Are you sure you want to unlink CLI from "${slug}"? This will remove the CLI device registration. (y/N) `);
|
|
42
|
+
if (!confirmed) {
|
|
43
|
+
console.log("Cancelled.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const restClient = createRestClient(serverUrl, token, match.orgId);
|
|
47
|
+
// Disconnect WebSocket first (if connected), then unlink
|
|
48
|
+
try {
|
|
49
|
+
await restClient.disconnectCli({ cliId: config.cliId });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Ignore disconnect errors — CLI may not be connected
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
await restClient.unlinkCli({ cliId: config.cliId });
|
|
56
|
+
removeLinkedOrg(slug);
|
|
57
|
+
console.log(chalk.green(`CLI unlinked from "${slug}"`));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(chalk.red(`Failed to unlink from "${slug}": ${error instanceof Error ? error.message : String(error)}`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=org-unlink.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { sendIpcCommand, DaemonNotRunningError } from "../lib/ipc-client.js";
|
|
3
|
+
/**
|
|
4
|
+
* Print reconnect status message.
|
|
5
|
+
*/
|
|
6
|
+
function printReconnectStatus(response) {
|
|
7
|
+
if ("status" in response) {
|
|
8
|
+
if (response.status === "already_connected") {
|
|
9
|
+
console.log(chalk.green("Already connected."));
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
console.log(chalk.green("Reconnecting..."));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if ("orgs" in response && response.orgs) {
|
|
16
|
+
for (const org of response.orgs) {
|
|
17
|
+
const status = org.connected
|
|
18
|
+
? chalk.green("connected")
|
|
19
|
+
: chalk.yellow("reconnecting");
|
|
20
|
+
console.log(` ${org.slug}: ${status}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function reconnectAction(options) {
|
|
25
|
+
try {
|
|
26
|
+
const response = await sendIpcCommand({
|
|
27
|
+
cmd: "reconnect",
|
|
28
|
+
org: options.org,
|
|
29
|
+
});
|
|
30
|
+
if (response.ok) {
|
|
31
|
+
printReconnectStatus(response);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.error(chalk.red(response.error));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof DaemonNotRunningError) {
|
|
39
|
+
console.error(chalk.red("No daemon running. Run `forge connect` first."));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(chalk.red(`Reconnect failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=reconnect.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { getConfig, getServerUrl } from "../config/config-manager.js";
|
|
5
|
+
import { loadToken } from "../config/token-manager.js";
|
|
6
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
7
|
+
import { cloneAndCheckout } from "../lib/clone-and-checkout.js";
|
|
8
|
+
import { openDirectory } from "../lib/open-directory.js";
|
|
9
|
+
export async function releaseOpenAction(releaseId) {
|
|
10
|
+
const token = loadToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.error(chalk.red('Not authenticated. Run "forge auth login" first.'));
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const config = getConfig();
|
|
17
|
+
const orgId = config.defaultOrgId;
|
|
18
|
+
if (!orgId) {
|
|
19
|
+
console.error(chalk.red('No organization selected. Run "forge connect" first.'));
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const serverUrl = getServerUrl();
|
|
24
|
+
const client = createRestClient(serverUrl, token, orgId);
|
|
25
|
+
const release = await client.getRelease(releaseId);
|
|
26
|
+
const branch = release.releaseBranch ?? release.targetBranch;
|
|
27
|
+
if (!branch) {
|
|
28
|
+
console.error(chalk.red("Release has no branch assigned yet."));
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!release.project.repoUrl) {
|
|
33
|
+
console.error(chalk.red("Project has no repository URL configured."));
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const branchSlug = branch.replaceAll("/", "--");
|
|
38
|
+
const targetDir = join(homedir(), ".forgeai", "open", release.project.slug, branchSlug);
|
|
39
|
+
await cloneAndCheckout(release.project.repoUrl, branch, targetDir);
|
|
40
|
+
openDirectory(targetDir, config.opener ?? "terminal");
|
|
41
|
+
console.log(chalk.green(`Opened release "${release.version}" at ${targetDir}`));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=release-open.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfig, getServerUrl } from "../config/config-manager.js";
|
|
3
|
+
import { loadToken } from "../config/token-manager.js";
|
|
4
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
5
|
+
export async function sessionRespondAction(taskId, response) {
|
|
6
|
+
const token = loadToken();
|
|
7
|
+
if (!token) {
|
|
8
|
+
console.error(chalk.red("Not authenticated. Run `forge auth login` first."));
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const config = getConfig();
|
|
13
|
+
const serverUrl = getServerUrl();
|
|
14
|
+
if (!config.defaultOrgId) {
|
|
15
|
+
console.error(chalk.red("No organization configured. Run `forge auth login` to select one."));
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const restClient = createRestClient(serverUrl, token, config.defaultOrgId);
|
|
20
|
+
try {
|
|
21
|
+
await restClient.respondToAgent(taskId, response);
|
|
22
|
+
console.log(chalk.green("Response sent — agent will resume shortly."));
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error(chalk.red(`Failed to send response: ${error instanceof Error ? error.message : String(error)}`));
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=session-respond.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { getConfig, getServerUrl } from "../config/config-manager.js";
|
|
5
|
+
import { loadToken } from "../config/token-manager.js";
|
|
6
|
+
import { createRestClient } from "../lib/rest-client.js";
|
|
7
|
+
import { cloneAndCheckout } from "../lib/clone-and-checkout.js";
|
|
8
|
+
import { openDirectory } from "../lib/open-directory.js";
|
|
9
|
+
export async function taskOpenAction(taskId) {
|
|
10
|
+
const token = loadToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.error(chalk.red('Not authenticated. Run "forge auth login" first.'));
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const config = getConfig();
|
|
17
|
+
const orgId = config.defaultOrgId;
|
|
18
|
+
if (!orgId) {
|
|
19
|
+
console.error(chalk.red('No organization selected. Run "forge connect" first.'));
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const serverUrl = getServerUrl();
|
|
24
|
+
const client = createRestClient(serverUrl, token, orgId);
|
|
25
|
+
const task = await client.getTask(taskId);
|
|
26
|
+
if (!task.taskBranch) {
|
|
27
|
+
console.error(chalk.red("Task has no branch assigned yet."));
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!task.project?.repoUrl) {
|
|
32
|
+
console.error(chalk.red("Project has no repository URL configured."));
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const branchSlug = task.taskBranch.replaceAll("/", "--");
|
|
37
|
+
const targetDir = join(homedir(), ".forgeai", "open", task.project.slug, branchSlug);
|
|
38
|
+
await cloneAndCheckout(task.project.repoUrl, task.taskBranch, targetDir);
|
|
39
|
+
openDirectory(targetDir, config.opener ?? "terminal");
|
|
40
|
+
console.log(chalk.green(`Opened task "${task.title}" at ${targetDir}`));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=task-open.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* forge vm init — Check Docker availability and pull/build agent image.
|
|
3
|
+
*/
|
|
4
|
+
export declare function vmInitAction(): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* forge vm status — List active Docker container sessions.
|
|
7
|
+
*/
|
|
8
|
+
export declare function vmStatusAction(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* forge vm cleanup — Remove stopped session containers.
|
|
11
|
+
*/
|
|
12
|
+
export declare function vmCleanupAction(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* forge vm stop-all — Stop all containers and shut down.
|
|
15
|
+
*/
|
|
16
|
+
export declare function vmStopAllAction(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* forge auth setup-claude — Obtain a long-lived Claude Code OAuth token.
|
|
19
|
+
*/
|
|
20
|
+
export declare function setupClaudeTokenAction(): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=vm.d.ts.map
|