jfl 0.8.1 โ†’ 0.9.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 (296) hide show
  1. package/README.md +35 -4
  2. package/dist/commands/digest.d.ts +6 -0
  3. package/dist/commands/digest.d.ts.map +1 -1
  4. package/dist/commands/digest.js +70 -69
  5. package/dist/commands/digest.js.map +1 -1
  6. package/dist/commands/doctor.d.ts +1 -0
  7. package/dist/commands/doctor.d.ts.map +1 -1
  8. package/dist/commands/doctor.js +30 -1
  9. package/dist/commands/doctor.js.map +1 -1
  10. package/dist/commands/eval.d.ts +40 -0
  11. package/dist/commands/eval.d.ts.map +1 -1
  12. package/dist/commands/eval.js +8 -8
  13. package/dist/commands/eval.js.map +1 -1
  14. package/dist/commands/findings.d.ts +7 -0
  15. package/dist/commands/findings.d.ts.map +1 -1
  16. package/dist/commands/findings.js +4 -4
  17. package/dist/commands/findings.js.map +1 -1
  18. package/dist/commands/ide.d.ts +2 -1
  19. package/dist/commands/ide.d.ts.map +1 -1
  20. package/dist/commands/ide.js +61 -1
  21. package/dist/commands/ide.js.map +1 -1
  22. package/dist/commands/init-from-service.d.ts +15 -0
  23. package/dist/commands/init-from-service.d.ts.map +1 -0
  24. package/dist/commands/init-from-service.js +541 -0
  25. package/dist/commands/init-from-service.js.map +1 -0
  26. package/dist/commands/init.d.ts +1 -0
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +32 -1
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/kanban.d.ts.map +1 -1
  31. package/dist/commands/kanban.js +13 -4
  32. package/dist/commands/kanban.js.map +1 -1
  33. package/dist/commands/linear.d.ts +41 -0
  34. package/dist/commands/linear.d.ts.map +1 -0
  35. package/dist/commands/linear.js +715 -0
  36. package/dist/commands/linear.js.map +1 -0
  37. package/dist/commands/peter.d.ts.map +1 -1
  38. package/dist/commands/peter.js +232 -25
  39. package/dist/commands/peter.js.map +1 -1
  40. package/dist/commands/portfolio.d.ts +5 -0
  41. package/dist/commands/portfolio.d.ts.map +1 -1
  42. package/dist/commands/portfolio.js +193 -203
  43. package/dist/commands/portfolio.js.map +1 -1
  44. package/dist/commands/predict.d.ts +19 -0
  45. package/dist/commands/predict.d.ts.map +1 -1
  46. package/dist/commands/predict.js +4 -4
  47. package/dist/commands/predict.js.map +1 -1
  48. package/dist/commands/services.d.ts.map +1 -1
  49. package/dist/commands/services.js +146 -0
  50. package/dist/commands/services.js.map +1 -1
  51. package/dist/commands/setup.d.ts.map +1 -1
  52. package/dist/commands/setup.js +279 -20
  53. package/dist/commands/setup.js.map +1 -1
  54. package/dist/commands/start.d.ts +25 -0
  55. package/dist/commands/start.d.ts.map +1 -0
  56. package/dist/commands/start.js +191 -0
  57. package/dist/commands/start.js.map +1 -0
  58. package/dist/commands/telemetry-monitor.d.ts +11 -0
  59. package/dist/commands/telemetry-monitor.d.ts.map +1 -0
  60. package/dist/commands/telemetry-monitor.js +224 -0
  61. package/dist/commands/telemetry-monitor.js.map +1 -0
  62. package/dist/commands/telemetry-test.d.ts +11 -0
  63. package/dist/commands/telemetry-test.d.ts.map +1 -0
  64. package/dist/commands/telemetry-test.js +67 -0
  65. package/dist/commands/telemetry-test.js.map +1 -0
  66. package/dist/commands/tenet-agents.d.ts +13 -0
  67. package/dist/commands/tenet-agents.d.ts.map +1 -0
  68. package/dist/commands/tenet-agents.js +191 -0
  69. package/dist/commands/tenet-agents.js.map +1 -0
  70. package/dist/commands/tenet-setup.d.ts +20 -0
  71. package/dist/commands/tenet-setup.d.ts.map +1 -0
  72. package/dist/commands/tenet-setup.js +135 -0
  73. package/dist/commands/tenet-setup.js.map +1 -0
  74. package/dist/commands/train.d.ts +18 -0
  75. package/dist/commands/train.d.ts.map +1 -1
  76. package/dist/commands/train.js +182 -0
  77. package/dist/commands/train.js.map +1 -1
  78. package/dist/commands/viz.d.ts +33 -0
  79. package/dist/commands/viz.d.ts.map +1 -1
  80. package/dist/commands/viz.js +9 -9
  81. package/dist/commands/viz.js.map +1 -1
  82. package/dist/commands/whoami.d.ts +2 -0
  83. package/dist/commands/whoami.d.ts.map +1 -0
  84. package/dist/commands/whoami.js +24 -0
  85. package/dist/commands/whoami.js.map +1 -0
  86. package/dist/index.js +230 -30
  87. package/dist/index.js.map +1 -1
  88. package/dist/lib/advanced-setup.d.ts +78 -0
  89. package/dist/lib/advanced-setup.d.ts.map +1 -0
  90. package/dist/lib/advanced-setup.js +433 -0
  91. package/dist/lib/advanced-setup.js.map +1 -0
  92. package/dist/lib/agent-config.d.ts +33 -0
  93. package/dist/lib/agent-config.d.ts.map +1 -1
  94. package/dist/lib/agent-config.js +26 -0
  95. package/dist/lib/agent-config.js.map +1 -1
  96. package/dist/lib/counterfactual-training-bridge.d.ts +114 -0
  97. package/dist/lib/counterfactual-training-bridge.d.ts.map +1 -0
  98. package/dist/lib/counterfactual-training-bridge.js +322 -0
  99. package/dist/lib/counterfactual-training-bridge.js.map +1 -0
  100. package/dist/lib/discovery-agent.d.ts +48 -0
  101. package/dist/lib/discovery-agent.d.ts.map +1 -0
  102. package/dist/lib/discovery-agent.js +111 -0
  103. package/dist/lib/discovery-agent.js.map +1 -0
  104. package/dist/lib/flow-engine.d.ts.map +1 -1
  105. package/dist/lib/flow-engine.js +46 -8
  106. package/dist/lib/flow-engine.js.map +1 -1
  107. package/dist/lib/gtm-generator.d.ts +29 -0
  108. package/dist/lib/gtm-generator.d.ts.map +1 -0
  109. package/dist/lib/gtm-generator.js +252 -0
  110. package/dist/lib/gtm-generator.js.map +1 -0
  111. package/dist/lib/hub-health.d.ts +40 -0
  112. package/dist/lib/hub-health.d.ts.map +1 -0
  113. package/dist/lib/hub-health.js +89 -0
  114. package/dist/lib/hub-health.js.map +1 -0
  115. package/dist/lib/invariant-monitor.d.ts +6 -2
  116. package/dist/lib/invariant-monitor.d.ts.map +1 -1
  117. package/dist/lib/invariant-monitor.js +89 -2
  118. package/dist/lib/invariant-monitor.js.map +1 -1
  119. package/dist/lib/journal-analyzer.d.ts +71 -0
  120. package/dist/lib/journal-analyzer.d.ts.map +1 -0
  121. package/dist/lib/journal-analyzer.js +306 -0
  122. package/dist/lib/journal-analyzer.js.map +1 -0
  123. package/dist/lib/linear-client.d.ts +73 -0
  124. package/dist/lib/linear-client.d.ts.map +1 -0
  125. package/dist/lib/linear-client.js +112 -0
  126. package/dist/lib/linear-client.js.map +1 -0
  127. package/dist/lib/linear-id-map.d.ts +20 -0
  128. package/dist/lib/linear-id-map.d.ts.map +1 -0
  129. package/dist/lib/linear-id-map.js +59 -0
  130. package/dist/lib/linear-id-map.js.map +1 -0
  131. package/dist/lib/linear-kanban.d.ts +66 -0
  132. package/dist/lib/linear-kanban.d.ts.map +1 -0
  133. package/dist/lib/linear-kanban.js +175 -0
  134. package/dist/lib/linear-kanban.js.map +1 -0
  135. package/dist/lib/onboarding.d.ts +40 -0
  136. package/dist/lib/onboarding.d.ts.map +1 -0
  137. package/dist/lib/onboarding.js +213 -0
  138. package/dist/lib/onboarding.js.map +1 -0
  139. package/dist/lib/physical-world-model.d.ts +50 -0
  140. package/dist/lib/physical-world-model.d.ts.map +1 -0
  141. package/dist/lib/physical-world-model.js +251 -0
  142. package/dist/lib/physical-world-model.js.map +1 -0
  143. package/dist/lib/planning-loop.d.ts +157 -0
  144. package/dist/lib/planning-loop.d.ts.map +1 -0
  145. package/dist/lib/planning-loop.js +537 -0
  146. package/dist/lib/planning-loop.js.map +1 -0
  147. package/dist/lib/policy-head.d.ts +13 -0
  148. package/dist/lib/policy-head.d.ts.map +1 -1
  149. package/dist/lib/policy-head.js +168 -2
  150. package/dist/lib/policy-head.js.map +1 -1
  151. package/dist/lib/resource-optimizer-middleware.d.ts +39 -0
  152. package/dist/lib/resource-optimizer-middleware.d.ts.map +1 -0
  153. package/dist/lib/resource-optimizer-middleware.js +222 -0
  154. package/dist/lib/resource-optimizer-middleware.js.map +1 -0
  155. package/dist/lib/resource-optimizer.d.ts +71 -0
  156. package/dist/lib/resource-optimizer.d.ts.map +1 -0
  157. package/dist/lib/resource-optimizer.js +228 -0
  158. package/dist/lib/resource-optimizer.js.map +1 -0
  159. package/dist/lib/rl-manager.d.ts +74 -0
  160. package/dist/lib/rl-manager.d.ts.map +1 -0
  161. package/dist/lib/rl-manager.js +245 -0
  162. package/dist/lib/rl-manager.js.map +1 -0
  163. package/dist/lib/service-analyzer.d.ts +76 -0
  164. package/dist/lib/service-analyzer.d.ts.map +1 -0
  165. package/dist/lib/service-analyzer.js +704 -0
  166. package/dist/lib/service-analyzer.js.map +1 -0
  167. package/dist/lib/service-gtm.js +2 -2
  168. package/dist/lib/service-gtm.js.map +1 -1
  169. package/dist/lib/service-questionnaire.d.ts +11 -0
  170. package/dist/lib/service-questionnaire.d.ts.map +1 -0
  171. package/dist/lib/service-questionnaire.js +89 -0
  172. package/dist/lib/service-questionnaire.js.map +1 -0
  173. package/dist/lib/setup/agent-generator.d.ts +2 -0
  174. package/dist/lib/setup/agent-generator.d.ts.map +1 -1
  175. package/dist/lib/setup/agent-generator.js +128 -4
  176. package/dist/lib/setup/agent-generator.js.map +1 -1
  177. package/dist/lib/setup/flow-generator.d.ts +10 -0
  178. package/dist/lib/setup/flow-generator.d.ts.map +1 -0
  179. package/dist/lib/setup/flow-generator.js +113 -0
  180. package/dist/lib/setup/flow-generator.js.map +1 -0
  181. package/dist/lib/setup/invariant-bridge.d.ts +91 -0
  182. package/dist/lib/setup/invariant-bridge.d.ts.map +1 -0
  183. package/dist/lib/setup/invariant-bridge.js +384 -0
  184. package/dist/lib/setup/invariant-bridge.js.map +1 -0
  185. package/dist/lib/setup/spec-generator.d.ts +41 -5
  186. package/dist/lib/setup/spec-generator.d.ts.map +1 -1
  187. package/dist/lib/setup/spec-generator.js +503 -29
  188. package/dist/lib/setup/spec-generator.js.map +1 -1
  189. package/dist/lib/setup/starter-intelligence.d.ts +25 -0
  190. package/dist/lib/setup/starter-intelligence.d.ts.map +1 -0
  191. package/dist/lib/setup/starter-intelligence.js +309 -0
  192. package/dist/lib/setup/starter-intelligence.js.map +1 -0
  193. package/dist/lib/stratus-client.js +1 -1
  194. package/dist/lib/stratus-client.js.map +1 -1
  195. package/dist/lib/surface-agent.d.ts +78 -0
  196. package/dist/lib/surface-agent.d.ts.map +1 -0
  197. package/dist/lib/surface-agent.js +105 -0
  198. package/dist/lib/surface-agent.js.map +1 -0
  199. package/dist/lib/surface-coordination-example.d.ts +30 -0
  200. package/dist/lib/surface-coordination-example.d.ts.map +1 -0
  201. package/dist/lib/surface-coordination-example.js +164 -0
  202. package/dist/lib/surface-coordination-example.js.map +1 -0
  203. package/dist/lib/telemetry/physical-world-collector.d.ts +15 -0
  204. package/dist/lib/telemetry/physical-world-collector.d.ts.map +1 -0
  205. package/dist/lib/telemetry/physical-world-collector.js +177 -0
  206. package/dist/lib/telemetry/physical-world-collector.js.map +1 -0
  207. package/dist/lib/telemetry/training-bridge.d.ts +51 -0
  208. package/dist/lib/telemetry/training-bridge.d.ts.map +1 -0
  209. package/dist/lib/telemetry/training-bridge.js +185 -0
  210. package/dist/lib/telemetry/training-bridge.js.map +1 -0
  211. package/dist/lib/telemetry.d.ts +2 -1
  212. package/dist/lib/telemetry.d.ts.map +1 -1
  213. package/dist/lib/telemetry.js +23 -2
  214. package/dist/lib/telemetry.js.map +1 -1
  215. package/dist/lib/tenet-board-agent.d.ts +52 -0
  216. package/dist/lib/tenet-board-agent.d.ts.map +1 -0
  217. package/dist/lib/tenet-board-agent.js +226 -0
  218. package/dist/lib/tenet-board-agent.js.map +1 -0
  219. package/dist/lib/tenet-ide-agent.d.ts +40 -0
  220. package/dist/lib/tenet-ide-agent.d.ts.map +1 -0
  221. package/dist/lib/tenet-ide-agent.js +199 -0
  222. package/dist/lib/tenet-ide-agent.js.map +1 -0
  223. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -1
  224. package/dist/lib/workspace/data-pipeline.js +27 -5
  225. package/dist/lib/workspace/data-pipeline.js.map +1 -1
  226. package/dist/lib/workspace/sidebar-runner.d.ts +13 -0
  227. package/dist/lib/workspace/sidebar-runner.d.ts.map +1 -0
  228. package/dist/lib/workspace/sidebar-runner.js +419 -0
  229. package/dist/lib/workspace/sidebar-runner.js.map +1 -0
  230. package/dist/lib/workspace/surface-registry.d.ts.map +1 -1
  231. package/dist/lib/workspace/surface-registry.js +9 -1
  232. package/dist/lib/workspace/surface-registry.js.map +1 -1
  233. package/dist/lib/workspace/surfaces/agent-overview.d.ts +3 -3
  234. package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -1
  235. package/dist/lib/workspace/surfaces/agent-overview.js +3 -3
  236. package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -1
  237. package/dist/lib/workspace/surfaces/index.d.ts +3 -0
  238. package/dist/lib/workspace/surfaces/index.d.ts.map +1 -1
  239. package/dist/lib/workspace/surfaces/index.js +3 -0
  240. package/dist/lib/workspace/surfaces/index.js.map +1 -1
  241. package/dist/lib/workspace/surfaces/kanban.d.ts +15 -0
  242. package/dist/lib/workspace/surfaces/kanban.d.ts.map +1 -0
  243. package/dist/lib/workspace/surfaces/kanban.js +43 -0
  244. package/dist/lib/workspace/surfaces/kanban.js.map +1 -0
  245. package/dist/lib/workspace/surfaces/physical-world.d.ts +15 -0
  246. package/dist/lib/workspace/surfaces/physical-world.d.ts.map +1 -0
  247. package/dist/lib/workspace/surfaces/physical-world.js +37 -0
  248. package/dist/lib/workspace/surfaces/physical-world.js.map +1 -0
  249. package/dist/lib/workspace/surfaces/sidebar.d.ts +22 -0
  250. package/dist/lib/workspace/surfaces/sidebar.d.ts.map +1 -0
  251. package/dist/lib/workspace/surfaces/sidebar.js +94 -0
  252. package/dist/lib/workspace/surfaces/sidebar.js.map +1 -0
  253. package/dist/lib/workspace/tmux-adapter.d.ts +8 -5
  254. package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -1
  255. package/dist/lib/workspace/tmux-adapter.js +38 -7
  256. package/dist/lib/workspace/tmux-adapter.js.map +1 -1
  257. package/dist/lib/workspace/tmux-sidebar.d.ts +14 -0
  258. package/dist/lib/workspace/tmux-sidebar.d.ts.map +1 -0
  259. package/dist/lib/workspace/tmux-sidebar.js +230 -0
  260. package/dist/lib/workspace/tmux-sidebar.js.map +1 -0
  261. package/dist/types/flows.d.ts +2 -1
  262. package/dist/types/flows.d.ts.map +1 -1
  263. package/dist/types/physical-world-model.d.ts +65 -0
  264. package/dist/types/physical-world-model.d.ts.map +1 -0
  265. package/dist/types/physical-world-model.js +43 -0
  266. package/dist/types/physical-world-model.js.map +1 -0
  267. package/dist/types/telemetry.d.ts +37 -0
  268. package/dist/types/telemetry.d.ts.map +1 -1
  269. package/dist/types/world-model.d.ts.map +1 -1
  270. package/dist/types/world-model.js +14 -7
  271. package/dist/types/world-model.js.map +1 -1
  272. package/dist/utils/context-hub-port.d.ts.map +1 -1
  273. package/dist/utils/context-hub-port.js +6 -1
  274. package/dist/utils/context-hub-port.js.map +1 -1
  275. package/dist/utils/jfl-config.d.ts +7 -2
  276. package/dist/utils/jfl-config.d.ts.map +1 -1
  277. package/dist/utils/jfl-config.js +14 -4
  278. package/dist/utils/jfl-config.js.map +1 -1
  279. package/package.json +3 -2
  280. package/packages/pi/extensions/context.ts +51 -1
  281. package/packages/pi/extensions/hub-tools.ts +247 -0
  282. package/packages/pi/extensions/index.ts +38 -6
  283. package/packages/pi/extensions/memory-tool.ts +84 -4
  284. package/packages/pi/extensions/service-skills.ts +214 -0
  285. package/scripts/telemetry-dashboard.sh +44 -0
  286. package/scripts/test-planning-loop-e2e.ts +181 -0
  287. package/scripts/test-server-inference.ts +49 -0
  288. package/scripts/test-state-sensitivity.ts +32 -0
  289. package/scripts/train/v2/benchmark.py +661 -0
  290. package/scripts/train/v2/generate_balanced.py +439 -0
  291. package/scripts/train/v2/generate_hard_negatives.py +219 -0
  292. package/scripts/train/v2/infer.py +149 -36
  293. package/scripts/train/v2/infer_server.py +224 -0
  294. package/scripts/train/v2/online_train.py +576 -0
  295. package/scripts/train/v2/precompute.py +24 -6
  296. package/template/CLAUDE.md +74 -132
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Hub Tools Extension
3
+ *
4
+ * Registers tools for direct Context Hub interaction that were previously
5
+ * only available via MCP: events_publish, events_recent, context_status,
6
+ * context_sessions, query_experiment_history.
7
+ *
8
+ * Closes the parity gap between Claude Code (MCP) and Pi (extensions).
9
+ *
10
+ * @purpose Hub API tools โ€” events, status, sessions, experiments (MCP parity)
11
+ */
12
+
13
+ import type { PiContext, JflConfig } from "./types.js"
14
+ import { hubUrl, authToken } from "./map-bridge.js"
15
+
16
+ async function hubFetch(path: string, method: "GET" | "POST" = "GET", body?: unknown): Promise<any> {
17
+ const resp = await fetch(`${hubUrl}${path}`, {
18
+ method,
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
22
+ },
23
+ ...(body ? { body: JSON.stringify(body) } : {}),
24
+ signal: AbortSignal.timeout(5000),
25
+ })
26
+ if (!resp.ok) throw new Error(`Hub ${path}: ${resp.status}`)
27
+ return resp.json()
28
+ }
29
+
30
+ export async function setupHubTools(ctx: PiContext, _config: JflConfig): Promise<void> {
31
+
32
+ // โ”€โ”€โ”€ jfl_events_publish โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
33
+ ctx.registerTool({
34
+ name: "jfl_events_publish",
35
+ description: "Publish an event to the MAP event bus. Use to signal state changes, completions, or trigger flows.",
36
+ promptSnippet: "Publish event to MAP bus",
37
+ inputSchema: {
38
+ type: "object",
39
+ properties: {
40
+ type: {
41
+ type: "string",
42
+ description: "Event type (e.g. 'task:completed', 'eval:scored', 'custom:my-event')",
43
+ },
44
+ data: {
45
+ type: "string",
46
+ description: "Event data as JSON string",
47
+ },
48
+ },
49
+ required: ["type"],
50
+ },
51
+ async handler(input) {
52
+ const { type, data } = input as { type: string; data?: string }
53
+ try {
54
+ let eventData: unknown = {}
55
+ if (data) {
56
+ try { eventData = JSON.parse(data) } catch { eventData = { message: data } }
57
+ }
58
+
59
+ await hubFetch("/api/events", "POST", {
60
+ type,
61
+ source: `pi:${ctx.session.id}`,
62
+ data: eventData,
63
+ ts: new Date().toISOString(),
64
+ })
65
+ return `Event published: ${type}`
66
+ } catch {
67
+ return "Event publish failed โ€” Context Hub may not be running."
68
+ }
69
+ },
70
+ })
71
+
72
+ // โ”€โ”€โ”€ jfl_events_recent โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
73
+ ctx.registerTool({
74
+ name: "jfl_events_recent",
75
+ description: "Get recent events from the MAP event bus. Check what happened in the system โ€” agent actions, eval results, flow triggers.",
76
+ promptSnippet: "View recent MAP events",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ limit: {
81
+ type: "number",
82
+ description: "Number of events to return (default: 20)",
83
+ },
84
+ type: {
85
+ type: "string",
86
+ description: "Filter by event type prefix (e.g. 'eval:', 'hook:', 'task:')",
87
+ },
88
+ },
89
+ },
90
+ async handler(input) {
91
+ const { limit, type } = input as { limit?: number; type?: string }
92
+ try {
93
+ const params = new URLSearchParams()
94
+ if (limit) params.set("limit", String(limit))
95
+ if (type) params.set("type", type)
96
+
97
+ const data = await hubFetch(`/api/events?${params}`)
98
+ const events = data.events ?? data ?? []
99
+
100
+ if (!Array.isArray(events) || events.length === 0) return "No recent events."
101
+
102
+ return events
103
+ .slice(0, limit ?? 20)
104
+ .map((e: any) => {
105
+ const ts = e.ts ? new Date(e.ts).toLocaleTimeString() : "?"
106
+ const src = e.source ?? "?"
107
+ return `[${ts}] ${e.type} (${src})${e.data ? " โ€” " + JSON.stringify(e.data).slice(0, 100) : ""}`
108
+ })
109
+ .join("\n")
110
+ } catch {
111
+ return "Events unavailable โ€” Context Hub may not be running."
112
+ }
113
+ },
114
+ })
115
+
116
+ // โ”€โ”€โ”€ jfl_context_status โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
117
+ ctx.registerTool({
118
+ name: "jfl_context_status",
119
+ description: "Check Context Hub status โ€” available sources, memory index health, connected services.",
120
+ promptSnippet: "Check Context Hub health",
121
+ inputSchema: {
122
+ type: "object",
123
+ properties: {},
124
+ },
125
+ async handler() {
126
+ try {
127
+ const data = await hubFetch("/api/context/status")
128
+
129
+ const lines: string[] = ["Context Hub Status:"]
130
+ for (const [k, v] of Object.entries(data)) {
131
+ if (typeof v === "object" && v !== null) {
132
+ lines.push(` ${k}: ${JSON.stringify(v)}`)
133
+ } else {
134
+ lines.push(` ${k}: ${v}`)
135
+ }
136
+ }
137
+ return lines.join("\n")
138
+ } catch {
139
+ return "Context Hub status unavailable."
140
+ }
141
+ },
142
+ })
143
+
144
+ // โ”€โ”€โ”€ jfl_context_sessions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
145
+ ctx.registerTool({
146
+ name: "jfl_context_sessions",
147
+ description: "See activity from other sessions โ€” what other agents/users are working on. Informational only.",
148
+ promptSnippet: "View other session activity",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ limit: {
153
+ type: "number",
154
+ description: "Number of sessions to return (default: 5)",
155
+ },
156
+ },
157
+ },
158
+ async handler(input) {
159
+ const { limit } = input as { limit?: number }
160
+ try {
161
+ // Try the sessions endpoint
162
+ const data = await hubFetch(`/api/context/sessions?limit=${limit ?? 5}`)
163
+ const sessions = data.sessions ?? data ?? []
164
+
165
+ if (!Array.isArray(sessions) || sessions.length === 0) return "No other sessions found."
166
+
167
+ return sessions
168
+ .map((s: any) => {
169
+ const age = s.lastSeen ? `(${new Date(s.lastSeen).toLocaleString()})` : ""
170
+ return `${s.branch ?? s.id ?? "?"} ${age} โ€” ${s.summary ?? s.status ?? "active"}`
171
+ })
172
+ .join("\n")
173
+ } catch {
174
+ // Fallback: read journal files to find other sessions
175
+ try {
176
+ const { execSync } = await import("child_process")
177
+ const journalDir = `${ctx.session.projectRoot}/.jfl/journal`
178
+ const files = execSync(`ls -t ${journalDir}/*.jsonl 2>/dev/null | head -5`, {
179
+ encoding: "utf-8",
180
+ }).trim()
181
+
182
+ if (!files) return "No other sessions found."
183
+
184
+ return files.split("\n")
185
+ .map(f => {
186
+ const branch = f.split("/").pop()?.replace(".jsonl", "") ?? "?"
187
+ return `Session: ${branch}`
188
+ })
189
+ .join("\n")
190
+ } catch {
191
+ return "Sessions unavailable."
192
+ }
193
+ }
194
+ },
195
+ })
196
+
197
+ // โ”€โ”€โ”€ jfl_experiment_history โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
198
+ ctx.registerTool({
199
+ name: "jfl_experiment_history",
200
+ description: "Search past experiments, their outcomes, and what was learned. Useful for avoiding repeat experiments.",
201
+ promptSnippet: "Search experiment history",
202
+ inputSchema: {
203
+ type: "object",
204
+ properties: {
205
+ query: {
206
+ type: "string",
207
+ description: "Search query for experiment history",
208
+ },
209
+ limit: {
210
+ type: "number",
211
+ description: "Max results (default: 10)",
212
+ },
213
+ },
214
+ required: ["query"],
215
+ },
216
+ async handler(input) {
217
+ const { query, limit } = input as { query: string; limit?: number }
218
+ try {
219
+ const data = await hubFetch("/api/eval/trajectory", "GET")
220
+ const entries = data.entries ?? data ?? []
221
+
222
+ if (!Array.isArray(entries) || entries.length === 0) return "No experiment history found."
223
+
224
+ // Filter by query (simple text match)
225
+ const queryLower = query.toLowerCase()
226
+ const filtered = entries
227
+ .filter((e: any) => {
228
+ const text = JSON.stringify(e).toLowerCase()
229
+ return text.includes(queryLower)
230
+ })
231
+ .slice(0, limit ?? 10)
232
+
233
+ if (filtered.length === 0) return `No experiments matching "${query}".`
234
+
235
+ return filtered
236
+ .map((e: any) => {
237
+ const ts = e.ts ? new Date(e.ts).toLocaleDateString() : "?"
238
+ const score = e.composite != null ? ` (score: ${e.composite})` : ""
239
+ return `[${ts}]${score} ${e.label ?? e.summary ?? JSON.stringify(e).slice(0, 100)}`
240
+ })
241
+ .join("\n")
242
+ } catch {
243
+ return "Experiment history unavailable โ€” Context Hub may not be running."
244
+ }
245
+ },
246
+ })
247
+ }
@@ -32,6 +32,8 @@ import { setupEvalTool } from "./eval-tool.js"
32
32
  import { setupPolicyHeadTool } from "./policy-head-tool.js"
