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,20 @@
1
+ {
2
+ "id": "jfl",
3
+ "uiHints": {
4
+ "workspace": {
5
+ "label": "GTM Workspace",
6
+ "placeholder": "Auto-detected from .jfl/config.json",
7
+ "help": "Path to GTM workspace. Leave empty to auto-detect."
8
+ }
9
+ },
10
+ "configSchema": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "workspace": {
15
+ "type": "string"
16
+ }
17
+ },
18
+ "required": []
19
+ }
20
+ }
@@ -0,0 +1,555 @@
1
+ /**
2
+ * JFL Plugin for Clawdbot
3
+ *
4
+ * Single installable unit. Dormant until /jfl activates it.
5
+ * Hooks, commands, tools, and agent instructions — all in one.
6
+ *
7
+ * Install: copy to ~/.clawdbot/plugins/jfl/ or register in config
8
+ * Activate: /jfl in Telegram
9
+ */
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import * as os from "node:os";
13
+ import { exec as execCb } from "node:child_process";
14
+ import { promisify } from "node:util";
15
+ const execAsync = promisify(execCb);
16
+ // ============================================================================
17
+ // Agent Instructions — injected into Claude's context on activation
18
+ // This is the "brain" that tells the agent HOW to behave as a JFL team member
19
+ // ============================================================================
20
+ const AGENT_INSTRUCTIONS = `<jfl-agent>
21
+ You are a JFL team member. You have persistent memory, sessions, and team coordination.
22
+
23
+ ## What's Happening Automatically
24
+ - Context is searched before each of your responses (relevant results injected)
25
+ - Decisions you make are captured to the journal
26
+ - Your work is auto-committed every ~2 minutes
27
+ - Session branch isolates your work from others
28
+
29
+ ## What You Should Do Proactively
30
+ - When a decision is made → use jfl_journal tool with type "decision"
31
+ - When you finish a task → use jfl_journal tool with type "feature" or "fix"
32
+ - When you learn something important → use jfl_journal tool with type "discovery"
33
+ - When someone asks "what's next?" → use jfl_context tool to search for roadmap
34
+ - When someone mentions the project → use jfl_context tool for relevant info
35
+
36
+ ## Key Principle
37
+ You are not a CLI wrapper. You are a team member. Read context, synthesize understanding, make decisions, write journals. The value is in coordination and memory.
38
+ </jfl-agent>`;
39
+ function readJflConfig(dir) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(path.join(dir, ".jfl", "config.json"), "utf-8"));
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ function scanForGtms() {
48
+ const home = os.homedir();
49
+ const searchDirs = [
50
+ path.join(home, "CascadeProjects"),
51
+ path.join(home, "Projects"),
52
+ path.join(home, "code"),
53
+ ];
54
+ const gtms = [];
55
+ for (const dir of searchDirs) {
56
+ if (!fs.existsSync(dir))
57
+ continue;
58
+ try {
59
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
60
+ for (const entry of entries) {
61
+ if (!entry.isDirectory())
62
+ continue;
63
+ const candidate = path.join(dir, entry.name);
64
+ const config = readJflConfig(candidate);
65
+ if (!config)
66
+ continue;
67
+ if (config.type === "gtm") {
68
+ gtms.push({ name: config.name || entry.name, path: candidate, type: "gtm" });
69
+ }
70
+ else if (!config.type && fs.existsSync(path.join(candidate, "knowledge"))) {
71
+ gtms.push({ name: config.name || entry.name, path: candidate, type: "legacy" });
72
+ }
73
+ }
74
+ }
75
+ catch { /* skip */ }
76
+ }
77
+ return gtms;
78
+ }
79
+ // Also check OpenClaw registry
80
+ function getRegistryGtms() {
81
+ try {
82
+ const regPath = path.join(os.homedir(), ".config", "jfl", "openclaw-agents.json");
83
+ const reg = JSON.parse(fs.readFileSync(regPath, "utf-8"));
84
+ const gtms = [];
85
+ for (const agent of Object.values(reg.agents ?? {})) {
86
+ for (const g of agent.registered_gtms ?? []) {
87
+ if (g.path && fs.existsSync(g.path) && !gtms.some((x) => x.path === g.path)) {
88
+ gtms.push({ name: g.name || path.basename(g.path), path: g.path, type: "registered" });
89
+ }
90
+ }
91
+ }
92
+ return gtms;
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ }
98
+ function findAllGtms(configWorkspace) {
99
+ const all = [];
100
+ const seen = new Set();
101
+ // Config override first
102
+ if (configWorkspace) {
103
+ const resolved = configWorkspace.startsWith("~")
104
+ ? path.join(os.homedir(), configWorkspace.slice(1))
105
+ : configWorkspace;
106
+ const config = readJflConfig(resolved);
107
+ if (config) {
108
+ all.push({ name: config.name || path.basename(resolved), path: resolved, type: config.type || "config" });
109
+ seen.add(resolved);
110
+ }
111
+ }
112
+ // Registry
113
+ for (const g of getRegistryGtms()) {
114
+ if (!seen.has(g.path)) {
115
+ all.push(g);
116
+ seen.add(g.path);
117
+ }
118
+ }
119
+ // Filesystem scan
120
+ for (const g of scanForGtms()) {
121
+ if (!seen.has(g.path)) {
122
+ all.push(g);
123
+ seen.add(g.path);
124
+ }
125
+ }
126
+ return all;
127
+ }
128
+ // ============================================================================
129
+ // Context Hub Client — direct HTTP to Context Hub API
130
+ // ============================================================================
131
+ function createHubClient(workspacePath, port = 4242) {
132
+ let availableCache = null;
133
+ let checkedAt = 0;
134
+ function getToken() {
135
+ try {
136
+ return fs.readFileSync(path.join(workspacePath, ".jfl", "context-hub.token"), "utf-8").trim();
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ }
142
+ async function isAvailable() {
143
+ if (availableCache !== null && Date.now() - checkedAt < 30_000)
144
+ return availableCache;
145
+ try {
146
+ const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(2000) });
147
+ availableCache = res.ok;
148
+ }
149
+ catch {
150
+ availableCache = false;
151
+ }
152
+ checkedAt = Date.now();
153
+ return availableCache;
154
+ }
155
+ async function search(query, limit = 5) {
156
+ if (!(await isAvailable()))
157
+ return [];
158
+ const token = getToken();
159
+ const headers = { "Content-Type": "application/json" };
160
+ if (token)
161
+ headers["Authorization"] = `Bearer ${token}`;
162
+ try {
163
+ const res = await fetch(`http://localhost:${port}/api/context/search`, {
164
+ method: "POST",
165
+ headers,
166
+ body: JSON.stringify({ query, maxItems: limit }),
167
+ signal: AbortSignal.timeout(5000),
168
+ });
169
+ if (!res.ok)
170
+ return [];
171
+ const data = (await res.json());
172
+ return (data.items ?? []).map((i) => ({
173
+ source: i.source ?? "unknown",
174
+ title: i.title ?? "",
175
+ content: i.content ?? "",
176
+ }));
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ return { isAvailable, search };
183
+ }
184
+ // ============================================================================
185
+ // CLI helpers — shell out to jfl openclaw
186
+ // ============================================================================
187
+ async function jflExec(cmd) {
188
+ const { stdout } = await execAsync(`jfl openclaw ${cmd}`, { timeout: 30000 });
189
+ return stdout.trim();
190
+ }
191
+ async function jflJSON(cmd) {
192
+ return JSON.parse(await jflExec(`${cmd} --json`));
193
+ }
194
+ async function hasJflCli() {
195
+ try {
196
+ await execAsync("jfl --version", { timeout: 5000 });
197
+ return true;
198
+ }
199
+ catch {
200
+ return false;
201
+ }
202
+ }
203
+ async function installJflCli() {
204
+ try {
205
+ await execAsync("npm install -g jfl", { timeout: 60000 });
206
+ return true;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
212
+ const jflPlugin = {
213
+ id: "jfl",
214
+ name: "JFL",
215
+ description: "Project context layer — dormant until /jfl",
216
+ register(api) {
217
+ const cfg = api.pluginConfig ?? {};
218
+ // ── State ─────────────────────────────────────────────────────────
219
+ let active = false;
220
+ let gtmPath = null;
221
+ let gtmName = "";
222
+ let sessionBranch = null;
223
+ let agentId = "cash"; // default, overridden on activation
224
+ let hub = null;
225
+ // ── Activation ────────────────────────────────────────────────────
226
+ async function activate(gtm) {
227
+ gtmPath = gtm.path;
228
+ gtmName = gtm.name;
229
+ hub = createHubClient(gtm.path);
230
+ active = true;
231
+ // Ensure CLI
232
+ if (!(await hasJflCli())) {
233
+ const installed = await installJflCli();
234
+ if (!installed)
235
+ return `JFL activated: ${gtmName}\n\nWarning: jfl CLI not found. Install with: npm install -g jfl`;
236
+ }
237
+ // Register + start session
238
+ try {
239
+ await jflExec(`register -g "${gtmPath}" -a ${agentId}`);
240
+ }
241
+ catch { /* idempotent, may already be registered */ }
242
+ try {
243
+ const session = await jflJSON(`session-start -a ${agentId} -g "${gtmPath}"`);
244
+ sessionBranch = session.session_id || session.branch || null;
245
+ }
246
+ catch {
247
+ // Session start failed but we're still activated for commands
248
+ }
249
+ api.logger.info(`jfl: activated → ${gtmName} (${gtmPath})`);
250
+ const hubUp = hub ? await hub.isAvailable() : false;
251
+ return [
252
+ `JFL activated: ${gtmName}`,
253
+ ``,
254
+ `Session: ${sessionBranch || "none"}`,
255
+ `Context Hub: ${hubUp ? "running" : "offline"}`,
256
+ ``,
257
+ `What I do now:`,
258
+ `- Search context before each response`,
259
+ `- Capture decisions to journal`,
260
+ `- Auto-commit work`,
261
+ ``,
262
+ `Commands:`,
263
+ `/context <query> — Search project context`,
264
+ `/journal <type> <title> | <summary> — Log work`,
265
+ `/hud — Dashboard`,
266
+ `/jfl — This status`,
267
+ ].join("\n");
268
+ }
269
+ // ── Startup ───────────────────────────────────────────────────────
270
+ api.registerService({
271
+ id: "jfl",
272
+ async start() {
273
+ const gtms = findAllGtms(cfg.workspace);
274
+ api.logger.info(`jfl: ${gtms.length} GTM(s) found, dormant until /jfl`);
275
+ },
276
+ stop() {
277
+ if (active && sessionBranch) {
278
+ // Best-effort session end
279
+ try {
280
+ execCb(`jfl openclaw session-end --sync`, () => { });
281
+ }
282
+ catch { }
283
+ }
284
+ api.logger.info("jfl: stopped");
285
+ },
286
+ });
287
+ // ── Hooks (gated by activation) ───────────────────────────────────
288
+ // Inject agent instructions + context on activation
289
+ api.on("before_agent_start", async (event) => {
290
+ if (!active || !hub)
291
+ return;
292
+ const parts = [];
293
+ // Always inject agent instructions so Claude knows how to behave
294
+ parts.push(AGENT_INSTRUCTIONS);
295
+ // Search context relevant to this message
296
+ const prompt = event.prompt;
297
+ if (prompt && prompt.length > 10) {
298
+ const results = await hub.search(prompt.slice(0, 200), 3);
299
+ if (results.length > 0) {
300
+ const items = results.map((r) => `- [${r.source}] ${r.title}: ${r.content.slice(0, 150)}`).join("\n");
301
+ parts.push(`<jfl-context>\nRelevant to this message:\n${items}\n</jfl-context>`);
302
+ }
303
+ }
304
+ // Session info
305
+ parts.push(`<jfl-session>GTM: ${gtmName} | Session: ${sessionBranch || "none"}</jfl-session>`);
306
+ return { prependContext: parts.join("\n\n") };
307
+ });
308
+ // Auto-capture decisions after agent responds
309
+ api.on("agent_end", async (event) => {
310
+ if (!active || !gtmPath)
311
+ return;
312
+ // Heartbeat (auto-commit)
313
+ try {
314
+ await jflExec("heartbeat");
315
+ }
316
+ catch { }
317
+ // Scan last assistant message for decision indicators
318
+ if (event.messages && event.messages.length > 0) {
319
+ const last = event.messages[event.messages.length - 1];
320
+ const text = typeof last === "string" ? last : last?.content ?? "";
321
+ const lower = text.toLowerCase();
322
+ const isDecision = /\b(decided|choosing|going with|let's go with|picking)\b/.test(lower);
323
+ const isComplete = /\b(done|completed|finished|shipped|built|implemented)\b/.test(lower);
324
+ if (isDecision || isComplete) {
325
+ const type = isDecision ? "decision" : "feature";
326
+ const title = text.slice(0, 80).replace(/\n/g, " ").replace(/"/g, "'");
327
+ try {
328
+ await jflExec(`journal --type ${type} --title "${title}" --summary "${title}"`);
329
+ }
330
+ catch { }
331
+ }
332
+ }
333
+ });
334
+ // Session lifecycle
335
+ api.on("session_end", async () => {
336
+ if (!active || !sessionBranch)
337
+ return;
338
+ try {
339
+ await jflJSON("session-end --sync");
340
+ }
341
+ catch { }
342
+ sessionBranch = null;
343
+ });
344
+ // ── Tools (Claude can use these proactively) ──────────────────────
345
+ api.registerTool({
346
+ name: "jfl_context",
347
+ description: "Search JFL project context — knowledge docs, journal entries, code. Use when someone asks about the project, past decisions, or what's been done.",
348
+ parameters: {
349
+ type: "object",
350
+ properties: {
351
+ query: { type: "string", description: "Search query" },
352
+ },
353
+ required: ["query"],
354
+ },
355
+ async execute(_id, params) {
356
+ if (!active)
357
+ return { content: [{ type: "text", text: "JFL not active. User should run /jfl first." }] };
358
+ if (!hub)
359
+ return { content: [{ type: "text", text: "No workspace set." }] };
360
+ const results = await hub.search(params.query, 5);
361
+ if (results.length === 0) {
362
+ // Fallback to CLI
363
+ try {
364
+ const raw = await jflExec(`context -q "${params.query.replace(/"/g, '\\"')}"`);
365
+ return { content: [{ type: "text", text: raw || "No results." }] };
366
+ }
367
+ catch {
368
+ return { content: [{ type: "text", text: "No results found." }] };
369
+ }
370
+ }
371
+ const text = results.map((r, i) => `${i + 1}. [${r.source}] ${r.title}\n ${r.content.slice(0, 200)}`).join("\n\n");
372
+ return { content: [{ type: "text", text }] };
373
+ },
374
+ });
375
+ api.registerTool({
376
+ name: "jfl_journal",
377
+ description: "Write a journal entry. Use after decisions, task completion, bug fixes, or discoveries. Types: decision, feature, fix, discovery, milestone.",
378
+ parameters: {
379
+ type: "object",
380
+ properties: {
381
+ type: { type: "string", enum: ["decision", "feature", "fix", "discovery", "milestone"], description: "Entry type" },
382
+ title: { type: "string", description: "Short title" },
383
+ summary: { type: "string", description: "What happened, why, what's next" },
384
+ },
385
+ required: ["type", "title", "summary"],
386
+ },
387
+ async execute(_id, params) {
388
+ if (!active)
389
+ return { content: [{ type: "text", text: "JFL not active." }] };
390
+ try {
391
+ await jflExec(`journal --type "${params.type}" --title "${params.title.replace(/"/g, "'")}" --summary "${params.summary.replace(/"/g, "'").slice(0, 500)}"`);
392
+ return { content: [{ type: "text", text: `Journal entry written: [${params.type}] ${params.title}` }] };
393
+ }
394
+ catch (e) {
395
+ return { content: [{ type: "text", text: `Failed: ${e.message}` }] };
396
+ }
397
+ },
398
+ });
399
+ // ── Commands (show up in Telegram) ────────────────────────────────
400
+ api.registerCommand({
401
+ name: "jfl",
402
+ description: "Activate JFL or show status",
403
+ acceptsArgs: true,
404
+ handler: async (ctx) => {
405
+ const arg = ctx.args?.trim();
406
+ // Already active — show status
407
+ if (active && !arg) {
408
+ const hubUp = hub ? await hub.isAvailable() : false;
409
+ return {
410
+ text: [
411
+ `JFL active: ${gtmName}`,
412
+ `Session: ${sessionBranch || "none"}`,
413
+ `Context Hub: ${hubUp ? "running" : "offline"}`,
414
+ ``,
415
+ `Automatic:`,
416
+ `- Context injected before responses`,
417
+ `- Decisions captured to journal`,
418
+ `- Work auto-committed`,
419
+ ``,
420
+ `Commands:`,
421
+ `/context <query> — Search`,
422
+ `/journal <type> <title> | <summary> — Log`,
423
+ `/hud — Dashboard`,
424
+ ].join("\n"),
425
+ };
426
+ }
427
+ // Find GTMs
428
+ const gtms = findAllGtms(cfg.workspace);
429
+ if (gtms.length === 0) {
430
+ return {
431
+ text: [
432
+ "JFL - Just Fucking Launch",
433
+ "",
434
+ "No projects found.",
435
+ "",
436
+ "Create one:",
437
+ " jfl init -n 'My Project'",
438
+ "",
439
+ "Then restart the gateway and run /jfl",
440
+ ].join("\n"),
441
+ };
442
+ }
443
+ // User specified which one
444
+ if (arg) {
445
+ const idx = parseInt(arg) - 1;
446
+ const match = !isNaN(idx) && idx >= 0 && idx < gtms.length
447
+ ? gtms[idx]
448
+ : gtms.find((g) => g.name.toLowerCase().includes(arg.toLowerCase()));
449
+ if (match)
450
+ return { text: await activate(match) };
451
+ return { text: `No project matching "${arg}".\n\n${gtms.map((g, i) => `${i + 1}. ${g.name}`).join("\n")}\n\nUse /jfl <number>` };
452
+ }
453
+ // Single GTM — auto-activate
454
+ if (gtms.length === 1) {
455
+ return { text: await activate(gtms[0]) };
456
+ }
457
+ // Multiple — list them
458
+ return {
459
+ text: [
460
+ "Select a project:",
461
+ "",
462
+ ...gtms.map((g, i) => `${i + 1}. ${g.name} (${g.path})`),
463
+ "",
464
+ "Use /jfl <number> to activate.",
465
+ ].join("\n"),
466
+ };
467
+ },
468
+ });
469
+ api.registerCommand({
470
+ name: "context",
471
+ description: "Search project context",
472
+ acceptsArgs: true,
473
+ handler: async (ctx) => {
474
+ if (!active) {
475
+ // Try auto-activate
476
+ const gtms = findAllGtms(cfg.workspace);
477
+ if (gtms.length === 1) {
478
+ await activate(gtms[0]);
479
+ }
480
+ else {
481
+ return { text: "Use /jfl first to select a project." };
482
+ }
483
+ }
484
+ const q = ctx.args?.trim();
485
+ if (!q)
486
+ return { text: "Usage: /context <query>" };
487
+ if (hub) {
488
+ const results = await hub.search(q, 5);
489
+ if (results.length > 0) {
490
+ return {
491
+ text: results.map((r, i) => `${i + 1}. [${r.source}] ${r.title}\n ${r.content.slice(0, 150)}`).join("\n\n"),
492
+ };
493
+ }
494
+ }
495
+ // Fallback to CLI
496
+ try {
497
+ const raw = await jflExec(`context -q "${q.replace(/"/g, '\\"')}"`);
498
+ return { text: raw || "No results." };
499
+ }
500
+ catch {
501
+ return { text: "No results. Context Hub may be offline." };
502
+ }
503
+ },
504
+ });
505
+ api.registerCommand({
506
+ name: "journal",
507
+ description: "Write a journal entry",
508
+ acceptsArgs: true,
509
+ handler: async (ctx) => {
510
+ if (!active)
511
+ return { text: "Use /jfl first." };
512
+ const raw = ctx.args?.trim();
513
+ if (!raw)
514
+ return { text: "Usage: /journal <type> <title> | <summary>\nTypes: feature, fix, decision, discovery, milestone" };
515
+ const pipeIdx = raw.indexOf("|");
516
+ const before = pipeIdx >= 0 ? raw.substring(0, pipeIdx).trim() : raw;
517
+ const summary = pipeIdx >= 0 ? raw.substring(pipeIdx + 1).trim() : "";
518
+ const parts = before.split(/\s+/);
519
+ const type = parts[0] ?? "feature";
520
+ const title = parts.slice(1).join(" ") || "Untitled";
521
+ try {
522
+ await jflExec(`journal --type "${type}" --title "${title.replace(/"/g, "'")}" --summary "${(summary || title).replace(/"/g, "'")}"`);
523
+ return { text: `Journal: [${type}] ${title}` };
524
+ }
525
+ catch (e) {
526
+ return { text: `Failed: ${e.message?.split("\n")[0]}` };
527
+ }
528
+ },
529
+ });
530
+ api.registerCommand({
531
+ name: "hud",
532
+ description: "Project dashboard",
533
+ handler: async () => {
534
+ if (!active)
535
+ return { text: "Use /jfl first." };
536
+ const hubUp = hub ? await hub.isAvailable() : false;
537
+ return {
538
+ text: [
539
+ `--- ${gtmName} ---`,
540
+ ``,
541
+ `Session: ${sessionBranch || "none"}`,
542
+ `Context Hub: ${hubUp ? "running" : "offline"}`,
543
+ `Workspace: ${gtmPath}`,
544
+ ``,
545
+ `/context <query> — Search`,
546
+ `/journal <type> <title> | <summary> — Log`,
547
+ ].join("\n"),
548
+ };
549
+ },
550
+ });
551
+ api.logger.info("jfl: loaded (dormant — /jfl to activate)");
552
+ },
553
+ };
554
+ export default jflPlugin;
555
+ //# sourceMappingURL=index.js.map