@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,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon entry point — spawned as a detached child process by `spawnDaemon()`.
|
|
3
|
+
* Bootstraps the IPC server and establishes connections to all linked orgs.
|
|
4
|
+
*/
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { registerDaemonCleanup, DAEMON_PATHS } from "./daemon.js";
|
|
7
|
+
import { createIpcServer, } from "./ipc-server.js";
|
|
8
|
+
import { resolveConnectContext, establishConnections, activeConnections, shutdownConnections, runMcpProbe, } from "./connection-manager.js";
|
|
9
|
+
import { isVmCapable, initVmManager } from "../vm/vm-manager-facade.js";
|
|
10
|
+
// Register cleanup handlers first
|
|
11
|
+
registerDaemonCleanup();
|
|
12
|
+
console.log(`[daemon] Starting (PID: ${process.pid})`);
|
|
13
|
+
// Resolve connection context (mutable — reconnect handler may update it)
|
|
14
|
+
let ctx = resolveConnectContext({});
|
|
15
|
+
if (!ctx) {
|
|
16
|
+
console.error("[daemon] Failed to resolve connection context — exiting");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Check Claude OAuth token availability
|
|
20
|
+
try {
|
|
21
|
+
const { loadClaudeToken } = await import("../vm/claude-token-manager.js");
|
|
22
|
+
if (!(await loadClaudeToken())) {
|
|
23
|
+
console.warn(chalk.yellow("[daemon] No Claude OAuth token stored — agents will fail to authenticate."));
|
|
24
|
+
console.warn(chalk.yellow("[daemon] Run: forge auth setup-claude"));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Non-fatal
|
|
29
|
+
}
|
|
30
|
+
// Initialize VM manager (helper process or direct addon)
|
|
31
|
+
if (isVmCapable()) {
|
|
32
|
+
try {
|
|
33
|
+
await initVmManager();
|
|
34
|
+
console.log(chalk.green("[daemon] VM manager initialized"));
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.warn(chalk.yellow(`[daemon] VM manager failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.warn(chalk.yellow("[daemon] No VM capability — agent execution disabled"));
|
|
42
|
+
}
|
|
43
|
+
// Establish connections to all target orgs
|
|
44
|
+
establishConnections(ctx);
|
|
45
|
+
// Run MCP health probe (non-blocking — heartbeats update when result arrives)
|
|
46
|
+
void runMcpProbe(ctx);
|
|
47
|
+
console.log(`[daemon] Connected to ${ctx.targetOrgs.length} org(s): ${ctx.targetOrgs.map((o) => o.orgSlug).join(", ")}`);
|
|
48
|
+
// Helper functions for IPC commands
|
|
49
|
+
async function handleStatusCommand() {
|
|
50
|
+
const orgs = Array.from(activeConnections.values()).map((conn) => ({
|
|
51
|
+
slug: conn.orgSlug,
|
|
52
|
+
connected: conn.socketClient.socket.connected,
|
|
53
|
+
}));
|
|
54
|
+
return { ok: true, orgs };
|
|
55
|
+
}
|
|
56
|
+
function reconnectDisconnectedSockets(orgFilter) {
|
|
57
|
+
const conns = orgFilter
|
|
58
|
+
? Array.from(activeConnections.values()).filter((c) => c.orgSlug === orgFilter)
|
|
59
|
+
: Array.from(activeConnections.values());
|
|
60
|
+
for (const conn of conns) {
|
|
61
|
+
if (!conn.socketClient.socket.connected) {
|
|
62
|
+
conn.socketClient.socket.connect();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function handleReconnectCommand(cmd) {
|
|
67
|
+
// Re-read config from disk to pick up org/token changes (e.g., after re-login)
|
|
68
|
+
const freshCtx = resolveConnectContext({});
|
|
69
|
+
if (!freshCtx) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: "Failed to resolve connection context (not authenticated?)",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const tokenChanged = freshCtx.token !== ctx?.token;
|
|
76
|
+
ctx = freshCtx;
|
|
77
|
+
const freshOrgIds = new Set(ctx.targetOrgs.map((o) => o.orgId));
|
|
78
|
+
// Remove stale connections: orgs no longer in config, or ALL if token changed
|
|
79
|
+
for (const [orgId, conn] of activeConnections) {
|
|
80
|
+
if (tokenChanged || !freshOrgIds.has(orgId)) {
|
|
81
|
+
clearInterval(conn.heartbeatInterval);
|
|
82
|
+
conn.socketClient.disconnect();
|
|
83
|
+
activeConnections.delete(orgId);
|
|
84
|
+
console.log(`[daemon] Removed stale org: ${conn.orgSlug}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Reconnect existing connections that lost their socket
|
|
88
|
+
if (!tokenChanged) {
|
|
89
|
+
reconnectDisconnectedSockets(cmd.org);
|
|
90
|
+
}
|
|
91
|
+
// Establish connections for new/missing orgs
|
|
92
|
+
const newOrgs = ctx.targetOrgs.filter((o) => !activeConnections.has(o.orgId));
|
|
93
|
+
if (newOrgs.length > 0) {
|
|
94
|
+
establishConnections({ ...ctx, targetOrgs: newOrgs }, true);
|
|
95
|
+
console.log(`[daemon] Added org(s): ${newOrgs.map((o) => o.orgSlug).join(", ")}`);
|
|
96
|
+
}
|
|
97
|
+
// Build response
|
|
98
|
+
const allConns = cmd.org
|
|
99
|
+
? Array.from(activeConnections.values()).filter((c) => c.orgSlug === cmd.org)
|
|
100
|
+
: Array.from(activeConnections.values());
|
|
101
|
+
const orgs = allConns.map((conn) => ({
|
|
102
|
+
slug: conn.orgSlug,
|
|
103
|
+
connected: conn.socketClient.socket.connected,
|
|
104
|
+
}));
|
|
105
|
+
const status = newOrgs.length === 0 && orgs.every((o) => o.connected)
|
|
106
|
+
? "already_connected"
|
|
107
|
+
: "reconnecting";
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
status,
|
|
111
|
+
orgs,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function handleDisconnectCommand(cmd) {
|
|
115
|
+
const targetConns = cmd.org
|
|
116
|
+
? Array.from(activeConnections.values()).filter((c) => c.orgSlug === cmd.org)
|
|
117
|
+
: Array.from(activeConnections.values());
|
|
118
|
+
if (targetConns.length === 0) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: cmd.org
|
|
122
|
+
? `Organization "${cmd.org}" not found in active connections`
|
|
123
|
+
: "No active connections",
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
for (const conn of targetConns) {
|
|
127
|
+
// REST disconnect call to notify Cloud
|
|
128
|
+
try {
|
|
129
|
+
const config = (await import("../config/config-manager.js")).getConfig();
|
|
130
|
+
if (config.cliId) {
|
|
131
|
+
await conn.restClient.disconnectCli({ cliId: config.cliId });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Best-effort REST disconnect
|
|
136
|
+
}
|
|
137
|
+
// Close WebSocket — daemon stays alive
|
|
138
|
+
conn.socketClient.disconnect();
|
|
139
|
+
}
|
|
140
|
+
const orgs = targetConns.map((conn) => ({
|
|
141
|
+
slug: conn.orgSlug,
|
|
142
|
+
connected: false,
|
|
143
|
+
}));
|
|
144
|
+
return { ok: true, status: "disconnected", orgs };
|
|
145
|
+
}
|
|
146
|
+
async function handleReRegisterCommand() {
|
|
147
|
+
// Re-read config and re-register with Cloud using updated values
|
|
148
|
+
const freshCtxReg = resolveConnectContext({});
|
|
149
|
+
if (!freshCtxReg) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
error: "Failed to resolve connection context",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
ctx = freshCtxReg;
|
|
156
|
+
// Re-register on all active connections using PATCH to explicitly update config
|
|
157
|
+
// (PATCH includes agentSlots from the CLI config to sync with Cloud)
|
|
158
|
+
for (const conn of activeConnections.values()) {
|
|
159
|
+
try {
|
|
160
|
+
await conn.restClient.updateCliConfig({
|
|
161
|
+
cliId: ctx.cliId,
|
|
162
|
+
name: ctx.cliName,
|
|
163
|
+
agentType: ctx.agentType,
|
|
164
|
+
agentSlots: ctx.agentSlots,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// Best-effort re-registration
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { ok: true, status: "re-registered" };
|
|
172
|
+
}
|
|
173
|
+
async function handleShutdownCommand() {
|
|
174
|
+
console.log("[daemon] Shutdown requested via IPC");
|
|
175
|
+
// Graceful shutdown: sync pending events, disconnect, clean up
|
|
176
|
+
await shutdownConnections();
|
|
177
|
+
// Close IPC server
|
|
178
|
+
ipcServer.close();
|
|
179
|
+
// Remove PID/socket files (cleanup handler will also run on exit)
|
|
180
|
+
try {
|
|
181
|
+
const fs = await import("node:fs");
|
|
182
|
+
if (fs.existsSync(DAEMON_PATHS.pid))
|
|
183
|
+
fs.unlinkSync(DAEMON_PATHS.pid);
|
|
184
|
+
if (fs.existsSync(DAEMON_PATHS.sock))
|
|
185
|
+
fs.unlinkSync(DAEMON_PATHS.sock);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Best-effort
|
|
189
|
+
}
|
|
190
|
+
// Respond before exiting
|
|
191
|
+
setTimeout(() => process.exit(0), 100);
|
|
192
|
+
return { ok: true, status: "shutting_down" };
|
|
193
|
+
}
|
|
194
|
+
// Start IPC server
|
|
195
|
+
const ipcServer = createIpcServer(async (cmd) => {
|
|
196
|
+
switch (cmd.cmd) {
|
|
197
|
+
case "status":
|
|
198
|
+
return handleStatusCommand();
|
|
199
|
+
case "reconnect":
|
|
200
|
+
return handleReconnectCommand(cmd);
|
|
201
|
+
case "disconnect":
|
|
202
|
+
return handleDisconnectCommand(cmd);
|
|
203
|
+
case "re-register":
|
|
204
|
+
return handleReRegisterCommand();
|
|
205
|
+
case "shutdown":
|
|
206
|
+
return handleShutdownCommand();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
console.log(`[daemon] IPC server listening on ${DAEMON_PATHS.sock}`);
|
|
210
|
+
//# sourceMappingURL=daemon-entry.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export declare const DAEMON_PATHS: {
|
|
2
|
+
readonly dir: string;
|
|
3
|
+
readonly pid: string;
|
|
4
|
+
readonly sock: string;
|
|
5
|
+
readonly log: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Read the stored daemon PID from the PID file.
|
|
9
|
+
* Returns null if no PID file exists or it's unreadable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readDaemonPid(): number | null;
|
|
12
|
+
/**
|
|
13
|
+
* Check if a process with the given PID is currently alive.
|
|
14
|
+
* Uses signal 0 which checks existence without sending a signal.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Check if the daemon is currently running.
|
|
19
|
+
* Returns the PID if running, null if not.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isDaemonRunning(): number | null;
|
|
22
|
+
/**
|
|
23
|
+
* Remove stale PID file and socket file when the daemon process
|
|
24
|
+
* no longer exists.
|
|
25
|
+
*/
|
|
26
|
+
export declare function cleanStalePidFile(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Spawn the daemon as a detached background process.
|
|
29
|
+
* Returns the child PID.
|
|
30
|
+
*/
|
|
31
|
+
export declare function spawnDaemon(): number;
|
|
32
|
+
/**
|
|
33
|
+
* Register cleanup handlers on the current process to ensure
|
|
34
|
+
* PID and socket files are removed on exit.
|
|
35
|
+
* Should be called from the daemon entry point.
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerDaemonCleanup(): void;
|
|
38
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, openSync, } from "node:fs";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
const FORGEAI_DIR = join(homedir(), ".forgeai");
|
|
6
|
+
export const DAEMON_PATHS = {
|
|
7
|
+
dir: FORGEAI_DIR,
|
|
8
|
+
pid: join(FORGEAI_DIR, "daemon.pid"),
|
|
9
|
+
sock: join(FORGEAI_DIR, "daemon.sock"),
|
|
10
|
+
log: join(FORGEAI_DIR, "daemon.log"),
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Read the stored daemon PID from the PID file.
|
|
14
|
+
* Returns null if no PID file exists or it's unreadable.
|
|
15
|
+
*/
|
|
16
|
+
export function readDaemonPid() {
|
|
17
|
+
try {
|
|
18
|
+
if (!existsSync(DAEMON_PATHS.pid))
|
|
19
|
+
return null;
|
|
20
|
+
const content = readFileSync(DAEMON_PATHS.pid, "utf-8").trim();
|
|
21
|
+
const pid = Number.parseInt(content, 10);
|
|
22
|
+
return Number.isNaN(pid) ? null : pid;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if a process with the given PID is currently alive.
|
|
30
|
+
* Uses signal 0 which checks existence without sending a signal.
|
|
31
|
+
*/
|
|
32
|
+
export function isProcessAlive(pid) {
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 0);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const code = error.code;
|
|
39
|
+
// ESRCH = no such process (stale)
|
|
40
|
+
// EPERM = process exists but no permission (alive)
|
|
41
|
+
if (code === "EPERM")
|
|
42
|
+
return true;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if the daemon is currently running.
|
|
48
|
+
* Returns the PID if running, null if not.
|
|
49
|
+
*/
|
|
50
|
+
export function isDaemonRunning() {
|
|
51
|
+
const pid = readDaemonPid();
|
|
52
|
+
if (pid === null)
|
|
53
|
+
return null;
|
|
54
|
+
if (isProcessAlive(pid)) {
|
|
55
|
+
return pid;
|
|
56
|
+
}
|
|
57
|
+
// PID exists but process is dead — clean up stale files
|
|
58
|
+
cleanStalePidFile();
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Remove stale PID file and socket file when the daemon process
|
|
63
|
+
* no longer exists.
|
|
64
|
+
*/
|
|
65
|
+
export function cleanStalePidFile() {
|
|
66
|
+
try {
|
|
67
|
+
if (existsSync(DAEMON_PATHS.pid)) {
|
|
68
|
+
unlinkSync(DAEMON_PATHS.pid);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Ignore cleanup errors
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
if (existsSync(DAEMON_PATHS.sock)) {
|
|
76
|
+
unlinkSync(DAEMON_PATHS.sock);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Ignore cleanup errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Spawn the daemon as a detached background process.
|
|
85
|
+
* Returns the child PID.
|
|
86
|
+
*/
|
|
87
|
+
export function spawnDaemon() {
|
|
88
|
+
// Resolve daemon entry point relative to this file's location
|
|
89
|
+
// In compiled output: dist/lib/daemon.ts → dist/lib/daemon-entry.js
|
|
90
|
+
const daemonEntryPath = resolve(import.meta.dirname, "daemon-entry.js");
|
|
91
|
+
// Open log file in append mode for daemon stdout/stderr
|
|
92
|
+
const logFd = openSync(DAEMON_PATHS.log, "a");
|
|
93
|
+
const child = spawn(process.execPath, [daemonEntryPath], {
|
|
94
|
+
detached: true,
|
|
95
|
+
stdio: ["ignore", logFd, logFd],
|
|
96
|
+
env: { ...process.env, FORGEAI_DAEMON: "1" },
|
|
97
|
+
});
|
|
98
|
+
child.unref();
|
|
99
|
+
const pid = child.pid;
|
|
100
|
+
if (pid === undefined) {
|
|
101
|
+
throw new Error("Failed to spawn daemon: no PID returned");
|
|
102
|
+
}
|
|
103
|
+
writeFileSync(DAEMON_PATHS.pid, String(pid), "utf-8");
|
|
104
|
+
return pid;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Register cleanup handlers on the current process to ensure
|
|
108
|
+
* PID and socket files are removed on exit.
|
|
109
|
+
* Should be called from the daemon entry point.
|
|
110
|
+
*/
|
|
111
|
+
export function registerDaemonCleanup() {
|
|
112
|
+
const cleanup = () => {
|
|
113
|
+
try {
|
|
114
|
+
if (existsSync(DAEMON_PATHS.pid))
|
|
115
|
+
unlinkSync(DAEMON_PATHS.pid);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Best-effort cleanup
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
if (existsSync(DAEMON_PATHS.sock))
|
|
122
|
+
unlinkSync(DAEMON_PATHS.sock);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Best-effort cleanup
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
process.on("exit", cleanup);
|
|
129
|
+
process.on("SIGINT", () => {
|
|
130
|
+
cleanup();
|
|
131
|
+
process.exit(0);
|
|
132
|
+
});
|
|
133
|
+
process.on("SIGTERM", () => {
|
|
134
|
+
cleanup();
|
|
135
|
+
process.exit(0);
|
|
136
|
+
});
|
|
137
|
+
process.on("uncaughtException", (err) => {
|
|
138
|
+
console.error("Daemon uncaught exception:", err);
|
|
139
|
+
cleanup();
|
|
140
|
+
process.exit(1);
|
|
141
|
+
});
|
|
142
|
+
process.on("unhandledRejection", (reason) => {
|
|
143
|
+
// Log but don't exit — unhandled rejections are often recoverable
|
|
144
|
+
// (e.g., socket emit on disconnected socket, async spawn errors)
|
|
145
|
+
console.error("Daemon unhandled rejection:", reason instanceof Error ? reason : String(reason));
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type JournalEntry = {
|
|
2
|
+
eventId: string;
|
|
3
|
+
type: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
cliId: string;
|
|
6
|
+
orgId: string;
|
|
7
|
+
projectId: string | null;
|
|
8
|
+
payload: Record<string, unknown>;
|
|
9
|
+
synced: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type JournalAppendInput = Omit<JournalEntry, "eventId" | "synced">;
|
|
12
|
+
export declare class EventJournal {
|
|
13
|
+
private readonly filePath;
|
|
14
|
+
constructor(projectId: string);
|
|
15
|
+
private ensureDir;
|
|
16
|
+
append(event: JournalAppendInput): JournalEntry;
|
|
17
|
+
readAll(): JournalEntry[];
|
|
18
|
+
readSince(cursor: string): JournalEntry[];
|
|
19
|
+
getCursor(): string;
|
|
20
|
+
clear(upToCursor: string): void;
|
|
21
|
+
pendingCount(): number;
|
|
22
|
+
lastEventTimestamp(): string | null;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=event-journal.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
const EVENTS_DIR = join(homedir(), ".forgeai", "events");
|
|
6
|
+
export class EventJournal {
|
|
7
|
+
filePath;
|
|
8
|
+
constructor(projectId) {
|
|
9
|
+
this.filePath = join(EVENTS_DIR, `${projectId}.ndjson`);
|
|
10
|
+
this.ensureDir();
|
|
11
|
+
}
|
|
12
|
+
ensureDir() {
|
|
13
|
+
if (!existsSync(EVENTS_DIR)) {
|
|
14
|
+
mkdirSync(EVENTS_DIR, { recursive: true, mode: 0o700 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
append(event) {
|
|
18
|
+
const entry = {
|
|
19
|
+
...event,
|
|
20
|
+
eventId: randomUUID(),
|
|
21
|
+
synced: false,
|
|
22
|
+
};
|
|
23
|
+
appendFileSync(this.filePath, JSON.stringify(entry) + "\n", "utf-8");
|
|
24
|
+
return entry;
|
|
25
|
+
}
|
|
26
|
+
readAll() {
|
|
27
|
+
if (!existsSync(this.filePath))
|
|
28
|
+
return [];
|
|
29
|
+
const content = readFileSync(this.filePath, "utf-8");
|
|
30
|
+
const lines = content.split("\n").filter(Boolean);
|
|
31
|
+
const entries = [];
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
try {
|
|
34
|
+
entries.push(JSON.parse(line));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Skip corrupted lines gracefully
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
readSince(cursor) {
|
|
43
|
+
const entries = this.readAll();
|
|
44
|
+
if (!cursor)
|
|
45
|
+
return entries;
|
|
46
|
+
const cursorIndex = entries.findIndex((e) => e.eventId === cursor);
|
|
47
|
+
if (cursorIndex === -1)
|
|
48
|
+
return entries;
|
|
49
|
+
return entries.slice(cursorIndex + 1);
|
|
50
|
+
}
|
|
51
|
+
getCursor() {
|
|
52
|
+
const entries = this.readAll();
|
|
53
|
+
if (entries.length === 0)
|
|
54
|
+
return "";
|
|
55
|
+
return entries.at(-1).eventId;
|
|
56
|
+
}
|
|
57
|
+
clear(upToCursor) {
|
|
58
|
+
const entries = this.readAll();
|
|
59
|
+
const cursorIndex = entries.findIndex((e) => e.eventId === upToCursor);
|
|
60
|
+
if (cursorIndex === -1)
|
|
61
|
+
return;
|
|
62
|
+
const remaining = entries.slice(cursorIndex + 1);
|
|
63
|
+
const content = remaining.map((e) => JSON.stringify(e)).join("\n");
|
|
64
|
+
writeFileSync(this.filePath, content ? content + "\n" : "", "utf-8");
|
|
65
|
+
}
|
|
66
|
+
pendingCount() {
|
|
67
|
+
return this.readAll().length;
|
|
68
|
+
}
|
|
69
|
+
lastEventTimestamp() {
|
|
70
|
+
const entries = this.readAll();
|
|
71
|
+
if (entries.length === 0)
|
|
72
|
+
return null;
|
|
73
|
+
return entries.at(-1).timestamp;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=event-journal.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IpcCommand, IpcResponse } from "./ipc-server.js";
|
|
2
|
+
/**
|
|
3
|
+
* Send a command to the daemon via IPC (Unix domain socket).
|
|
4
|
+
* Returns the parsed response or throws on connection/timeout errors.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sendIpcCommand(cmd: IpcCommand, timeoutMs?: number): Promise<IpcResponse>;
|
|
7
|
+
export declare class DaemonNotRunningError extends Error {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=ipc-client.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createConnection } from "node:net";
|
|
2
|
+
import { DAEMON_PATHS } from "./daemon.js";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
4
|
+
/**
|
|
5
|
+
* Send a command to the daemon via IPC (Unix domain socket).
|
|
6
|
+
* Returns the parsed response or throws on connection/timeout errors.
|
|
7
|
+
*/
|
|
8
|
+
export async function sendIpcCommand(cmd, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const socket = createConnection(DAEMON_PATHS.sock);
|
|
11
|
+
let buffer = "";
|
|
12
|
+
let settled = false;
|
|
13
|
+
const timeout = setTimeout(() => {
|
|
14
|
+
if (!settled) {
|
|
15
|
+
settled = true;
|
|
16
|
+
socket.destroy();
|
|
17
|
+
reject(new Error("IPC command timed out"));
|
|
18
|
+
}
|
|
19
|
+
}, timeoutMs);
|
|
20
|
+
socket.on("connect", () => {
|
|
21
|
+
socket.write(JSON.stringify(cmd) + "\n");
|
|
22
|
+
});
|
|
23
|
+
socket.on("data", (chunk) => {
|
|
24
|
+
buffer += chunk.toString("utf8");
|
|
25
|
+
const lines = buffer.split("\n");
|
|
26
|
+
buffer = lines.pop() ?? "";
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
if (!line.trim())
|
|
29
|
+
continue;
|
|
30
|
+
if (!settled) {
|
|
31
|
+
settled = true;
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
socket.destroy();
|
|
34
|
+
try {
|
|
35
|
+
resolve(JSON.parse(line));
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
reject(new Error(`Invalid IPC response: ${err instanceof Error ? err.message : String(err)}`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
socket.on("error", (err) => {
|
|
44
|
+
if (!settled) {
|
|
45
|
+
settled = true;
|
|
46
|
+
clearTimeout(timeout);
|
|
47
|
+
if (err.code === "ECONNREFUSED" || err.code === "ENOENT") {
|
|
48
|
+
reject(new DaemonNotRunningError());
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
reject(new Error(`IPC connection error: ${err.message}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
socket.on("close", () => {
|
|
56
|
+
if (!settled) {
|
|
57
|
+
settled = true;
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
reject(new Error("IPC connection closed before response"));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export class DaemonNotRunningError extends Error {
|
|
65
|
+
constructor() {
|
|
66
|
+
super("No daemon running. Run `forge connect` first.");
|
|
67
|
+
this.name = "DaemonNotRunningError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ipc-client.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Server } from "node:net";
|
|
2
|
+
export type IpcCommand = {
|
|
3
|
+
cmd: "status";
|
|
4
|
+
} | {
|
|
5
|
+
cmd: "reconnect";
|
|
6
|
+
org?: string;
|
|
7
|
+
} | {
|
|
8
|
+
cmd: "disconnect";
|
|
9
|
+
org?: string;
|
|
10
|
+
} | {
|
|
11
|
+
cmd: "re-register";
|
|
12
|
+
} | {
|
|
13
|
+
cmd: "shutdown";
|
|
14
|
+
};
|
|
15
|
+
export type OrgStatus = {
|
|
16
|
+
slug: string;
|
|
17
|
+
connected: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type IpcResponse = {
|
|
20
|
+
ok: true;
|
|
21
|
+
orgs: OrgStatus[];
|
|
22
|
+
} | {
|
|
23
|
+
ok: true;
|
|
24
|
+
status: string;
|
|
25
|
+
orgs?: OrgStatus[];
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
error: string;
|
|
29
|
+
};
|
|
30
|
+
export type IpcCommandHandler = (cmd: IpcCommand) => Promise<IpcResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* Create and start the IPC server on a Unix domain socket.
|
|
33
|
+
* Uses JSON Lines protocol (one JSON object per line, delimited by \n).
|
|
34
|
+
*/
|
|
35
|
+
export declare function createIpcServer(handler: IpcCommandHandler): Server;
|
|
36
|
+
//# sourceMappingURL=ipc-server.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createServer } from "node:net";
|
|
2
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
3
|
+
import { DAEMON_PATHS } from "./daemon.js";
|
|
4
|
+
/**
|
|
5
|
+
* Create and start the IPC server on a Unix domain socket.
|
|
6
|
+
* Uses JSON Lines protocol (one JSON object per line, delimited by \n).
|
|
7
|
+
*/
|
|
8
|
+
export function createIpcServer(handler) {
|
|
9
|
+
const server = createServer((socket) => {
|
|
10
|
+
let buffer = "";
|
|
11
|
+
socket.on("data", (chunk) => {
|
|
12
|
+
buffer += chunk.toString("utf8");
|
|
13
|
+
const lines = buffer.split("\n");
|
|
14
|
+
buffer = lines.pop() ?? ""; // keep incomplete trailing line
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (!line.trim())
|
|
17
|
+
continue;
|
|
18
|
+
void (async () => {
|
|
19
|
+
try {
|
|
20
|
+
const cmd = JSON.parse(line);
|
|
21
|
+
const response = await handler(cmd);
|
|
22
|
+
const responseLine = JSON.stringify(response) + "\n";
|
|
23
|
+
if (!socket.destroyed) {
|
|
24
|
+
socket.write(responseLine);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
const errorResponse = {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31
|
+
};
|
|
32
|
+
if (!socket.destroyed) {
|
|
33
|
+
socket.write(JSON.stringify(errorResponse) + "\n");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
socket.on("error", () => {
|
|
40
|
+
// Prevent ECONNRESET/EPIPE crashes — client disconnected unexpectedly
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
server.on("error", (err) => {
|
|
44
|
+
if (err.code === "EADDRINUSE") {
|
|
45
|
+
// Stale socket file — remove and retry
|
|
46
|
+
try {
|
|
47
|
+
unlinkSync(DAEMON_PATHS.sock);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignore
|
|
51
|
+
}
|
|
52
|
+
server.listen(DAEMON_PATHS.sock);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error("IPC server error:", err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// Remove stale socket file before listening
|
|
59
|
+
if (existsSync(DAEMON_PATHS.sock)) {
|
|
60
|
+
try {
|
|
61
|
+
unlinkSync(DAEMON_PATHS.sock);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Ignore
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
server.listen(DAEMON_PATHS.sock);
|
|
68
|
+
return server;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=ipc-server.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** True when `current` is at least `required`. Exported for tests. */
|
|
2
|
+
export declare function isSupportedNodeVersion(current: string, required: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Print an actionable message and exit(1) when Node is too old.
|
|
5
|
+
* Called once from the CLI entry point, before anything else runs.
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertSupportedNodeVersion(current?: string, required?: string): void;
|
|
8
|
+
//# sourceMappingURL=node-version-guard.d.ts.map
|