33
33
  import { setupTrainingBufferTool, onTrainingAgentEnd } from "./training-buffer-tool.js"
34
34
  import { setupAutoresearch } from "./autoresearch.js"
35
+ import { setupServiceSkills } from "./service-skills.js"
36
+ import { setupHubTools } from "./hub-tools.js"
35
37
  import { setupFooter } from "./footer.js"
36
38
  import { setupShortcuts } from "./shortcuts.js"
37
39
  import { setupNotifications } from "./notifications.js"
@@ -319,6 +321,8 @@ export default async function jflExtension(pi: any): Promise<void> {
319
321
  await setupPolicyHeadTool(ctx, config)
320
322
  await setupTrainingBufferTool(ctx, config)
321
323
  await setupAutoresearch(ctx, config)
324
+ await setupServiceSkills(ctx, config)
325
+ await setupHubTools(ctx, config)
322
326
 
323
327
  initStratusBridge(projectCwd)
324
328
  initAgentNames(projectCwd)
@@ -347,15 +351,19 @@ export default async function jflExtension(pi: any): Promise<void> {
347
351
  content: [
348
352
  `JFL session ready: "${projectName}" on branch ${ctx.session.branch}.`,
349
353
  "",
350
- "You have full project context injected in your system prompt (CLAUDE.md + recent journal + knowledge docs).",
351
- "Tools available: jfl_context, jfl_memory_search, jfl_hud, jfl_journal.",
354
+ "Your system prompt already contains full project context: CLAUDE.md, recent journal entries, knowledge docs, and code headers.",
355
+ "Everything was loaded during the boot screen. You do NOT need to call any tools.",
352
356
  "",
353
- "Greet the user naturally with a brief status:",
354
- "- What was worked on recently (from your injected context)",
357
+ "DO NOT call jfl_context, jfl_hud, jfl_memory_search, or run any bash commands at startup.",
358
+ "DO NOT run session-sync.sh, jfl-doctor.sh, or read journal files.",
359
+ "All of that is already done and injected into your system prompt.",
360
+ "",
361
+ "Just greet the user naturally with a brief status (3-5 lines):",
362
+ "- What was worked on recently (from the journal entries in your system prompt)",
355
363
  "- Current phase and any blockers",
356
364
  "- A suggested next action",
357
365
  "",
358
- "Keep it to 3-5 lines. No setup noise. Just be ready to work.",
366
+ "No tool calls. No setup noise. Just talk.",
359
367
  "Write journal entries as you work. Capture decisions immediately.",
360
368
  ].join("\n"),
361
369
  display: false,
@@ -380,7 +388,31 @@ export default async function jflExtension(pi: any): Promise<void> {
380
388
  latestPiCtx = piCtx
381
389
  const result = await injectContext(ctx, event)
382
390
  if (result?.systemPromptAddition) {
383
- const current = piCtx.getSystemPrompt?.() ?? ""
391
+ let current = piCtx.getSystemPrompt?.() ?? ""
392
+
393
+ // Strip Path B (Claude Code manual startup) from system prompt.
394
+ // We're running in Pi with the extension โ€” Path B instructions are
395
+ // noise that can confuse the LLM into running manual startup commands.
396
+ const pathBStart = "### Path B: Claude Code / No Extension"
397
+ const pathBEnd = "### How to Tell Which Path You're On"
398
+ const startIdx = current.indexOf(pathBStart)
399
+ const endIdx = current.indexOf(pathBEnd)
400
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
401
+ current = current.slice(0, startIdx) + current.slice(endIdx)
402
+ }
403
+
404
+ // Also strip the "How to Tell" section โ€” it references Path B
405
+ const howToTell = "### How to Tell Which Path You're On"
406
+ const howToTellIdx = current.indexOf(howToTell)
407
+ if (howToTellIdx !== -1) {
408
+ // Find the next ### or ## heading after it
409
+ const afterHowToTell = current.slice(howToTellIdx + howToTell.length)
410
+ const nextHeading = afterHowToTell.search(/\n###? /)
411
+ if (nextHeading !== -1) {
412
+ current = current.slice(0, howToTellIdx) + afterHowToTell.slice(nextHeading)
413
+ }
414
+ }
415
+
384
416
  return {
385
417
  systemPrompt: current
386
418
  ? `${current}\n\n${result.systemPromptAddition}`
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Memory Tool Extension
3
3
  *
4
- * Registers jfl_memory_search tool with custom TUI rendering.
5
- * Queries Context Hub memory API and renders results with
6
- * type-colored headers and collapsible sections.
4
+ * Registers jfl_memory_search, jfl_memory_add, and jfl_memory_status tools.
5
+ * Full parity with the Context Hub MCP server's memory tools.
7
6
  *
8
- * @purpose jfl_memory_search tool โ€” themed semantic memory search results
7
+ * @purpose Memory tools โ€” search, add, and status for project memory
9
8
  */
10
9
 
11
10
  import type { PiContext } from "./types.js"
@@ -13,6 +12,7 @@ import { hubUrl, authToken } from "./map-bridge.js"
13
12
  import { memoryRenderCall, memoryRenderResult } from "./tool-renderers.js"
14
13
 
15
14
  export function setupMemoryTool(ctx: PiContext): void {
15
+ // โ”€โ”€โ”€ jfl_memory_search โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
16
16
  ctx.registerTool({
17
17
  name: "jfl_memory_search",
18
18
  description: "Search JFL project memory โ€” find past decisions, learnings, and patterns across all sessions",
@@ -70,4 +70,84 @@ export function setupMemoryTool(ctx: PiContext): void {
70
70
  renderCall: memoryRenderCall,
71
71
  renderResult: memoryRenderResult,
72
72
  })
73
+
74
+ // โ”€โ”€โ”€ jfl_memory_add โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
75
+ ctx.registerTool({
76
+ name: "jfl_memory_add",
77
+ description: "Manually add a memory or note to the project memory system. Use to capture insights, decisions, or important context that should persist across sessions.",
78
+ promptSnippet: "Add a memory/note to project memory",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ title: {
83
+ type: "string",
84
+ description: "Title for the memory entry",
85
+ },
86
+ content: {
87
+ type: "string",
88
+ description: "Content of the memory โ€” the insight, decision, or note",
89
+ },
90
+ type: {
91
+ type: "string",
92
+ description: "Type of memory entry",
93
+ enum: ["decision", "discovery", "insight", "note"],
94
+ },
95
+ },
96
+ required: ["title", "content"],
97
+ },
98
+ async handler(input) {
99
+ const { title, content, type } = input as { title: string; content: string; type?: string }
100
+
101
+ try {
102
+ const resp = await fetch(`${hubUrl}/api/memory/add`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
107
+ },
108
+ body: JSON.stringify({ title, content, type: type ?? "note" }),
109
+ signal: AbortSignal.timeout(5000),
110
+ })
111
+
112
+ if (!resp.ok) return "Failed to add memory โ€” hub returned error."
113
+ const data = await resp.json() as { ok?: boolean; id?: string }
114
+ return data.ok
115
+ ? `Memory added: "${title}" (${type ?? "note"})`
116
+ : "Memory add returned unexpected response."
117
+ } catch {
118
+ return "Memory add unavailable โ€” Context Hub may not be running."
119
+ }
120
+ },
121
+ })
122
+
123
+ // โ”€โ”€โ”€ jfl_memory_status โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
124
+ ctx.registerTool({
125
+ name: "jfl_memory_status",
126
+ description: "Get memory system statistics and health โ€” total entries, indexed count, embedding status.",
127
+ promptSnippet: "Check memory system health and stats",
128
+ inputSchema: {
129
+ type: "object",
130
+ properties: {},
131
+ },
132
+ async handler() {
133
+ try {
134
+ const resp = await fetch(`${hubUrl}/api/memory/status`, {
135
+ method: "GET",
136
+ headers: {
137
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
138
+ },
139
+ signal: AbortSignal.timeout(5000),
140
+ })
141
+
142
+ if (!resp.ok) return "Memory status unavailable."
143
+ const data = await resp.json() as Record<string, unknown>
144
+
145
+ return Object.entries(data)
146
+ .map(([k, v]) => `${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`)
147
+ .join("\n")
148
+ } catch {
149
+ return "Memory status unavailable โ€” Context Hub may not be running."
150
+ }
151
+ },
152
+ })
73
153
  }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Service Skills Bridge
3
+ *
4
+ * Scans .claude/skills/ for service-type skills (generated during jfl init
5
+ * service onboarding) and registers them as Pi commands + tools.
6
+ * This gives Pi parity with Claude Code's service agent access.
7
+ *
8
+ * Service skills in .claude/skills/ have frontmatter `type: service` and
9
+ * include commands like status, logs, start, stop, restart, health.
10
+ *
11
+ * @purpose Bridge .claude/skills/ service agents into Pi sessions
12
+ */
13
+
14
+ import { existsSync, readdirSync, readFileSync } from "fs"
15
+ import { join } from "path"
16
+ import { execSync } from "child_process"
17
+ import type { PiContext, JflConfig } from "./types.js"
18
+
19
+ interface ServiceSkill {
20
+ name: string
21
+ description: string
22
+ servicePath: string
23
+ commands: string[]
24
+ }
25
+
26
+ function parseFrontmatter(content: string): Record<string, string> {
27
+ const match = content.match(/^---\n([\s\S]*?)\n---/)
28
+ if (!match) return {}
29
+ const fm: Record<string, string> = {}
30
+ for (const line of match[1].split("\n")) {
31
+ const [key, ...rest] = line.split(":")
32
+ if (key && rest.length) {
33
+ fm[key.trim()] = rest.join(":").trim()
34
+ }
35
+ }
36
+ return fm
37
+ }
38
+
39
+ function discoverServiceSkills(projectRoot: string): ServiceSkill[] {
40
+ const claudeSkillsDir = join(projectRoot, ".claude", "skills")
41
+ if (!existsSync(claudeSkillsDir)) return []
42
+
43
+ const skills: ServiceSkill[] = []
44
+
45
+ try {
46
+ const entries = readdirSync(claudeSkillsDir, { withFileTypes: true })
47
+
48
+ for (const entry of entries) {
49
+ if (!entry.isDirectory()) continue
50
+
51
+ const skillPath = join(claudeSkillsDir, entry.name, "SKILL.md")
52
+ if (!existsSync(skillPath)) continue
53
+
54
+ try {
55
+ const content = readFileSync(skillPath, "utf-8")
56
+ const fm = parseFrontmatter(content)
57
+
58
+ if (fm.type !== "service") continue
59
+
60
+ // Extract available commands from the markdown table
61
+ const commands: string[] = []
62
+ const cmdMatch = content.match(/\| `(\w+)` \|/g)
63
+ if (cmdMatch) {
64
+ for (const m of cmdMatch) {
65
+ const cmd = m.match(/`(\w+)`/)
66
+ if (cmd) commands.push(cmd[1])
67
+ }
68
+ }
69
+ // Fallback defaults
70
+ if (commands.length === 0) {
71
+ commands.push("status", "logs", "start", "stop", "restart", "health", "recent")
72
+ }
73
+
74
+ skills.push({
75
+ name: entry.name,
76
+ description: fm.description || `${entry.name} service agent`,
77
+ servicePath: fm.service_path || "",
78
+ commands,
79
+ })
80
+ } catch {
81
+ // Skip unparseable skills
82
+ }
83
+ }
84
+ } catch {
85
+ // .claude/skills not readable
86
+ }
87
+
88
+ return skills
89
+ }
90
+
91
+ function runServiceCommand(serviceName: string, command: string, servicePath: string, projectRoot: string): string {
92
+ // Try jfl service-agent first
93
+ try {
94
+ const result = execSync(
95
+ `jfl service-agent run ${serviceName} ${command}`,
96
+ { cwd: projectRoot, encoding: "utf-8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }
97
+ )
98
+ return result.trim()
99
+ } catch {
100
+ // Fallback: direct execution based on command type
101
+ }
102
+
103
+ if (!servicePath || !existsSync(servicePath)) {
104
+ return `Service path not found: ${servicePath}`
105
+ }
106
+
107
+ try {
108
+ switch (command) {
109
+ case "status": {
110
+ const branch = execSync("git branch --show-current", { cwd: servicePath, encoding: "utf-8" }).trim()
111
+ const lastCommit = execSync("git log -1 --pretty='%h %s (%cr)'", { cwd: servicePath, encoding: "utf-8" }).trim()
112
+ const dirty = execSync("git status --porcelain", { cwd: servicePath, encoding: "utf-8" }).trim()
113
+ return [
114
+ `Service: ${serviceName}`,
115
+ `Path: ${servicePath}`,
116
+ `Branch: ${branch}`,
117
+ `Last commit: ${lastCommit}`,
118
+ `Working tree: ${dirty ? `${dirty.split("\n").length} changes` : "clean"}`,
119
+ ].join("\n")
120
+ }
121
+
122
+ case "logs": {
123
+ return execSync("git log --oneline -10", { cwd: servicePath, encoding: "utf-8" }).trim()
124
+ }
125
+
126
+ case "recent": {
127
+ const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
128
+ try {
129
+ return execSync(`git log --oneline --since="${since}"`, { cwd: servicePath, encoding: "utf-8" }).trim() || "No changes in last 24h"
130
+ } catch {
131
+ return "No changes in last 24h"
132
+ }
133
+ }
134
+
135
+ case "health": {
136
+ // Check if package.json exists and has a test script
137
+ const pkgPath = join(servicePath, "package.json")
138
+ if (existsSync(pkgPath)) {
139
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
140
+ const hasTests = !!pkg.scripts?.test
141
+ const hasBuild = !!pkg.scripts?.build
142
+ return [
143
+ `Service: ${serviceName}`,
144
+ `Has tests: ${hasTests ? "yes" : "no"}`,
145
+ `Has build: ${hasBuild ? "yes" : "no"}`,
146
+ `Dependencies: ${Object.keys(pkg.dependencies || {}).length}`,
147
+ `Dev dependencies: ${Object.keys(pkg.devDependencies || {}).length}`,
148
+ ].join("\n")
149
+ }
150
+ return `No package.json found at ${servicePath}`
151
+ }
152
+
153
+ default:
154
+ return `Unknown command: ${command}. Available: status, logs, recent, health`
155
+ }
156
+ } catch (err) {
157
+ return `Error running ${command} on ${serviceName}: ${err}`
158
+ }
159
+ }
160
+
161
+ export async function setupServiceSkills(ctx: PiContext, _config: JflConfig): Promise<void> {
162
+ const root = ctx.session.projectRoot
163
+ const services = discoverServiceSkills(root)
164
+
165
+ if (services.length === 0) return
166
+
167
+ ctx.log(`Discovered ${services.length} service skill(s): ${services.map(s => s.name).join(", ")}`, "debug")
168
+
169
+ // Register a unified service tool
170
+ ctx.registerTool({
171
+ name: "jfl_service",
172
+ description: `Query registered service agents. Available services: ${services.map(s => s.name).join(", ")}. Commands: status, logs, recent, health, start, stop, restart.`,
173
+ promptSnippet: `Service agents: ${services.map(s => s.name).join(", ")}`,
174
+ promptGuidelines: [
175
+ `Use this tool to interact with registered services: ${services.map(s => s.name).join(", ")}`,
176
+ "Common commands: status (git info), logs (recent commits), recent (24h changes), health (package info)",
177
+ ],
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ service: {
182
+ type: "string",
183
+ description: `Service name: ${services.map(s => s.name).join(", ")}`,
184
+ enum: services.map(s => s.name),
185
+ },
186
+ command: {
187
+ type: "string",
188
+ description: "Command to run on the service",
189
+ enum: ["status", "logs", "recent", "health", "start", "stop", "restart"],
190
+ },
191
+ },
192
+ required: ["service", "command"],
193
+ },
194
+ async handler(input) {
195
+ const { service, command } = input as { service: string; command: string }
196
+ const svc = services.find(s => s.name === service)
197
+ if (!svc) return `Unknown service: ${service}. Available: ${services.map(s => s.name).join(", ")}`
198
+ return runServiceCommand(service, command, svc.servicePath, root)
199
+ },
200
+ })
201
+
202
+ // Register per-service commands (e.g., /jfl-cli status, /jfl-platform logs)
203
+ for (const svc of services) {
204
+ ctx.registerCommand({
205
+ name: svc.name,
206
+ description: `${svc.description} โ€” commands: ${svc.commands.join(", ")}`,
207
+ async handler(args: string, _ctx: PiContext) {
208
+ const command = args.trim().split(/\s+/)[0] || "status"
209
+ const result = runServiceCommand(svc.name, command, svc.servicePath, root)
210
+ ctx.ui.notify(result, { level: "info" })
211
+ },
212
+ })
213
+ }
214
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Physical-World Telemetry Dashboard
4
+ # Quick overview of telemetry monitoring capabilities
5
+
6
+ set -euo pipefail
7
+
8
+ echo "๐ŸŒ Physical-World Telemetry Dashboard"
9
+ echo "====================================="
10
+ echo
11
+
12
+ # Check if telemetry is enabled
13
+ echo "๐Ÿ“Š Telemetry Status:"
14
+ jfl telemetry status
15
+ echo
16
+
17
+ # Generate some sample data if none exists
18
+ echo "๐Ÿงช Generating sample data..."
19
+ jfl telemetry test --count 3 --interval 100 --categories "command,performance"
20
+ echo
21
+
22
+ # Show current snapshot
23
+ echo "๐Ÿ“ˆ Current Physical-World Metrics:"
24
+ jfl telemetry monitor --compact
25
+ echo
26
+
27
+ echo "๐Ÿ” Available Monitoring Options:"
28
+ echo " jfl telemetry monitor --live # Real-time updates"
29
+ echo " jfl telemetry monitor --filter cpu # Filter for CPU-related events"
30
+ echo " jfl telemetry monitor --compact # Compact display"
31
+ echo
32
+
33
+ echo "๐Ÿ’ก Physical-World Data Captured:"
34
+ echo " โ€ข System: CPU usage, memory, thermal state, system load"
35
+ echo " โ€ข Hardware: battery level, disk space, sensors"
36
+ echo " โ€ข Workflow: git branch, dirty files, terminal count, IDE"
37
+ echo " โ€ข Network: latency, connectivity, local dev servers"
38
+ echo " โ€ข Performance: build times, test durations, command timing"
39
+ echo
40
+
41
+ echo "๐ŸŽฏ Competitive Advantage:"
42
+ echo " This data is impossible for web UIs to access!"
43
+ echo " You own the edge - local machine context during live operations."
44
+ echo