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.
Files changed (262) hide show
  1. package/README.md +443 -145
  2. package/clawdbot-plugin/clawdbot.plugin.json +20 -0
  3. package/clawdbot-plugin/index.js +555 -0
  4. package/clawdbot-plugin/index.ts +582 -0
  5. package/clawdbot-skill/SKILL.md +33 -336
  6. package/clawdbot-skill/index.ts +491 -321
  7. package/clawdbot-skill/skill.json +4 -13
  8. package/dist/commands/clawdbot.d.ts +11 -0
  9. package/dist/commands/clawdbot.d.ts.map +1 -0
  10. package/dist/commands/clawdbot.js +215 -0
  11. package/dist/commands/clawdbot.js.map +1 -0
  12. package/dist/commands/context-hub.d.ts +5 -0
  13. package/dist/commands/context-hub.d.ts.map +1 -1
  14. package/dist/commands/context-hub.js +394 -28
  15. package/dist/commands/context-hub.js.map +1 -1
  16. package/dist/commands/gtm-process-update.d.ts +10 -0
  17. package/dist/commands/gtm-process-update.d.ts.map +1 -0
  18. package/dist/commands/gtm-process-update.js +101 -0
  19. package/dist/commands/gtm-process-update.js.map +1 -0
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +278 -4
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/login.d.ts.map +1 -1
  24. package/dist/commands/login.js +32 -33
  25. package/dist/commands/login.js.map +1 -1
  26. package/dist/commands/memory.d.ts +38 -0
  27. package/dist/commands/memory.d.ts.map +1 -0
  28. package/dist/commands/memory.js +229 -0
  29. package/dist/commands/memory.js.map +1 -0
  30. package/dist/commands/migrate-services.d.ts +8 -0
  31. package/dist/commands/migrate-services.d.ts.map +1 -0
  32. package/dist/commands/migrate-services.js +182 -0
  33. package/dist/commands/migrate-services.js.map +1 -0
  34. package/dist/commands/onboard.d.ts +24 -0
  35. package/dist/commands/onboard.d.ts.map +1 -0
  36. package/dist/commands/onboard.js +663 -0
  37. package/dist/commands/onboard.js.map +1 -0
  38. package/dist/commands/openclaw.d.ts +56 -0
  39. package/dist/commands/openclaw.d.ts.map +1 -0
  40. package/dist/commands/openclaw.js +700 -0
  41. package/dist/commands/openclaw.js.map +1 -0
  42. package/dist/commands/orchestrate.d.ts +14 -0
  43. package/dist/commands/orchestrate.d.ts.map +1 -0
  44. package/dist/commands/orchestrate.js +270 -0
  45. package/dist/commands/orchestrate.js.map +1 -0
  46. package/dist/commands/profile.d.ts +46 -0
  47. package/dist/commands/profile.d.ts.map +1 -0
  48. package/dist/commands/profile.js +498 -0
  49. package/dist/commands/profile.js.map +1 -0
  50. package/dist/commands/repair.d.ts.map +1 -1
  51. package/dist/commands/repair.js +37 -0
  52. package/dist/commands/repair.js.map +1 -1
  53. package/dist/commands/service-agent.d.ts +16 -0
  54. package/dist/commands/service-agent.d.ts.map +1 -0
  55. package/dist/commands/service-agent.js +375 -0
  56. package/dist/commands/service-agent.js.map +1 -0
  57. package/dist/commands/service-manager.d.ts +12 -0
  58. package/dist/commands/service-manager.d.ts.map +1 -0
  59. package/dist/commands/service-manager.js +967 -0
  60. package/dist/commands/service-manager.js.map +1 -0
  61. package/dist/commands/service-validate.d.ts +12 -0
  62. package/dist/commands/service-validate.d.ts.map +1 -0
  63. package/dist/commands/service-validate.js +611 -0
  64. package/dist/commands/service-validate.js.map +1 -0
  65. package/dist/commands/services-create.d.ts +15 -0
  66. package/dist/commands/services-create.d.ts.map +1 -0
  67. package/dist/commands/services-create.js +1452 -0
  68. package/dist/commands/services-create.js.map +1 -0
  69. package/dist/commands/services-scan.d.ts +13 -0
  70. package/dist/commands/services-scan.d.ts.map +1 -0
  71. package/dist/commands/services-scan.js +251 -0
  72. package/dist/commands/services-scan.js.map +1 -0
  73. package/dist/commands/services-sync-agents.d.ts +23 -0
  74. package/dist/commands/services-sync-agents.d.ts.map +1 -0
  75. package/dist/commands/services-sync-agents.js +207 -0
  76. package/dist/commands/services-sync-agents.js.map +1 -0
  77. package/dist/commands/services.d.ts +19 -0
  78. package/dist/commands/services.d.ts.map +1 -0
  79. package/dist/commands/services.js +742 -0
  80. package/dist/commands/services.js.map +1 -0
  81. package/dist/commands/session.d.ts +5 -1
  82. package/dist/commands/session.d.ts.map +1 -1
  83. package/dist/commands/session.js +68 -586
  84. package/dist/commands/session.js.map +1 -1
  85. package/dist/commands/status.d.ts.map +1 -1
  86. package/dist/commands/status.js +17 -0
  87. package/dist/commands/status.js.map +1 -1
  88. package/dist/commands/update.d.ts.map +1 -1
  89. package/dist/commands/update.js +75 -21
  90. package/dist/commands/update.js.map +1 -1
  91. package/dist/commands/validate-settings.d.ts +37 -0
  92. package/dist/commands/validate-settings.d.ts.map +1 -0
  93. package/dist/commands/validate-settings.js +197 -0
  94. package/dist/commands/validate-settings.js.map +1 -0
  95. package/dist/commands/voice.d.ts +0 -1
  96. package/dist/commands/voice.d.ts.map +1 -1
  97. package/dist/commands/voice.js +16 -15
  98. package/dist/commands/voice.js.map +1 -1
  99. package/dist/index.js +395 -141
  100. package/dist/index.js.map +1 -1
  101. package/dist/lib/agent-generator.d.ts +26 -0
  102. package/dist/lib/agent-generator.d.ts.map +1 -0
  103. package/dist/lib/agent-generator.js +331 -0
  104. package/dist/lib/agent-generator.js.map +1 -0
  105. package/dist/lib/memory-db.d.ts +102 -0
  106. package/dist/lib/memory-db.d.ts.map +1 -0
  107. package/dist/lib/memory-db.js +313 -0
  108. package/dist/lib/memory-db.js.map +1 -0
  109. package/dist/lib/memory-indexer.d.ts +47 -0
  110. package/dist/lib/memory-indexer.d.ts.map +1 -0
  111. package/dist/lib/memory-indexer.js +215 -0
  112. package/dist/lib/memory-indexer.js.map +1 -0
  113. package/dist/lib/memory-search.d.ts +41 -0
  114. package/dist/lib/memory-search.d.ts.map +1 -0
  115. package/dist/lib/memory-search.js +246 -0
  116. package/dist/lib/memory-search.js.map +1 -0
  117. package/dist/lib/openclaw-registry.d.ts +48 -0
  118. package/dist/lib/openclaw-registry.d.ts.map +1 -0
  119. package/dist/lib/openclaw-registry.js +181 -0
  120. package/dist/lib/openclaw-registry.js.map +1 -0
  121. package/dist/lib/openclaw-sdk.d.ts +107 -0
  122. package/dist/lib/openclaw-sdk.d.ts.map +1 -0
  123. package/dist/lib/openclaw-sdk.js +208 -0
  124. package/dist/lib/openclaw-sdk.js.map +1 -0
  125. package/dist/lib/peer-agent-generator.d.ts +44 -0
  126. package/dist/lib/peer-agent-generator.d.ts.map +1 -0
  127. package/dist/lib/peer-agent-generator.js +286 -0
  128. package/dist/lib/peer-agent-generator.js.map +1 -0
  129. package/dist/lib/service-dependencies.d.ts +44 -0
  130. package/dist/lib/service-dependencies.d.ts.map +1 -0
  131. package/dist/lib/service-dependencies.js +314 -0
  132. package/dist/lib/service-dependencies.js.map +1 -0
  133. package/dist/lib/service-detector.d.ts +61 -0
  134. package/dist/lib/service-detector.d.ts.map +1 -0
  135. package/dist/lib/service-detector.js +521 -0
  136. package/dist/lib/service-detector.js.map +1 -0
  137. package/dist/lib/service-gtm.d.ts +157 -0
  138. package/dist/lib/service-gtm.d.ts.map +1 -0
  139. package/dist/lib/service-gtm.js +786 -0
  140. package/dist/lib/service-gtm.js.map +1 -0
  141. package/dist/lib/service-mcp-base.d.ts +103 -0
  142. package/dist/lib/service-mcp-base.d.ts.map +1 -0
  143. package/dist/lib/service-mcp-base.js +263 -0
  144. package/dist/lib/service-mcp-base.js.map +1 -0
  145. package/dist/lib/service-utils.d.ts +103 -0
  146. package/dist/lib/service-utils.d.ts.map +1 -0
  147. package/dist/lib/service-utils.js +368 -0
  148. package/dist/lib/service-utils.js.map +1 -0
  149. package/dist/lib/skill-generator.d.ts +21 -0
  150. package/dist/lib/skill-generator.d.ts.map +1 -0
  151. package/dist/lib/skill-generator.js +253 -0
  152. package/dist/lib/skill-generator.js.map +1 -0
  153. package/dist/lib/stratus-client.d.ts +100 -0
  154. package/dist/lib/stratus-client.d.ts.map +1 -0
  155. package/dist/lib/stratus-client.js +255 -0
  156. package/dist/lib/stratus-client.js.map +1 -0
  157. package/dist/mcp/context-hub-mcp.js +135 -53
  158. package/dist/mcp/context-hub-mcp.js.map +1 -1
  159. package/dist/mcp/service-mcp-server.d.ts +12 -0
  160. package/dist/mcp/service-mcp-server.d.ts.map +1 -0
  161. package/dist/mcp/service-mcp-server.js +434 -0
  162. package/dist/mcp/service-mcp-server.js.map +1 -0
  163. package/dist/mcp/service-peer-mcp.d.ts +36 -0
  164. package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
  165. package/dist/mcp/service-peer-mcp.js +220 -0
  166. package/dist/mcp/service-peer-mcp.js.map +1 -0
  167. package/dist/mcp/service-registry-mcp.d.ts +13 -0
  168. package/dist/mcp/service-registry-mcp.d.ts.map +1 -0
  169. package/dist/mcp/service-registry-mcp.js +330 -0
  170. package/dist/mcp/service-registry-mcp.js.map +1 -0
  171. package/dist/ui/banner.js +1 -1
  172. package/dist/ui/banner.js.map +1 -1
  173. package/dist/ui/context-hub-logs.d.ts +10 -0
  174. package/dist/ui/context-hub-logs.d.ts.map +1 -0
  175. package/dist/ui/context-hub-logs.js +175 -0
  176. package/dist/ui/context-hub-logs.js.map +1 -0
  177. package/dist/ui/service-dashboard.d.ts +11 -0
  178. package/dist/ui/service-dashboard.d.ts.map +1 -0
  179. package/dist/ui/service-dashboard.js +357 -0
  180. package/dist/ui/service-dashboard.js.map +1 -0
  181. package/dist/ui/services-manager.d.ts +11 -0
  182. package/dist/ui/services-manager.d.ts.map +1 -0
  183. package/dist/ui/services-manager.js +507 -0
  184. package/dist/ui/services-manager.js.map +1 -0
  185. package/dist/utils/auth-guard.d.ts.map +1 -1
  186. package/dist/utils/auth-guard.js +8 -9
  187. package/dist/utils/auth-guard.js.map +1 -1
  188. package/dist/utils/claude-md-generator.d.ts +10 -0
  189. package/dist/utils/claude-md-generator.d.ts.map +1 -0
  190. package/dist/utils/claude-md-generator.js +215 -0
  191. package/dist/utils/claude-md-generator.js.map +1 -0
  192. package/dist/utils/ensure-context-hub.d.ts +20 -0
  193. package/dist/utils/ensure-context-hub.d.ts.map +1 -0
  194. package/dist/utils/ensure-context-hub.js +65 -0
  195. package/dist/utils/ensure-context-hub.js.map +1 -0
  196. package/dist/utils/ensure-project.d.ts.map +1 -1
  197. package/dist/utils/ensure-project.js +3 -4
  198. package/dist/utils/ensure-project.js.map +1 -1
  199. package/dist/utils/jfl-config.d.ts +19 -0
  200. package/dist/utils/jfl-config.d.ts.map +1 -0
  201. package/dist/utils/jfl-config.js +112 -0
  202. package/dist/utils/jfl-config.js.map +1 -0
  203. package/dist/utils/jfl-migration.d.ts +29 -0
  204. package/dist/utils/jfl-migration.d.ts.map +1 -0
  205. package/dist/utils/jfl-migration.js +142 -0
  206. package/dist/utils/jfl-migration.js.map +1 -0
  207. package/dist/utils/jfl-paths.d.ts +55 -0
  208. package/dist/utils/jfl-paths.d.ts.map +1 -0
  209. package/dist/utils/jfl-paths.js +120 -0
  210. package/dist/utils/jfl-paths.js.map +1 -0
  211. package/dist/utils/settings-validator.d.ts +73 -0
  212. package/dist/utils/settings-validator.d.ts.map +1 -0
  213. package/dist/utils/settings-validator.js +222 -0
  214. package/dist/utils/settings-validator.js.map +1 -0
  215. package/package.json +19 -3
  216. package/scripts/commit-gtm.sh +56 -0
  217. package/scripts/commit-product.sh +68 -0
  218. package/scripts/context-query.sh +45 -0
  219. package/scripts/session/auto-commit.sh +297 -0
  220. package/scripts/session/jfl-doctor.sh +707 -0
  221. package/scripts/session/session-cleanup.sh +268 -0
  222. package/scripts/session/session-end.sh +198 -0
  223. package/scripts/session/session-init.sh +350 -0
  224. package/scripts/session/session-init.sh.backup +292 -0
  225. package/scripts/session/session-sync.sh +167 -0
  226. package/scripts/session/test-context-preservation.sh +160 -0
  227. package/scripts/session/test-critical-infrastructure.sh +293 -0
  228. package/scripts/session/test-experience-level.sh +336 -0
  229. package/scripts/session/test-session-cleanup.sh +268 -0
  230. package/scripts/session/test-session-sync.sh +320 -0
  231. package/scripts/voice-start.sh +36 -8
  232. package/scripts/where-am-i.sh +78 -0
  233. package/template/.claude/service-settings.json +32 -0
  234. package/template/.claude/settings.json +14 -1
  235. package/template/.claude/skills/end/SKILL.md +1780 -0
  236. package/template/.jfl/config.json +2 -1
  237. package/template/CLAUDE.md +1039 -134
  238. package/template/CLAUDE.md.bak +1187 -0
  239. package/template/scripts/commit-gtm.sh +56 -0
  240. package/template/scripts/commit-product.sh +68 -0
  241. package/template/scripts/migrate-to-branch-sessions.sh +201 -0
  242. package/template/scripts/session/auto-commit.sh +58 -6
  243. package/template/scripts/session/jfl-doctor.sh +137 -17
  244. package/template/scripts/session/session-cleanup.sh +268 -0
  245. package/template/scripts/session/session-end.sh +4 -0
  246. package/template/scripts/session/session-init.sh +253 -66
  247. package/template/scripts/session/test-critical-infrastructure.sh +293 -0
  248. package/template/scripts/session/test-experience-level.sh +336 -0
  249. package/template/scripts/session/test-session-cleanup.sh +268 -0
  250. package/template/scripts/session/test-session-sync.sh +320 -0
  251. package/template/scripts/where-am-i.sh +78 -0
  252. package/template/templates/service-agent/.claude/settings.json +32 -0
  253. package/template/templates/service-agent/CLAUDE.md +334 -0
  254. package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
  255. package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
  256. package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
  257. package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
  258. package/dist/commands/session-mgmt.d.ts +0 -33
  259. package/dist/commands/session-mgmt.d.ts.map +0 -1
  260. package/dist/commands/session-mgmt.js +0 -404
  261. package/dist/commands/session-mgmt.js.map +0 -1
  262. 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