jfl 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/clawdbot-skill/README.md +328 -0
- package/clawdbot-skill/SKILL.md +362 -0
- package/clawdbot-skill/index.ts +486 -0
- package/clawdbot-skill/package.json +28 -0
- package/clawdbot-skill/skill.json +28 -0
- package/dist/commands/agents.d.ts +5 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +399 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/context-hub.d.ts +12 -0
- package/dist/commands/context-hub.d.ts.map +1 -0
- package/dist/commands/context-hub.js +642 -0
- package/dist/commands/context-hub.js.map +1 -0
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +370 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/feedback.d.ts +2 -0
- package/dist/commands/feedback.d.ts.map +1 -0
- package/dist/commands/feedback.js +178 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/hud.d.ts +4 -0
- package/dist/commands/hud.d.ts.map +1 -0
- package/dist/commands/hud.js +262 -0
- package/dist/commands/hud.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +553 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +23 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +818 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/ralph.d.ts +9 -0
- package/dist/commands/ralph.d.ts.map +1 -0
- package/dist/commands/ralph.js +67 -0
- package/dist/commands/ralph.js.map +1 -0
- package/dist/commands/repair.d.ts +7 -0
- package/dist/commands/repair.d.ts.map +1 -0
- package/dist/commands/repair.js +283 -0
- package/dist/commands/repair.js.map +1 -0
- package/dist/commands/session-mgmt.d.ts +33 -0
- package/dist/commands/session-mgmt.d.ts.map +1 -0
- package/dist/commands/session-mgmt.js +404 -0
- package/dist/commands/session-mgmt.js.map +1 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +639 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/skills.d.ts +31 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +314 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +127 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/synopsis.d.ts +10 -0
- package/dist/commands/synopsis.d.ts.map +1 -0
- package/dist/commands/synopsis.js +277 -0
- package/dist/commands/synopsis.js.map +1 -0
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +165 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/voice.d.ts +410 -0
- package/dist/commands/voice.d.ts.map +1 -0
- package/dist/commands/voice.js +4763 -0
- package/dist/commands/voice.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +512 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/context-hub-mcp.d.ts +11 -0
- package/dist/mcp/context-hub-mcp.d.ts.map +1 -0
- package/dist/mcp/context-hub-mcp.js +548 -0
- package/dist/mcp/context-hub-mcp.js.map +1 -0
- package/dist/telegram/voice.d.ts +146 -0
- package/dist/telegram/voice.d.ts.map +1 -0
- package/dist/telegram/voice.js +351 -0
- package/dist/telegram/voice.js.map +1 -0
- package/dist/types/skills.d.ts +44 -0
- package/dist/types/skills.d.ts.map +1 -0
- package/dist/types/skills.js +5 -0
- package/dist/types/skills.js.map +1 -0
- package/dist/ui/banner.d.ts +18 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +323 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/prompts.d.ts +52 -0
- package/dist/ui/prompts.d.ts.map +1 -0
- package/dist/ui/prompts.js +72 -0
- package/dist/ui/prompts.js.map +1 -0
- package/dist/ui/theme.d.ts +82 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +142 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/utils/auth-guard.d.ts +66 -0
- package/dist/utils/auth-guard.d.ts.map +1 -0
- package/dist/utils/auth-guard.js +348 -0
- package/dist/utils/auth-guard.js.map +1 -0
- package/dist/utils/ensure-project.d.ts +11 -0
- package/dist/utils/ensure-project.d.ts.map +1 -0
- package/dist/utils/ensure-project.js +70 -0
- package/dist/utils/ensure-project.js.map +1 -0
- package/dist/utils/git.d.ts +73 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +219 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/github-auth.d.ts +54 -0
- package/dist/utils/github-auth.d.ts.map +1 -0
- package/dist/utils/github-auth.js +375 -0
- package/dist/utils/github-auth.js.map +1 -0
- package/dist/utils/github-repo.d.ts +30 -0
- package/dist/utils/github-repo.d.ts.map +1 -0
- package/dist/utils/github-repo.js +219 -0
- package/dist/utils/github-repo.js.map +1 -0
- package/dist/utils/platform-auth.d.ts +81 -0
- package/dist/utils/platform-auth.d.ts.map +1 -0
- package/dist/utils/platform-auth.js +191 -0
- package/dist/utils/platform-auth.js.map +1 -0
- package/dist/utils/project-config.d.ts +43 -0
- package/dist/utils/project-config.d.ts.map +1 -0
- package/dist/utils/project-config.js +97 -0
- package/dist/utils/project-config.js.map +1 -0
- package/dist/utils/skill-registry.d.ts +49 -0
- package/dist/utils/skill-registry.d.ts.map +1 -0
- package/dist/utils/skill-registry.js +192 -0
- package/dist/utils/skill-registry.js.map +1 -0
- package/dist/utils/wallet.d.ts +62 -0
- package/dist/utils/wallet.d.ts.map +1 -0
- package/dist/utils/wallet.js +252 -0
- package/dist/utils/wallet.js.map +1 -0
- package/dist/utils/x402-client.d.ts +86 -0
- package/dist/utils/x402-client.d.ts.map +1 -0
- package/dist/utils/x402-client.js +265 -0
- package/dist/utils/x402-client.js.map +1 -0
- package/package.json +76 -0
- package/scripts/postinstall.js +116 -0
- package/scripts/test-onboarding.sh +121 -0
- package/scripts/voice-start.sh +128 -0
- package/scripts/voice-stop.sh +33 -0
- package/template/.claude/settings.json +92 -0
- package/template/.claude/skills/agent-browser/SKILL.md +116 -0
- package/template/.claude/skills/brand-architect/SKILL.md +240 -0
- package/template/.claude/skills/brand-architect/config.yaml +137 -0
- package/template/.claude/skills/campaign-hud/config.yaml +112 -0
- package/template/.claude/skills/content-creator/SKILL.md +294 -0
- package/template/.claude/skills/debug/MULTI_AGENT.md +360 -0
- package/template/.claude/skills/debug/SKILL.md +549 -0
- package/template/.claude/skills/fly-deploy/SKILL.md +676 -0
- package/template/.claude/skills/founder-video/SKILL.md +467 -0
- package/template/.claude/skills/hud/SKILL.md +157 -0
- package/template/.claude/skills/ralph-tui/SKILL.md +210 -0
- package/template/.claude/skills/react-best-practices/AGENTS.md +2249 -0
- package/template/.claude/skills/react-best-practices/README.md +123 -0
- package/template/.claude/skills/react-best-practices/SKILL.md +125 -0
- package/template/.claude/skills/react-best-practices/metadata.json +15 -0
- package/template/.claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/template/.claude/skills/react-best-practices/rules/_template.md +28 -0
- package/template/.claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/template/.claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/template/.claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/template/.claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/template/.claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/template/.claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/template/.claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/template/.claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/template/.claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/template/.claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/template/.claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/template/.claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/template/.claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/template/.claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/template/.claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/template/.claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/template/.claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/template/.claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/template/.claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/template/.claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/template/.claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/template/.claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/template/.claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/template/.claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/template/.claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/template/.claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/template/.claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/template/.claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/template/.claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/template/.claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/template/.claude/skills/react-best-practices/rules/server-cache-react.md +26 -0
- package/template/.claude/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/template/.claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/template/.claude/skills/remotion-best-practices/SKILL.md +43 -0
- package/template/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
- package/template/.claude/skills/remotion-best-practices/rules/animations.md +29 -0
- package/template/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/template/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/template/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/template/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
- package/template/.claude/skills/remotion-best-practices/rules/audio.md +172 -0
- package/template/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
- package/template/.claude/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/template/.claude/skills/remotion-best-practices/rules/charts.md +58 -0
- package/template/.claude/skills/remotion-best-practices/rules/compositions.md +146 -0
- package/template/.claude/skills/remotion-best-practices/rules/display-captions.md +126 -0
- package/template/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/template/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/template/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/template/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/template/.claude/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
- package/template/.claude/skills/remotion-best-practices/rules/gifs.md +138 -0
- package/template/.claude/skills/remotion-best-practices/rules/images.md +130 -0
- package/template/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
- package/template/.claude/skills/remotion-best-practices/rules/lottie.md +68 -0
- package/template/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
- package/template/.claude/skills/remotion-best-practices/rules/measuring-text.md +143 -0
- package/template/.claude/skills/remotion-best-practices/rules/sequencing.md +106 -0
- package/template/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/template/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/template/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
- package/template/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
- package/template/.claude/skills/remotion-best-practices/rules/transitions.md +122 -0
- package/template/.claude/skills/remotion-best-practices/rules/trimming.md +53 -0
- package/template/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
- package/template/.claude/skills/search/SKILL.md +220 -0
- package/template/.claude/skills/spec/SKILL.md +377 -0
- package/template/.claude/skills/startup/SKILL.md +310 -0
- package/template/.claude/skills/web-architect/SKILL.md +309 -0
- package/template/.claude/skills/x-algorithm/SKILL.md +305 -0
- package/template/.jfl/config.json +8 -0
- package/template/.mcp.json +11 -0
- package/template/CLAUDE.md +960 -0
- package/template/content/.gitkeep +0 -0
- package/template/context-hub +3 -0
- package/template/knowledge/BRAND_BRIEF.md +124 -0
- package/template/knowledge/BRAND_DECISIONS.md +168 -0
- package/template/knowledge/NARRATIVE.md +114 -0
- package/template/knowledge/ROADMAP.md +128 -0
- package/template/knowledge/THESIS.md +108 -0
- package/template/knowledge/VISION.md +74 -0
- package/template/knowledge/VOICE_AND_TONE.md +146 -0
- package/template/previews/.gitkeep +0 -0
- package/template/scripts/session/auto-commit.sh +245 -0
- package/template/scripts/session/auto-merge.sh +325 -0
- package/template/scripts/session/jfl-doctor.sh +587 -0
- package/template/scripts/session/session-end.sh +194 -0
- package/template/scripts/session/session-init.sh +163 -0
- package/template/scripts/session/session-sync.sh +167 -0
- package/template/scripts/session/test-context-preservation.sh +160 -0
- package/template/skills/agent-browser/SKILL.md +116 -0
- package/template/skills/brand-architect/SKILL.md +240 -0
- package/template/skills/brand-architect/config.yaml +137 -0
- package/template/skills/campaign-hud/config.yaml +112 -0
- package/template/skills/content-creator/SKILL.md +294 -0
- package/template/skills/debug/MULTI_AGENT.md +360 -0
- package/template/skills/debug/SKILL.md +549 -0
- package/template/skills/fly-deploy/SKILL.md +676 -0
- package/template/skills/founder-video/SKILL.md +467 -0
- package/template/skills/hud/SKILL.md +204 -0
- package/template/skills/ralph-tui/SKILL.md +210 -0
- package/template/skills/react-best-practices/AGENTS.md +2249 -0
- package/template/skills/react-best-practices/README.md +123 -0
- package/template/skills/react-best-practices/SKILL.md +125 -0
- package/template/skills/react-best-practices/metadata.json +15 -0
- package/template/skills/react-best-practices/rules/_sections.md +46 -0
- package/template/skills/react-best-practices/rules/_template.md +28 -0
- package/template/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/template/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/template/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/template/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/template/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/template/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/template/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/template/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/template/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/template/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/template/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/template/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/template/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/template/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/template/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/template/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/template/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/template/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/template/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/template/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/template/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/template/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/template/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/template/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/template/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/template/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/template/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/template/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/template/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/template/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/template/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/template/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/template/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/template/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/template/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/template/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/template/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/template/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/template/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/template/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/template/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/template/skills/react-best-practices/rules/server-cache-react.md +26 -0
- package/template/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/template/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/template/skills/remotion-best-practices/SKILL.md +43 -0
- package/template/skills/remotion-best-practices/rules/3d.md +86 -0
- package/template/skills/remotion-best-practices/rules/animations.md +29 -0
- package/template/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/template/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/template/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/template/skills/remotion-best-practices/rules/assets.md +78 -0
- package/template/skills/remotion-best-practices/rules/audio.md +172 -0
- package/template/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
- package/template/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/template/skills/remotion-best-practices/rules/charts.md +58 -0
- package/template/skills/remotion-best-practices/rules/compositions.md +146 -0
- package/template/skills/remotion-best-practices/rules/display-captions.md +126 -0
- package/template/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/template/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/template/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/template/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/template/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
- package/template/skills/remotion-best-practices/rules/gifs.md +138 -0
- package/template/skills/remotion-best-practices/rules/images.md +130 -0
- package/template/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
- package/template/skills/remotion-best-practices/rules/lottie.md +68 -0
- package/template/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
- package/template/skills/remotion-best-practices/rules/measuring-text.md +143 -0
- package/template/skills/remotion-best-practices/rules/sequencing.md +106 -0
- package/template/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/template/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/template/skills/remotion-best-practices/rules/timing.md +179 -0
- package/template/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
- package/template/skills/remotion-best-practices/rules/transitions.md +122 -0
- package/template/skills/remotion-best-practices/rules/trimming.md +53 -0
- package/template/skills/remotion-best-practices/rules/videos.md +171 -0
- package/template/skills/search/SKILL.md +220 -0
- package/template/skills/spec/SKILL.md +377 -0
- package/template/skills/startup/SKILL.md +310 -0
- package/template/skills/web-architect/SKILL.md +309 -0
- package/template/skills/x-algorithm/SKILL.md +305 -0
- package/template/suggestions/.gitkeep +0 -0
- package/template/templates/QUICKSTART_SKILL_TO_PRODUCT.md +242 -0
- package/template/templates/brand/BRAND_BRIEF.md +124 -0
- package/template/templates/brand/BRAND_DECISIONS.md +168 -0
- package/template/templates/brand/BRAND_GUIDELINES.md +251 -0
- package/template/templates/brand/VOICE_AND_TONE.md +146 -0
- package/template/templates/brand/global.css +240 -0
- package/template/templates/collaboration/CONTRIBUTOR.md +74 -0
- package/template/templates/collaboration/CRM.md +97 -0
- package/template/templates/collaboration/TASKS.md +83 -0
- package/template/templates/strategic/NARRATIVE.md +114 -0
- package/template/templates/strategic/ROADMAP.md +128 -0
- package/template/templates/strategic/THESIS.md +108 -0
- package/template/templates/strategic/VISION.md +74 -0
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jfl context-hub - Unified context layer for AI agents
|
|
3
|
+
*
|
|
4
|
+
* Provides context from journal, knowledge docs, and code to any AI.
|
|
5
|
+
* Works locally (CLI) and hosted (platform).
|
|
6
|
+
*
|
|
7
|
+
* @purpose CLI command for Context Hub daemon management
|
|
8
|
+
*/
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import ora from "ora";
|
|
11
|
+
import { spawn } from "child_process";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
import * as http from "http";
|
|
15
|
+
const DEFAULT_PORT = 4242;
|
|
16
|
+
const PID_FILE = ".jfl/context-hub.pid";
|
|
17
|
+
const LOG_FILE = ".jfl/logs/context-hub.log";
|
|
18
|
+
const TOKEN_FILE = ".jfl/context-hub.token";
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Security
|
|
21
|
+
// ============================================================================
|
|
22
|
+
function generateToken() {
|
|
23
|
+
return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
24
|
+
}
|
|
25
|
+
function getTokenFile(projectRoot) {
|
|
26
|
+
return path.join(projectRoot, TOKEN_FILE);
|
|
27
|
+
}
|
|
28
|
+
function getOrCreateToken(projectRoot) {
|
|
29
|
+
const tokenFile = getTokenFile(projectRoot);
|
|
30
|
+
if (fs.existsSync(tokenFile)) {
|
|
31
|
+
return fs.readFileSync(tokenFile, 'utf-8').trim();
|
|
32
|
+
}
|
|
33
|
+
const token = generateToken();
|
|
34
|
+
fs.writeFileSync(tokenFile, token, { mode: 0o600 }); // Owner read/write only
|
|
35
|
+
return token;
|
|
36
|
+
}
|
|
37
|
+
function validateAuth(req, projectRoot) {
|
|
38
|
+
const tokenFile = getTokenFile(projectRoot);
|
|
39
|
+
// If no token file exists, allow access (backwards compatibility during migration)
|
|
40
|
+
if (!fs.existsSync(tokenFile)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const expectedToken = fs.readFileSync(tokenFile, 'utf-8').trim();
|
|
44
|
+
const authHeader = req.headers['authorization'];
|
|
45
|
+
if (!authHeader) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
// Support "Bearer <token>" format
|
|
49
|
+
const providedToken = authHeader.startsWith('Bearer ')
|
|
50
|
+
? authHeader.slice(7)
|
|
51
|
+
: authHeader;
|
|
52
|
+
return providedToken === expectedToken;
|
|
53
|
+
}
|
|
54
|
+
function isPortInUse(port) {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
const server = http.createServer();
|
|
57
|
+
server.once('error', (err) => {
|
|
58
|
+
if (err.code === 'EADDRINUSE') {
|
|
59
|
+
resolve(true);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
resolve(false);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
server.once('listening', () => {
|
|
66
|
+
server.close();
|
|
67
|
+
resolve(false);
|
|
68
|
+
});
|
|
69
|
+
server.listen(port);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Journal Reader
|
|
74
|
+
// ============================================================================
|
|
75
|
+
function readJournalEntries(projectRoot, limit = 20) {
|
|
76
|
+
const journalDir = path.join(projectRoot, ".jfl", "journal");
|
|
77
|
+
const items = [];
|
|
78
|
+
if (!fs.existsSync(journalDir)) {
|
|
79
|
+
return items;
|
|
80
|
+
}
|
|
81
|
+
const files = fs.readdirSync(journalDir)
|
|
82
|
+
.filter(f => f.endsWith(".jsonl"))
|
|
83
|
+
.sort()
|
|
84
|
+
.reverse();
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
if (items.length >= limit)
|
|
87
|
+
break;
|
|
88
|
+
const filePath = path.join(journalDir, file);
|
|
89
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
90
|
+
const lines = content.trim().split("\n").filter(l => l.trim());
|
|
91
|
+
for (const line of lines.reverse()) {
|
|
92
|
+
if (items.length >= limit)
|
|
93
|
+
break;
|
|
94
|
+
try {
|
|
95
|
+
const entry = JSON.parse(line);
|
|
96
|
+
items.push({
|
|
97
|
+
source: "journal",
|
|
98
|
+
type: entry.type || "entry",
|
|
99
|
+
title: entry.title || "Untitled",
|
|
100
|
+
content: entry.summary || entry.detail || "",
|
|
101
|
+
timestamp: entry.ts,
|
|
102
|
+
path: filePath
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Skip malformed lines
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return items;
|
|
111
|
+
}
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// Knowledge Reader
|
|
114
|
+
// ============================================================================
|
|
115
|
+
function readKnowledgeDocs(projectRoot) {
|
|
116
|
+
const knowledgeDir = path.join(projectRoot, "knowledge");
|
|
117
|
+
const items = [];
|
|
118
|
+
if (!fs.existsSync(knowledgeDir)) {
|
|
119
|
+
return items;
|
|
120
|
+
}
|
|
121
|
+
const priorityFiles = [
|
|
122
|
+
"VISION.md",
|
|
123
|
+
"ROADMAP.md",
|
|
124
|
+
"NARRATIVE.md",
|
|
125
|
+
"THESIS.md",
|
|
126
|
+
"BRAND_DECISIONS.md",
|
|
127
|
+
"TASKS.md"
|
|
128
|
+
];
|
|
129
|
+
for (const filename of priorityFiles) {
|
|
130
|
+
const filePath = path.join(knowledgeDir, filename);
|
|
131
|
+
if (fs.existsSync(filePath)) {
|
|
132
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
133
|
+
const title = filename.replace(".md", "").replace(/_/g, " ");
|
|
134
|
+
items.push({
|
|
135
|
+
source: "knowledge",
|
|
136
|
+
type: "doc",
|
|
137
|
+
title,
|
|
138
|
+
content: content.slice(0, 2000), // Truncate for context
|
|
139
|
+
path: filePath
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return items;
|
|
144
|
+
}
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Code Context Reader
|
|
147
|
+
// ============================================================================
|
|
148
|
+
function extractPurpose(content) {
|
|
149
|
+
const match = content.match(/@purpose\s+(.+?)(?:\n|\*)/i);
|
|
150
|
+
return match ? match[1].trim() : null;
|
|
151
|
+
}
|
|
152
|
+
function readCodeContext(projectRoot, limit = 30) {
|
|
153
|
+
const items = [];
|
|
154
|
+
// Look for files with @purpose in common locations
|
|
155
|
+
const searchDirs = [
|
|
156
|
+
path.join(projectRoot, "src"),
|
|
157
|
+
path.join(projectRoot, "app"),
|
|
158
|
+
path.join(projectRoot, "lib"),
|
|
159
|
+
path.join(projectRoot, "components"),
|
|
160
|
+
path.join(projectRoot, "product", "src"),
|
|
161
|
+
path.join(projectRoot, "product", "packages")
|
|
162
|
+
];
|
|
163
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
164
|
+
function scanDir(dir, depth = 0) {
|
|
165
|
+
if (depth > 4 || items.length >= limit)
|
|
166
|
+
return;
|
|
167
|
+
if (!fs.existsSync(dir))
|
|
168
|
+
return;
|
|
169
|
+
try {
|
|
170
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (items.length >= limit)
|
|
173
|
+
break;
|
|
174
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
175
|
+
continue;
|
|
176
|
+
const fullPath = path.join(dir, entry.name);
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
scanDir(fullPath, depth + 1);
|
|
179
|
+
}
|
|
180
|
+
else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
181
|
+
try {
|
|
182
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
183
|
+
const purpose = extractPurpose(content);
|
|
184
|
+
if (purpose) {
|
|
185
|
+
items.push({
|
|
186
|
+
source: "code",
|
|
187
|
+
type: "file",
|
|
188
|
+
title: entry.name,
|
|
189
|
+
content: purpose,
|
|
190
|
+
path: fullPath
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Skip unreadable files
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Skip unreadable directories
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const dir of searchDirs) {
|
|
205
|
+
scanDir(dir);
|
|
206
|
+
}
|
|
207
|
+
return items;
|
|
208
|
+
}
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// Search & Scoring (TF-IDF style)
|
|
211
|
+
// ============================================================================
|
|
212
|
+
function tokenize(text) {
|
|
213
|
+
return text
|
|
214
|
+
.toLowerCase()
|
|
215
|
+
.replace(/[^\w\s]/g, " ")
|
|
216
|
+
.split(/\s+/)
|
|
217
|
+
.filter(t => t.length > 2);
|
|
218
|
+
}
|
|
219
|
+
function computeTF(tokens) {
|
|
220
|
+
const tf = new Map();
|
|
221
|
+
for (const token of tokens) {
|
|
222
|
+
tf.set(token, (tf.get(token) || 0) + 1);
|
|
223
|
+
}
|
|
224
|
+
// Normalize by document length
|
|
225
|
+
for (const [term, count] of tf) {
|
|
226
|
+
tf.set(term, count / tokens.length);
|
|
227
|
+
}
|
|
228
|
+
return tf;
|
|
229
|
+
}
|
|
230
|
+
function computeIDF(documents) {
|
|
231
|
+
const idf = new Map();
|
|
232
|
+
const N = documents.length;
|
|
233
|
+
// Count documents containing each term
|
|
234
|
+
const docCount = new Map();
|
|
235
|
+
for (const doc of documents) {
|
|
236
|
+
const uniqueTerms = new Set(doc);
|
|
237
|
+
for (const term of uniqueTerms) {
|
|
238
|
+
docCount.set(term, (docCount.get(term) || 0) + 1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Compute IDF
|
|
242
|
+
for (const [term, count] of docCount) {
|
|
243
|
+
idf.set(term, Math.log((N + 1) / (count + 1)) + 1);
|
|
244
|
+
}
|
|
245
|
+
return idf;
|
|
246
|
+
}
|
|
247
|
+
function scoreItem(item, queryTokens, idf) {
|
|
248
|
+
const text = `${item.title} ${item.content}`;
|
|
249
|
+
const tokens = tokenize(text);
|
|
250
|
+
const tf = computeTF(tokens);
|
|
251
|
+
let score = 0;
|
|
252
|
+
for (const queryTerm of queryTokens) {
|
|
253
|
+
const termTF = tf.get(queryTerm) || 0;
|
|
254
|
+
const termIDF = idf.get(queryTerm) || 1;
|
|
255
|
+
score += termTF * termIDF;
|
|
256
|
+
}
|
|
257
|
+
// Boost title matches
|
|
258
|
+
const titleTokens = new Set(tokenize(item.title));
|
|
259
|
+
for (const queryTerm of queryTokens) {
|
|
260
|
+
if (titleTokens.has(queryTerm)) {
|
|
261
|
+
score *= 1.5;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Boost recent items (journal)
|
|
265
|
+
if (item.source === "journal" && item.timestamp) {
|
|
266
|
+
const age = Date.now() - new Date(item.timestamp).getTime();
|
|
267
|
+
const daysSinceUpdate = age / (1000 * 60 * 60 * 24);
|
|
268
|
+
if (daysSinceUpdate < 7) {
|
|
269
|
+
score *= 1.3; // Boost recent entries
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return score;
|
|
273
|
+
}
|
|
274
|
+
function semanticSearch(items, query) {
|
|
275
|
+
const queryTokens = tokenize(query);
|
|
276
|
+
if (queryTokens.length === 0)
|
|
277
|
+
return items;
|
|
278
|
+
// Build corpus for IDF
|
|
279
|
+
const documents = items.map(item => tokenize(`${item.title} ${item.content}`));
|
|
280
|
+
const idf = computeIDF(documents);
|
|
281
|
+
// Score and sort
|
|
282
|
+
for (const item of items) {
|
|
283
|
+
item.relevance = scoreItem(item, queryTokens, idf);
|
|
284
|
+
}
|
|
285
|
+
return items
|
|
286
|
+
.filter(item => (item.relevance || 0) > 0)
|
|
287
|
+
.sort((a, b) => (b.relevance || 0) - (a.relevance || 0));
|
|
288
|
+
}
|
|
289
|
+
// ============================================================================
|
|
290
|
+
// Orchestrator
|
|
291
|
+
// ============================================================================
|
|
292
|
+
function getUnifiedContext(projectRoot, query, taskType) {
|
|
293
|
+
const journalItems = readJournalEntries(projectRoot);
|
|
294
|
+
const knowledgeItems = readKnowledgeDocs(projectRoot);
|
|
295
|
+
const codeItems = readCodeContext(projectRoot);
|
|
296
|
+
let items = [...journalItems, ...knowledgeItems, ...codeItems];
|
|
297
|
+
// Apply semantic search if query provided
|
|
298
|
+
if (query) {
|
|
299
|
+
items = semanticSearch(items, query);
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
items,
|
|
303
|
+
sources: {
|
|
304
|
+
journal: journalItems.length > 0,
|
|
305
|
+
knowledge: knowledgeItems.length > 0,
|
|
306
|
+
code: codeItems.length > 0,
|
|
307
|
+
memory: false
|
|
308
|
+
},
|
|
309
|
+
query,
|
|
310
|
+
taskType
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// HTTP Server
|
|
315
|
+
// ============================================================================
|
|
316
|
+
function createServer(projectRoot, port) {
|
|
317
|
+
const server = http.createServer((req, res) => {
|
|
318
|
+
// CORS - include Authorization header
|
|
319
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
320
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
321
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
322
|
+
if (req.method === "OPTIONS") {
|
|
323
|
+
res.writeHead(200);
|
|
324
|
+
res.end();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
328
|
+
// Health check - no auth required (for monitoring)
|
|
329
|
+
if (url.pathname === "/health" && req.method === "GET") {
|
|
330
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
331
|
+
res.end(JSON.stringify({ status: "ok", port }));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// All other endpoints require auth
|
|
335
|
+
if (!validateAuth(req, projectRoot)) {
|
|
336
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
337
|
+
res.end(JSON.stringify({
|
|
338
|
+
error: "Unauthorized",
|
|
339
|
+
message: "Provide token via Authorization header: Bearer <token>",
|
|
340
|
+
tokenFile: ".jfl/context-hub.token"
|
|
341
|
+
}));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Status
|
|
345
|
+
if (url.pathname === "/api/context/status" && req.method === "GET") {
|
|
346
|
+
const context = getUnifiedContext(projectRoot);
|
|
347
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
348
|
+
res.end(JSON.stringify({
|
|
349
|
+
status: "running",
|
|
350
|
+
port,
|
|
351
|
+
sources: context.sources,
|
|
352
|
+
itemCount: context.items.length
|
|
353
|
+
}));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// Get context
|
|
357
|
+
if (url.pathname === "/api/context" && req.method === "POST") {
|
|
358
|
+
let body = "";
|
|
359
|
+
req.on("data", chunk => body += chunk);
|
|
360
|
+
req.on("end", () => {
|
|
361
|
+
try {
|
|
362
|
+
const { query, taskType, maxItems } = JSON.parse(body || "{}");
|
|
363
|
+
const context = getUnifiedContext(projectRoot, query, taskType);
|
|
364
|
+
if (maxItems && context.items.length > maxItems) {
|
|
365
|
+
context.items = context.items.slice(0, maxItems);
|
|
366
|
+
}
|
|
367
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
368
|
+
res.end(JSON.stringify(context));
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
372
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// Search
|
|
378
|
+
if (url.pathname === "/api/context/search" && req.method === "POST") {
|
|
379
|
+
let body = "";
|
|
380
|
+
req.on("data", chunk => body += chunk);
|
|
381
|
+
req.on("end", () => {
|
|
382
|
+
try {
|
|
383
|
+
const { query, maxItems = 20 } = JSON.parse(body || "{}");
|
|
384
|
+
if (!query) {
|
|
385
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
386
|
+
res.end(JSON.stringify({ error: "query required" }));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const context = getUnifiedContext(projectRoot, query);
|
|
390
|
+
context.items = context.items
|
|
391
|
+
.filter(item => item.relevance && item.relevance > 0)
|
|
392
|
+
.slice(0, maxItems);
|
|
393
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
394
|
+
res.end(JSON.stringify(context));
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
398
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
// 404
|
|
404
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
405
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
406
|
+
});
|
|
407
|
+
return server;
|
|
408
|
+
}
|
|
409
|
+
// ============================================================================
|
|
410
|
+
// Daemon Management
|
|
411
|
+
// ============================================================================
|
|
412
|
+
function getPidFile(projectRoot) {
|
|
413
|
+
return path.join(projectRoot, PID_FILE);
|
|
414
|
+
}
|
|
415
|
+
function getLogFile(projectRoot) {
|
|
416
|
+
const logFile = path.join(projectRoot, LOG_FILE);
|
|
417
|
+
const logDir = path.dirname(logFile);
|
|
418
|
+
if (!fs.existsSync(logDir)) {
|
|
419
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
420
|
+
}
|
|
421
|
+
return logFile;
|
|
422
|
+
}
|
|
423
|
+
function isRunning(projectRoot) {
|
|
424
|
+
const pidFile = getPidFile(projectRoot);
|
|
425
|
+
if (!fs.existsSync(pidFile)) {
|
|
426
|
+
return { running: false };
|
|
427
|
+
}
|
|
428
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
429
|
+
try {
|
|
430
|
+
process.kill(pid, 0); // Check if process exists
|
|
431
|
+
return { running: true, pid };
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// Process doesn't exist, clean up stale PID file
|
|
435
|
+
fs.unlinkSync(pidFile);
|
|
436
|
+
return { running: false };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async function startDaemon(projectRoot, port) {
|
|
440
|
+
const status = isRunning(projectRoot);
|
|
441
|
+
if (status.running) {
|
|
442
|
+
return { success: true, message: `Context Hub already running (PID: ${status.pid})` };
|
|
443
|
+
}
|
|
444
|
+
// Check if port is in use by another process
|
|
445
|
+
const portInUse = await isPortInUse(port);
|
|
446
|
+
if (portInUse) {
|
|
447
|
+
return { success: false, message: `Port ${port} is already in use by another process` };
|
|
448
|
+
}
|
|
449
|
+
const logFile = getLogFile(projectRoot);
|
|
450
|
+
const pidFile = getPidFile(projectRoot);
|
|
451
|
+
// Generate auth token before starting
|
|
452
|
+
const token = getOrCreateToken(projectRoot);
|
|
453
|
+
// Start as detached process
|
|
454
|
+
const child = spawn(process.execPath, [process.argv[1], "context-hub", "serve", "--port", String(port)], {
|
|
455
|
+
cwd: projectRoot,
|
|
456
|
+
detached: true,
|
|
457
|
+
stdio: ["ignore", fs.openSync(logFile, "a"), fs.openSync(logFile, "a")]
|
|
458
|
+
});
|
|
459
|
+
child.unref();
|
|
460
|
+
// Write PID file
|
|
461
|
+
if (child.pid) {
|
|
462
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
463
|
+
return { success: true, message: `Started (PID: ${child.pid}). Token: ${token.slice(0, 8)}...` };
|
|
464
|
+
}
|
|
465
|
+
return { success: false, message: "Failed to spawn daemon process" };
|
|
466
|
+
}
|
|
467
|
+
async function stopDaemon(projectRoot) {
|
|
468
|
+
const status = isRunning(projectRoot);
|
|
469
|
+
if (!status.running || !status.pid) {
|
|
470
|
+
return { success: true, message: "Context Hub is not running" };
|
|
471
|
+
}
|
|
472
|
+
const pidFile = getPidFile(projectRoot);
|
|
473
|
+
const tokenFile = getTokenFile(projectRoot);
|
|
474
|
+
try {
|
|
475
|
+
// Send SIGTERM first (graceful)
|
|
476
|
+
process.kill(status.pid, "SIGTERM");
|
|
477
|
+
// Wait up to 3 seconds for graceful shutdown
|
|
478
|
+
let attempts = 0;
|
|
479
|
+
while (attempts < 6) {
|
|
480
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
481
|
+
try {
|
|
482
|
+
process.kill(status.pid, 0); // Check if still running
|
|
483
|
+
attempts++;
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
// Process is gone
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// If still running after 3 seconds, force kill
|
|
491
|
+
try {
|
|
492
|
+
process.kill(status.pid, 0);
|
|
493
|
+
process.kill(status.pid, "SIGKILL");
|
|
494
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
// Process is gone, that's fine
|
|
498
|
+
}
|
|
499
|
+
// Clean up PID and token files
|
|
500
|
+
if (fs.existsSync(pidFile)) {
|
|
501
|
+
fs.unlinkSync(pidFile);
|
|
502
|
+
}
|
|
503
|
+
if (fs.existsSync(tokenFile)) {
|
|
504
|
+
fs.unlinkSync(tokenFile);
|
|
505
|
+
}
|
|
506
|
+
return { success: true, message: "Context Hub stopped" };
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
return { success: false, message: `Failed to stop daemon: ${err}` };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// ============================================================================
|
|
513
|
+
// CLI Command
|
|
514
|
+
// ============================================================================
|
|
515
|
+
export async function contextHubCommand(action, options = {}) {
|
|
516
|
+
const projectRoot = process.cwd();
|
|
517
|
+
const port = options.port || DEFAULT_PORT;
|
|
518
|
+
// Ensure .jfl directory exists
|
|
519
|
+
const jflDir = path.join(projectRoot, ".jfl");
|
|
520
|
+
if (!fs.existsSync(jflDir)) {
|
|
521
|
+
fs.mkdirSync(jflDir, { recursive: true });
|
|
522
|
+
}
|
|
523
|
+
switch (action) {
|
|
524
|
+
case "start": {
|
|
525
|
+
const spinner = ora("Starting Context Hub...").start();
|
|
526
|
+
const result = await startDaemon(projectRoot, port);
|
|
527
|
+
if (result.success) {
|
|
528
|
+
// Check if it was already running
|
|
529
|
+
if (result.message.includes("already running")) {
|
|
530
|
+
spinner.info(result.message);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
// Wait for server to be ready
|
|
534
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
535
|
+
const status = isRunning(projectRoot);
|
|
536
|
+
spinner.succeed(`Context Hub started on port ${port} (PID: ${status.pid})`);
|
|
537
|
+
console.log(chalk.gray(` Token file: .jfl/context-hub.token`));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
spinner.fail(result.message);
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
case "stop": {
|
|
546
|
+
const spinner = ora("Stopping Context Hub...").start();
|
|
547
|
+
const result = await stopDaemon(projectRoot);
|
|
548
|
+
if (result.success) {
|
|
549
|
+
spinner.succeed(result.message);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
spinner.fail(result.message);
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case "restart": {
|
|
557
|
+
await contextHubCommand("stop", options);
|
|
558
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
559
|
+
await contextHubCommand("start", options);
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case "status": {
|
|
563
|
+
const status = isRunning(projectRoot);
|
|
564
|
+
if (status.running) {
|
|
565
|
+
console.log(chalk.green(`\n Context Hub is running`));
|
|
566
|
+
console.log(chalk.gray(` PID: ${status.pid}`));
|
|
567
|
+
console.log(chalk.gray(` Port: ${port}`));
|
|
568
|
+
// Try to get more info from the API
|
|
569
|
+
try {
|
|
570
|
+
const response = await fetch(`http://localhost:${port}/api/context/status`);
|
|
571
|
+
const data = await response.json();
|
|
572
|
+
console.log(chalk.gray(` Sources: ${Object.entries(data.sources).filter(([, v]) => v).map(([k]) => k).join(", ")}`));
|
|
573
|
+
console.log(chalk.gray(` Items: ${data.itemCount}`));
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
// Server might not be responding yet
|
|
577
|
+
}
|
|
578
|
+
console.log();
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
console.log(chalk.yellow("\n Context Hub is not running"));
|
|
582
|
+
console.log(chalk.gray(" Run: jfl context-hub start\n"));
|
|
583
|
+
}
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
case "ensure": {
|
|
587
|
+
const status = isRunning(projectRoot);
|
|
588
|
+
if (status.running) {
|
|
589
|
+
// Already running, nothing to do
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
// Start silently
|
|
593
|
+
await startDaemon(projectRoot, port);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
case "serve": {
|
|
597
|
+
// Run server in foreground (used by daemon)
|
|
598
|
+
const server = createServer(projectRoot, port);
|
|
599
|
+
server.listen(port, () => {
|
|
600
|
+
console.log(`Context Hub listening on port ${port}`);
|
|
601
|
+
});
|
|
602
|
+
// Handle shutdown
|
|
603
|
+
process.on("SIGTERM", () => {
|
|
604
|
+
server.close();
|
|
605
|
+
process.exit(0);
|
|
606
|
+
});
|
|
607
|
+
process.on("SIGINT", () => {
|
|
608
|
+
server.close();
|
|
609
|
+
process.exit(0);
|
|
610
|
+
});
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
case "query": {
|
|
614
|
+
// Quick query for testing
|
|
615
|
+
const context = getUnifiedContext(projectRoot);
|
|
616
|
+
console.log(chalk.bold("\n Context Hub Query\n"));
|
|
617
|
+
console.log(chalk.gray(` Sources: ${Object.entries(context.sources).filter(([, v]) => v).map(([k]) => k).join(", ")}`));
|
|
618
|
+
console.log(chalk.gray(` Items: ${context.items.length}\n`));
|
|
619
|
+
for (const item of context.items.slice(0, 10)) {
|
|
620
|
+
console.log(chalk.cyan(` [${item.source}] ${item.title}`));
|
|
621
|
+
console.log(chalk.gray(` ${item.content.slice(0, 100)}${item.content.length > 100 ? "..." : ""}`));
|
|
622
|
+
}
|
|
623
|
+
console.log();
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
default: {
|
|
627
|
+
console.log(chalk.bold("\n Context Hub - Unified context for AI agents\n"));
|
|
628
|
+
console.log(chalk.gray(" Commands:"));
|
|
629
|
+
console.log(" jfl context-hub start Start the daemon");
|
|
630
|
+
console.log(" jfl context-hub stop Stop the daemon");
|
|
631
|
+
console.log(" jfl context-hub restart Restart the daemon");
|
|
632
|
+
console.log(" jfl context-hub status Check if running");
|
|
633
|
+
console.log(" jfl context-hub ensure Start if not running (for hooks)");
|
|
634
|
+
console.log(" jfl context-hub query Quick context query");
|
|
635
|
+
console.log();
|
|
636
|
+
console.log(chalk.gray(" Options:"));
|
|
637
|
+
console.log(" --port <port> Port to run on (default: 4242)");
|
|
638
|
+
console.log();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
//# sourceMappingURL=context-hub.js.map
|