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,663 @@
1
+ /**
2
+ * Onboard Command
3
+ *
4
+ * Onboard a service repo as a service agent in the GTM ecosystem
5
+ *
6
+ * Usage:
7
+ * jfl onboard <path|url>
8
+ * jfl onboard /Users/user/code/myservice
9
+ * jfl onboard git@github.com:user/repo.git
10
+ *
11
+ * @purpose Onboard services with full agent infrastructure
12
+ */
13
+ import chalk from "chalk";
14
+ import ora from "ora";
15
+ import * as p from "@clack/prompts";
16
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
17
+ import { execSync } from "child_process";
18
+ import { join, resolve, basename } from "path";
19
+ import { extractServiceMetadata, } from "../lib/service-detector.js";
20
+ import { generateAgentDefinition, writeAgentDefinition, } from "../lib/agent-generator.js";
21
+ import { writeSkillFiles } from "../lib/skill-generator.js";
22
+ import { syncPeerAgents, getRegisteredServices } from "../lib/peer-agent-generator.js";
23
+ /**
24
+ * Find GTM directory (current dir or parent)
25
+ */
26
+ function findGTMDirectory() {
27
+ let currentDir = process.cwd();
28
+ // Check current directory and up to 3 levels up
29
+ for (let i = 0; i < 4; i++) {
30
+ const configPath = join(currentDir, ".jfl/config.json");
31
+ if (existsSync(configPath)) {
32
+ try {
33
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
34
+ if (config.type === "gtm") {
35
+ return currentDir;
36
+ }
37
+ }
38
+ catch {
39
+ // Invalid config, continue
40
+ }
41
+ }
42
+ const parent = join(currentDir, "..");
43
+ if (parent === currentDir)
44
+ break; // Reached root
45
+ currentDir = parent;
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Clone git repository to standard location
51
+ */
52
+ async function cloneRepository(url, targetDir) {
53
+ // Extract repo name from URL
54
+ const match = url.match(/\/([^\/]+?)(\.git)?$/);
55
+ if (!match) {
56
+ throw new Error(`Could not extract repo name from URL: ${url}`);
57
+ }
58
+ const repoName = match[1];
59
+ // Default clone directory - use user's code directory preference
60
+ let cloneDir;
61
+ if (targetDir) {
62
+ cloneDir = targetDir;
63
+ }
64
+ else {
65
+ const { getCodeDirectory } = await import("../utils/jfl-config.js");
66
+ const codeDir = await getCodeDirectory();
67
+ cloneDir = join(codeDir, "repos");
68
+ }
69
+ // Check if repo already exists
70
+ const repoPath = join(cloneDir, repoName);
71
+ if (existsSync(repoPath)) {
72
+ console.log(chalk.yellow(`\nāš ļø Repository already exists at ${repoPath}`));
73
+ // Pull latest
74
+ const spinner = ora("Pulling latest changes...").start();
75
+ try {
76
+ execSync("git pull", { cwd: repoPath, stdio: "pipe" });
77
+ spinner.succeed("Updated to latest");
78
+ }
79
+ catch (err) {
80
+ spinner.fail("Failed to pull (may have uncommitted changes)");
81
+ console.log(chalk.gray(" Continuing with existing repo"));
82
+ }
83
+ return repoPath;
84
+ }
85
+ // Clone the repo
86
+ const spinner = ora(`Cloning ${repoName}...`).start();
87
+ try {
88
+ execSync(`git clone ${url} ${repoPath}`, { stdio: "pipe" });
89
+ spinner.succeed(`Cloned to ${repoPath}`);
90
+ return repoPath;
91
+ }
92
+ catch (err) {
93
+ spinner.fail("Failed to clone repository");
94
+ throw new Error(`Git clone failed: ${err.message}`);
95
+ }
96
+ }
97
+ /**
98
+ * Run GTM onboard-service.sh script (optional - continue if it fails)
99
+ */
100
+ function runGTMOnboardScript(servicePath, serviceName, serviceType, description, gtmPath) {
101
+ const scriptPath = join(gtmPath, "scripts/services/onboard-service.sh");
102
+ if (!existsSync(scriptPath)) {
103
+ console.log(chalk.yellow("āš ļø GTM onboard script not found, skipping"));
104
+ return;
105
+ }
106
+ console.log(chalk.cyan("Setting up service agent infrastructure..."));
107
+ try {
108
+ execSync(`bash ${scriptPath} "${servicePath}" "${serviceName}" "${serviceType}" "${description}"`, {
109
+ cwd: gtmPath,
110
+ stdio: "inherit",
111
+ });
112
+ console.log(chalk.green("āœ“ Service agent infrastructure created"));
113
+ }
114
+ catch (err) {
115
+ console.log(chalk.yellow("āš ļø GTM onboard script failed (Service Manager may not be running)"));
116
+ console.log(chalk.gray(" Continuing with basic setup..."));
117
+ // Don't throw - continue with our own setup
118
+ }
119
+ }
120
+ /**
121
+ * Update GTM services.json
122
+ */
123
+ function updateServicesJSON(metadata, servicePath, gtmPath) {
124
+ const servicesFile = join(gtmPath, ".jfl/services.json");
125
+ if (!existsSync(servicesFile)) {
126
+ console.log(chalk.yellow("āš ļø services.json not found, skipping"));
127
+ return;
128
+ }
129
+ const services = JSON.parse(readFileSync(servicesFile, "utf-8"));
130
+ // Check if service already exists
131
+ if (services[metadata.name]) {
132
+ console.log(chalk.yellow(` Service ${metadata.name} already in services.json, updating...`));
133
+ }
134
+ // Build service entry
135
+ const serviceEntry = {
136
+ name: metadata.name.charAt(0).toUpperCase() + metadata.name.slice(1),
137
+ type: metadata.type === "web" || metadata.type === "api" ? "process" : metadata.type,
138
+ description: metadata.description,
139
+ path: servicePath,
140
+ };
141
+ if (metadata.port) {
142
+ serviceEntry.port = metadata.port;
143
+ serviceEntry.detection = `lsof -i :${metadata.port} | grep LISTEN`;
144
+ }
145
+ if (metadata.commands) {
146
+ serviceEntry.commands = {};
147
+ if (metadata.commands.start)
148
+ serviceEntry.commands.start = metadata.commands.start;
149
+ if (metadata.commands.stop)
150
+ serviceEntry.commands.stop = metadata.commands.stop;
151
+ if (metadata.commands.logs)
152
+ serviceEntry.commands.logs = metadata.commands.logs;
153
+ }
154
+ if (metadata.healthcheck) {
155
+ serviceEntry.healthcheck = metadata.healthcheck;
156
+ }
157
+ // Add to services
158
+ services[metadata.name] = serviceEntry;
159
+ // Write back
160
+ writeFileSync(servicesFile, JSON.stringify(services, null, 2) + "\n");
161
+ console.log(chalk.green(`āœ“ Updated services.json`));
162
+ }
163
+ /**
164
+ * Update GTM projects.manifest.json
165
+ */
166
+ function updateProjectsManifest(metadata, servicePath, gtmPath) {
167
+ const manifestFile = join(gtmPath, ".jfl/projects.manifest.json");
168
+ if (!existsSync(manifestFile)) {
169
+ console.log(chalk.yellow("āš ļø projects.manifest.json not found, skipping"));
170
+ return;
171
+ }
172
+ const manifest = JSON.parse(readFileSync(manifestFile, "utf-8"));
173
+ // Check if already exists
174
+ if (manifest.projects && manifest.projects[metadata.name]) {
175
+ // Update agent_enabled flag
176
+ manifest.projects[metadata.name].agent_enabled = true;
177
+ console.log(chalk.yellow(` Service ${metadata.name} already in manifest, enabled agent`));
178
+ }
179
+ else {
180
+ // Add new entry
181
+ if (!manifest.projects) {
182
+ manifest.projects = {};
183
+ }
184
+ manifest.projects[metadata.name] = {
185
+ type: "service",
186
+ service_type: metadata.type,
187
+ location: servicePath,
188
+ description: metadata.description,
189
+ agent_enabled: true,
190
+ };
191
+ console.log(chalk.green(`āœ“ Added to projects.manifest.json`));
192
+ }
193
+ // Write back
194
+ writeFileSync(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
195
+ }
196
+ /**
197
+ * Create basic service directory structure
198
+ */
199
+ function createServiceStructure(servicePath, serviceName) {
200
+ // Create .jfl directory
201
+ const jflDir = join(servicePath, ".jfl");
202
+ mkdirSync(join(jflDir, "journal"), { recursive: true });
203
+ mkdirSync(join(jflDir, "logs"), { recursive: true });
204
+ // Create .claude directory
205
+ const claudeDir = join(servicePath, ".claude");
206
+ mkdirSync(join(claudeDir, "skills"), { recursive: true });
207
+ mkdirSync(join(claudeDir, "agents"), { recursive: true });
208
+ // Create knowledge directory
209
+ mkdirSync(join(servicePath, "knowledge"), { recursive: true });
210
+ console.log(chalk.green("āœ“ Created service directory structure"));
211
+ }
212
+ /**
213
+ * Set up service-GTM link
214
+ */
215
+ function setupServiceGTMLink(servicePath, serviceName, serviceType, description, gtmPath, workingBranch, metadata) {
216
+ // 1. Create/update service's .jfl/config.json
217
+ const serviceJflDir = join(servicePath, ".jfl");
218
+ mkdirSync(serviceJflDir, { recursive: true });
219
+ const serviceConfigPath = join(serviceJflDir, "config.json");
220
+ let serviceConfig;
221
+ if (existsSync(serviceConfigPath)) {
222
+ serviceConfig = JSON.parse(readFileSync(serviceConfigPath, "utf-8"));
223
+ }
224
+ else {
225
+ serviceConfig = {
226
+ name: serviceName,
227
+ type: "service",
228
+ description,
229
+ };
230
+ }
231
+ // Set service-specific fields
232
+ serviceConfig.type = "service";
233
+ serviceConfig.service_type = serviceType;
234
+ serviceConfig.gtm_parent = gtmPath;
235
+ serviceConfig.working_branch = workingBranch;
236
+ serviceConfig.sync_to_parent = {
237
+ journal: true,
238
+ knowledge: false,
239
+ content: false,
240
+ };
241
+ // Add environments config with detected commands
242
+ if (metadata) {
243
+ serviceConfig.environments = {
244
+ development: {
245
+ code_path: servicePath,
246
+ start_command: metadata.commands?.start || "echo 'No start command configured'",
247
+ port: metadata.port || null,
248
+ env: { NODE_ENV: "development" },
249
+ health_check: metadata.healthcheck ? {
250
+ enabled: true,
251
+ url: metadata.healthcheck,
252
+ interval: 30000,
253
+ timeout: 5000
254
+ } : null
255
+ }
256
+ };
257
+ }
258
+ writeFileSync(serviceConfigPath, JSON.stringify(serviceConfig, null, 2) + "\n");
259
+ console.log(chalk.green(`āœ“ Created service config at ${servicePath}/.jfl/config.json`));
260
+ // 2. Update GTM's .jfl/config.json to register this service
261
+ const gtmConfigPath = join(gtmPath, ".jfl", "config.json");
262
+ if (existsSync(gtmConfigPath)) {
263
+ const gtmConfig = JSON.parse(readFileSync(gtmConfigPath, "utf-8"));
264
+ // Initialize registered_services if needed
265
+ if (!gtmConfig.registered_services) {
266
+ gtmConfig.registered_services = [];
267
+ }
268
+ // Check if already registered
269
+ const existing = gtmConfig.registered_services.find((s) => s.name === serviceName);
270
+ if (existing) {
271
+ // Update existing
272
+ existing.status = "active";
273
+ existing.type = serviceType;
274
+ }
275
+ else {
276
+ // Add new
277
+ const relativePath = resolve(servicePath).replace(resolve(gtmPath) + "/", "");
278
+ gtmConfig.registered_services.push({
279
+ name: serviceName,
280
+ path: relativePath,
281
+ type: serviceType,
282
+ registered_at: new Date().toISOString(),
283
+ status: "active",
284
+ });
285
+ }
286
+ writeFileSync(gtmConfigPath, JSON.stringify(gtmConfig, null, 2) + "\n");
287
+ console.log(chalk.green(`āœ“ Registered service in GTM config`));
288
+ }
289
+ }
290
+ /**
291
+ * Emit onboard event to service-events.jsonl
292
+ */
293
+ function emitOnboardEvent(metadata, gtmPath) {
294
+ const eventsFile = join(gtmPath, ".jfl/service-events.jsonl");
295
+ const event = {
296
+ ts: new Date().toISOString(),
297
+ service: metadata.name,
298
+ type: "onboard",
299
+ message: `Service agent onboarded for ${metadata.name}`,
300
+ session: "jfl-cli",
301
+ };
302
+ try {
303
+ const eventLine = JSON.stringify(event) + "\n";
304
+ if (existsSync(eventsFile)) {
305
+ // Append
306
+ const currentContent = readFileSync(eventsFile, "utf-8");
307
+ writeFileSync(eventsFile, currentContent + eventLine);
308
+ }
309
+ else {
310
+ // Create new
311
+ writeFileSync(eventsFile, eventLine);
312
+ }
313
+ console.log(chalk.green(`āœ“ Emitted onboard event`));
314
+ }
315
+ catch (err) {
316
+ console.log(chalk.yellow(`āš ļø Failed to emit event: ${err.message}`));
317
+ }
318
+ }
319
+ /**
320
+ * Main onboard command
321
+ */
322
+ export async function onboardCommand(pathOrUrl, options = {}) {
323
+ p.intro(chalk.hex("#FFD700")("ā”Œ JFL - Onboard Service Agent"));
324
+ // Find GTM directory
325
+ const gtmPath = findGTMDirectory();
326
+ if (!gtmPath) {
327
+ p.log.error("Not in a GTM directory");
328
+ p.log.info("Run this command from inside a JFL GTM workspace");
329
+ p.log.info("Or run: jfl init to create a new GTM workspace");
330
+ p.outro(chalk.red("Onboarding failed"));
331
+ return;
332
+ }
333
+ console.log(chalk.gray(`GTM Path: ${gtmPath}\n`));
334
+ let servicePath;
335
+ // Determine if path or URL
336
+ const isGitURL = pathOrUrl.startsWith("git@") ||
337
+ pathOrUrl.startsWith("https://") ||
338
+ pathOrUrl.startsWith("http://");
339
+ if (isGitURL && !options.skipGit) {
340
+ // Clone the repository
341
+ console.log(chalk.cyan("šŸ“¦ Git repository detected"));
342
+ servicePath = await cloneRepository(pathOrUrl);
343
+ console.log();
344
+ }
345
+ else {
346
+ // Resolve local path
347
+ servicePath = resolve(pathOrUrl);
348
+ if (!existsSync(servicePath)) {
349
+ p.log.error(`Path does not exist: ${servicePath}`);
350
+ p.outro(chalk.red("Onboarding failed"));
351
+ return;
352
+ }
353
+ console.log(chalk.gray(`Service Path: ${servicePath}\n`));
354
+ }
355
+ // Auto-detect service metadata (but will ask user to confirm/override)
356
+ const spinner = ora("Auto-detecting service metadata...").start();
357
+ let detectedMetadata;
358
+ try {
359
+ detectedMetadata = extractServiceMetadata(servicePath);
360
+ spinner.succeed("Service metadata detected");
361
+ }
362
+ catch (err) {
363
+ spinner.fail("Auto-detection failed, will ask for details");
364
+ // Create empty metadata
365
+ detectedMetadata = {
366
+ name: basename(servicePath),
367
+ type: "api",
368
+ description: "",
369
+ version: "0.1.0",
370
+ dependencies: [],
371
+ commands: {},
372
+ port: null
373
+ };
374
+ }
375
+ console.log();
376
+ p.note(`Auto-detected:\n` +
377
+ ` Name: ${chalk.cyan(detectedMetadata.name)}\n` +
378
+ ` Type: ${chalk.cyan(detectedMetadata.type)}\n` +
379
+ ` Description: ${chalk.gray(detectedMetadata.description || "N/A")}\n` +
380
+ ` Port: ${detectedMetadata.port ? chalk.cyan(detectedMetadata.port) : chalk.gray("N/A")}`, "šŸ” Detection Results");
381
+ console.log(chalk.cyan("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
382
+ console.log(chalk.cyan(" Service Configuration Wizard"));
383
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
384
+ // Interactive wizard
385
+ const serviceName = options.name || await p.text({
386
+ message: "Service name?",
387
+ placeholder: detectedMetadata.name,
388
+ initialValue: detectedMetadata.name,
389
+ validate: (value) => {
390
+ if (!value)
391
+ return "Service name is required";
392
+ if (!/^[a-z0-9-]+$/.test(value))
393
+ return "Use lowercase, numbers, and hyphens only";
394
+ return undefined;
395
+ }
396
+ });
397
+ if (p.isCancel(serviceName)) {
398
+ p.cancel("Onboarding cancelled");
399
+ return;
400
+ }
401
+ const serviceType = options.type || await p.select({
402
+ message: "Service type?",
403
+ options: [
404
+ { value: "api", label: "API - REST/GraphQL service", hint: detectedMetadata.type === "api" ? "detected" : "" },
405
+ { value: "web", label: "Web - Frontend application", hint: detectedMetadata.type === "web" ? "detected" : "" },
406
+ { value: "worker", label: "Worker - Background jobs/queue", hint: detectedMetadata.type === "worker" ? "detected" : "" },
407
+ { value: "cli", label: "CLI - Command-line tool", hint: detectedMetadata.type === "cli" ? "detected" : "" },
408
+ { value: "library", label: "Library - Plugin, package, or shared code", hint: detectedMetadata.type === "library" ? "detected" : "" },
409
+ { value: "infrastructure", label: "Infrastructure - Database, cache, etc.", hint: detectedMetadata.type === "infrastructure" ? "detected" : "" },
410
+ { value: "container", label: "Container - Docker service", hint: detectedMetadata.type === "container" ? "detected" : "" },
411
+ ],
412
+ initialValue: detectedMetadata.type
413
+ });
414
+ if (p.isCancel(serviceType)) {
415
+ p.cancel("Onboarding cancelled");
416
+ return;
417
+ }
418
+ const description = options.description || await p.text({
419
+ message: "Service description?",
420
+ placeholder: detectedMetadata.description || "What does this service do?",
421
+ initialValue: detectedMetadata.description,
422
+ validate: (value) => {
423
+ if (!value)
424
+ return "Description is required";
425
+ return undefined;
426
+ }
427
+ });
428
+ if (p.isCancel(description)) {
429
+ p.cancel("Onboarding cancelled");
430
+ return;
431
+ }
432
+ const hasPort = await p.confirm({
433
+ message: "Does this service expose a port?",
434
+ initialValue: !!detectedMetadata.port
435
+ });
436
+ if (p.isCancel(hasPort)) {
437
+ p.cancel("Onboarding cancelled");
438
+ return;
439
+ }
440
+ let port;
441
+ if (hasPort) {
442
+ const portInput = await p.text({
443
+ message: "Port number?",
444
+ placeholder: detectedMetadata.port?.toString() || "3000",
445
+ initialValue: detectedMetadata.port?.toString() || "",
446
+ validate: (value) => {
447
+ const num = parseInt(value, 10);
448
+ if (isNaN(num) || num < 1 || num > 65535)
449
+ return "Enter a valid port (1-65535)";
450
+ return undefined;
451
+ }
452
+ });
453
+ if (p.isCancel(portInput)) {
454
+ p.cancel("Onboarding cancelled");
455
+ return;
456
+ }
457
+ port = parseInt(portInput, 10);
458
+ }
459
+ // If no start command detected, prompt user (skip for library types)
460
+ if (!detectedMetadata.commands?.start && serviceType !== "library") {
461
+ console.log();
462
+ p.note(`Could not auto-detect start command.\n\n` +
463
+ `Examples:\n` +
464
+ ` • npm run dev\n` +
465
+ ` • yarn start\n` +
466
+ ` • make run\n` +
467
+ ` • docker-compose up\n` +
468
+ ` • python manage.py runserver\n` +
469
+ ` • go run main.go\n` +
470
+ ` • mint dev`, "āš ļø Manual Input Required");
471
+ const startCommand = await p.text({
472
+ message: "How do you start this service in development?",
473
+ placeholder: "npm run dev",
474
+ validate: (value) => {
475
+ if (!value)
476
+ return "Start command is required";
477
+ return undefined;
478
+ }
479
+ });
480
+ if (p.isCancel(startCommand)) {
481
+ p.cancel("Onboarding cancelled");
482
+ return;
483
+ }
484
+ detectedMetadata.commands = { ...detectedMetadata.commands, start: startCommand };
485
+ console.log(chalk.green(`āœ“ Using: ${startCommand}`));
486
+ }
487
+ else if (serviceType === "library" && !detectedMetadata.commands?.start) {
488
+ // For libraries, start command is optional - use build if available, otherwise skip
489
+ console.log(chalk.gray("ℹ Libraries don't require a start command (not standalone services)"));
490
+ }
491
+ const enableMCP = await p.confirm({
492
+ message: "Enable MCP (Model Context Protocol) for AI agent coordination?",
493
+ initialValue: false
494
+ });
495
+ if (p.isCancel(enableMCP)) {
496
+ p.cancel("Onboarding cancelled");
497
+ return;
498
+ }
499
+ // Ask for working branch
500
+ const workingBranch = await p.text({
501
+ message: "Working branch? (session branches will be created from this)",
502
+ placeholder: "main",
503
+ initialValue: "main",
504
+ validate: (value) => {
505
+ if (!value)
506
+ return "Working branch is required";
507
+ if (!/^[a-zA-Z0-9/_-]+$/.test(value))
508
+ return "Invalid branch name";
509
+ return undefined;
510
+ }
511
+ });
512
+ if (p.isCancel(workingBranch)) {
513
+ p.cancel("Onboarding cancelled");
514
+ return;
515
+ }
516
+ // Build final metadata
517
+ const metadata = {
518
+ name: serviceName,
519
+ type: serviceType,
520
+ description: description,
521
+ version: detectedMetadata.version,
522
+ dependencies: detectedMetadata.dependencies,
523
+ commands: detectedMetadata.commands,
524
+ port: port || null
525
+ };
526
+ console.log();
527
+ p.note(`Name: ${chalk.cyan(metadata.name)}\n` +
528
+ `Type: ${chalk.cyan(metadata.type)}\n` +
529
+ `Description: ${chalk.gray(metadata.description)}\n` +
530
+ `Port: ${metadata.port ? chalk.cyan(metadata.port) : chalk.gray("None")}\n` +
531
+ `Working Branch: ${chalk.cyan(workingBranch)}\n` +
532
+ `MCP: ${enableMCP ? chalk.green("Enabled") : chalk.gray("Disabled")}\n` +
533
+ `Version: ${chalk.gray(metadata.version)}`, chalk.hex("#00FF88")("šŸ“‹ Final Configuration"));
534
+ const confirmed = await p.confirm({
535
+ message: "Proceed with onboarding?",
536
+ initialValue: true,
537
+ });
538
+ if (p.isCancel(confirmed) || !confirmed) {
539
+ p.cancel("Onboarding cancelled");
540
+ return;
541
+ }
542
+ // Write service.json with MCP config if enabled
543
+ if (enableMCP) {
544
+ const serviceJsonPath = join(servicePath, "service.json");
545
+ const serviceJson = {
546
+ name: metadata.name,
547
+ version: metadata.version,
548
+ type: metadata.type,
549
+ description: metadata.description,
550
+ mcp: {
551
+ enabled: true,
552
+ transport: "stdio",
553
+ capabilities: {
554
+ tools: true,
555
+ resources: true
556
+ }
557
+ }
558
+ };
559
+ if (existsSync(serviceJsonPath)) {
560
+ const existing = JSON.parse(readFileSync(serviceJsonPath, "utf-8"));
561
+ Object.assign(existing, serviceJson);
562
+ writeFileSync(serviceJsonPath, JSON.stringify(existing, null, 2) + "\n");
563
+ }
564
+ else {
565
+ writeFileSync(serviceJsonPath, JSON.stringify(serviceJson, null, 2) + "\n");
566
+ }
567
+ console.log(chalk.green("āœ“ Created service.json with MCP config"));
568
+ }
569
+ console.log();
570
+ // Step 1: Run GTM onboard-service.sh script
571
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
572
+ console.log(chalk.cyan(" Step 1: Service Agent Infrastructure"));
573
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
574
+ runGTMOnboardScript(servicePath, metadata.name, metadata.type, metadata.description, gtmPath);
575
+ // Ensure basic service structure exists (in case script failed)
576
+ createServiceStructure(servicePath, metadata.name);
577
+ // Set up service-GTM link (for sync)
578
+ setupServiceGTMLink(servicePath, metadata.name, metadata.type, metadata.description, gtmPath, workingBranch, metadata);
579
+ console.log();
580
+ // Step 2: Generate agent definition
581
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
582
+ console.log(chalk.cyan(" Step 2: GTM Agent Integration"));
583
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
584
+ const agentDef = generateAgentDefinition(metadata, servicePath, gtmPath);
585
+ const agentFile = writeAgentDefinition(agentDef, gtmPath);
586
+ console.log(chalk.green(`āœ“ Created agent definition: ${agentFile}`));
587
+ // Step 2.5: Sync peer agents
588
+ console.log(chalk.cyan("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
589
+ console.log(chalk.cyan(" Peer Agent Synchronization"));
590
+ console.log(chalk.cyan("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
591
+ try {
592
+ // Sync peer agents for this new service
593
+ const peerSyncResult = syncPeerAgents(servicePath, gtmPath);
594
+ console.log(chalk.green(`āœ“ Synced ${peerSyncResult.added} peer agent(s) to this service`));
595
+ // Also sync TO other services (add this new service as a peer)
596
+ const registeredServices = getRegisteredServices(gtmPath);
597
+ let peersUpdated = 0;
598
+ for (const peer of registeredServices) {
599
+ if (peer.name !== metadata.name) {
600
+ const peerPath = resolve(peer.path.startsWith("/") ? peer.path : join(gtmPath, peer.path));
601
+ if (existsSync(peerPath)) {
602
+ try {
603
+ const peerResult = syncPeerAgents(peerPath, gtmPath);
604
+ if (peerResult.added > 0 || peerResult.updated > 0) {
605
+ peersUpdated++;
606
+ }
607
+ }
608
+ catch (err) {
609
+ // Non-fatal - peer sync can fail if peer service isn't fully set up
610
+ console.log(chalk.yellow(`āš ļø Could not sync to peer ${peer.name}: ${err.message}`));
611
+ }
612
+ }
613
+ }
614
+ }
615
+ if (peersUpdated > 0) {
616
+ console.log(chalk.green(`āœ“ Updated ${peersUpdated} peer service(s) with this service`));
617
+ }
618
+ }
619
+ catch (error) {
620
+ console.log(chalk.yellow(`āš ļø Peer sync failed: ${error.message}`));
621
+ console.log(chalk.gray(" Run 'jfl services sync-agents' later to sync peer agents"));
622
+ }
623
+ console.log();
624
+ // Step 3: Generate skill wrapper
625
+ const skillDir = writeSkillFiles(metadata, servicePath, gtmPath);
626
+ console.log(chalk.green(`āœ“ Created skill wrapper: ${skillDir}`));
627
+ // Step 4: Update GTM manifests
628
+ updateServicesJSON(metadata, servicePath, gtmPath);
629
+ updateProjectsManifest(metadata, servicePath, gtmPath);
630
+ // Step 5: Emit event
631
+ emitOnboardEvent(metadata, gtmPath);
632
+ console.log();
633
+ // Success summary
634
+ p.note(`Service agent is ready!\n\n` +
635
+ `Now you can:\n` +
636
+ ` • Use @-mentions: ${chalk.cyan(`@${metadata.name} what's your status?`)}\n` +
637
+ ` • Run skill commands: ${chalk.cyan(`/${metadata.name} status`)}\n` +
638
+ ` • Service agent manages its own codebase\n\n` +
639
+ `Files created:\n` +
640
+ ` ${chalk.gray(`${servicePath}/CLAUDE.md`)}\n` +
641
+ ` ${chalk.gray(`${servicePath}/.jfl/`)}\n` +
642
+ ` ${chalk.gray(`${servicePath}/.claude/`)}\n` +
643
+ ` ${chalk.gray(`${servicePath}/knowledge/`)}\n` +
644
+ ` ${chalk.gray(`${gtmPath}/.claude/agents/service-${metadata.name}.md`)}\n` +
645
+ ` ${chalk.gray(`${gtmPath}/.claude/skills/${metadata.name}/`)}\n\n` +
646
+ `Next steps:\n` +
647
+ ` 1. Fill in service knowledge docs:\n` +
648
+ ` ${chalk.cyan(`cd ${servicePath}`)}\n` +
649
+ ` ${chalk.gray(`# Edit knowledge/SERVICE_SPEC.md`)}\n` +
650
+ ` ${chalk.gray(`# Edit knowledge/ARCHITECTURE.md`)}\n` +
651
+ ` ${chalk.gray(`# Edit knowledge/DEPLOYMENT.md`)}\n` +
652
+ ` ${chalk.gray(`# Edit knowledge/RUNBOOK.md`)}\n\n` +
653
+ ` 2. Test the service agent:\n` +
654
+ ` ${chalk.cyan(`cd ${servicePath}`)}\n` +
655
+ ` ${chalk.cyan(`claude`)}\n` +
656
+ ` ${chalk.gray(`# Should greet you as service agent`)}\n\n` +
657
+ ` 3. Test @-mention from GTM:\n` +
658
+ ` ${chalk.cyan(`cd ${gtmPath}`)}\n` +
659
+ ` ${chalk.cyan(`claude`)}\n` +
660
+ ` ${chalk.cyan(`> @${metadata.name} what's your status?`)}`, chalk.hex("#00FF88")("āœ… Service Onboarded Successfully"));
661
+ p.outro(chalk.hex("#FFA500")("Happy shipping! šŸš€"));
662
+ }
663
+ //# sourceMappingURL=onboard.js.map