jfl 0.1.0 → 0.2.0
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/README.md +443 -145
- package/clawdbot-plugin/clawdbot.plugin.json +20 -0
- package/clawdbot-plugin/index.js +555 -0
- package/clawdbot-plugin/index.ts +582 -0
- package/clawdbot-skill/SKILL.md +33 -336
- package/clawdbot-skill/index.ts +491 -321
- package/clawdbot-skill/skill.json +4 -13
- package/dist/commands/clawdbot.d.ts +11 -0
- package/dist/commands/clawdbot.d.ts.map +1 -0
- package/dist/commands/clawdbot.js +215 -0
- package/dist/commands/clawdbot.js.map +1 -0
- package/dist/commands/context-hub.d.ts +5 -0
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +394 -28
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/gtm-process-update.d.ts +10 -0
- package/dist/commands/gtm-process-update.d.ts.map +1 -0
- package/dist/commands/gtm-process-update.js +101 -0
- package/dist/commands/gtm-process-update.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +278 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +32 -33
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory.d.ts +38 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +229 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/migrate-services.d.ts +8 -0
- package/dist/commands/migrate-services.d.ts.map +1 -0
- package/dist/commands/migrate-services.js +182 -0
- package/dist/commands/migrate-services.js.map +1 -0
- package/dist/commands/onboard.d.ts +24 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +663 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/openclaw.d.ts +56 -0
- package/dist/commands/openclaw.d.ts.map +1 -0
- package/dist/commands/openclaw.js +700 -0
- package/dist/commands/openclaw.js.map +1 -0
- package/dist/commands/orchestrate.d.ts +14 -0
- package/dist/commands/orchestrate.d.ts.map +1 -0
- package/dist/commands/orchestrate.js +270 -0
- package/dist/commands/orchestrate.js.map +1 -0
- package/dist/commands/profile.d.ts +46 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +498 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/repair.d.ts.map +1 -1
- package/dist/commands/repair.js +37 -0
- package/dist/commands/repair.js.map +1 -1
- package/dist/commands/service-agent.d.ts +16 -0
- package/dist/commands/service-agent.d.ts.map +1 -0
- package/dist/commands/service-agent.js +375 -0
- package/dist/commands/service-agent.js.map +1 -0
- package/dist/commands/service-manager.d.ts +12 -0
- package/dist/commands/service-manager.d.ts.map +1 -0
- package/dist/commands/service-manager.js +967 -0
- package/dist/commands/service-manager.js.map +1 -0
- package/dist/commands/service-validate.d.ts +12 -0
- package/dist/commands/service-validate.d.ts.map +1 -0
- package/dist/commands/service-validate.js +611 -0
- package/dist/commands/service-validate.js.map +1 -0
- package/dist/commands/services-create.d.ts +15 -0
- package/dist/commands/services-create.d.ts.map +1 -0
- package/dist/commands/services-create.js +1452 -0
- package/dist/commands/services-create.js.map +1 -0
- package/dist/commands/services-scan.d.ts +13 -0
- package/dist/commands/services-scan.d.ts.map +1 -0
- package/dist/commands/services-scan.js +251 -0
- package/dist/commands/services-scan.js.map +1 -0
- package/dist/commands/services-sync-agents.d.ts +23 -0
- package/dist/commands/services-sync-agents.d.ts.map +1 -0
- package/dist/commands/services-sync-agents.js +207 -0
- package/dist/commands/services-sync-agents.js.map +1 -0
- package/dist/commands/services.d.ts +19 -0
- package/dist/commands/services.d.ts.map +1 -0
- package/dist/commands/services.js +742 -0
- package/dist/commands/services.js.map +1 -0
- package/dist/commands/session.d.ts +5 -1
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +68 -586
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +17 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +75 -21
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate-settings.d.ts +37 -0
- package/dist/commands/validate-settings.d.ts.map +1 -0
- package/dist/commands/validate-settings.js +197 -0
- package/dist/commands/validate-settings.js.map +1 -0
- package/dist/commands/voice.d.ts +0 -1
- package/dist/commands/voice.d.ts.map +1 -1
- package/dist/commands/voice.js +16 -15
- package/dist/commands/voice.js.map +1 -1
- package/dist/index.js +395 -141
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts +26 -0
- package/dist/lib/agent-generator.d.ts.map +1 -0
- package/dist/lib/agent-generator.js +331 -0
- package/dist/lib/agent-generator.js.map +1 -0
- package/dist/lib/memory-db.d.ts +102 -0
- package/dist/lib/memory-db.d.ts.map +1 -0
- package/dist/lib/memory-db.js +313 -0
- package/dist/lib/memory-db.js.map +1 -0
- package/dist/lib/memory-indexer.d.ts +47 -0
- package/dist/lib/memory-indexer.d.ts.map +1 -0
- package/dist/lib/memory-indexer.js +215 -0
- package/dist/lib/memory-indexer.js.map +1 -0
- package/dist/lib/memory-search.d.ts +41 -0
- package/dist/lib/memory-search.d.ts.map +1 -0
- package/dist/lib/memory-search.js +246 -0
- package/dist/lib/memory-search.js.map +1 -0
- package/dist/lib/openclaw-registry.d.ts +48 -0
- package/dist/lib/openclaw-registry.d.ts.map +1 -0
- package/dist/lib/openclaw-registry.js +181 -0
- package/dist/lib/openclaw-registry.js.map +1 -0
- package/dist/lib/openclaw-sdk.d.ts +107 -0
- package/dist/lib/openclaw-sdk.d.ts.map +1 -0
- package/dist/lib/openclaw-sdk.js +208 -0
- package/dist/lib/openclaw-sdk.js.map +1 -0
- package/dist/lib/peer-agent-generator.d.ts +44 -0
- package/dist/lib/peer-agent-generator.d.ts.map +1 -0
- package/dist/lib/peer-agent-generator.js +286 -0
- package/dist/lib/peer-agent-generator.js.map +1 -0
- package/dist/lib/service-dependencies.d.ts +44 -0
- package/dist/lib/service-dependencies.d.ts.map +1 -0
- package/dist/lib/service-dependencies.js +314 -0
- package/dist/lib/service-dependencies.js.map +1 -0
- package/dist/lib/service-detector.d.ts +61 -0
- package/dist/lib/service-detector.d.ts.map +1 -0
- package/dist/lib/service-detector.js +521 -0
- package/dist/lib/service-detector.js.map +1 -0
- package/dist/lib/service-gtm.d.ts +157 -0
- package/dist/lib/service-gtm.d.ts.map +1 -0
- package/dist/lib/service-gtm.js +786 -0
- package/dist/lib/service-gtm.js.map +1 -0
- package/dist/lib/service-mcp-base.d.ts +103 -0
- package/dist/lib/service-mcp-base.d.ts.map +1 -0
- package/dist/lib/service-mcp-base.js +263 -0
- package/dist/lib/service-mcp-base.js.map +1 -0
- package/dist/lib/service-utils.d.ts +103 -0
- package/dist/lib/service-utils.d.ts.map +1 -0
- package/dist/lib/service-utils.js +368 -0
- package/dist/lib/service-utils.js.map +1 -0
- package/dist/lib/skill-generator.d.ts +21 -0
- package/dist/lib/skill-generator.d.ts.map +1 -0
- package/dist/lib/skill-generator.js +253 -0
- package/dist/lib/skill-generator.js.map +1 -0
- package/dist/lib/stratus-client.d.ts +100 -0
- package/dist/lib/stratus-client.d.ts.map +1 -0
- package/dist/lib/stratus-client.js +255 -0
- package/dist/lib/stratus-client.js.map +1 -0
- package/dist/mcp/context-hub-mcp.js +135 -53
- package/dist/mcp/context-hub-mcp.js.map +1 -1
- package/dist/mcp/service-mcp-server.d.ts +12 -0
- package/dist/mcp/service-mcp-server.d.ts.map +1 -0
- package/dist/mcp/service-mcp-server.js +434 -0
- package/dist/mcp/service-mcp-server.js.map +1 -0
- package/dist/mcp/service-peer-mcp.d.ts +36 -0
- package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
- package/dist/mcp/service-peer-mcp.js +220 -0
- package/dist/mcp/service-peer-mcp.js.map +1 -0
- package/dist/mcp/service-registry-mcp.d.ts +13 -0
- package/dist/mcp/service-registry-mcp.d.ts.map +1 -0
- package/dist/mcp/service-registry-mcp.js +330 -0
- package/dist/mcp/service-registry-mcp.js.map +1 -0
- package/dist/ui/banner.js +1 -1
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/context-hub-logs.d.ts +10 -0
- package/dist/ui/context-hub-logs.d.ts.map +1 -0
- package/dist/ui/context-hub-logs.js +175 -0
- package/dist/ui/context-hub-logs.js.map +1 -0
- package/dist/ui/service-dashboard.d.ts +11 -0
- package/dist/ui/service-dashboard.d.ts.map +1 -0
- package/dist/ui/service-dashboard.js +357 -0
- package/dist/ui/service-dashboard.js.map +1 -0
- package/dist/ui/services-manager.d.ts +11 -0
- package/dist/ui/services-manager.d.ts.map +1 -0
- package/dist/ui/services-manager.js +507 -0
- package/dist/ui/services-manager.js.map +1 -0
- package/dist/utils/auth-guard.d.ts.map +1 -1
- package/dist/utils/auth-guard.js +8 -9
- package/dist/utils/auth-guard.js.map +1 -1
- package/dist/utils/claude-md-generator.d.ts +10 -0
- package/dist/utils/claude-md-generator.d.ts.map +1 -0
- package/dist/utils/claude-md-generator.js +215 -0
- package/dist/utils/claude-md-generator.js.map +1 -0
- package/dist/utils/ensure-context-hub.d.ts +20 -0
- package/dist/utils/ensure-context-hub.d.ts.map +1 -0
- package/dist/utils/ensure-context-hub.js +65 -0
- package/dist/utils/ensure-context-hub.js.map +1 -0
- package/dist/utils/ensure-project.d.ts.map +1 -1
- package/dist/utils/ensure-project.js +3 -4
- package/dist/utils/ensure-project.js.map +1 -1
- package/dist/utils/jfl-config.d.ts +19 -0
- package/dist/utils/jfl-config.d.ts.map +1 -0
- package/dist/utils/jfl-config.js +112 -0
- package/dist/utils/jfl-config.js.map +1 -0
- package/dist/utils/jfl-migration.d.ts +29 -0
- package/dist/utils/jfl-migration.d.ts.map +1 -0
- package/dist/utils/jfl-migration.js +142 -0
- package/dist/utils/jfl-migration.js.map +1 -0
- package/dist/utils/jfl-paths.d.ts +55 -0
- package/dist/utils/jfl-paths.d.ts.map +1 -0
- package/dist/utils/jfl-paths.js +120 -0
- package/dist/utils/jfl-paths.js.map +1 -0
- package/dist/utils/settings-validator.d.ts +73 -0
- package/dist/utils/settings-validator.d.ts.map +1 -0
- package/dist/utils/settings-validator.js +222 -0
- package/dist/utils/settings-validator.js.map +1 -0
- package/package.json +19 -3
- package/scripts/commit-gtm.sh +56 -0
- package/scripts/commit-product.sh +68 -0
- package/scripts/context-query.sh +45 -0
- package/scripts/session/auto-commit.sh +297 -0
- package/scripts/session/jfl-doctor.sh +707 -0
- package/scripts/session/session-cleanup.sh +268 -0
- package/scripts/session/session-end.sh +198 -0
- package/scripts/session/session-init.sh +350 -0
- package/scripts/session/session-init.sh.backup +292 -0
- package/scripts/session/session-sync.sh +167 -0
- package/scripts/session/test-context-preservation.sh +160 -0
- package/scripts/session/test-critical-infrastructure.sh +293 -0
- package/scripts/session/test-experience-level.sh +336 -0
- package/scripts/session/test-session-cleanup.sh +268 -0
- package/scripts/session/test-session-sync.sh +320 -0
- package/scripts/voice-start.sh +36 -8
- package/scripts/where-am-i.sh +78 -0
- package/template/.claude/service-settings.json +32 -0
- package/template/.claude/settings.json +14 -1
- package/template/.claude/skills/end/SKILL.md +1780 -0
- package/template/.jfl/config.json +2 -1
- package/template/CLAUDE.md +1039 -134
- package/template/CLAUDE.md.bak +1187 -0
- package/template/scripts/commit-gtm.sh +56 -0
- package/template/scripts/commit-product.sh +68 -0
- package/template/scripts/migrate-to-branch-sessions.sh +201 -0
- package/template/scripts/session/auto-commit.sh +58 -6
- package/template/scripts/session/jfl-doctor.sh +137 -17
- package/template/scripts/session/session-cleanup.sh +268 -0
- package/template/scripts/session/session-end.sh +4 -0
- package/template/scripts/session/session-init.sh +253 -66
- package/template/scripts/session/test-critical-infrastructure.sh +293 -0
- package/template/scripts/session/test-experience-level.sh +336 -0
- package/template/scripts/session/test-session-cleanup.sh +268 -0
- package/template/scripts/session/test-session-sync.sh +320 -0
- package/template/scripts/where-am-i.sh +78 -0
- package/template/templates/service-agent/.claude/settings.json +32 -0
- package/template/templates/service-agent/CLAUDE.md +334 -0
- package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
- package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
- package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
- package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
- package/dist/commands/session-mgmt.d.ts +0 -33
- package/dist/commands/session-mgmt.d.ts.map +0 -1
- package/dist/commands/session-mgmt.js +0 -404
- package/dist/commands/session-mgmt.js.map +0 -1
- package/template/scripts/session/auto-merge.sh +0 -325
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Service Management
|
|
3
|
+
*
|
|
4
|
+
* Manages services across all GTM projects with port allocation.
|
|
5
|
+
*
|
|
6
|
+
* @purpose Global service management with dynamic port allocation
|
|
7
|
+
* @spec Multi-GTM Service Management Plan - Phase 2
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { exec } from "child_process";
|
|
13
|
+
import { promisify } from "util";
|
|
14
|
+
import { JFL_PATHS } from "../utils/jfl-paths.js";
|
|
15
|
+
import { restartCoreServices, validateCoreServices } from "../lib/service-utils.js";
|
|
16
|
+
import { findGTMParent, validateGTMParent, getRegisteredServices, updateServiceSync, syncJournalsToGTM, phoneHomeToGTM, } from "../lib/service-gtm.js";
|
|
17
|
+
import { serviceValidate } from "./service-validate.js";
|
|
18
|
+
import { servicesSyncAgentsCommand } from "./services-sync-agents.js";
|
|
19
|
+
import { servicesCreateCommand } from "./services-create.js";
|
|
20
|
+
import chalk from "chalk";
|
|
21
|
+
const execAsync = promisify(exec);
|
|
22
|
+
const GLOBAL_SERVICES_FILE = path.join(JFL_PATHS.data, "services.json");
|
|
23
|
+
const PORT_REGISTRY_FILE = path.join(JFL_PATHS.data, "service-ports.json");
|
|
24
|
+
/**
|
|
25
|
+
* Load global services configuration
|
|
26
|
+
*/
|
|
27
|
+
function loadGlobalServices() {
|
|
28
|
+
if (!fs.existsSync(GLOBAL_SERVICES_FILE)) {
|
|
29
|
+
return { version: "1.0", services: {} };
|
|
30
|
+
}
|
|
31
|
+
const content = fs.readFileSync(GLOBAL_SERVICES_FILE, "utf-8");
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load project services configuration
|
|
36
|
+
*/
|
|
37
|
+
function loadProjectServices(projectRoot) {
|
|
38
|
+
const projectServicesFile = path.join(projectRoot, ".jfl", "services.json");
|
|
39
|
+
if (!fs.existsSync(projectServicesFile)) {
|
|
40
|
+
return { version: "1.0", services: {} };
|
|
41
|
+
}
|
|
42
|
+
const content = fs.readFileSync(projectServicesFile, "utf-8");
|
|
43
|
+
return JSON.parse(content);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Load port registry
|
|
47
|
+
*/
|
|
48
|
+
function loadPortRegistry() {
|
|
49
|
+
if (!fs.existsSync(PORT_REGISTRY_FILE)) {
|
|
50
|
+
return { version: "1.0", allocated_ports: {} };
|
|
51
|
+
}
|
|
52
|
+
const content = fs.readFileSync(PORT_REGISTRY_FILE, "utf-8");
|
|
53
|
+
return JSON.parse(content);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Save port registry
|
|
57
|
+
*/
|
|
58
|
+
function savePortRegistry(registry) {
|
|
59
|
+
const dir = path.dirname(PORT_REGISTRY_FILE);
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
61
|
+
fs.writeFileSync(PORT_REGISTRY_FILE, JSON.stringify(registry, null, 2));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if a port is available
|
|
65
|
+
*/
|
|
66
|
+
async function isPortAvailable(port) {
|
|
67
|
+
try {
|
|
68
|
+
const { stdout } = await execAsync(`lsof -ti:${port}`);
|
|
69
|
+
return stdout.trim().length === 0;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// lsof returns non-zero if no process found (port is available)
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Allocate a port for a service
|
|
78
|
+
*/
|
|
79
|
+
async function allocatePort(serviceName, projectPath, basePort) {
|
|
80
|
+
const registry = loadPortRegistry();
|
|
81
|
+
// Check if already allocated
|
|
82
|
+
for (const [port, allocation] of Object.entries(registry.allocated_ports)) {
|
|
83
|
+
if (allocation.service === serviceName &&
|
|
84
|
+
allocation.project === projectPath) {
|
|
85
|
+
const portNum = parseInt(port);
|
|
86
|
+
if (await isPortAvailable(portNum)) {
|
|
87
|
+
return portNum;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Find next available port
|
|
92
|
+
const startPort = basePort || 3000;
|
|
93
|
+
for (let port = startPort; port < startPort + 100; port++) {
|
|
94
|
+
// Skip if port is already in registry
|
|
95
|
+
if (registry.allocated_ports[port.toString()]) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// Check if port is actually available
|
|
99
|
+
if (await isPortAvailable(port)) {
|
|
100
|
+
// Allocate port
|
|
101
|
+
registry.allocated_ports[port.toString()] = {
|
|
102
|
+
service: serviceName,
|
|
103
|
+
project: projectPath,
|
|
104
|
+
allocated_at: new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
savePortRegistry(registry);
|
|
107
|
+
return port;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Failed to allocate port for ${serviceName} (tried ${startPort}-${startPort + 99})`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Release a port allocation
|
|
114
|
+
*/
|
|
115
|
+
function releasePort(port) {
|
|
116
|
+
const registry = loadPortRegistry();
|
|
117
|
+
delete registry.allocated_ports[port.toString()];
|
|
118
|
+
savePortRegistry(registry);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Substitute variables in a string
|
|
122
|
+
*/
|
|
123
|
+
function substituteVariables(str, vars) {
|
|
124
|
+
let result = str;
|
|
125
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
126
|
+
result = result.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value);
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Check if service is running
|
|
132
|
+
*/
|
|
133
|
+
async function isServiceRunning(service, port) {
|
|
134
|
+
try {
|
|
135
|
+
const vars = {
|
|
136
|
+
PORT: port?.toString() || service.port?.toString() || "",
|
|
137
|
+
HOME: homedir(),
|
|
138
|
+
};
|
|
139
|
+
const detectionCmd = substituteVariables(service.detection_command, vars);
|
|
140
|
+
const { stdout } = await execAsync(detectionCmd);
|
|
141
|
+
return stdout.trim().length > 0;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Start a service
|
|
149
|
+
*/
|
|
150
|
+
async function startService(serviceName, service, projectRoot, allocatedPort) {
|
|
151
|
+
const port = allocatedPort || service.port;
|
|
152
|
+
const vars = {
|
|
153
|
+
PORT: port?.toString() || "",
|
|
154
|
+
HOME: homedir(),
|
|
155
|
+
WORKSPACE: projectRoot || process.cwd(),
|
|
156
|
+
};
|
|
157
|
+
const startCmd = substituteVariables(service.start_command, vars);
|
|
158
|
+
console.log(`Starting ${serviceName}...`);
|
|
159
|
+
console.log(`Command: ${startCmd}`);
|
|
160
|
+
try {
|
|
161
|
+
await execAsync(startCmd, { cwd: projectRoot || process.cwd() });
|
|
162
|
+
console.log(`✓ ${serviceName} started successfully`);
|
|
163
|
+
if (port) {
|
|
164
|
+
console.log(` Port: ${port}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw new Error(`Failed to start ${serviceName}: ${error.message}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Stop a service
|
|
173
|
+
*/
|
|
174
|
+
async function stopService(serviceName, service, port) {
|
|
175
|
+
const vars = {
|
|
176
|
+
PORT: port?.toString() || service.port?.toString() || "",
|
|
177
|
+
HOME: homedir(),
|
|
178
|
+
};
|
|
179
|
+
const stopCmd = substituteVariables(service.stop_command, vars);
|
|
180
|
+
console.log(`Stopping ${serviceName}...`);
|
|
181
|
+
try {
|
|
182
|
+
await execAsync(stopCmd);
|
|
183
|
+
console.log(`✓ ${serviceName} stopped`);
|
|
184
|
+
// Release port if allocated
|
|
185
|
+
if (port) {
|
|
186
|
+
releasePort(port);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
throw new Error(`Failed to stop ${serviceName}: ${error.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* List all services
|
|
195
|
+
*/
|
|
196
|
+
async function listServices() {
|
|
197
|
+
const globalServices = loadGlobalServices();
|
|
198
|
+
const projectRoot = process.cwd();
|
|
199
|
+
const projectServices = loadProjectServices(projectRoot);
|
|
200
|
+
const registry = loadPortRegistry();
|
|
201
|
+
console.log("Global Services:");
|
|
202
|
+
console.log("================");
|
|
203
|
+
if (globalServices?.services) {
|
|
204
|
+
for (const [name, service] of Object.entries(globalServices.services)) {
|
|
205
|
+
const running = await isServiceRunning(service);
|
|
206
|
+
const status = running ? "✓ Running" : "✗ Stopped";
|
|
207
|
+
const port = service.port || "N/A";
|
|
208
|
+
console.log(` ${name}: ${status} (port: ${port})`);
|
|
209
|
+
console.log(` ${service.description}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
console.log("");
|
|
213
|
+
console.log("Project Services:");
|
|
214
|
+
console.log("=================");
|
|
215
|
+
if (projectServices?.services) {
|
|
216
|
+
for (const [name, service] of Object.entries(projectServices.services)) {
|
|
217
|
+
// Find allocated port
|
|
218
|
+
let allocatedPort;
|
|
219
|
+
if (registry?.allocated_ports) {
|
|
220
|
+
for (const [port, allocation] of Object.entries(registry.allocated_ports)) {
|
|
221
|
+
if (allocation.service === name &&
|
|
222
|
+
allocation.project === projectRoot) {
|
|
223
|
+
allocatedPort = parseInt(port);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const running = await isServiceRunning(service, allocatedPort);
|
|
229
|
+
const status = running ? "✓ Running" : "✗ Stopped";
|
|
230
|
+
const port = allocatedPort || service.base_port || "Not allocated";
|
|
231
|
+
console.log(` ${name}: ${status} (port: ${port})`);
|
|
232
|
+
console.log(` ${service.description}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Show service status
|
|
238
|
+
*/
|
|
239
|
+
async function showStatus() {
|
|
240
|
+
const globalServices = loadGlobalServices();
|
|
241
|
+
const projectRoot = process.cwd();
|
|
242
|
+
const projectServices = loadProjectServices(projectRoot);
|
|
243
|
+
const registry = loadPortRegistry();
|
|
244
|
+
let runningCount = 0;
|
|
245
|
+
let totalCount = 0;
|
|
246
|
+
// Check global services
|
|
247
|
+
if (globalServices?.services) {
|
|
248
|
+
for (const [name, service] of Object.entries(globalServices.services)) {
|
|
249
|
+
totalCount++;
|
|
250
|
+
if (await isServiceRunning(service)) {
|
|
251
|
+
runningCount++;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Check project services
|
|
256
|
+
if (projectServices?.services) {
|
|
257
|
+
for (const [name, service] of Object.entries(projectServices.services)) {
|
|
258
|
+
totalCount++;
|
|
259
|
+
// Find allocated port
|
|
260
|
+
let allocatedPort;
|
|
261
|
+
for (const [port, allocation] of Object.entries(registry.allocated_ports)) {
|
|
262
|
+
if (allocation.service === name &&
|
|
263
|
+
allocation.project === projectRoot) {
|
|
264
|
+
allocatedPort = parseInt(port);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (await isServiceRunning(service, allocatedPort)) {
|
|
269
|
+
runningCount++;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
console.log(`Services: ${runningCount}/${totalCount} running`);
|
|
274
|
+
console.log(`Allocated ports: ${Object.keys(registry?.allocated_ports || {}).length}`);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Start a specific service
|
|
278
|
+
*/
|
|
279
|
+
async function startSpecificService(serviceName) {
|
|
280
|
+
const globalServices = loadGlobalServices();
|
|
281
|
+
const projectRoot = process.cwd();
|
|
282
|
+
const projectServices = loadProjectServices(projectRoot);
|
|
283
|
+
// Check global services first
|
|
284
|
+
if (globalServices.services[serviceName]) {
|
|
285
|
+
const service = globalServices.services[serviceName];
|
|
286
|
+
await startService(serviceName, service);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// Check project services
|
|
290
|
+
if (projectServices.services[serviceName]) {
|
|
291
|
+
const service = projectServices.services[serviceName];
|
|
292
|
+
// Allocate port if needed
|
|
293
|
+
let port;
|
|
294
|
+
if (service.base_port) {
|
|
295
|
+
port = await allocatePort(serviceName, projectRoot, service.base_port);
|
|
296
|
+
}
|
|
297
|
+
await startService(serviceName, service, projectRoot, port);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
throw new Error(`Service not found: ${serviceName}`);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Stop a specific service
|
|
304
|
+
*/
|
|
305
|
+
async function stopSpecificService(serviceName) {
|
|
306
|
+
const globalServices = loadGlobalServices();
|
|
307
|
+
const projectRoot = process.cwd();
|
|
308
|
+
const projectServices = loadProjectServices(projectRoot);
|
|
309
|
+
const registry = loadPortRegistry();
|
|
310
|
+
// Check global services first
|
|
311
|
+
if (globalServices.services[serviceName]) {
|
|
312
|
+
const service = globalServices.services[serviceName];
|
|
313
|
+
await stopService(serviceName, service);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// Check project services
|
|
317
|
+
if (projectServices.services[serviceName]) {
|
|
318
|
+
const service = projectServices.services[serviceName];
|
|
319
|
+
// Find allocated port
|
|
320
|
+
let allocatedPort;
|
|
321
|
+
for (const [port, allocation] of Object.entries(registry.allocated_ports)) {
|
|
322
|
+
if (allocation.service === serviceName &&
|
|
323
|
+
allocation.project === projectRoot) {
|
|
324
|
+
allocatedPort = parseInt(port);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
await stopService(serviceName, service, allocatedPort);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
throw new Error(`Service not found: ${serviceName}`);
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Deploy skill to registered services
|
|
335
|
+
*/
|
|
336
|
+
async function deploySkillToServices(skillName, targetService, dryRun = false) {
|
|
337
|
+
const projectRoot = process.cwd();
|
|
338
|
+
const configPath = path.join(projectRoot, ".jfl", "config.json");
|
|
339
|
+
if (!fs.existsSync(configPath)) {
|
|
340
|
+
console.error("Error: Not in a JFL project (no .jfl/config.json)");
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
344
|
+
if (config.type !== "gtm") {
|
|
345
|
+
console.error("Error: This command only works in GTM workspaces");
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
const services = getRegisteredServices(projectRoot);
|
|
349
|
+
if (services.length === 0) {
|
|
350
|
+
console.log("No services registered. Use 'jfl services register' to add services.");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const skillSourcePath = path.join(projectRoot, ".claude", "skills", skillName);
|
|
354
|
+
if (!fs.existsSync(skillSourcePath)) {
|
|
355
|
+
console.error(`Error: Skill not found: ${skillSourcePath}`);
|
|
356
|
+
console.log("\nAvailable skills:");
|
|
357
|
+
const skillsDir = path.join(projectRoot, ".claude", "skills");
|
|
358
|
+
if (fs.existsSync(skillsDir)) {
|
|
359
|
+
const skills = fs.readdirSync(skillsDir).filter((f) => fs.statSync(path.join(skillsDir, f)).isDirectory());
|
|
360
|
+
skills.forEach((s) => console.log(` - ${s}`));
|
|
361
|
+
}
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
const servicesToDeploy = targetService
|
|
365
|
+
? services.filter((s) => s.name === targetService)
|
|
366
|
+
: services;
|
|
367
|
+
if (servicesToDeploy.length === 0) {
|
|
368
|
+
console.error(`Error: Service not found: ${targetService}`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
console.log(chalk.cyan(`\n📦 Deploying skill: ${skillName}\n`));
|
|
372
|
+
for (const service of servicesToDeploy) {
|
|
373
|
+
const servicePath = path.isAbsolute(service.path)
|
|
374
|
+
? service.path
|
|
375
|
+
: path.join(projectRoot, service.path);
|
|
376
|
+
const skillDestPath = path.join(servicePath, ".claude", "skills", skillName);
|
|
377
|
+
if (dryRun) {
|
|
378
|
+
console.log(`Would deploy to: ${servicePath}`);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
// Create destination directory
|
|
383
|
+
fs.mkdirSync(path.dirname(skillDestPath), { recursive: true });
|
|
384
|
+
// Copy skill directory
|
|
385
|
+
copyDirectory(skillSourcePath, skillDestPath);
|
|
386
|
+
console.log(chalk.green(` ✓ ${service.name}`));
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
console.log(chalk.red(` ✗ ${service.name}: ${error.message}`));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
console.log("");
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Copy directory recursively
|
|
396
|
+
*/
|
|
397
|
+
function copyDirectory(src, dest) {
|
|
398
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
399
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
400
|
+
for (const entry of entries) {
|
|
401
|
+
const srcPath = path.join(src, entry.name);
|
|
402
|
+
const destPath = path.join(dest, entry.name);
|
|
403
|
+
if (entry.isDirectory()) {
|
|
404
|
+
copyDirectory(srcPath, destPath);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
fs.copyFileSync(srcPath, destPath);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Sync service to GTM manually
|
|
413
|
+
*/
|
|
414
|
+
async function syncServiceToGTM(serviceName, dryRun = false) {
|
|
415
|
+
const projectRoot = process.cwd();
|
|
416
|
+
const configPath = path.join(projectRoot, ".jfl", "config.json");
|
|
417
|
+
if (!fs.existsSync(configPath)) {
|
|
418
|
+
console.error("Error: Not in a JFL project (no .jfl/config.json)");
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
422
|
+
if (config.type !== "gtm") {
|
|
423
|
+
console.error("Error: This command only works in GTM workspaces");
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
const services = getRegisteredServices(projectRoot);
|
|
427
|
+
if (services.length === 0) {
|
|
428
|
+
console.log("No services registered.");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const servicesToSync = serviceName
|
|
432
|
+
? services.filter((s) => s.name === serviceName)
|
|
433
|
+
: services;
|
|
434
|
+
if (servicesToSync.length === 0) {
|
|
435
|
+
console.error(`Error: Service not found: ${serviceName}`);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
console.log(chalk.cyan(`\n📡 Syncing services to GTM...\n`));
|
|
439
|
+
for (const service of servicesToSync) {
|
|
440
|
+
const servicePath = path.isAbsolute(service.path)
|
|
441
|
+
? service.path
|
|
442
|
+
: path.join(projectRoot, service.path);
|
|
443
|
+
if (dryRun) {
|
|
444
|
+
console.log(`Would sync: ${service.name}`);
|
|
445
|
+
const journalPath = path.join(servicePath, ".jfl", "journal");
|
|
446
|
+
if (fs.existsSync(journalPath)) {
|
|
447
|
+
const files = fs.readdirSync(journalPath).filter((f) => f.endsWith(".jsonl"));
|
|
448
|
+
console.log(` ${files.length} journal file(s)`);
|
|
449
|
+
}
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
// Sync journals
|
|
454
|
+
const syncedCount = syncJournalsToGTM(servicePath, projectRoot, service.name);
|
|
455
|
+
// Update timestamp
|
|
456
|
+
const timestamp = new Date().toISOString();
|
|
457
|
+
updateServiceSync(projectRoot, service.name, timestamp);
|
|
458
|
+
// Create sync entry in GTM journal
|
|
459
|
+
const gtmBranch = config.working_branch || "main";
|
|
460
|
+
const gtmJournalFile = path.join(projectRoot, ".jfl", "journal", `${gtmBranch}.jsonl`);
|
|
461
|
+
const syncEntry = {
|
|
462
|
+
v: 1,
|
|
463
|
+
ts: timestamp,
|
|
464
|
+
session: gtmBranch,
|
|
465
|
+
type: "sync",
|
|
466
|
+
title: `Service sync: ${service.name}`,
|
|
467
|
+
summary: `Synced ${syncedCount} journal file(s) from ${service.name}`,
|
|
468
|
+
service: service.name,
|
|
469
|
+
files_synced: syncedCount,
|
|
470
|
+
};
|
|
471
|
+
fs.appendFileSync(gtmJournalFile, JSON.stringify(syncEntry) + "\n");
|
|
472
|
+
console.log(chalk.green(` ✓ ${service.name}`));
|
|
473
|
+
console.log(chalk.gray(` Synced ${syncedCount} journal file(s)`));
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
console.log(chalk.red(` ✗ ${service.name}: ${error.message}`));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
console.log("");
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Enhanced health check with GTM connectivity
|
|
483
|
+
*/
|
|
484
|
+
async function checkServiceGTMHealth(serviceName) {
|
|
485
|
+
const projectRoot = process.cwd();
|
|
486
|
+
const configPath = path.join(projectRoot, ".jfl", "config.json");
|
|
487
|
+
if (!fs.existsSync(configPath)) {
|
|
488
|
+
console.error("Error: Not in a JFL project (no .jfl/config.json)");
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
492
|
+
if (config.type !== "gtm") {
|
|
493
|
+
console.error("Error: This command only works in GTM workspaces");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
const services = getRegisteredServices(projectRoot);
|
|
497
|
+
if (services.length === 0) {
|
|
498
|
+
console.log("No services registered.");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const servicesToCheck = serviceName
|
|
502
|
+
? services.filter((s) => s.name === serviceName)
|
|
503
|
+
: services;
|
|
504
|
+
if (servicesToCheck.length === 0) {
|
|
505
|
+
console.error(`Error: Service not found: ${serviceName}`);
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
console.log(chalk.cyan(`\n🔍 Service Health Check\n`));
|
|
509
|
+
for (const service of servicesToCheck) {
|
|
510
|
+
console.log(chalk.bold(`Service: ${service.name}`));
|
|
511
|
+
const servicePath = path.isAbsolute(service.path)
|
|
512
|
+
? service.path
|
|
513
|
+
: path.join(projectRoot, service.path);
|
|
514
|
+
// Check directory exists
|
|
515
|
+
if (!fs.existsSync(servicePath)) {
|
|
516
|
+
console.log(chalk.red(` ✗ Directory not found: ${servicePath}`));
|
|
517
|
+
console.log("");
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
console.log(chalk.green(` ✓ Directory exists`));
|
|
521
|
+
// Check GTM parent configured
|
|
522
|
+
const gtmParent = findGTMParent(servicePath);
|
|
523
|
+
if (!gtmParent) {
|
|
524
|
+
console.log(chalk.yellow(` ⚠ GTM parent not configured`));
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
console.log(chalk.green(` ✓ GTM parent configured: ${gtmParent}`));
|
|
528
|
+
// Validate GTM parent
|
|
529
|
+
if (!validateGTMParent(gtmParent)) {
|
|
530
|
+
console.log(chalk.red(` ✗ GTM parent is invalid or not accessible`));
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
console.log(chalk.green(` ✓ GTM parent is valid`));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// Check /end skill deployed
|
|
537
|
+
const endSkillPath = path.join(servicePath, ".claude", "skills", "end");
|
|
538
|
+
if (!fs.existsSync(endSkillPath)) {
|
|
539
|
+
console.log(chalk.red(` ✗ /end skill NOT deployed`));
|
|
540
|
+
console.log(chalk.gray(` Run: jfl services deploy-skill end ${service.name}`));
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
console.log(chalk.green(` ✓ /end skill deployed`));
|
|
544
|
+
}
|
|
545
|
+
// Check last sync
|
|
546
|
+
if (service.last_sync) {
|
|
547
|
+
const lastSync = new Date(service.last_sync);
|
|
548
|
+
const now = new Date();
|
|
549
|
+
const daysSince = (now.getTime() - lastSync.getTime()) / (1000 * 60 * 60 * 24);
|
|
550
|
+
if (daysSince > 7) {
|
|
551
|
+
console.log(chalk.yellow(` ⚠ Last sync: ${Math.floor(daysSince)} days ago (consider syncing)`));
|
|
552
|
+
}
|
|
553
|
+
else if (daysSince > 1) {
|
|
554
|
+
console.log(chalk.green(` ✓ Last sync: ${Math.floor(daysSince)} days ago`));
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
const hoursSince = Math.floor(daysSince * 24);
|
|
558
|
+
console.log(chalk.green(` ✓ Last sync: ${hoursSince} hours ago`));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
console.log(chalk.yellow(` ⚠ Never synced`));
|
|
563
|
+
}
|
|
564
|
+
console.log("");
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Main services command
|
|
569
|
+
*/
|
|
570
|
+
export async function servicesCommand(action, serviceName, options = {}) {
|
|
571
|
+
try {
|
|
572
|
+
switch (action) {
|
|
573
|
+
case "list":
|
|
574
|
+
await listServices();
|
|
575
|
+
break;
|
|
576
|
+
case "status":
|
|
577
|
+
await showStatus();
|
|
578
|
+
break;
|
|
579
|
+
case "start":
|
|
580
|
+
if (!serviceName) {
|
|
581
|
+
console.error("Error: Service name required for start");
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
await startSpecificService(serviceName);
|
|
585
|
+
break;
|
|
586
|
+
case "stop":
|
|
587
|
+
if (!serviceName) {
|
|
588
|
+
console.error("Error: Service name required for stop");
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
await stopSpecificService(serviceName);
|
|
592
|
+
break;
|
|
593
|
+
case "restart":
|
|
594
|
+
if (serviceName) {
|
|
595
|
+
// Restart specific service
|
|
596
|
+
console.log(`Restarting ${serviceName}...`);
|
|
597
|
+
await stopSpecificService(serviceName);
|
|
598
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
599
|
+
await startSpecificService(serviceName);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
// Restart all core services (Context Hub + Service Manager)
|
|
603
|
+
const results = await restartCoreServices();
|
|
604
|
+
if (results.contextHub && results.serviceManager) {
|
|
605
|
+
console.log(chalk.green("\n✓ All core services restarted\n"));
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
console.log(chalk.yellow("\n⚠️ Some services failed to restart\n"));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
case "health":
|
|
613
|
+
if (serviceName) {
|
|
614
|
+
// Health check for specific service with GTM connectivity
|
|
615
|
+
await checkServiceGTMHealth(serviceName);
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
// Check if we're in a GTM with registered services
|
|
619
|
+
const projectRoot = process.cwd();
|
|
620
|
+
const configPath = path.join(projectRoot, ".jfl", "config.json");
|
|
621
|
+
if (fs.existsSync(configPath)) {
|
|
622
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
623
|
+
if (config.type === "gtm" && config.registered_services?.length > 0) {
|
|
624
|
+
// GTM with services - check GTM health
|
|
625
|
+
await checkServiceGTMHealth();
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
// Regular core services health check
|
|
629
|
+
console.log(chalk.cyan("\n🔍 Checking service health...\n"));
|
|
630
|
+
const validation = await validateCoreServices();
|
|
631
|
+
if (validation.healthy) {
|
|
632
|
+
console.log(chalk.green("✓ All core services are healthy\n"));
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
console.log(chalk.yellow("⚠️ Service health issues detected:\n"));
|
|
636
|
+
for (const issue of validation.issues) {
|
|
637
|
+
console.log(chalk.yellow(` • ${issue.service}: ${issue.message}`));
|
|
638
|
+
console.log(chalk.gray(` Fix: ${issue.remedy}\n`));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
// Fallback to core services check
|
|
645
|
+
console.log(chalk.cyan("\n🔍 Checking service health...\n"));
|
|
646
|
+
const validation = await validateCoreServices();
|
|
647
|
+
if (validation.healthy) {
|
|
648
|
+
console.log(chalk.green("✓ All core services are healthy\n"));
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
console.log(chalk.yellow("⚠️ Service health issues detected:\n"));
|
|
652
|
+
for (const issue of validation.issues) {
|
|
653
|
+
console.log(chalk.yellow(` • ${issue.service}: ${issue.message}`));
|
|
654
|
+
console.log(chalk.gray(` Fix: ${issue.remedy}\n`));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
break;
|
|
660
|
+
case "deploy-skill":
|
|
661
|
+
if (!serviceName) {
|
|
662
|
+
console.error("Error: Skill name required");
|
|
663
|
+
console.log("Usage: jfl services deploy-skill <skill-name> [service-name]");
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
// serviceName is actually the skill name in this case
|
|
667
|
+
// The third argument would be the target service
|
|
668
|
+
await deploySkillToServices(serviceName);
|
|
669
|
+
break;
|
|
670
|
+
case "sync":
|
|
671
|
+
await syncServiceToGTM(serviceName);
|
|
672
|
+
break;
|
|
673
|
+
case "sync-agents":
|
|
674
|
+
await servicesSyncAgentsCommand(serviceName, {
|
|
675
|
+
dryRun: options.dryRun,
|
|
676
|
+
current: options.current,
|
|
677
|
+
});
|
|
678
|
+
break;
|
|
679
|
+
case "validate":
|
|
680
|
+
await serviceValidate({ fix: options.fix, json: options.json });
|
|
681
|
+
break;
|
|
682
|
+
case "phone-home":
|
|
683
|
+
// Phone home to GTM with comprehensive session data
|
|
684
|
+
// Usage: jfl services phone-home <gtm-path> <session-branch>
|
|
685
|
+
if (!serviceName) {
|
|
686
|
+
console.error("Error: GTM path required");
|
|
687
|
+
console.log("Usage: jfl services phone-home <gtm-path> <session-branch>");
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
const gtmPath = serviceName; // First arg is GTM path
|
|
691
|
+
const sessionBranch = process.argv[4]; // Second arg is session branch
|
|
692
|
+
if (!sessionBranch) {
|
|
693
|
+
console.error("Error: Session branch required");
|
|
694
|
+
console.log("Usage: jfl services phone-home <gtm-path> <session-branch>");
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const servicePath = process.cwd();
|
|
699
|
+
const payload = await phoneHomeToGTM(servicePath, gtmPath, sessionBranch);
|
|
700
|
+
// Output JSON for script consumption
|
|
701
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
702
|
+
// Exit with error code if any errors occurred
|
|
703
|
+
if (payload.errors.length > 0) {
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
console.error(JSON.stringify({ error: error.message }));
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
break;
|
|
712
|
+
case "create":
|
|
713
|
+
await servicesCreateCommand({
|
|
714
|
+
name: serviceName,
|
|
715
|
+
skipAI: options.skipAI,
|
|
716
|
+
});
|
|
717
|
+
break;
|
|
718
|
+
default:
|
|
719
|
+
console.log("Usage: jfl services <action> [service-name]");
|
|
720
|
+
console.log("");
|
|
721
|
+
console.log("Actions:");
|
|
722
|
+
console.log(" create [name] Create new service with AI assistance");
|
|
723
|
+
console.log(" list List all services");
|
|
724
|
+
console.log(" status Show service status summary");
|
|
725
|
+
console.log(" start <service> Start a service");
|
|
726
|
+
console.log(" stop <service> Stop a service");
|
|
727
|
+
console.log(" restart [service] Restart a service (or all core services)");
|
|
728
|
+
console.log(" health [service] Check service health (GTM-aware)");
|
|
729
|
+
console.log(" deploy-skill <skill> [svc] Deploy skill to registered services");
|
|
730
|
+
console.log(" sync [service] Sync service to GTM manually");
|
|
731
|
+
console.log(" sync-agents [service] Sync peer agent definitions (--current for current service)");
|
|
732
|
+
console.log(" phone-home <gtm> <branch> Comprehensive sync with session metadata");
|
|
733
|
+
console.log(" validate [--fix] [--json] Validate service configuration (run from service dir)");
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
console.error(`Error: ${error.message}`);
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
//# sourceMappingURL=services.js.map
|