@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.
Files changed (221) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +80 -0
  3. package/dist/bin/forgeai.d.ts +3 -0
  4. package/dist/bin/forgeai.js +8 -0
  5. package/dist/cli.d.ts +4 -0
  6. package/dist/cli.js +180 -0
  7. package/dist/commands/auth-login.d.ts +4 -0
  8. package/dist/commands/auth-login.js +202 -0
  9. package/dist/commands/auth-logout.d.ts +2 -0
  10. package/dist/commands/auth-logout.js +41 -0
  11. package/dist/commands/auth-status.d.ts +4 -0
  12. package/dist/commands/auth-status.js +81 -0
  13. package/dist/commands/cli-list.d.ts +4 -0
  14. package/dist/commands/cli-list.js +40 -0
  15. package/dist/commands/cli-unlink.d.ts +4 -0
  16. package/dist/commands/cli-unlink.js +45 -0
  17. package/dist/commands/config-set.d.ts +5 -0
  18. package/dist/commands/config-set.js +63 -0
  19. package/dist/commands/connect.d.ts +9 -0
  20. package/dist/commands/connect.js +165 -0
  21. package/dist/commands/disconnect.d.ts +5 -0
  22. package/dist/commands/disconnect.js +126 -0
  23. package/dist/commands/journal-clear.d.ts +2 -0
  24. package/dist/commands/journal-clear.js +16 -0
  25. package/dist/commands/journal-list.d.ts +2 -0
  26. package/dist/commands/journal-list.js +20 -0
  27. package/dist/commands/journal-status.d.ts +2 -0
  28. package/dist/commands/journal-status.js +18 -0
  29. package/dist/commands/mcp-serve.d.ts +13 -0
  30. package/dist/commands/mcp-serve.js +74 -0
  31. package/dist/commands/org-list.d.ts +4 -0
  32. package/dist/commands/org-list.js +56 -0
  33. package/dist/commands/org-unlink.d.ts +4 -0
  34. package/dist/commands/org-unlink.js +63 -0
  35. package/dist/commands/reconnect.d.ts +4 -0
  36. package/dist/commands/reconnect.js +46 -0
  37. package/dist/commands/release-open.d.ts +2 -0
  38. package/dist/commands/release-open.js +43 -0
  39. package/dist/commands/session-respond.d.ts +2 -0
  40. package/dist/commands/session-respond.js +29 -0
  41. package/dist/commands/task-open.d.ts +2 -0
  42. package/dist/commands/task-open.js +42 -0
  43. package/dist/commands/vm.d.ts +21 -0
  44. package/dist/commands/vm.js +122 -0
  45. package/dist/commands/workflow-open.d.ts +2 -0
  46. package/dist/commands/workflow-open.js +43 -0
  47. package/dist/config/config-manager.d.ts +31 -0
  48. package/dist/config/config-manager.js +70 -0
  49. package/dist/config/token-manager.d.ts +11 -0
  50. package/dist/config/token-manager.js +87 -0
  51. package/dist/index.d.ts +11 -0
  52. package/dist/index.js +22 -0
  53. package/dist/lib/auth-client.d.ts +1670 -0
  54. package/dist/lib/auth-client.js +10 -0
  55. package/dist/lib/branch-manager.d.ts +79 -0
  56. package/dist/lib/branch-manager.js +620 -0
  57. package/dist/lib/clone-and-checkout.d.ts +6 -0
  58. package/dist/lib/clone-and-checkout.js +41 -0
  59. package/dist/lib/connection-manager.d.ts +58 -0
  60. package/dist/lib/connection-manager.js +436 -0
  61. package/dist/lib/daemon-entry.d.ts +2 -0
  62. package/dist/lib/daemon-entry.js +210 -0
  63. package/dist/lib/daemon.d.ts +38 -0
  64. package/dist/lib/daemon.js +148 -0
  65. package/dist/lib/event-journal.d.ts +24 -0
  66. package/dist/lib/event-journal.js +76 -0
  67. package/dist/lib/ipc-client.d.ts +10 -0
  68. package/dist/lib/ipc-client.js +70 -0
  69. package/dist/lib/ipc-server.d.ts +36 -0
  70. package/dist/lib/ipc-server.js +70 -0
  71. package/dist/lib/node-version-guard.d.ts +8 -0
  72. package/dist/lib/node-version-guard.js +60 -0
  73. package/dist/lib/open-directory.d.ts +7 -0
  74. package/dist/lib/open-directory.js +41 -0
  75. package/dist/lib/repo-manager.d.ts +29 -0
  76. package/dist/lib/repo-manager.js +103 -0
  77. package/dist/lib/resilient-emitter.d.ts +53 -0
  78. package/dist/lib/resilient-emitter.js +160 -0
  79. package/dist/lib/rest-client.d.ts +164 -0
  80. package/dist/lib/rest-client.js +139 -0
  81. package/dist/lib/skill-manager.d.ts +23 -0
  82. package/dist/lib/skill-manager.js +167 -0
  83. package/dist/lib/socket-client.d.ts +68 -0
  84. package/dist/lib/socket-client.js +160 -0
  85. package/dist/mcp/mcp-probe.d.ts +11 -0
  86. package/dist/mcp/mcp-probe.js +106 -0
  87. package/dist/mcp/mcp-response.d.ts +29 -0
  88. package/dist/mcp/mcp-response.js +67 -0
  89. package/dist/mcp/mcp-server.d.ts +19 -0
  90. package/dist/mcp/mcp-server.js +76 -0
  91. package/dist/mcp/resources/project-conventions.d.ts +4 -0
  92. package/dist/mcp/resources/project-conventions.js +50 -0
  93. package/dist/mcp/resources/task-current.d.ts +4 -0
  94. package/dist/mcp/resources/task-current.js +51 -0
  95. package/dist/mcp/tools/forge-add-group-dependency.d.ts +4 -0
  96. package/dist/mcp/tools/forge-add-group-dependency.js +20 -0
  97. package/dist/mcp/tools/forge-add-task-dependency.d.ts +4 -0
  98. package/dist/mcp/tools/forge-add-task-dependency.js +20 -0
  99. package/dist/mcp/tools/forge-ask-human.d.ts +4 -0
  100. package/dist/mcp/tools/forge-ask-human.js +80 -0
  101. package/dist/mcp/tools/forge-column-done.d.ts +4 -0
  102. package/dist/mcp/tools/forge-column-done.js +46 -0
  103. package/dist/mcp/tools/forge-complete-step.d.ts +4 -0
  104. package/dist/mcp/tools/forge-complete-step.js +139 -0
  105. package/dist/mcp/tools/forge-create-task-group.d.ts +4 -0
  106. package/dist/mcp/tools/forge-create-task-group.js +22 -0
  107. package/dist/mcp/tools/forge-create-task.d.ts +4 -0
  108. package/dist/mcp/tools/forge-create-task.js +28 -0
  109. package/dist/mcp/tools/forge-delete-task-group.d.ts +4 -0
  110. package/dist/mcp/tools/forge-delete-task-group.js +17 -0
  111. package/dist/mcp/tools/forge-delete-task.d.ts +4 -0
  112. package/dist/mcp/tools/forge-delete-task.js +17 -0
  113. package/dist/mcp/tools/forge-get-task.d.ts +4 -0
  114. package/dist/mcp/tools/forge-get-task.js +17 -0
  115. package/dist/mcp/tools/forge-merge-back.d.ts +4 -0
  116. package/dist/mcp/tools/forge-merge-back.js +179 -0
  117. package/dist/mcp/tools/forge-release-merge.d.ts +4 -0
  118. package/dist/mcp/tools/forge-release-merge.js +159 -0
  119. package/dist/mcp/tools/forge-release-notes.d.ts +4 -0
  120. package/dist/mcp/tools/forge-release-notes.js +51 -0
  121. package/dist/mcp/tools/forge-release-tag.d.ts +4 -0
  122. package/dist/mcp/tools/forge-release-tag.js +125 -0
  123. package/dist/mcp/tools/forge-remove-group-dependency.d.ts +4 -0
  124. package/dist/mcp/tools/forge-remove-group-dependency.js +19 -0
  125. package/dist/mcp/tools/forge-remove-task-dependency.d.ts +4 -0
  126. package/dist/mcp/tools/forge-remove-task-dependency.js +19 -0
  127. package/dist/mcp/tools/forge-requeue.d.ts +4 -0
  128. package/dist/mcp/tools/forge-requeue.js +39 -0
  129. package/dist/mcp/tools/forge-submit-pr.d.ts +4 -0
  130. package/dist/mcp/tools/forge-submit-pr.js +68 -0
  131. package/dist/mcp/tools/forge-update-status.d.ts +4 -0
  132. package/dist/mcp/tools/forge-update-status.js +77 -0
  133. package/dist/mcp/tools/forge-update-task-group.d.ts +4 -0
  134. package/dist/mcp/tools/forge-update-task-group.js +21 -0
  135. package/dist/mcp/tools/forge-update-task.d.ts +4 -0
  136. package/dist/mcp/tools/forge-update-task.js +25 -0
  137. package/dist/mcp/tools/forge-validate-merge.d.ts +4 -0
  138. package/dist/mcp/tools/forge-validate-merge.js +194 -0
  139. package/dist/mcp/types.d.ts +39 -0
  140. package/dist/mcp/types.js +8 -0
  141. package/dist/methodology/artifact-manager.d.ts +14 -0
  142. package/dist/methodology/artifact-manager.js +55 -0
  143. package/dist/orchestrator/agent-registry.d.ts +53 -0
  144. package/dist/orchestrator/agent-registry.js +96 -0
  145. package/dist/orchestrator/agent-spawner.d.ts +112 -0
  146. package/dist/orchestrator/agent-spawner.js +1518 -0
  147. package/dist/orchestrator/connectors/claude-code-connector.d.ts +3 -0
  148. package/dist/orchestrator/connectors/claude-code-connector.js +60 -0
  149. package/dist/orchestrator/connectors/connector.d.ts +95 -0
  150. package/dist/orchestrator/connectors/connector.js +25 -0
  151. package/dist/orchestrator/connectors/docker-connector.d.ts +14 -0
  152. package/dist/orchestrator/connectors/docker-connector.js +337 -0
  153. package/dist/orchestrator/connectors/native-vm-connector.d.ts +11 -0
  154. package/dist/orchestrator/connectors/native-vm-connector.js +162 -0
  155. package/dist/orchestrator/connectors/opencode-connector.d.ts +3 -0
  156. package/dist/orchestrator/connectors/opencode-connector.js +39 -0
  157. package/dist/orchestrator/connectors/vm-connector.d.ts +3 -0
  158. package/dist/orchestrator/connectors/vm-connector.js +139 -0
  159. package/dist/orchestrator/dispatch-queue.d.ts +43 -0
  160. package/dist/orchestrator/dispatch-queue.js +93 -0
  161. package/dist/orchestrator/resume-handler.d.ts +34 -0
  162. package/dist/orchestrator/resume-handler.js +159 -0
  163. package/dist/orchestrator/stream-batcher.d.ts +16 -0
  164. package/dist/orchestrator/stream-batcher.js +50 -0
  165. package/dist/orchestrator/stream-json-extractor.d.ts +97 -0
  166. package/dist/orchestrator/stream-json-extractor.js +579 -0
  167. package/dist/vm/claude-token-manager.d.ts +36 -0
  168. package/dist/vm/claude-token-manager.js +223 -0
  169. package/dist/vm/docker-executor.d.ts +131 -0
  170. package/dist/vm/docker-executor.js +360 -0
  171. package/dist/vm/docker-process-proxy.d.ts +31 -0
  172. package/dist/vm/docker-process-proxy.js +105 -0
  173. package/dist/vm/forge-vm-agent.d.ts +11 -0
  174. package/dist/vm/forge-vm-agent.js +159 -0
  175. package/dist/vm/git-credentials.d.ts +43 -0
  176. package/dist/vm/git-credentials.js +53 -0
  177. package/dist/vm/image-manager.d.ts +7 -0
  178. package/dist/vm/image-manager.js +61 -0
  179. package/dist/vm/log.d.ts +9 -0
  180. package/dist/vm/log.js +18 -0
  181. package/dist/vm/mcp-bridge.d.ts +63 -0
  182. package/dist/vm/mcp-bridge.js +243 -0
  183. package/dist/vm/mcp-stub.d.ts +15 -0
  184. package/dist/vm/mcp-stub.js +67 -0
  185. package/dist/vm/native-addon.d.ts +69 -0
  186. package/dist/vm/native-addon.js +68 -0
  187. package/dist/vm/provider-resolver.d.ts +7 -0
  188. package/dist/vm/provider-resolver.js +27 -0
  189. package/dist/vm/providers/lima.d.ts +17 -0
  190. package/dist/vm/providers/lima.js +191 -0
  191. package/dist/vm/providers/qemu.d.ts +28 -0
  192. package/dist/vm/providers/qemu.js +260 -0
  193. package/dist/vm/providers/vfkit.d.ts +40 -0
  194. package/dist/vm/providers/vfkit.js +371 -0
  195. package/dist/vm/providers/wsl2.d.ts +27 -0
  196. package/dist/vm/providers/wsl2.js +226 -0
  197. package/dist/vm/session-manager.d.ts +130 -0
  198. package/dist/vm/session-manager.js +494 -0
  199. package/dist/vm/session-snapshot.d.ts +31 -0
  200. package/dist/vm/session-snapshot.js +180 -0
  201. package/dist/vm/types.d.ts +73 -0
  202. package/dist/vm/types.js +2 -0
  203. package/dist/vm/vm-helper-client.d.ts +81 -0
  204. package/dist/vm/vm-helper-client.js +335 -0
  205. package/dist/vm/vm-helper.d.ts +11 -0
  206. package/dist/vm/vm-helper.js +156 -0
  207. package/dist/vm/vm-ipc-protocol.d.ts +84 -0
  208. package/dist/vm/vm-ipc-protocol.js +9 -0
  209. package/dist/vm/vm-manager-facade.d.ts +23 -0
  210. package/dist/vm/vm-manager-facade.js +43 -0
  211. package/dist/vm/vm-manager.d.ts +74 -0
  212. package/dist/vm/vm-manager.js +515 -0
  213. package/dist/vm/vm-process-proxy.d.ts +34 -0
  214. package/dist/vm/vm-process-proxy.js +64 -0
  215. package/dist/vm/vsock-bridge.d.ts +44 -0
  216. package/dist/vm/vsock-bridge.js +140 -0
  217. package/dist/vm/vsock-protocol.d.ts +98 -0
  218. package/dist/vm/vsock-protocol.js +57 -0
  219. package/package.json +65 -0
  220. package/rootfs/Dockerfile +50 -0
  221. package/rootfs/forge-mcp-relay.mjs +158 -0
