jfl 0.1.1 → 0.2.1

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 (112) hide show
  1. package/README.md +77 -7
  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 +516 -319
  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/gtm-process-update.d.ts +10 -0
  13. package/dist/commands/gtm-process-update.d.ts.map +1 -0
  14. package/dist/commands/gtm-process-update.js +101 -0
  15. package/dist/commands/gtm-process-update.js.map +1 -0
  16. package/dist/commands/onboard.d.ts.map +1 -1
  17. package/dist/commands/onboard.js +203 -15
  18. package/dist/commands/onboard.js.map +1 -1
  19. package/dist/commands/openclaw.d.ts +56 -0
  20. package/dist/commands/openclaw.d.ts.map +1 -0
  21. package/dist/commands/openclaw.js +700 -0
  22. package/dist/commands/openclaw.js.map +1 -0
  23. package/dist/commands/service-validate.d.ts +12 -0
  24. package/dist/commands/service-validate.d.ts.map +1 -0
  25. package/dist/commands/service-validate.js +611 -0
  26. package/dist/commands/service-validate.js.map +1 -0
  27. package/dist/commands/services-create.d.ts +15 -0
  28. package/dist/commands/services-create.d.ts.map +1 -0
  29. package/dist/commands/services-create.js +1452 -0
  30. package/dist/commands/services-create.js.map +1 -0
  31. package/dist/commands/services-sync-agents.d.ts +23 -0
  32. package/dist/commands/services-sync-agents.d.ts.map +1 -0
  33. package/dist/commands/services-sync-agents.js +207 -0
  34. package/dist/commands/services-sync-agents.js.map +1 -0
  35. package/dist/commands/services.d.ts +7 -1
  36. package/dist/commands/services.d.ts.map +1 -1
  37. package/dist/commands/services.js +347 -22
  38. package/dist/commands/services.js.map +1 -1
  39. package/dist/commands/update.js +0 -0
  40. package/dist/commands/validate-settings.d.ts +37 -0
  41. package/dist/commands/validate-settings.d.ts.map +1 -0
  42. package/dist/commands/validate-settings.js +197 -0
  43. package/dist/commands/validate-settings.js.map +1 -0
  44. package/dist/index.js +155 -60
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/agent-generator.d.ts.map +1 -1
  47. package/dist/lib/agent-generator.js +94 -1
  48. package/dist/lib/agent-generator.js.map +1 -1
  49. package/dist/lib/openclaw-registry.d.ts +48 -0
  50. package/dist/lib/openclaw-registry.d.ts.map +1 -0
  51. package/dist/lib/openclaw-registry.js +181 -0
  52. package/dist/lib/openclaw-registry.js.map +1 -0
  53. package/dist/lib/openclaw-sdk.d.ts +107 -0
  54. package/dist/lib/openclaw-sdk.d.ts.map +1 -0
  55. package/dist/lib/openclaw-sdk.js +208 -0
  56. package/dist/lib/openclaw-sdk.js.map +1 -0
  57. package/dist/lib/peer-agent-generator.d.ts +44 -0
  58. package/dist/lib/peer-agent-generator.d.ts.map +1 -0
  59. package/dist/lib/peer-agent-generator.js +286 -0
  60. package/dist/lib/peer-agent-generator.js.map +1 -0
  61. package/dist/lib/service-detector.d.ts +1 -1
  62. package/dist/lib/service-detector.d.ts.map +1 -1
  63. package/dist/lib/service-detector.js +118 -5
  64. package/dist/lib/service-detector.js.map +1 -1
  65. package/dist/lib/service-gtm.d.ts +157 -0
  66. package/dist/lib/service-gtm.d.ts.map +1 -0
  67. package/dist/lib/service-gtm.js +786 -0
  68. package/dist/lib/service-gtm.js.map +1 -0
  69. package/dist/lib/service-mcp-base.d.ts +10 -1
  70. package/dist/lib/service-mcp-base.d.ts.map +1 -1
  71. package/dist/lib/service-mcp-base.js +20 -1
  72. package/dist/lib/service-mcp-base.js.map +1 -1
  73. package/dist/mcp/service-peer-mcp.d.ts +36 -0
  74. package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
  75. package/dist/mcp/service-peer-mcp.js +220 -0
  76. package/dist/mcp/service-peer-mcp.js.map +1 -0
  77. package/dist/mcp/service-registry-mcp.js +0 -0
  78. package/dist/utils/settings-validator.d.ts +4 -1
  79. package/dist/utils/settings-validator.d.ts.map +1 -1
  80. package/dist/utils/settings-validator.js +25 -1
  81. package/dist/utils/settings-validator.js.map +1 -1
  82. package/package.json +2 -1
  83. package/template/.claude/service-settings.json +32 -0
  84. package/template/.claude/settings.json +10 -0
  85. package/template/.claude/skills/end/SKILL.md +1780 -0
  86. package/template/.jfl/config.json +2 -1
  87. package/template/.mcp.json +1 -7
  88. package/template/CLAUDE.md +1042 -248
  89. package/template/CLAUDE.md.bak +1187 -0
  90. package/template/scripts/commit-gtm.sh +56 -0
  91. package/template/scripts/commit-product.sh +68 -0
  92. package/template/scripts/migrate-to-branch-sessions.sh +201 -0
  93. package/template/scripts/session/auto-commit.sh +4 -3
  94. package/template/scripts/session/jfl-doctor.sh +222 -83
  95. package/template/scripts/session/session-cleanup.sh +109 -21
  96. package/template/scripts/session/session-end.sh +26 -13
  97. package/template/scripts/session/session-init.sh +280 -98
  98. package/template/scripts/session/test-critical-infrastructure.sh +293 -0
  99. package/template/scripts/session/test-experience-level.sh +336 -0
  100. package/template/scripts/session/test-session-cleanup.sh +268 -0
  101. package/template/scripts/session/test-session-sync.sh +320 -0
  102. package/template/scripts/where-am-i.sh +78 -0
  103. package/template/templates/service-agent/.claude/settings.json +32 -0
  104. package/template/templates/service-agent/CLAUDE.md +334 -0
  105. package/template/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
  106. package/template/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
  107. package/template/templates/service-agent/knowledge/RUNBOOK.md +412 -0
  108. package/template/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
  109. package/dist/commands/session-mgmt.d.ts +0 -33
  110. package/dist/commands/session-mgmt.d.ts.map +0 -1
  111. package/dist/commands/session-mgmt.js +0 -404
  112. package/dist/commands/session-mgmt.js.map +0 -1
