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,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
|