@@ -0,0 +1,202 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import chalk from "chalk";
3
+ import open from "open";
4
+ import ora from "ora";
5
+ import { createCliAuthClient } from "../lib/auth-client.js";
6
+ import { getConfig, getServerUrl, setConfig, } from "../config/config-manager.js";
7
+ import { saveToken } from "../config/token-manager.js";
8
+ import { createRestClient, getOrgSelection } from "../lib/rest-client.js";
9
+ const CLIENT_ID = "forgeai-cli";
10
+ const GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
11
+ const MAX_POLL_DURATION_MS = 30 * 60 * 1000; // 30 minutes
12
+ export async function loginAction(options) {
13
+ const authClient = createCliAuthClient(options.server);
14
+ const spinner = ora("Requesting device authorization...").start();
15
+ const { data, error } = await authClient.device.code({
16
+ client_id: CLIENT_ID,
17
+ });
18
+ if (error || !data) {
19
+ spinner.fail("Failed to request device authorization.");
20
+ if (error) {
21
+ const errorMsg = typeof error === "object" && error !== null && "message" in error
22
+ ? String(error.message)
23
+ : JSON.stringify(error);
24
+ console.error(chalk.red(`Error: ${errorMsg}`));
25
+ }
26
+ process.exit(1);
27
+ }
28
+ const { device_code, user_code, verification_uri, verification_uri_complete, interval = 5, } = data;
29
+ spinner.stop();
30
+ console.log();
31
+ console.log(chalk.bold("Device Authorization"));
32
+ console.log();
33
+ console.log(` Visit: ${chalk.cyan(verification_uri)}`);
34
+ console.log(` Code: ${chalk.bold.yellow(user_code)}`);
35
+ console.log();
36
+ const urlToOpen = verification_uri_complete ?? verification_uri;
37
+ try {
38
+ await open(urlToOpen);
39
+ console.log(chalk.dim("Browser opened automatically."));
40
+ }
41
+ catch {
42
+ console.log(chalk.dim("Open the URL above in your browser."));
43
+ }
44
+ console.log();
45
+ const pollSpinner = ora("Waiting for authorization...").start();
46
+ const serverUrl = options.server ?? getServerUrl();
47
+ await pollForToken(authClient, device_code, interval, pollSpinner, user_code, serverUrl);
48
+ // Persist server URL on successful login
49
+ if (options.server) {
50
+ setConfig({ serverUrl: options.server });
51
+ }
52
+ }
53
+ async function handleSuccessfulAuth(authClient, accessToken, userCode, serverUrl, spinner) {
54
+ try {
55
+ saveToken(accessToken);
56
+ }
57
+ catch (saveError) {
58
+ spinner.fail(`Failed to save token: ${String(saveError)}`);
59
+ process.exit(1);
60
+ }
61
+ const { data: session } = await authClient.getSession({
62
+ fetchOptions: {
63
+ headers: {
64
+ Authorization: `Bearer ${accessToken}`,
65
+ },
66
+ },
67
+ });
68
+ try {
69
+ await handleOrgSelection(authClient, serverUrl, accessToken, userCode);
70
+ }
71
+ catch {
72
+ // Non-fatal: org fetch failure shouldn't block login
73
+ }
74
+ spinner.succeed("Authentication successful!");
75
+ console.log();
76
+ if (session?.user) {
77
+ console.log(` Signed in as: ${chalk.bold(session.user.name ?? session.user.email)}`);
78
+ console.log(` Email: ${session.user.email}`);
79
+ }
80
+ const config = getConfig();
81
+ if (config.linkedOrgs && config.linkedOrgs.length > 1) {
82
+ console.log(` Linked to: ${config.linkedOrgs.map((o) => o.orgSlug).join(", ")}`);
83
+ }
84
+ else if (config.defaultOrg) {
85
+ console.log(` Org: ${config.defaultOrg}`);
86
+ }
87
+ console.log();
88
+ }
89
+ async function handleOrgSelection(authClient, serverUrl, accessToken, userCode) {
90
+ const orgSelection = await getOrgSelection(serverUrl, accessToken, userCode);
91
+ if (orgSelection.orgIds.length > 0) {
92
+ await handleApprovedOrgSelection(orgSelection, serverUrl, accessToken);
93
+ }
94
+ else {
95
+ await handleFallbackOrgList(authClient, accessToken);
96
+ }
97
+ }
98
+ async function handleApprovedOrgSelection(orgSelection, serverUrl, accessToken) {
99
+ const linkedOrgs = orgSelection.orgIds.map((orgId, i) => ({
100
+ orgId,
101
+ orgSlug: orgSelection.orgSlugs[i] ?? "",
102
+ }));
103
+ setConfig({
104
+ defaultOrg: linkedOrgs[0]?.orgSlug,
105
+ defaultOrgId: linkedOrgs[0]?.orgId,
106
+ linkedOrgs,
107
+ });
108
+ const cliId = getConfig().cliId ?? randomUUID();
109
+ const { stableHostname } = await import("../config/token-manager.js");
110
+ const cliName = getConfig().cliName ?? stableHostname();
111
+ if (!getConfig().cliId) {
112
+ setConfig({ cliId });
113
+ }
114
+ for (const org of linkedOrgs) {
115
+ try {
116
+ const restClient = createRestClient(serverUrl, accessToken, org.orgId);
117
+ await restClient.registerCli({ cliId, name: cliName });
118
+ }
119
+ catch {
120
+ // Non-fatal: registration failure per org shouldn't block login
121
+ }
122
+ }
123
+ }
124
+ async function handleFallbackOrgList(authClient, accessToken) {
125
+ const { data: orgsData } = await authClient.organization.list({
126
+ fetchOptions: {
127
+ headers: { Authorization: `Bearer ${accessToken}` },
128
+ },
129
+ });
130
+ if (orgsData && orgsData.length > 0) {
131
+ const defaultOrg = orgsData[0];
132
+ const linkedOrgs = orgsData.map((org) => ({
133
+ orgId: org.id,
134
+ orgSlug: org.slug,
135
+ orgName: org.name,
136
+ }));
137
+ setConfig({
138
+ defaultOrg: defaultOrg.slug,
139
+ defaultOrgId: defaultOrg.id,
140
+ linkedOrgs,
141
+ });
142
+ }
143
+ }
144
+ function handleTokenError(error, spinner, pollingInterval) {
145
+ const errorCode = error.error;
146
+ switch (errorCode) {
147
+ case "authorization_pending":
148
+ return true;
149
+ case "slow_down":
150
+ pollingInterval.value += 5;
151
+ return true;
152
+ case "access_denied":
153
+ spinner.fail("Access was denied by the user.");
154
+ process.exit(1);
155
+ case "expired_token":
156
+ spinner.fail("The device code has expired. Please try again.");
157
+ process.exit(1);
158
+ default: {
159
+ const failMsg = typeof error === "object" && error !== null && "message" in error
160
+ ? String(error.message)
161
+ : JSON.stringify(error);
162
+ spinner.fail(`Authorization failed: ${failMsg}`);
163
+ process.exit(1);
164
+ }
165
+ }
166
+ }
167
+ async function pollForToken(authClient, deviceCode, initialInterval, spinner, userCode, serverUrl) {
168
+ const pollingInterval = { value: initialInterval };
169
+ const startTime = Date.now();
170
+ const poll = async () => {
171
+ if (Date.now() - startTime > MAX_POLL_DURATION_MS) {
172
+ spinner.fail("Device authorization timed out. Please try again.");
173
+ process.exit(1);
174
+ }
175
+ try {
176
+ const { data, error } = await authClient.device.token({
177
+ grant_type: GRANT_TYPE,
178
+ device_code: deviceCode,
179
+ client_id: CLIENT_ID,
180
+ });
181
+ if (data?.access_token) {
182
+ await handleSuccessfulAuth(authClient, data.access_token, userCode, serverUrl, spinner);
183
+ return;
184
+ }
185
+ if (error) {
186
+ handleTokenError(error, spinner, pollingInterval);
187
+ }
188
+ }
189
+ catch (networkError) {
190
+ spinner.fail(`Network error: ${String(networkError)}`);
191
+ process.exit(1);
192
+ }
193
+ await sleep(pollingInterval.value * 1000);
194
+ return poll();
195
+ };
196
+ await sleep(pollingInterval.value * 1000);
197
+ return poll();
198
+ }
199
+ function sleep(ms) {
200
+ return new Promise((resolve) => setTimeout(resolve, ms));
201
+ }
202
+ //# sourceMappingURL=auth-login.js.map
@@ -0,0 +1,2 @@
1
+ export declare function logoutAction(): Promise<void>;
2
+ //# sourceMappingURL=auth-logout.d.ts.map
@@ -0,0 +1,41 @@
1
+ import chalk from "chalk";
2
+ import { clearSessionConfig } from "../config/config-manager.js";
3
+ import { deleteToken, loadToken } from "../config/token-manager.js";
4
+ import { isDaemonRunning } from "../lib/daemon.js";
5
+ import { DaemonNotRunningError, sendIpcCommand } from "../lib/ipc-client.js";
6
+ export async function logoutAction() {
7
+ const token = loadToken();
8
+ if (!token) {
9
+ console.log();
10
+ console.log(chalk.dim("No stored credentials found."));
11
+ console.log();
12
+ return;
13
+ }
14
+ // Stop running daemon before clearing credentials — the daemon holds
15
+ // in-memory org context and socket connections tied to the current token.
16
+ const daemonPid = isDaemonRunning();
17
+ if (daemonPid) {
18
+ try {
19
+ await sendIpcCommand({ cmd: "shutdown" });
20
+ console.log(chalk.dim("Daemon stopped."));
21
+ }
22
+ catch (error) {
23
+ if (!(error instanceof DaemonNotRunningError)) {
24
+ // Daemon PID exists but IPC failed — force kill
25
+ try {
26
+ process.kill(daemonPid, "SIGTERM");
27
+ console.log(chalk.dim("Daemon stopped."));
28
+ }
29
+ catch {
30
+ // Process already gone
31
+ }
32
+ }
33
+ }
34
+ }
35
+ deleteToken();
36
+ clearSessionConfig();
37
+ console.log();
38
+ console.log(chalk.green("Signed out successfully."));
39
+ console.log();
40
+ }
41
+ //# sourceMappingURL=auth-logout.js.map
@@ -0,0 +1,4 @@
1
+ export declare function statusAction(options: {
2
+ server?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=auth-status.d.ts.map
@@ -0,0 +1,81 @@
1
+ import chalk from "chalk";
2
+ import { createCliAuthClient } from "../lib/auth-client.js";
3
+ import { getConfig, getServerUrl } from "../config/config-manager.js";
4
+ import { loadToken } from "../config/token-manager.js";
5
+ import { createRestClient } from "../lib/rest-client.js";
6
+ export async function statusAction(options) {
7
+ const token = loadToken();
8
+ const serverUrl = options.server ?? getServerUrl();
9
+ if (!token) {
10
+ console.log();
11
+ console.log(chalk.yellow("Not authenticated."));
12
+ console.log();
13
+ console.log(` Run ${chalk.cyan("forge auth login")} to sign in.`);
14
+ console.log();
15
+ return;
16
+ }
17
+ const authClient = createCliAuthClient(serverUrl);
18
+ try {
19
+ const { data: session } = await authClient.getSession({
20
+ fetchOptions: {
21
+ headers: {
22
+ Authorization: `Bearer ${token}`,
23
+ },
24
+ },
25
+ });
26
+ if (session?.user) {
27
+ console.log();
28
+ console.log(chalk.green("Authenticated"));
29
+ console.log();
30
+ console.log(` User: ${chalk.bold(session.user.name ?? session.user.email)}`);
31
+ console.log(` Email: ${session.user.email}`);
32
+ console.log(` Server: ${serverUrl}`);
33
+ const config = getConfig();
34
+ if (config.defaultOrg) {
35
+ console.log(` Org: ${config.defaultOrg}`);
36
+ }
37
+ // Cloud connectivity check via heartbeat
38
+ if (config.defaultOrgId) {
39
+ await checkCloudConnectivity(serverUrl, token, config.defaultOrgId);
40
+ }
41
+ else {
42
+ console.log();
43
+ console.log(chalk.dim(" No org linked. Run forge auth login to link an org."));
44
+ }
45
+ console.log();
46
+ }
47
+ else {
48
+ console.log();
49
+ console.log(chalk.yellow("Token is invalid or expired."));
50
+ console.log();
51
+ console.log(` Run ${chalk.cyan("forge auth login")} to re-authenticate.`);
52
+ console.log();
53
+ }
54
+ }
55
+ catch {
56
+ console.log();
57
+ console.log(chalk.yellow("Token is invalid or expired."));
58
+ console.log();
59
+ console.log(` Run ${chalk.cyan("forge auth login")} to re-authenticate.`);
60
+ console.log();
61
+ }
62
+ }
63
+ async function checkCloudConnectivity(serverUrl, token, orgId) {
64
+ try {
65
+ const client = createRestClient(serverUrl, token, orgId, {
66
+ timeoutMs: 5000,
67
+ });
68
+ const result = await client.heartbeat({
69
+ cliId: "status-probe",
70
+ agentSlots: 0,
71
+ activeAgents: 0,
72
+ });
73
+ if (result.status === "ok") {
74
+ console.log(` Cloud: ${chalk.green("Connected")}`);
75
+ }
76
+ }
77
+ catch {
78
+ console.log(` Cloud: ${chalk.yellow("Unreachable")}`);
79
+ }
80
+ }
81
+ //# sourceMappingURL=auth-status.js.map
@@ -0,0 +1,4 @@
1
+ export declare function cliListAction(options: {
2
+ server?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=cli-list.d.ts.map
@@ -0,0 +1,40 @@
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 cliListAction(options) {
6
+ const token = loadToken();
7
+ if (!token) {
8
+ console.error(chalk.red("Not authenticated. Run `forge auth login` first."));
9
+ return;
10
+ }
11
+ const config = getConfig();
12
+ const serverUrl = options.server ?? getServerUrl();
13
+ if (!config.defaultOrgId) {
14
+ console.error(chalk.red("No organization configured. Run `forge auth login` to select one."));
15
+ return;
16
+ }
17
+ const restClient = createRestClient(serverUrl, token, config.defaultOrgId);
18
+ try {
19
+ const { devices } = await restClient.listCliDevices();
20
+ if (devices.length === 0) {
21
+ console.log(chalk.yellow("No CLI devices registered."));
22
+ return;
23
+ }
24
+ console.log(chalk.bold(`\nRegistered CLIs (${devices.length}):\n`));
25
+ for (const device of devices) {
26
+ const isCurrent = device.cliId === config.cliId;
27
+ const marker = isCurrent ? chalk.green(" (current)") : "";
28
+ const lastSeen = device.lastSeenAt
29
+ ? new Date(device.lastSeenAt).toLocaleString()
30
+ : "never";
31
+ console.log(` ${chalk.bold(device.name)}${marker}`);
32
+ console.log(` ID: ${device.cliId} Owner: ${device.memberName} Agents: ${device.agentSlots} Last seen: ${lastSeen}`);
33
+ console.log();
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.error(chalk.red(`Failed to list CLIs: ${error instanceof Error ? error.message : String(error)}`));
38
+ }
39
+ }
40
+ //# sourceMappingURL=cli-list.js.map
@@ -0,0 +1,4 @@
1
+ export declare function cliUnlinkAction(cliId: string, options: {
2
+ server?: string;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=cli-unlink.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { createInterface } from "node:readline";
2
+ import chalk from "chalk";
3
+ import { getConfig, getServerUrl } 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 cliUnlinkAction(cliId, 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.defaultOrgId) {
24
+ console.error(chalk.red("No organization configured. Run `forge auth login` to select one."));
25
+ return;
26
+ }
27
+ const isSelf = cliId === config.cliId;
28
+ const warning = isSelf
29
+ ? chalk.yellow("Warning: This is your current CLI device!\n")
30
+ : "";
31
+ const confirmed = await confirm(`${warning}Are you sure you want to unlink CLI "${cliId}"? (y/N) `);
32
+ if (!confirmed) {
33
+ console.log("Cancelled.");
34
+ return;
35
+ }
36
+ const restClient = createRestClient(serverUrl, token, config.defaultOrgId);
37
+ try {
38
+ await restClient.unlinkCli({ cliId });
39
+ console.log(chalk.green(`CLI "${cliId}" unlinked successfully.`));
40
+ }
41
+ catch (error) {
42
+ console.error(chalk.red(`Failed to unlink CLI: ${error instanceof Error ? error.message : String(error)}`));
43
+ }
44
+ }
45
+ //# sourceMappingURL=cli-unlink.js.map
@@ -0,0 +1,5 @@
1
+ export declare function configSetAgentTypeAction(value: string): Promise<void>;
2
+ export declare function configSetAgentSlotsAction(value: string): Promise<void>;
3
+ export declare function configSetOpenerAction(value: string): void;
4
+ export declare function configGetAction(): void;
5
+ //# sourceMappingURL=config-set.d.ts.map
@@ -0,0 +1,63 @@
1
+ import chalk from "chalk";
2
+ import { getConfig, setConfig } from "../config/config-manager.js";
3
+ import { isDaemonRunning } from "../lib/daemon.js";
4
+ import { DaemonNotRunningError, sendIpcCommand } from "../lib/ipc-client.js";
5
+ const VALID_AGENT_TYPES = ["CLAUDE_CODE"];
6
+ const VALID_OPENERS = ["terminal", "vscode", "finder"];
7
+ export async function configSetAgentTypeAction(value) {
8
+ const upper = value.toUpperCase();
9
+ if (!VALID_AGENT_TYPES.includes(upper)) {
10
+ console.error(chalk.red(`Invalid agent type "${value}". Allowed: ${VALID_AGENT_TYPES.join(", ")}`));
11
+ process.exitCode = 1;
12
+ return;
13
+ }
14
+ setConfig({ agentType: upper });
15
+ const applied = await tryReRegister();
16
+ console.log(chalk.green(`Agent type set to ${upper}. ${applied ? "(Applied immediately)" : "(Will apply on next connect)"}`));
17
+ }
18
+ export async function configSetAgentSlotsAction(value) {
19
+ const num = Number.parseInt(value, 10);
20
+ if (Number.isNaN(num) || num < 1 || num > 20) {
21
+ console.error(chalk.red("Invalid agent slots. Must be an integer between 1 and 20."));
22
+ process.exitCode = 1;
23
+ return;
24
+ }
25
+ setConfig({ agentSlots: num });
26
+ const applied = await tryReRegister();
27
+ console.log(chalk.green(`Agent slots set to ${num}. ${applied ? "(Applied immediately)" : "(Will apply on next connect)"}`));
28
+ }
29
+ export function configSetOpenerAction(value) {
30
+ const lower = value.toLowerCase();
31
+ if (!VALID_OPENERS.includes(lower)) {
32
+ console.error(chalk.red(`Invalid opener "${value}". Allowed: ${VALID_OPENERS.join(", ")}`));
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ setConfig({ opener: lower });
37
+ console.log(chalk.green(`Opener set to ${lower}.`));
38
+ }
39
+ export function configGetAction() {
40
+ const config = getConfig();
41
+ console.log(chalk.dim("Current configuration:"));
42
+ console.log(` Agent type: ${config.agentType ?? "CLAUDE_CODE"}`);
43
+ console.log(` Agent slots: ${config.agentSlots ?? 5}`);
44
+ console.log(` Opener: ${config.opener ?? "terminal"}`);
45
+ console.log(` Server URL: ${config.serverUrl}`);
46
+ console.log(` CLI ID: ${config.cliId ?? "(not set)"}`);
47
+ console.log(` CLI name: ${config.cliName ?? "(auto-detected)"}`);
48
+ }
49
+ async function tryReRegister() {
50
+ const pid = isDaemonRunning();
51
+ if (!pid)
52
+ return false;
53
+ try {
54
+ const response = await sendIpcCommand({ cmd: "re-register" });
55
+ return response.ok;
56
+ }
57
+ catch (error) {
58
+ if (error instanceof DaemonNotRunningError)
59
+ return false;
60
+ return false;
61
+ }
62
+ }
63
+ //# sourceMappingURL=config-set.js.map
@@ -0,0 +1,9 @@
1
+ export { activeConnections } from "../lib/connection-manager.js";
2
+ export type { OrgConnection } from "../lib/connection-manager.js";
3
+ export declare function connectAction(options: {
4
+ server?: string;
5
+ org?: string;
6
+ attach?: boolean;
7
+ stop?: boolean;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1,165 @@
1
+ import chalk from "chalk";
2
+ import { isDaemonRunning, spawnDaemon } from "../lib/daemon.js";
3
+ import { sendIpcCommand, DaemonNotRunningError } from "../lib/ipc-client.js";
4
+ import { resolveConnectContext, establishConnections, shutdownConnections, runMcpProbe, } from "../lib/connection-manager.js";
5
+ // Re-export for backward compatibility with existing tests
6
+ export { activeConnections } from "../lib/connection-manager.js";
7
+ export async function connectAction(options) {
8
+ // --stop: send shutdown command to running daemon
9
+ if (options.stop) {
10
+ try {
11
+ const response = await sendIpcCommand({ cmd: "shutdown" });
12
+ if (response.ok) {
13
+ console.log(chalk.green("Daemon shutdown initiated."));
14
+ }
15
+ else {
16
+ console.error(chalk.red(`Shutdown failed: ${response.error}`));
17
+ }
18
+ }
19
+ catch (error) {
20
+ if (error instanceof DaemonNotRunningError) {
21
+ console.log(chalk.yellow("No daemon running."));
22
+ }
23
+ else {
24
+ console.error(chalk.red(`Failed to send shutdown: ${error instanceof Error ? error.message : String(error)}`));
25
+ }
26
+ }
27
+ return;
28
+ }
29
+ // --attach: foreground mode (original behavior)
30
+ if (options.attach) {
31
+ return connectForeground(options);
32
+ }
33
+ // Default: daemon mode
34
+ return connectDaemon(options);
35
+ }
36
+ /**
37
+ * Handle status response from existing daemon.
38
+ */
39
+ async function handleExistingDaemonStatus(existingPid, response) {
40
+ if (!response.ok || !("orgs" in response) || !response.orgs) {
41
+ console.log(chalk.green(`Daemon running (PID: ${existingPid})`));
42
+ return;
43
+ }
44
+ const config = (await import("../config/config-manager.js")).getConfig();
45
+ const linkedSlugs = (config.linkedOrgs ?? []).map((o) => o.orgSlug);
46
+ const connectedSlugs = new Set(response.orgs.map((o) => o.slug));
47
+ const missingSlugs = linkedSlugs.filter((s) => !connectedSlugs.has(s));
48
+ if (missingSlugs.length > 0) {
49
+ await attemptReconnectMissingOrgs(existingPid);
50
+ }
51
+ else {
52
+ printOrgStatus(existingPid, response.orgs);
53
+ }
54
+ }
55
+ /**
56
+ * Attempt to reconnect missing orgs and print status.
57
+ */
58
+ async function attemptReconnectMissingOrgs(existingPid) {
59
+ const reconnResp = await sendIpcCommand({ cmd: "reconnect" });
60
+ if (reconnResp.ok && "orgs" in reconnResp && reconnResp.orgs) {
61
+ printOrgStatus(existingPid, reconnResp.orgs);
62
+ }
63
+ }
64
+ /**
65
+ * Print organization connection status.
66
+ */
67
+ function printOrgStatus(pid, orgs, defaultStatus = "connected") {
68
+ console.log(chalk.green(`Daemon running (PID: ${pid})`));
69
+ for (const org of orgs) {
70
+ let statusText;
71
+ if (defaultStatus === "connected" || defaultStatus === "disconnected") {
72
+ statusText = org.connected
73
+ ? chalk.green("connected")
74
+ : chalk.yellow("disconnected");
75
+ }
76
+ else {
77
+ statusText = org.connected
78
+ ? chalk.green("connected")
79
+ : chalk.yellow(defaultStatus);
80
+ }
81
+ console.log(` ${org.slug}: ${statusText}`);
82
+ }
83
+ }
84
+ /**
85
+ * Daemon mode (default): spawn background daemon or query existing one.
86
+ */
87
+ async function connectDaemon(options) {
88
+ const existingPid = isDaemonRunning();
89
+ if (existingPid) {
90
+ try {
91
+ const response = await sendIpcCommand({ cmd: "status" });
92
+ await handleExistingDaemonStatus(existingPid, response);
93
+ }
94
+ catch {
95
+ console.log(chalk.yellow(`Daemon PID ${existingPid} found but IPC unreachable. Try \`forge connect --stop\` then \`forge connect\`.`));
96
+ }
97
+ return;
98
+ }
99
+ // No daemon running — validate context before spawning
100
+ const ctx = resolveConnectContext(options);
101
+ if (!ctx)
102
+ return;
103
+ // Spawn daemon
104
+ const pid = spawnDaemon();
105
+ console.log(chalk.green(`Daemon started (PID: ${pid})`));
106
+ // Wait briefly for daemon to initialize, then query status
107
+ await sleep(1_500);
108
+ try {
109
+ const response = await sendIpcCommand({ cmd: "status" });
110
+ if (response.ok && "orgs" in response && response.orgs) {
111
+ printOrgStatus(pid, response.orgs, "connecting...");
112
+ }
113
+ }
114
+ catch {
115
+ console.log(chalk.yellow("Daemon spawned but IPC not ready yet. Check status with `forge connect`."));
116
+ }
117
+ }
118
+ /**
119
+ * Foreground / attach mode: original blocking behavior for dev/debug.
120
+ */
121
+ async function connectForeground(options) {
122
+ const ctx = resolveConnectContext(options);
123
+ if (!ctx)
124
+ return;
125
+ // Ensure Claude OAuth token is available for container authentication
126
+ try {
127
+ const { ensureClaudeToken } = await import("../vm/claude-token-manager.js");
128
+ await ensureClaudeToken();
129
+ }
130
+ catch (err) {
131
+ console.warn(chalk.yellow(`[connect] Claude token setup failed: ${err instanceof Error ? err.message : String(err)}`));
132
+ console.warn(chalk.yellow("[connect] Agents may fail to authenticate. Run: forge auth setup-claude"));
133
+ }
134
+ // Initialize VM manager (helper process or direct addon)
135
+ try {
136
+ const { isVmCapable, initVmManager } = await import("../vm/vm-manager-facade.js");
137
+ if (isVmCapable()) {
138
+ await initVmManager();
139
+ console.log(chalk.green("[connect] VM manager initialized"));
140
+ }
141
+ else {
142
+ console.warn(chalk.yellow("[connect] Native VM addon not available — agent execution disabled"));
143
+ }
144
+ }
145
+ catch (err) {
146
+ console.warn(chalk.yellow(`[connect] VM init failed: ${err instanceof Error ? err.message : String(err)}`));
147
+ }
148
+ establishConnections(ctx);
149
+ // Run MCP health probe (non-blocking — heartbeats update when result arrives)
150
+ void runMcpProbe(ctx);
151
+ // Keep process alive until SIGINT/SIGTERM
152
+ await new Promise((resolve) => {
153
+ const shutdown = async () => {
154
+ console.log(chalk.cyan("\nDisconnecting..."));
155
+ await shutdownConnections();
156
+ resolve();
157
+ };
158
+ process.once("SIGINT", () => void shutdown());
159
+ process.once("SIGTERM", () => void shutdown());
160
+ });
161
+ }
162
+ function sleep(ms) {
163
+ return new Promise((resolve) => setTimeout(resolve, ms));
164
+ }
165
+ //# sourceMappingURL=connect.js.map