package/README.md CHANGED
@@ -182,6 +182,68 @@ All context is stored as markdown and JSONL in your git repository. This means:
182
182
 
183
183
  ---
184
184
 
185
+ ## Clawdbot Integration
186
+
187
+ JFL ships with a Clawdbot plugin for Telegram-based agents.
188
+
189
+ ```bash
190
+ # Install plugin into Clawdbot
191
+ jfl clawdbot setup
192
+
193
+ # Check installation status
194
+ jfl clawdbot status
195
+ ```
196
+
197
+ After setup, restart your gateway (`clawdbot gateway`). The plugin is dormant until you activate it:
198
+
199
+ 1. Send `/jfl` in Telegram
200
+ 2. Pick a project (or auto-picks if you have one)
201
+ 3. Plugin activates — context injection, decision capture, auto-commit
202
+
203
+ **Telegram Commands:**
204
+
205
+ | Command | What it does |
206
+ |---------|-------------|
207
+ | `/jfl` | Activate JFL / show status |
208
+ | `/context <query>` | Search project knowledge |
209
+ | `/journal <type> <title> \| <summary>` | Write a journal entry |
210
+ | `/hud` | Project dashboard |
211
+
212
+ **What the plugin does automatically:**
213
+ - Injects relevant project context before every AI response
214
+ - Captures decisions to the journal after responses
215
+ - Auto-commits work periodically
216
+ - Manages session branches for isolated work
217
+
218
+ **Claude also gets tools** (`jfl_context`, `jfl_journal`) it can use proactively without you asking.
219
+
220
+ ---
221
+
222
+ ## OpenClaw Protocol
223
+
224
+ OpenClaw is JFL's runtime-agnostic agent protocol. Any AI agent can become a JFL team member:
225
+
226
+ ```bash
227
+ # Register agent with a GTM workspace
228
+ jfl openclaw register -g /path/to/gtm -a my-agent
229
+
230
+ # Start session (creates branch, auto-commit, Context Hub)
231
+ jfl openclaw session-start -a my-agent --json
232
+
233
+ # Search project context
234
+ jfl openclaw context -q "pricing decisions" --json
235
+
236
+ # Write journal entry
237
+ jfl openclaw journal --type decision --title "Chose OAuth" --summary "Better for multi-tenant"
238
+
239
+ # End session (merge, cleanup)
240
+ jfl openclaw session-end --json
241
+ ```
242
+
243
+ All commands support `--json` for programmatic use. See `jfl openclaw --help` for the full command list.
244
+
245
+ ---
246
+
185
247
  ## Context Hub
186
248
 
187
249
  Context Hub is a local daemon (port 4242) that provides unified context to any AI:
@@ -514,13 +576,21 @@ jfl --version # Show version
514
576
 
515
577
  ---
516
578
 
517
- ## What's New in 0.1.0
518
-
519
- - ✨ Auto-update on session start (checks npm registry, 24h cache)
520
- - 🔍 Synopsis command (`jfl synopsis [hours] [author]`)
521
- - 🏥 Improved doctor checks with categorized output
522
- - 🐛 Fixed auto-merge failure that caused branch pileup
523
- - 📊 Context Hub productization improvements
579
+ ## What's New
580
+
581
+ **0.2.0**
582
+ - OpenClaw protocol runtime-agnostic agent integration (`jfl openclaw`)
583
+ - Clawdbot plugin single install, dormant until /jfl, full lifecycle hooks
584
+ - `jfl clawdbot setup` one command to install plugin
585
+ - Agent tools (jfl_context, jfl_journal) for proactive AI behavior
586
+ - GTM detection via config.json type field (no more false positives on service repos)
587
+
588
+ **0.1.0**
589
+ - Auto-update on session start (checks npm registry, 24h cache)
590
+ - Synopsis command (`jfl synopsis [hours] [author]`)
591
+ - Improved doctor checks with categorized output
592
+ - Fixed auto-merge failure that caused branch pileup
593
+ - Context Hub productization improvements
524
594
 
525
595
  ---
526
596
 
@@ -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