@orkify/cli 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/README.md +1701 -0
- package/bin/orkify +3 -0
- package/boot/systemd/orkify@.service +30 -0
- package/dist/agent-name.d.ts +4 -0
- package/dist/agent-name.js +42 -0
- package/dist/alerts/AlertEvaluator.d.ts +14 -0
- package/dist/alerts/AlertEvaluator.js +135 -0
- package/dist/cli/commands/autostart.d.ts +3 -0
- package/dist/cli/commands/autostart.js +11 -0
- package/dist/cli/commands/crash-test.d.ts +3 -0
- package/dist/cli/commands/crash-test.js +17 -0
- package/dist/cli/commands/daemon-reload.d.ts +3 -0
- package/dist/cli/commands/daemon-reload.js +72 -0
- package/dist/cli/commands/delete.d.ts +3 -0
- package/dist/cli/commands/delete.js +37 -0
- package/dist/cli/commands/deploy.d.ts +6 -0
- package/dist/cli/commands/deploy.js +266 -0
- package/dist/cli/commands/down.d.ts +3 -0
- package/dist/cli/commands/down.js +36 -0
- package/dist/cli/commands/flush.d.ts +3 -0
- package/dist/cli/commands/flush.js +28 -0
- package/dist/cli/commands/kill.d.ts +3 -0
- package/dist/cli/commands/kill.js +35 -0
- package/dist/cli/commands/list.d.ts +14 -0
- package/dist/cli/commands/list.js +361 -0
- package/dist/cli/commands/logs.d.ts +3 -0
- package/dist/cli/commands/logs.js +107 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.js +151 -0
- package/dist/cli/commands/reload.d.ts +3 -0
- package/dist/cli/commands/reload.js +54 -0
- package/dist/cli/commands/restart.d.ts +3 -0
- package/dist/cli/commands/restart.js +43 -0
- package/dist/cli/commands/restore.d.ts +3 -0
- package/dist/cli/commands/restore.js +88 -0
- package/dist/cli/commands/run.d.ts +8 -0
- package/dist/cli/commands/run.js +212 -0
- package/dist/cli/commands/snap.d.ts +3 -0
- package/dist/cli/commands/snap.js +30 -0
- package/dist/cli/commands/up.d.ts +3 -0
- package/dist/cli/commands/up.js +125 -0
- package/dist/cli/crash-recovery.d.ts +2 -0
- package/dist/cli/crash-recovery.js +67 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +46 -0
- package/dist/cli/parse.d.ts +28 -0
- package/dist/cli/parse.js +97 -0
- package/dist/cluster/ClusterWrapper.d.ts +18 -0
- package/dist/cluster/ClusterWrapper.js +602 -0
- package/dist/config/ConfigStore.d.ts +11 -0
- package/dist/config/ConfigStore.js +21 -0
- package/dist/config/schema.d.ts +103 -0
- package/dist/config/schema.js +49 -0
- package/dist/constants.d.ts +83 -0
- package/dist/constants.js +289 -0
- package/dist/cron/CronScheduler.d.ts +25 -0
- package/dist/cron/CronScheduler.js +149 -0
- package/dist/daemon/GracefulManager.d.ts +8 -0
- package/dist/daemon/GracefulManager.js +29 -0
- package/dist/daemon/ManagedProcess.d.ts +71 -0
- package/dist/daemon/ManagedProcess.js +1020 -0
- package/dist/daemon/Orchestrator.d.ts +51 -0
- package/dist/daemon/Orchestrator.js +416 -0
- package/dist/daemon/RotatingWriter.d.ts +27 -0
- package/dist/daemon/RotatingWriter.js +264 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +106 -0
- package/dist/daemon/startDaemon.d.ts +30 -0
- package/dist/daemon/startDaemon.js +693 -0
- package/dist/deploy/CommandPoller.d.ts +13 -0
- package/dist/deploy/CommandPoller.js +53 -0
- package/dist/deploy/DeployExecutor.d.ts +33 -0
- package/dist/deploy/DeployExecutor.js +340 -0
- package/dist/deploy/config.d.ts +20 -0
- package/dist/deploy/config.js +161 -0
- package/dist/deploy/env.d.ts +2 -0
- package/dist/deploy/env.js +17 -0
- package/dist/deploy/tarball.d.ts +32 -0
- package/dist/deploy/tarball.js +243 -0
- package/dist/detect/framework.d.ts +2 -0
- package/dist/detect/framework.js +24 -0
- package/dist/ipc/DaemonClient.d.ts +31 -0
- package/dist/ipc/DaemonClient.js +248 -0
- package/dist/ipc/DaemonServer.d.ts +28 -0
- package/dist/ipc/DaemonServer.js +166 -0
- package/dist/ipc/MultiUserClient.d.ts +27 -0
- package/dist/ipc/MultiUserClient.js +203 -0
- package/dist/ipc/protocol.d.ts +7 -0
- package/dist/ipc/protocol.js +53 -0
- package/dist/ipc/restoreDaemon.d.ts +8 -0
- package/dist/ipc/restoreDaemon.js +19 -0
- package/dist/machine-id.d.ts +11 -0
- package/dist/machine-id.js +51 -0
- package/dist/mcp/auth.d.ts +118 -0
- package/dist/mcp/auth.js +245 -0
- package/dist/mcp/http.d.ts +20 -0
- package/dist/mcp/http.js +229 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/server.d.ts +37 -0
- package/dist/mcp/server.js +413 -0
- package/dist/probe/compute-fingerprint.d.ts +27 -0
- package/dist/probe/compute-fingerprint.js +65 -0
- package/dist/probe/parse-frames.d.ts +21 -0
- package/dist/probe/parse-frames.js +57 -0
- package/dist/probe/resolve-sourcemaps.d.ts +25 -0
- package/dist/probe/resolve-sourcemaps.js +281 -0
- package/dist/state/StateStore.d.ts +11 -0
- package/dist/state/StateStore.js +78 -0
- package/dist/telemetry/TelemetryReporter.d.ts +49 -0
- package/dist/telemetry/TelemetryReporter.js +451 -0
- package/dist/types/index.d.ts +373 -0
- package/dist/types/index.js +2 -0
- package/package.json +148 -0
- package/packages/cache/README.md +114 -0
- package/packages/cache/dist/CacheClient.d.ts +26 -0
- package/packages/cache/dist/CacheClient.d.ts.map +1 -0
- package/packages/cache/dist/CacheClient.js +174 -0
- package/packages/cache/dist/CacheClient.js.map +1 -0
- package/packages/cache/dist/CacheFileStore.d.ts +45 -0
- package/packages/cache/dist/CacheFileStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheFileStore.js +446 -0
- package/packages/cache/dist/CacheFileStore.js.map +1 -0
- package/packages/cache/dist/CachePersistence.d.ts +9 -0
- package/packages/cache/dist/CachePersistence.d.ts.map +1 -0
- package/packages/cache/dist/CachePersistence.js +67 -0
- package/packages/cache/dist/CachePersistence.js.map +1 -0
- package/packages/cache/dist/CachePrimary.d.ts +25 -0
- package/packages/cache/dist/CachePrimary.d.ts.map +1 -0
- package/packages/cache/dist/CachePrimary.js +155 -0
- package/packages/cache/dist/CachePrimary.js.map +1 -0
- package/packages/cache/dist/CacheStore.d.ts +50 -0
- package/packages/cache/dist/CacheStore.d.ts.map +1 -0
- package/packages/cache/dist/CacheStore.js +271 -0
- package/packages/cache/dist/CacheStore.js.map +1 -0
- package/packages/cache/dist/constants.d.ts +6 -0
- package/packages/cache/dist/constants.d.ts.map +1 -0
- package/packages/cache/dist/constants.js +9 -0
- package/packages/cache/dist/constants.js.map +1 -0
- package/packages/cache/dist/index.d.ts +16 -0
- package/packages/cache/dist/index.d.ts.map +1 -0
- package/packages/cache/dist/index.js +86 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/serialize.d.ts +9 -0
- package/packages/cache/dist/serialize.d.ts.map +1 -0
- package/packages/cache/dist/serialize.js +40 -0
- package/packages/cache/dist/serialize.js.map +1 -0
- package/packages/cache/dist/types.d.ts +123 -0
- package/packages/cache/dist/types.d.ts.map +1 -0
- package/packages/cache/dist/types.js +2 -0
- package/packages/cache/dist/types.js.map +1 -0
- package/packages/cache/package.json +27 -0
- package/packages/cache/src/CacheClient.ts +227 -0
- package/packages/cache/src/CacheFileStore.ts +528 -0
- package/packages/cache/src/CachePersistence.ts +89 -0
- package/packages/cache/src/CachePrimary.ts +172 -0
- package/packages/cache/src/CacheStore.ts +308 -0
- package/packages/cache/src/constants.ts +10 -0
- package/packages/cache/src/index.ts +100 -0
- package/packages/cache/src/serialize.ts +49 -0
- package/packages/cache/src/types.ts +156 -0
- package/packages/cache/tsconfig.json +18 -0
- package/packages/cache/tsconfig.tsbuildinfo +1 -0
- package/packages/next/README.md +166 -0
- package/packages/next/dist/error-capture.d.ts +34 -0
- package/packages/next/dist/error-capture.d.ts.map +1 -0
- package/packages/next/dist/error-capture.js +130 -0
- package/packages/next/dist/error-capture.js.map +1 -0
- package/packages/next/dist/error-handler.d.ts +10 -0
- package/packages/next/dist/error-handler.d.ts.map +1 -0
- package/packages/next/dist/error-handler.js +186 -0
- package/packages/next/dist/error-handler.js.map +1 -0
- package/packages/next/dist/isr-cache.d.ts +9 -0
- package/packages/next/dist/isr-cache.d.ts.map +1 -0
- package/packages/next/dist/isr-cache.js +86 -0
- package/packages/next/dist/isr-cache.js.map +1 -0
- package/packages/next/dist/stream.d.ts +5 -0
- package/packages/next/dist/stream.d.ts.map +1 -0
- package/packages/next/dist/stream.js +22 -0
- package/packages/next/dist/stream.js.map +1 -0
- package/packages/next/dist/types.d.ts +33 -0
- package/packages/next/dist/types.d.ts.map +1 -0
- package/packages/next/dist/types.js +6 -0
- package/packages/next/dist/types.js.map +1 -0
- package/packages/next/dist/use-cache.d.ts +4 -0
- package/packages/next/dist/use-cache.d.ts.map +1 -0
- package/packages/next/dist/use-cache.js +86 -0
- package/packages/next/dist/use-cache.js.map +1 -0
- package/packages/next/dist/utils.d.ts +32 -0
- package/packages/next/dist/utils.d.ts.map +1 -0
- package/packages/next/dist/utils.js +88 -0
- package/packages/next/dist/utils.js.map +1 -0
- package/packages/next/package.json +52 -0
- package/packages/next/src/error-capture.ts +177 -0
- package/packages/next/src/error-handler.ts +221 -0
- package/packages/next/src/isr-cache.ts +100 -0
- package/packages/next/src/stream.ts +23 -0
- package/packages/next/src/types.ts +33 -0
- package/packages/next/src/use-cache.ts +99 -0
- package/packages/next/src/utils.ts +102 -0
- package/packages/next/tsconfig.json +19 -0
- package/packages/next/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command, Option } from 'commander';
|
|
3
|
+
import { IPCMessageType, MCP_DEFAULT_PORT } from '../../constants.js';
|
|
4
|
+
import { daemonClient } from '../../ipc/DaemonClient.js';
|
|
5
|
+
import { appendKeyToConfig, generateToken, loadMcpConfig, TOOL_NAMES, warnIfConfigInsecure, } from '../../mcp/auth.js';
|
|
6
|
+
import { startMcpServer } from '../../mcp/server.js';
|
|
7
|
+
export const mcpCommand = new Command('mcp')
|
|
8
|
+
.description('Start MCP server for AI tool integration (e.g., Claude Code)')
|
|
9
|
+
.addOption(new Option('--simple-http', 'Use HTTP transport with local key auth'))
|
|
10
|
+
.addOption(new Option('--advanced-http', 'Use HTTP transport with remote dashboard-managed keys'))
|
|
11
|
+
.addOption(new Option('--port <port>', 'HTTP port').default(MCP_DEFAULT_PORT).argParser(Number))
|
|
12
|
+
.addOption(new Option('--bind <address>', 'HTTP bind address').default('127.0.0.1'))
|
|
13
|
+
.addOption(new Option('--cors [origin]', 'Enable CORS ("*" for any origin, a specific URL, or comma-separated URLs)').preset('*'))
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
try {
|
|
16
|
+
if (opts.simpleHttp && opts.advancedHttp) {
|
|
17
|
+
console.error(chalk.red('✗ --simple-http and --advanced-http are mutually exclusive'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (opts.simpleHttp || opts.advancedHttp) {
|
|
21
|
+
const transport = opts.advancedHttp ? 'advanced-http' : 'simple-http';
|
|
22
|
+
if (opts.simpleHttp)
|
|
23
|
+
warnIfConfigInsecure();
|
|
24
|
+
// Delegate to daemon via IPC
|
|
25
|
+
const payload = {
|
|
26
|
+
transport: transport,
|
|
27
|
+
port: opts.port,
|
|
28
|
+
bind: opts.bind,
|
|
29
|
+
cors: opts.cors,
|
|
30
|
+
};
|
|
31
|
+
const response = await daemonClient.request(IPCMessageType.MCP_START, payload);
|
|
32
|
+
if (!response.success) {
|
|
33
|
+
console.error(chalk.red(`✗ ${response.error}`));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const data = response.data;
|
|
37
|
+
if (data.waitingForConfig) {
|
|
38
|
+
console.log(chalk.green(`✓ MCP advanced-http registered — server will start after first config sync`));
|
|
39
|
+
}
|
|
40
|
+
else if (data.started) {
|
|
41
|
+
console.log(chalk.green(`✓ MCP HTTP server started on http://${data.bind}:${data.port}/mcp`));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(chalk.yellow(`MCP HTTP server already running on http://${data.bind}:${data.port}/mcp`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
warnIfConfigInsecure();
|
|
49
|
+
await startMcpServer();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
// Use stderr for errors (stdout is reserved for MCP protocol)
|
|
54
|
+
console.error('MCP server error:', err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
daemonClient.disconnect();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
mcpCommand
|
|
62
|
+
.command('stop')
|
|
63
|
+
.description('Stop the MCP HTTP server')
|
|
64
|
+
.action(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const response = await daemonClient.request(IPCMessageType.MCP_STOP);
|
|
67
|
+
if (!response.success) {
|
|
68
|
+
console.error(chalk.red(`✗ ${response.error}`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const data = response.data;
|
|
72
|
+
if (data.stopped) {
|
|
73
|
+
console.log(chalk.green('✓ MCP HTTP server stopped'));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.log(chalk.yellow('MCP HTTP server is not running'));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.error(chalk.red(`✗ ${err.message}`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
daemonClient.disconnect();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
mcpCommand
|
|
88
|
+
.command('status')
|
|
89
|
+
.description('Show MCP HTTP server status')
|
|
90
|
+
.action(async () => {
|
|
91
|
+
try {
|
|
92
|
+
const response = await daemonClient.request(IPCMessageType.MCP_STATUS);
|
|
93
|
+
if (!response.success) {
|
|
94
|
+
console.error(chalk.red(`✗ ${response.error}`));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const data = response.data;
|
|
98
|
+
if (data.running) {
|
|
99
|
+
console.log(chalk.green(`MCP server running (${data.transport}) on http://${data.bind}:${data.port}/mcp`));
|
|
100
|
+
if (data.cors)
|
|
101
|
+
console.log(` CORS: ${data.cors}`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log('MCP HTTP server is not running');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.error(chalk.red(`✗ ${err.message}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
daemonClient.disconnect();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
mcpCommand
|
|
116
|
+
.command('keygen')
|
|
117
|
+
.description('Generate a new MCP API key and add it to ~/.orkify/mcp.yml')
|
|
118
|
+
.option('--name <name>', 'Key name for identification', 'default')
|
|
119
|
+
.option('--tools <tools>', 'Comma-separated list of allowed tools (default: all)')
|
|
120
|
+
.option('--allowed-ips <ips>', 'Comma-separated list of allowed IPs or CIDRs')
|
|
121
|
+
.action((opts) => {
|
|
122
|
+
const tools = opts.tools ? opts.tools.split(',').map((t) => t.trim()) : ['*'];
|
|
123
|
+
// Validate tool names (skip wildcard)
|
|
124
|
+
for (const tool of tools) {
|
|
125
|
+
if (tool !== '*' && !TOOL_NAMES.includes(tool)) {
|
|
126
|
+
console.error(`Unknown tool: "${tool}". Valid tools: ${TOOL_NAMES.join(', ')}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Warn about duplicate names
|
|
131
|
+
try {
|
|
132
|
+
const existing = loadMcpConfig();
|
|
133
|
+
if (existing.keys.some((k) => k.name === opts.name)) {
|
|
134
|
+
console.error(`Warning: a key named "${opts.name}" already exists — creating a second entry`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Config unreadable — appendKeyToConfig will handle it
|
|
139
|
+
}
|
|
140
|
+
const token = generateToken();
|
|
141
|
+
const key = { name: opts.name, token, tools };
|
|
142
|
+
if (opts.allowedIps) {
|
|
143
|
+
key.allowedIps = opts.allowedIps.split(',').map((ip) => ip.trim());
|
|
144
|
+
}
|
|
145
|
+
appendKeyToConfig(key);
|
|
146
|
+
// Print token to stdout for piping; info to stderr
|
|
147
|
+
const extras = key.allowedIps ? `, allowedIps: ${key.allowedIps.join(', ')}` : '';
|
|
148
|
+
console.error(`Key "${opts.name}" added to mcp.yml (tools: ${tools.join(', ')}${extras})`);
|
|
149
|
+
console.log(token);
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { IPCMessageType } from '../../constants.js';
|
|
4
|
+
import { daemonClient } from '../../ipc/DaemonClient.js';
|
|
5
|
+
import { formatProcessTable } from './list.js';
|
|
6
|
+
export const reloadCommand = new Command('reload')
|
|
7
|
+
.description('Zero-downtime reload - rolling restart of workers')
|
|
8
|
+
.argument('<target>', 'Process name, id, or "all"')
|
|
9
|
+
.action(async (target) => {
|
|
10
|
+
try {
|
|
11
|
+
const payload = {
|
|
12
|
+
target: target === 'all' ? 'all' : isNaN(Number(target)) ? target : Number(target),
|
|
13
|
+
};
|
|
14
|
+
console.log(chalk.blue(`⟳ Starting graceful reload...`));
|
|
15
|
+
const response = await daemonClient.request(IPCMessageType.RELOAD, payload);
|
|
16
|
+
if (response.success) {
|
|
17
|
+
const results = response.data;
|
|
18
|
+
if (results.length === 0) {
|
|
19
|
+
console.error(chalk.red(`✗ Process not found`));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
for (const info of results) {
|
|
23
|
+
const staleWorkers = info.workers.filter((w) => w.stale);
|
|
24
|
+
if (staleWorkers.length > 0) {
|
|
25
|
+
console.log(chalk.yellow(`⚠ Process "${info.name}" reload partially failed`));
|
|
26
|
+
console.log(chalk.yellow(` Stale workers: ${staleWorkers.map((w) => w.id).join(', ')} (old code still running)`));
|
|
27
|
+
}
|
|
28
|
+
else if (info.execMode === 'fork') {
|
|
29
|
+
console.log(chalk.green(`✓ Process "${info.name}" restarted`));
|
|
30
|
+
console.log(chalk.dim(` Fork mode does not support zero-downtime reload — performed a restart`));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(chalk.green(`✓ Process "${info.name}" reloaded`));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const listResponse = await daemonClient.request(IPCMessageType.LIST);
|
|
37
|
+
if (listResponse.success) {
|
|
38
|
+
console.log(formatProcessTable(listResponse.data));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.error(chalk.red(`✗ Failed to reload: ${response.error}`));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.error(chalk.red(`✗ Error: ${err.message}`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
daemonClient.disconnect();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=reload.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { IPCMessageType } from '../../constants.js';
|
|
4
|
+
import { daemonClient } from '../../ipc/DaemonClient.js';
|
|
5
|
+
import { formatProcessTable } from './list.js';
|
|
6
|
+
export const restartCommand = new Command('restart')
|
|
7
|
+
.description('Restart process(es) - hard restart (kill + start)')
|
|
8
|
+
.argument('<target>', 'Process name, id, or "all"')
|
|
9
|
+
.action(async (target) => {
|
|
10
|
+
try {
|
|
11
|
+
const payload = {
|
|
12
|
+
target: target === 'all' ? 'all' : isNaN(Number(target)) ? target : Number(target),
|
|
13
|
+
};
|
|
14
|
+
const response = await daemonClient.request(IPCMessageType.RESTART, payload);
|
|
15
|
+
if (response.success) {
|
|
16
|
+
const results = response.data;
|
|
17
|
+
if (results.length === 0) {
|
|
18
|
+
console.log(chalk.gray('No processes to restart'));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
for (const info of results) {
|
|
22
|
+
console.log(chalk.green(`↻ Process "${info.name}" restarted`));
|
|
23
|
+
}
|
|
24
|
+
const listResponse = await daemonClient.request(IPCMessageType.LIST);
|
|
25
|
+
if (listResponse.success) {
|
|
26
|
+
console.log(formatProcessTable(listResponse.data));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.error(chalk.red(`✗ Failed to restart: ${response.error}`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error(chalk.red(`✗ Error: ${err.message}`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
daemonClient.disconnect();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=restart.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { DEPLOY_META_FILE, IPC_DEPLOY_TIMEOUT, IPCMessageType, ORKIFY_DEPLOYS_DIR, TELEMETRY_DEFAULT_API_HOST, } from '../../constants.js';
|
|
6
|
+
import { daemonClient } from '../../ipc/DaemonClient.js';
|
|
7
|
+
export const restoreCommand = new Command('restore')
|
|
8
|
+
.description('Restore previously saved process list')
|
|
9
|
+
.argument('[file]', 'Path to snapshot file (default: ~/.orkify/snapshot.yml)')
|
|
10
|
+
.option('--no-remote', 'Skip remote deploy restore and force local snapshot restore')
|
|
11
|
+
.action(async (file, opts) => {
|
|
12
|
+
try {
|
|
13
|
+
// Deploy-aware restore: check for API key + deploy metadata
|
|
14
|
+
if (opts.remote) {
|
|
15
|
+
const apiKey = process.env.ORKIFY_API_KEY;
|
|
16
|
+
const metaPath = join(ORKIFY_DEPLOYS_DIR, 'current', DEPLOY_META_FILE);
|
|
17
|
+
if (apiKey && existsSync(metaPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
20
|
+
const apiHost = process.env.ORKIFY_API_HOST || TELEMETRY_DEFAULT_API_HOST;
|
|
21
|
+
const url = `${apiHost}/api/v1/deploy/restore?artifactId=${encodeURIComponent(meta.artifactId)}`;
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
console.error(chalk.yellow(`⚠ Remote restore failed (${res.status}), falling back to snapshot`));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const payload = (await res.json());
|
|
30
|
+
const response = await daemonClient.request(IPCMessageType.DEPLOY_RESTORE, payload, IPC_DEPLOY_TIMEOUT);
|
|
31
|
+
if (response.success) {
|
|
32
|
+
const data = response.data;
|
|
33
|
+
if ('deployed' in data) {
|
|
34
|
+
console.log(chalk.green(`✓ Deployed version ${data.version} from remote`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const count = data.started.length + data.reloaded.length;
|
|
38
|
+
if (count === 0) {
|
|
39
|
+
console.log(chalk.gray('No processes to restore'));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(chalk.green(`✓ Restored from local deploy:`));
|
|
43
|
+
for (const name of data.started) {
|
|
44
|
+
console.log(` - ${name} (started)`);
|
|
45
|
+
}
|
|
46
|
+
for (const name of data.reloaded) {
|
|
47
|
+
console.log(` - ${name} (reloaded)`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
console.error(chalk.yellow(`⚠ Deploy restore failed: ${response.error}, falling back to snapshot`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(chalk.yellow(`⚠ Deploy restore error: ${err.message}, falling back to snapshot`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Snapshot restore (default / fallback)
|
|
62
|
+
const payload = { file };
|
|
63
|
+
const response = await daemonClient.request(IPCMessageType.RESTORE, payload);
|
|
64
|
+
if (response.success) {
|
|
65
|
+
const results = response.data;
|
|
66
|
+
if (results.length === 0) {
|
|
67
|
+
console.log(chalk.gray('No processes to restore'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
console.log(chalk.green(`✓ Restored ${results.length} process(es):`));
|
|
71
|
+
for (const info of results) {
|
|
72
|
+
console.log(` - ${info.name} (${info.workers.length} worker(s))`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.error(chalk.red(`✗ Failed to restore: ${response.error}`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
console.error(chalk.red(`✗ Error: ${err.message}`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
daemonClient.disconnect();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=restore.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Run command - runs process in foreground with full daemon features
|
|
4
|
+
* Designed for container environments like Docker/Kubernetes
|
|
5
|
+
* Ideal for Docker/Kubernetes where process runs as PID 1
|
|
6
|
+
*/
|
|
7
|
+
export declare const runCommand: Command;
|
|
8
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command, Option } from 'commander';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { DEFAULT_LOG_MAX_AGE, DEFAULT_LOG_MAX_FILES, DEFAULT_LOG_MAX_SIZE, MCP_DEFAULT_PORT, } from '../../constants.js';
|
|
5
|
+
import { startDaemon } from '../../daemon/startDaemon.js';
|
|
6
|
+
import { parseCronSpecs, parseLogSize, parseMemorySize, parseWorkers } from '../parse.js';
|
|
7
|
+
/**
|
|
8
|
+
* Run command - runs process in foreground with full daemon features
|
|
9
|
+
* Designed for container environments like Docker/Kubernetes
|
|
10
|
+
* Ideal for Docker/Kubernetes where process runs as PID 1
|
|
11
|
+
*/
|
|
12
|
+
export const runCommand = new Command('run')
|
|
13
|
+
.description('Run a process in foreground (for containers) with full daemon features')
|
|
14
|
+
.argument('<script>', 'Script file to run')
|
|
15
|
+
.option('-n, --name <name>', 'Process name')
|
|
16
|
+
.option('-w, --workers <number>', 'Number of workers (0 = CPU cores, -1 = CPUs-1)', '1')
|
|
17
|
+
.option('--cwd <path>', 'Working directory')
|
|
18
|
+
.option('--node-args <args>', 'Node.js arguments (space-separated, quoted)')
|
|
19
|
+
.option('--args <args>', 'Script arguments (space-separated, quoted)')
|
|
20
|
+
.option('--sticky', 'Enable sticky sessions for Socket.IO')
|
|
21
|
+
.option('--port <port>', 'Port for sticky session routing (required with --sticky)')
|
|
22
|
+
.option('--kill-timeout <ms>', 'Time to wait for graceful shutdown before SIGKILL', '5000')
|
|
23
|
+
.option('--reload-retries <count>', 'Retries per worker slot during reload (0-3)', '3')
|
|
24
|
+
.option('--max-restarts <count>', 'Maximum restart attempts (default 0 in run mode)', '0')
|
|
25
|
+
.option('--min-uptime <ms>', 'Minimum uptime before restart counts', '1000')
|
|
26
|
+
.option('--restart-delay <ms>', 'Delay between restarts', '100')
|
|
27
|
+
.option('--watch', 'Watch for file changes and reload')
|
|
28
|
+
.option('--watch-paths <paths...>', 'Specific paths to watch')
|
|
29
|
+
.option('--health-check <path>', 'Health check endpoint path (e.g. /health)')
|
|
30
|
+
.option('--log-max-size <size>', 'Max log file size before rotation (e.g. 100M, 500K, 1G)', String(DEFAULT_LOG_MAX_SIZE))
|
|
31
|
+
.option('--log-max-files <count>', 'Rotated log files to keep (0 = no rotation)', String(DEFAULT_LOG_MAX_FILES))
|
|
32
|
+
.option('--log-max-age <days>', 'Delete rotated log files older than N days (0 = no age limit)', String(DEFAULT_LOG_MAX_AGE / (24 * 60 * 60 * 1000)))
|
|
33
|
+
.option('--restart-on-mem <size>', 'Restart when RSS exceeds threshold (e.g. 512M, 1G)')
|
|
34
|
+
.option('--restart-on-memory <size>', 'Alias for --restart-on-mem')
|
|
35
|
+
.option('--cron <spec...>', 'Cron job: "schedule path" (repeatable, e.g. "*/2 * * * * /api/cron/heartbeat-check")')
|
|
36
|
+
.option('--mcp-simple-http', 'Start MCP HTTP server (local key auth)')
|
|
37
|
+
.option('--mcp-port <port>', 'MCP HTTP port', String(MCP_DEFAULT_PORT))
|
|
38
|
+
.option('--mcp-bind <address>', 'MCP bind address', '127.0.0.1')
|
|
39
|
+
.addOption(new Option('--mcp-cors [origin]', 'MCP CORS setting ("*" if no origin given)').preset('*'))
|
|
40
|
+
.option('--silent', 'Suppress startup messages')
|
|
41
|
+
.action(async (script, options) => {
|
|
42
|
+
const cwd = options.cwd || process.cwd();
|
|
43
|
+
const scriptPath = resolve(cwd, script);
|
|
44
|
+
const workerCount = parseWorkers(options.workers);
|
|
45
|
+
const name = options.name ||
|
|
46
|
+
script
|
|
47
|
+
.split('/')
|
|
48
|
+
.pop()
|
|
49
|
+
?.replace(/\.[^.]+$/, '') ||
|
|
50
|
+
'app';
|
|
51
|
+
const silent = options.silent || false;
|
|
52
|
+
// Start daemon stack in-process (foreground mode).
|
|
53
|
+
// startDaemon() acquires an exclusive PID lock — if another daemon
|
|
54
|
+
// or orkify-run instance is active, it throws.
|
|
55
|
+
let ctx;
|
|
56
|
+
try {
|
|
57
|
+
ctx = await startDaemon({ foreground: true, skipTimestampPrefix: true });
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const msg = err.message;
|
|
61
|
+
if (msg.includes('already running')) {
|
|
62
|
+
console.error(chalk.red(`✗ ${msg}\n` + ' Stop it first with `orkify kill` or wait for it to exit.'));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.error(chalk.red(`✗ Failed to initialize daemon: ${msg}`));
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await ctx.startServer();
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error(chalk.red(`✗ Failed to start IPC server: ${err.message}`));
|
|
74
|
+
ctx.cleanup();
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
// Start MCP HTTP server if requested
|
|
78
|
+
if (options.mcpSimpleHttp) {
|
|
79
|
+
try {
|
|
80
|
+
const mcpPayload = {
|
|
81
|
+
transport: 'simple-http',
|
|
82
|
+
port: parseInt(options.mcpPort, 10),
|
|
83
|
+
bind: options.mcpBind,
|
|
84
|
+
cors: options.mcpCors,
|
|
85
|
+
};
|
|
86
|
+
await ctx.startMcpHttp(mcpPayload);
|
|
87
|
+
if (!silent) {
|
|
88
|
+
console.log(chalk.cyan(`[orkify] MCP HTTP server → ${options.mcpBind}:${options.mcpPort}`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error(chalk.yellow(`[orkify] MCP HTTP server failed: ${err.message}`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Parse --cron specs
|
|
96
|
+
const cronJobs = options.cron ? parseCronSpecs(options.cron) : [];
|
|
97
|
+
// Build UpPayload and start the process via orchestrator
|
|
98
|
+
const restartOnMemRaw = options.restartOnMem || options.restartOnMemory;
|
|
99
|
+
const payload = {
|
|
100
|
+
script: scriptPath,
|
|
101
|
+
name,
|
|
102
|
+
workers: workerCount,
|
|
103
|
+
watch: options.watch || false,
|
|
104
|
+
watchPaths: options.watchPaths,
|
|
105
|
+
cwd,
|
|
106
|
+
env: process.env,
|
|
107
|
+
nodeArgs: options.nodeArgs ? options.nodeArgs.split(/\s+/) : [],
|
|
108
|
+
args: options.args ? options.args.split(/\s+/) : [],
|
|
109
|
+
killTimeout: parseInt(options.killTimeout, 10),
|
|
110
|
+
maxRestarts: parseInt(options.maxRestarts, 10),
|
|
111
|
+
minUptime: parseInt(options.minUptime, 10),
|
|
112
|
+
restartDelay: parseInt(options.restartDelay, 10),
|
|
113
|
+
sticky: options.sticky || false,
|
|
114
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
115
|
+
reloadRetries: parseInt(options.reloadRetries, 10),
|
|
116
|
+
healthCheck: options.healthCheck,
|
|
117
|
+
logMaxSize: parseLogSize(options.logMaxSize),
|
|
118
|
+
logMaxFiles: parseInt(options.logMaxFiles, 10),
|
|
119
|
+
logMaxAge: parseInt(options.logMaxAge, 10) * 24 * 60 * 60 * 1000,
|
|
120
|
+
restartOnMemory: restartOnMemRaw ? parseMemorySize(restartOnMemRaw) : undefined,
|
|
121
|
+
cron: cronJobs.length > 0 ? cronJobs : undefined,
|
|
122
|
+
};
|
|
123
|
+
let info;
|
|
124
|
+
try {
|
|
125
|
+
info = await ctx.orchestrator.up(payload);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
console.error(chalk.red(`✗ Failed to start process: ${err.message}`));
|
|
129
|
+
await ctx.gracefulShutdown();
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
// Register cron jobs with the scheduler
|
|
133
|
+
if (payload.cron?.length) {
|
|
134
|
+
ctx.cronScheduler.register(info.name, payload.cron);
|
|
135
|
+
}
|
|
136
|
+
if (!silent) {
|
|
137
|
+
console.log(chalk.cyan(`[orkify] Starting ${info.name} in foreground mode`));
|
|
138
|
+
if (workerCount > 1) {
|
|
139
|
+
console.log(chalk.cyan(`[orkify] Cluster mode: ${workerCount} workers`));
|
|
140
|
+
}
|
|
141
|
+
if (options.sticky) {
|
|
142
|
+
console.log(chalk.cyan(`[orkify] Sticky sessions: port ${options.port}`));
|
|
143
|
+
}
|
|
144
|
+
if (ctx.telemetry) {
|
|
145
|
+
console.log(chalk.cyan(`[orkify] Telemetry enabled`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Forward process output to stdout/stderr (foreground mode shows output directly)
|
|
149
|
+
ctx.orchestrator.on('log', (data) => {
|
|
150
|
+
if (data.type === 'err') {
|
|
151
|
+
process.stderr.write(data.data);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
process.stdout.write(data.data);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Track exit code from the primary process
|
|
158
|
+
let exitCode = 0;
|
|
159
|
+
let shuttingDown = false;
|
|
160
|
+
async function shutdown(code) {
|
|
161
|
+
if (shuttingDown)
|
|
162
|
+
return undefined;
|
|
163
|
+
shuttingDown = true;
|
|
164
|
+
await ctx.gracefulShutdown();
|
|
165
|
+
process.exit(code);
|
|
166
|
+
}
|
|
167
|
+
// Wait for primary process to reach terminal state
|
|
168
|
+
const managedProcess = ctx.orchestrator.getProcess(info.name);
|
|
169
|
+
if (managedProcess) {
|
|
170
|
+
managedProcess.on('process:finished', (data) => {
|
|
171
|
+
if (shuttingDown)
|
|
172
|
+
return;
|
|
173
|
+
if (data.signal) {
|
|
174
|
+
const signalNum = data.signal === 'SIGTERM'
|
|
175
|
+
? 15
|
|
176
|
+
: data.signal === 'SIGINT'
|
|
177
|
+
? 2
|
|
178
|
+
: data.signal === 'SIGHUP'
|
|
179
|
+
? 1
|
|
180
|
+
: 9;
|
|
181
|
+
exitCode = 128 + signalNum;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
exitCode = data.code ?? 0;
|
|
185
|
+
}
|
|
186
|
+
if (!silent && exitCode !== 0) {
|
|
187
|
+
console.log(chalk.red(`[orkify] Process exited with code ${exitCode}`));
|
|
188
|
+
}
|
|
189
|
+
// Use setTimeout to let any pending I/O flush
|
|
190
|
+
setTimeout(() => void shutdown(exitCode), 100);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// Signal handlers
|
|
194
|
+
const handleSignal = (signal) => {
|
|
195
|
+
if (!silent) {
|
|
196
|
+
console.log(chalk.yellow(`\n[orkify] Received ${signal}, shutting down...`));
|
|
197
|
+
}
|
|
198
|
+
void shutdown(0);
|
|
199
|
+
};
|
|
200
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
201
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
202
|
+
process.on('SIGHUP', () => handleSignal('SIGHUP'));
|
|
203
|
+
process.on('uncaughtException', async (err) => {
|
|
204
|
+
console.error('Uncaught exception:', err);
|
|
205
|
+
await shutdown(1);
|
|
206
|
+
});
|
|
207
|
+
process.on('unhandledRejection', async (reason) => {
|
|
208
|
+
console.error('Unhandled rejection:', reason);
|
|
209
|
+
await shutdown(1);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { IPCMessageType, SNAPSHOT_FILE } from '../../constants.js';
|
|
4
|
+
import { daemonClient } from '../../ipc/DaemonClient.js';
|
|
5
|
+
export const snapCommand = new Command('snap')
|
|
6
|
+
.description('Snapshot current process list for later restoration')
|
|
7
|
+
.argument('[file]', 'Path to snapshot file (default: ~/.orkify/snapshot.yml)')
|
|
8
|
+
.option('--no-env', 'Do not save environment variables in snapshot file')
|
|
9
|
+
.action(async (file, options) => {
|
|
10
|
+
try {
|
|
11
|
+
const payload = { noEnv: !options.env, file };
|
|
12
|
+
const response = await daemonClient.request(IPCMessageType.SNAP, payload);
|
|
13
|
+
if (response.success) {
|
|
14
|
+
console.log(chalk.green(`✓ Snapshot saved`));
|
|
15
|
+
console.log(chalk.gray(` File: ${file || SNAPSHOT_FILE}`));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error(chalk.red(`✗ Failed to save snapshot: ${response.error}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error(chalk.red(`✗ Error: ${err.message}`));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
daemonClient.disconnect();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=snap.js.map
|