From c3a2173749263817e3eee9b719ad69393d053a66 Mon Sep 17 00:00:00 2001 From: Ivan Willig Date: Tue, 2 Dec 2025 18:45:37 -0500 Subject: [PATCH 1/4] Add better search results and output --- src/clojure_skills/cli.clj | 26 +++++++++++++++----------- src/clojure_skills/output.clj | 25 ++++++++++--------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/clojure_skills/cli.clj b/src/clojure_skills/cli.clj index fa5caed..38e49e9 100644 --- a/src/clojure_skills/cli.clj +++ b/src/clojure_skills/cli.clj @@ -181,12 +181,14 @@ :query query :category category :count (count skills) - :skills (map (fn [skill] - {:name (:skills/name skill) - :category (:skills/category skill) - :size-bytes (or (:skills/size_bytes skill) 0) - :token-count (or (:skills/token_count skill) 0)}) - skills)})))))) + :skills (map (fn [skill] + {:name (:skills/name skill) + :category (:skills/category skill) + :size-bytes (or (:skills/size_bytes skill) 0) + :token-count (or (:skills/token_count skill) 0) + :snippet (:snippet skill) + :rank (:skills_fts/rank skill)}) + skills)})))))) (defn cmd-search-prompts "Search prompts." @@ -203,11 +205,13 @@ {:type :prompt-search-results :query query :count (count prompts) - :prompts (map (fn [prompt] - {:name (:prompts/name prompt) - :size-bytes (or (:prompts/size_bytes prompt) 0) - :token-count (or (:prompts/token_count prompt) 0)}) - prompts)})))))) + :prompts (map (fn [prompt] + {:name (:prompts/name prompt) + :size-bytes (or (:prompts/size_bytes prompt) 0) + :token-count (or (:prompts/token_count prompt) 0) + :snippet (:snippet prompt) + :rank (:prompts_fts/rank prompt)}) + prompts)})))))) (defn cmd-list-skills "List all skills." diff --git a/src/clojure_skills/output.clj b/src/clojure_skills/output.clj index d430586..9c92292 100644 --- a/src/clojure_skills/output.clj +++ b/src/clojure_skills/output.clj @@ -262,14 +262,11 @@ (println (bling/bling [:bold (format "Found %d skills matching \"%s\"" count query)])) (when (seq skills) (println) - (table/print-table - [:name :category :size-kb :tokens] - (map (fn [skill] - {:name (:name skill) - :category (:category skill) - :size-kb (format "%.1f" (/ (:size-bytes skill) 1024.0)) - :tokens (:token-count skill)}) - skills)) + (doseq [skill skills] + (println (bling/bling [:bold (str "• " (:name skill))] [:dim (str " (" (:category skill) ")")])) + (when-let [snippet (:snippet skill)] + (println (str " " snippet))) + (println)) (println)))) ;; ------------------------------------------------------------ @@ -332,13 +329,11 @@ (println (bling/bling [:bold (format "Found %d prompts matching \"%s\"" count query)])) (when (seq prompts) (println) - (table/print-table - [:name :size-kb :tokens] - (map (fn [prompt] - {:name (:name prompt) - :size-kb (format "%.1f" (/ (:size-bytes prompt) 1024.0)) - :tokens (:token-count prompt)}) - prompts)) + (doseq [prompt prompts] + (println (bling/bling [:bold (str "• " (:name prompt))])) + (when-let [snippet (:snippet prompt)] + (println (str " " snippet))) + (println)) (println)))) ;; ------------------------------------------------------------ From ce179dfe84016a7e4ffa5ab7eb8293085dfb043e Mon Sep 17 00:00:00 2001 From: Ivan Willig Date: Tue, 2 Dec 2025 19:08:57 -0500 Subject: [PATCH 2/4] Clean plan, task and task-list Fix multimethod --- src/clojure_skills/cli.clj | 122 +++++++------- src/clojure_skills/output.clj | 289 ++++------------------------------ 2 files changed, 96 insertions(+), 315 deletions(-) diff --git a/src/clojure_skills/cli.clj b/src/clojure_skills/cli.clj index 38e49e9..e2f96ee 100644 --- a/src/clojure_skills/cli.clj +++ b/src/clojure_skills/cli.clj @@ -122,13 +122,14 @@ (defn cmd-search "Search skills and prompts." - [{:keys [_arguments category type max-results]}] + [{:keys [_arguments category type max-results json human]}] (let [query (first _arguments)] (validate-non-blank query "Search query cannot be empty") (handle-command-errors "Search" (fn [] - (let [[_config db] (load-config-and-db) + (let [[config db] (load-config-and-db) + format (output/get-output-format json human config) results (cond (= type "skills") {:skills (search/search-skills db query @@ -145,7 +146,7 @@ (search/search-all db query :max-results max-results :category category))] - (output/output-data + (output/output {:type :search-results :query query :category category @@ -162,68 +163,74 @@ {:name (:prompts/name prompt) :size-bytes (or (:prompts/size_bytes prompt) 0) :token-count (or (:prompts/token_count prompt) 0)}) - (:prompts results))}})))))) + (:prompts results))}} + format)))))) (defn cmd-search-skills "Search skills." - [{:keys [_arguments category max-results]}] + [{:keys [_arguments category max-results json human]}] (let [query (first _arguments)] (validate-non-blank query "Search query cannot be empty") (handle-command-errors "Search skills" (fn [] - (let [[_config db] (load-config-and-db) + (let [[config db] (load-config-and-db) skills (search/search-skills db query :max-results (or max-results 50) - :category category)] - (output/output-data + :category category) + format (output/get-output-format json human config)] + (output/output {:type :skill-search-results :query query :category category :count (count skills) - :skills (map (fn [skill] - {:name (:skills/name skill) - :category (:skills/category skill) - :size-bytes (or (:skills/size_bytes skill) 0) - :token-count (or (:skills/token_count skill) 0) - :snippet (:snippet skill) - :rank (:skills_fts/rank skill)}) - skills)})))))) + :skills (map (fn [skill] + {:name (:skills/name skill) + :category (:skills/category skill) + :size-bytes (or (:skills/size_bytes skill) 0) + :token-count (or (:skills/token_count skill) 0) + :snippet (:snippet skill) + :rank (:skills_fts/rank skill)}) + skills)} + format)))))) (defn cmd-search-prompts "Search prompts." - [{:keys [_arguments max-results]}] + [{:keys [_arguments max-results json human]}] (let [query (first _arguments)] (validate-non-blank query "Search query cannot be empty") (handle-command-errors "Search prompts" (fn [] - (let [[_config db] (load-config-and-db) + (let [[config db] (load-config-and-db) prompts (search/search-prompts db query - :max-results (or max-results 50))] - (output/output-data + :max-results (or max-results 50)) + format (output/get-output-format json human config)] + (output/output {:type :prompt-search-results :query query :count (count prompts) - :prompts (map (fn [prompt] - {:name (:prompts/name prompt) - :size-bytes (or (:prompts/size_bytes prompt) 0) - :token-count (or (:prompts/token_count prompt) 0) - :snippet (:snippet prompt) - :rank (:prompts_fts/rank prompt)}) - prompts)})))))) + :prompts (map (fn [prompt] + {:name (:prompts/name prompt) + :size-bytes (or (:prompts/size_bytes prompt) 0) + :token-count (or (:prompts/token_count prompt) 0) + :snippet (:snippet prompt) + :rank (:prompts_fts/rank prompt)}) + prompts)} + format)))))) (defn cmd-list-skills "List all skills." - [{:keys [category]}] + [{:keys [category json human]}] (handle-command-errors "List skills" (fn [] - (let [[_config db] (load-config-and-db) + (let [[config db] (load-config-and-db) skills (if category (search/list-skills db :category category) - (search/list-skills db))] - (output/output-data + (search/list-skills db)) + format (output/get-output-format json human config)] + (output/output {:type :skill-list :count (count skills) :skills (map (fn [skill] @@ -231,24 +238,27 @@ :category (:skills/category skill) :size-bytes (or (:skills/size_bytes skill) 0) :token-count (or (:skills/token_count skill) 0)}) - skills)}))))) + skills)} + format))))) (defn cmd-list-prompts "List all prompts." - [_opts] + [{:keys [json human]}] (handle-command-errors "List prompts" (fn [] - (let [[_config db] (load-config-and-db) - prompts (search/list-prompts db)] - (output/output-data + (let [[config db] (load-config-and-db) + prompts (search/list-prompts db) + format (output/get-output-format json human config)] + (output/output {:type :prompt-list :count (count prompts) :prompts (map (fn [prompt] {:name (:prompts/name prompt) :size-bytes (or (:prompts/size_bytes prompt) 0) :token-count (or (:prompts/token_count prompt) 0)}) - prompts)}))))) + prompts)} + format))))) (defn list-prompt-skills "List all skills associated with a prompt via fragments." @@ -330,14 +340,15 @@ (defn cmd-show-prompt "Show full content of a prompt with metadata and associated skills." - [{:keys [_arguments]}] + [{:keys [_arguments json human]}] (let [prompt-name (first _arguments)] (validate-non-blank prompt-name "Prompt name cannot be empty") (handle-command-errors "Show prompt" (fn [] - (let [[_config db] (load-config-and-db) - prompt (search/get-prompt-by-name db prompt-name)] + (let [[config db] (load-config-and-db) + prompt (search/get-prompt-by-name db prompt-name) + format (output/get-output-format json human config)] (if prompt (let [;; Show embedded fragments (skills that are embedded in the prompt) fragments (list-prompt-fragments db (:prompts/id prompt)) @@ -345,8 +356,8 @@ references (list-prompt-references db (:prompts/id prompt)) ;; Render full content full-content (render-prompt-content db prompt)] - (output/output-data - {:type "prompt" + (output/output + {:type :prompt :data {:name (:prompts/name prompt) :title (:prompts/title prompt) :description (:prompts/description prompt) @@ -366,7 +377,8 @@ :name (:skills/name skill) :title (:skills/title skill)}) references) - :content full-content}})) + :content full-content}} + format)) (do (print-error (str "Prompt not found: " prompt-name)) (*exit-fn* 1)))))))) @@ -390,15 +402,16 @@ (defn cmd-stats "Show database statistics and configuration." - [_opts] + [{:keys [json human]}] (handle-command-errors "Get stats" (fn [] (let [[config db] (load-config-and-db) stats (search/get-stats db) db-path (config/get-db-path config) - permissions (get config :permissions {})] - (output/output-data + permissions (get config :permissions {}) + format (output/get-output-format json human config)] + (output/output {:type :stats :configuration {:database-path db-path :auto-migrate (get-in config [:database :auto-migrate] true) @@ -420,7 +433,8 @@ :category-breakdown (map (fn [cat] {:category (or (:skills/category cat) (:category cat) "unknown") :count (or (:count cat) 0)}) - (:category-breakdown stats))}))))) + (:category-breakdown stats))} + format))))) (defn cmd-reset-db "Reset database (WARNING: destructive)." @@ -439,19 +453,21 @@ (print-success "Database reset complete")))))) (defn cmd-show-skill - "Show full content of a skill as JSON." - [{:keys [category _arguments]}] + "Show full content of a skill." + [{:keys [category _arguments json human]}] (let [skill-name (first _arguments)] (validate-non-blank skill-name "Skill name cannot be empty") (handle-command-errors "Show skill" (fn [] - (let [[_config db] (load-config-and-db) - skill (search/get-skill-by-name db skill-name :category category)] + (let [[config db] (load-config-and-db) + skill (search/get-skill-by-name db skill-name :category category) + format (output/get-output-format json human config)] (if skill - (output/output-data + (output/output {:type :skill - :data skill}) + :data skill} + format) (do (print-error (str "Skill not found: " skill-name (when category (str " in category " category)))) diff --git a/src/clojure_skills/output.clj b/src/clojure_skills/output.clj index 9c92292..6b1219a 100644 --- a/src/clojure_skills/output.clj +++ b/src/clojure_skills/output.clj @@ -1,9 +1,9 @@ (ns clojure-skills.output "Output formatting for clojure-skills CLI. - + Provides both JSON and human-readable output using multimethods that dispatch on data type. Each data type can define custom formatting for both formats. - + Default behavior: JSON output for all types unless custom human formatter exists." (:require [bling.core :as bling] @@ -28,181 +28,24 @@ (defmethod format-human :default [data] (json/pprint data)) -;; ============================================================ -;; Type-specific formatters -;; ============================================================ - -;; ------------------------------------------------------------ -;; task-list -;; ------------------------------------------------------------ -(defmethod format-json "task-list" [data] - (json/pprint data)) - -(defmethod format-human "task-list" [data] - (let [tl (:data data)] - (println) - (println (bling/bling [:bold (:name tl)])) - (println (str "ID: " (:id tl))) - (println (str "Plan ID: " (:plan_id tl))) - (when (:description tl) - (println (str "Description: " (:description tl)))) - (when (:position tl) - (println (str "Position: " (:position tl)))) - (println (str "Created at: " (:created-at tl))) - (when (:updated-at tl) - (println (str "Updated at: " (:updated-at tl)))) - - ;; Show tasks in this list - (when-let [tasks (:tasks tl)] - (when (seq tasks) - (println) - (println (bling/bling [:bold "Tasks:"])) - (doseq [task tasks] - (println (str " " (if (:completed task) "✓" "○") " " - "[" (:id task) "] " - (:name task))) - (when (:description task) - (println (str " " (:description task)))) - (when (:assigned-to task) - (println (str " Assigned to: " (:assigned-to task))))))))) - -;; ------------------------------------------------------------ -;; task -;; ------------------------------------------------------------ -(defmethod format-json "task" [data] - (json/pprint data)) - -(defmethod format-human "task" [data] - (let [t (:data data)] - (println) - (println (bling/bling [:bold (:name t)])) - (println (str "ID: " (:id t))) - (println (str "Task List ID: " (:list_id t))) - (println (str "Status: " (if (:completed t) "Completed" "Not completed"))) - - (when (:description t) - (println) - (println (bling/bling [:underline "Description:"])) - (println (:description t))) - - (when (:assigned-to t) - (println) - (println (str "Assigned to: " (:assigned-to t)))) - - (when (:position t) - (println (str "Position: " (:position t)))) - - (println) - (println (str "Created at: " (:created-at t))) - (when (:updated-at t) - (println (str "Updated at: " (:updated-at t)))) - (when (:completed-at t) - (println (str "Completed at: " (:completed-at t)))))) - -;; ------------------------------------------------------------ -;; plan-result -;; ------------------------------------------------------------ -(defmethod format-json "plan-result" [data] - (json/pprint data)) - -(defmethod format-human "plan-result" [data] - (let [r (:data data)] - (println) - (println (bling/bling [:bold "Plan Result"])) - (println (str "ID: " (:id r))) - (println (str "Plan ID: " (:plan_id r))) - (println (str "Outcome: " (:outcome r))) - - (println) - (println (bling/bling [:underline "Summary:"])) - (println (:summary r)) - - (when (:challenges r) - (println) - (println (bling/bling [:underline "Challenges:"])) - (println (:challenges r))) - - (when (:solutions r) - (println) - (println (bling/bling [:underline "Solutions:"])) - (println (:solutions r))) - - (when (:lessons_learned r) - (println) - (println (bling/bling [:underline "Lessons Learned:"])) - (println (:lessons_learned r))) - - (when (:metrics r) - (println) - (println (bling/bling [:underline "Metrics:"])) - (println (:metrics r))) - - (println) - (println (str "Created at: " (:created_at r))) - (println (str "Updated at: " (:updated_at r))))) - -;; ------------------------------------------------------------ -;; plan-skills-list -;; ------------------------------------------------------------ -(defmethod format-json "plan-skills-list" [data] - (json/pprint data)) - -(defmethod format-human "plan-skills-list" [data] - (let [plan-id (:plan-id data) - skills (:skills data)] - (if (empty? skills) - (println (str "No skills associated with plan " plan-id)) - (do - (println) - (println (bling/bling [:bold (format "Skills for plan %d:" plan-id)])) - (println) - (table/print-table - [:position :category :name :title] - (map (fn [skill] - {:position (:position skill) - :category (:category skill) - :name (:name skill) - :title (or (:title skill) "")}) - skills)) - (println))))) - -;; ------------------------------------------------------------ -;; plan-results-search -;; ------------------------------------------------------------ -(defmethod format-json "plan-results-search" [data] - (json/pprint data)) - -(defmethod format-human "plan-results-search" [data] - (let [results (:results data) - count (:count data)] - (println) - (println (bling/bling [:bold (format "Found %d results" count)])) - (doseq [result results] - (println) - (println (str "Plan ID: " (:plan_id result) - " | Outcome: " (:outcome result))) - (println (str "Snippet: " (:snippet result))) - (when (:rank result) - (println (str "Rank: " (:rank result))))))) - ;; ============================================================ ;; Public API ;; ============================================================ (defn get-output-format "Determine output format from CLI flags, config, or default. - + Priority: 1. CLI --json flag (overrides everything) 2. CLI --human flag (overrides config) 3. Config file setting (:output :format) 4. Default to :json - + Args: json-flag - Boolean or nil from CLI --json flag human-flag - Boolean or nil from CLI --human flag config - Configuration map - + Returns: :json or :human" [json-flag human-flag config] @@ -213,14 +56,14 @@ (defn output "Output data in specified format using multimethods. - + Dispatches to format-json or format-human based on format parameter. Falls back to JSON for unknown formats. - + Args: data - Data map with :type field for dispatch format - :json or :human - + Returns: nil (output goes to stdout)" [data format] @@ -232,12 +75,12 @@ (defn output-data "Output data as formatted JSON to stdout. - + Backward compatible wrapper - always outputs JSON. - + Args: data - Data structure to output - + Returns: nil (output goes to stdout)" [data] @@ -251,10 +94,10 @@ ;; ------------------------------------------------------------ ;; skill-search-results ;; ------------------------------------------------------------ -(defmethod format-json "skill-search-results" [data] +(defmethod format-json :skill-search-results [data] (json/pprint data)) -(defmethod format-human "skill-search-results" [data] +(defmethod format-human :skill-search-results [data] (let [query (:query data) skills (:skills data) count (:count data)] @@ -272,10 +115,10 @@ ;; ------------------------------------------------------------ ;; skill-list ;; ------------------------------------------------------------ -(defmethod format-json "skill-list" [data] +(defmethod format-json :skill-list [data] (json/pprint data)) -(defmethod format-human "skill-list" [data] +(defmethod format-human :skill-list [data] (let [skills (:skills data) count (:count data)] (println) @@ -295,10 +138,10 @@ ;; ------------------------------------------------------------ ;; skill ;; ------------------------------------------------------------ -(defmethod format-json "skill" [data] +(defmethod format-json :skill [data] (json/pprint data)) -(defmethod format-human "skill" [data] +(defmethod format-human :skill [data] (let [skill (:data data)] (println) (println (bling/bling [:bold (:skills/name skill)])) @@ -318,10 +161,10 @@ ;; ------------------------------------------------------------ ;; prompt-search-results ;; ------------------------------------------------------------ -(defmethod format-json "prompt-search-results" [data] +(defmethod format-json :prompt-search-results [data] (json/pprint data)) -(defmethod format-human "prompt-search-results" [data] +(defmethod format-human :prompt-search-results [data] (let [query (:query data) prompts (:prompts data) count (:count data)] @@ -339,10 +182,10 @@ ;; ------------------------------------------------------------ ;; prompt-list ;; ------------------------------------------------------------ -(defmethod format-json "prompt-list" [data] +(defmethod format-json :prompt-list [data] (json/pprint data)) -(defmethod format-human "prompt-list" [data] +(defmethod format-human :prompt-list [data] (let [prompts (:prompts data) count (:count data)] (println) @@ -361,10 +204,10 @@ ;; ------------------------------------------------------------ ;; prompt (complex - with embedded/reference skills) ;; ------------------------------------------------------------ -(defmethod format-json "prompt" [data] +(defmethod format-json :prompt [data] (json/pprint data)) -(defmethod format-human "prompt" [data] +(defmethod format-human :prompt [data] (let [p (:data data)] (println) (println (bling/bling [:bold (:name p)])) @@ -406,10 +249,10 @@ ;; ------------------------------------------------------------ ;; stats ;; ------------------------------------------------------------ -(defmethod format-json "stats" [data] +(defmethod format-json :stats [data] (json/pprint data)) -(defmethod format-human "stats" [data] +(defmethod format-human :stats [data] (let [config (:configuration data) db (:database data) perms (:permissions data) @@ -448,91 +291,13 @@ categories) (println)))) -;; ------------------------------------------------------------ -;; plan-list -;; ------------------------------------------------------------ -(defmethod format-json "plan-list" [data] - (json/pprint data)) - -(defmethod format-human "plan-list" [data] - (let [plans (:plans data) - count (:count data)] - (println) - (println (bling/bling [:bold (format "Total: %d plans" count)])) - (when (seq plans) - (println) - (table/print-table - [:id :name :status :created-by :assigned-to] - (map (fn [plan] - {:id (:id plan) - :name (:name plan) - :status (:status plan) - :created-by (or (:created-by plan) "") - :assigned-to (or (:assigned-to plan) "")}) - plans)) - (println)))) - -;; ------------------------------------------------------------ -;; plan (complex - with task lists and tasks) -;; ------------------------------------------------------------ -(defmethod format-json "plan" [data] - (json/pprint data)) - -(defmethod format-human "plan" [data] - (let [p (:data data)] - (println) - (println (bling/bling [:bold (:name p)])) - (when (:title p) - (println (bling/bling [:italic (:title p)]))) - (println (str "ID: " (:id p))) - (println (str "Status: " (:status p))) - (when (:summary p) - (println (str "Summary: " (:summary p)))) - (when (:created-by p) - (println (str "Created by: " (:created-by p)))) - (when (:assigned-to p) - (println (str "Assigned to: " (:assigned-to p)))) - (println (str "Created: " (:created-at p))) - (when (:completed-at p) - (println (str "Completed: " (:completed-at p)))) - - ;; Associated skills - (when-let [skills (:skills p)] - (when (seq skills) - (println) - (println (bling/bling [:underline "Associated Skills:"])) - (doseq [skill skills] - (println (str " " (:position skill) ". [" (:category skill) "] " (:name skill)))))) - - ;; Plan result - (when-let [result (:result p)] - (println) - (println (bling/bling [:underline "Result:"])) - (println (str " Outcome: " (:outcome result))) - (when (:summary result) - (println (str " Summary: " (:summary result))))) - - ;; Task lists and tasks - (when-let [task-lists (:task-lists p)] - (when (seq task-lists) - (println) - (println (bling/bling [:underline "Task Lists:"])) - (doseq [tl task-lists] - (println) - (println (str " [" (:id tl) "] " (:name tl))) - (when-let [tasks (:tasks tl)] - (when (seq tasks) - (doseq [task tasks] - (println (str " " (if (:completed task) "✓" "○") " " - "[" (:id task) "] " (:name task))))))))))) - ;; ------------------------------------------------------------ ;; search-results (combined skills + prompts) ;; ------------------------------------------------------------ -(defmethod format-json "search-results" [data] +(defmethod format-json :search-results [data] (json/pprint data)) -(defmethod format-human "search-results" [data] +(defmethod format-human :search-results [data] (let [query (:query data) skills (get-in data [:skills :results]) skill-count (get-in data [:skills :count]) From 3b36a5be2b3408d869c604e8413dc1ba537df40d Mon Sep 17 00:00:00 2001 From: Ivan Willig Date: Tue, 2 Dec 2025 19:13:59 -0500 Subject: [PATCH 3/4] Clean up build script --- bb.edn | 271 --------------------------------------------------------- 1 file changed, 271 deletions(-) diff --git a/bb.edn b/bb.edn index e899795..e42ef61 100644 --- a/bb.edn +++ b/bb.edn @@ -106,119 +106,6 @@ "Remove ANSI color codes from string" (str/replace s (re-pattern "\033\\[[0-9;]*m") ""))) - ;; Build a specific prompt file by name - build - {:doc "Build a specific prompt file by name (without .md extension)\nUsage: bb build clojure_skill_builder" - :task (let [name (require-arg "name") - output-file (str "_build/" name ".md")] - - (info-msg (str "Building " output-file "...")) - (fs/create-dirs "_build") - - ;; Use CLI to generate prompt content - (let [result (p/shell {:out :string - :err :string - :continue true} - (str "bb main prompt show " name))] - (if (zero? (:exit result)) - (let [content (:out result) - cleaned (strip-ansi content)] - (spit output-file cleaned) - (success-msg (str "Build successful: " output-file))) - (do - (error-msg (str "Failed to build prompt: " name)) - (println (:err result)) - (System/exit 1)))))} - - ;; Build all prompt files - build-all - {:doc "Build all prompt files" - :task (do - (info-msg "Building all prompts...") - - ;; Build clojure_skill_builder - (shell "bb build clojure_skill_builder") - - ;; Build clojure_build - (shell "bb build clojure_build") - (shell "bb build code_archivist") - (shell "bb build plan_executor") - - (shell "bb build shortcut_cli_builder") - (shell "bb build software_alchemist_build") - - (success-msg "All builds complete!"))} - - ;; List built prompts with token counts - list-prompts - {:doc "List all built prompts with file sizes and token counts" - :task (let [build-dir "_build"] - - (if-not (fs/exists? build-dir) - (do - (warning-msg "No _build directory found. Run 'bb build-all' first.") - (System/exit 1)) - - (let [prompt-files (sort (filter (fn [f] - (str/ends-with? (str f) ".md")) - (fs/list-dir build-dir))) - -;; Collect data for each prompt with metadata - prompts (for [file prompt-files] - (let [file-path (str file) - filename (fs/file-name file) - base-name (-> filename - (str/replace ".compressed.md" "") - (str/replace ".md" "")) - source-prompt (str "prompts/" base-name ".md") - metadata (if (fs/exists? source-prompt) - (extract-metadata-with-pandoc source-prompt) - (extract-metadata-with-pandoc file-path)) - content (slurp file-path) - file-size (fs/size file) - char-count (count content) - token-count (estimate-tokens content) - prompt-name (or (get metadata :name) - (get metadata :title) - base-name) - description (strip-html-tags (get metadata :description ""))] - {:filename filename - :name prompt-name - :description description - :file file-path - :size file-size - :size-str (format-size file-size) - :chars char-count - :tokens (long token-count)})) - - ;; Calculate totals - total-size (reduce + (map :size prompts)) - total-chars (reduce + (map :chars prompts)) - total-tokens (reduce + (map :tokens prompts))] - - (info-msg "Built Prompts") - - (println) - (table/print-table [:filename :name :description :size :chars :tokens] - (concat - (for [p prompts] - {:filename (:filename p) - :name (:name p) - :description (truncate-string (:description p) 60) - :size (:size-str p) - :chars (format-number (:chars p)) - :tokens (format-number (:tokens p))}) - [{:filename "TOTAL" - :name "" - :description "" - :size (format-size total-size) - :chars (format-number total-chars) - :tokens (format-number total-tokens)}])) - - (println) - (println (bling/bling [:dim "Note: Token count is estimated (~4 chars per token)"])) - (println (bling/bling [:dim " Anthropic's Claude models use this approximation."])))))} - ;; Clean all build artifacts and temporary files clean {:doc "Clean all build artifacts and temporary files" @@ -406,164 +293,6 @@ (run 'test) (success-msg "CI pipeline completed successfully"))} - list-skills - {:doc "List all skills with metadata in a table format" - :task (let [skills-dir "skills" - skill-files (sort (map str (fs/glob skills-dir "**/*.md"))) - - ;; Parse all skills with pandoc metadata - skills (for [file-path skill-files] - (let [metadata (extract-metadata-with-pandoc file-path) - file-size (fs/size file-path) - content (slurp file-path) - char-count (count content) - token-count (long (estimate-tokens content)) - relative-path (str/replace file-path - (str (fs/file skills-dir) "/") - "") - category (first (str/split relative-path (re-pattern "/"))) - skill-name (or (get metadata :name) - (get metadata :title) - (str/replace (fs/file-name file-path) ".md" "")) - description (strip-html-tags (get metadata :description ""))] - {:category category - :name skill-name - :path relative-path - :size (format-size file-size) - :tokens (format-number token-count) - :description description})) - - ;; Calculate totals - total-size (reduce + (map (fn [s] (fs/size (str "skills/" (:path s)))) skills)) - total-tokens (reduce + (map (fn [s] (long (estimate-tokens (slurp (str "skills/" (:path s)))))) skills))] - - (info-msg "Available Skills") - - (println) - (table/print-table [:category :name :description :size :tokens] - (concat - (for [s (sort-by (juxt :category :name) skills)] - (assoc s :description (truncate-string (:description s) 80))) - [{:category "TOTAL" - :name (str (count skills) " skills") - :description "" - :size (format-size total-size) - :tokens (format-number total-tokens)}])) - - (println) - (println (bling/bling [:dim "Note: Token count is estimated (~4 chars per token)"])))} - - watch - {:doc "Watch for changes and rebuild prompts automatically\nUsage: bb watch [prompt-name]" - :task (let [name (first *command-line-args*)] - (info-msg (if name - (str "Watching for changes to rebuild " name "...") - "Watching for changes to rebuild all prompts...")) - - ;; Use babashka.fs/watch - (let [watch-paths ["prompts" "skills" "prompt_configs"]] - (apply fs/watch - (for [path watch-paths - :when (fs/exists? path)] - path) - (fn [{:keys [type path]}] - (when (and (= :modify type) - (str/ends-with? (str path) ".md")) - (info-msg (str "Change detected: " path)) - (try - (if name - (shell (str "bb build " name)) - (shell "bb build-all")) - (catch Exception e - (error-msg (str "Build failed: " (.getMessage e))))))))))} - - ;; LLMLingua compression tasks - - setup-python - {:doc "Install Python dependencies using pipenv" - :task (do - (info-msg "Installing Python dependencies with pipenv...") - - (shell "pipenv install") - - (success-msg "Python environment ready!") - - (println) - (println "You can now use:") - (println " bb compress --ratio 2") - (println " bb build-compressed --ratio 3") - (println " bb compress-skill skills/path/to/file.md --ratio 2"))} - - compress - {:doc "Compress a built prompt file using LLMLingua (default: 2x ratio)\nUsage: bb compress clojure_skill_builder [--ratio 2]" - :task (let [args *command-line-args* - name (first args) - ratio (get-ratio-arg) - - input (str "_build/" name ".md") - output (str "_build/" name ".compressed.md")] - - (when-not name - (throw (ex-info "Usage: bb compress [--ratio N]" {}))) - - (when-not (fs/exists? input) - (error-msg (str "Built file not found: " input)) - (println "\nRun 'bb build " name "' first.") - (System/exit 1)) - - (info-msg (str "Compressing " input " with " ratio "x ratio...")) - - ;; Run Python compression script via pipenv - (let [result (shell {:out :string - :err :string - :continue true} - (str "pipenv run python scripts/compress_prompt.py" - " --input " input - " --output " output - " --ratio " ratio))] - (if (zero? (:exit result)) - (do - (println (:out result)) - (success-msg (str "Compression successful: " output))) - (do - (println (:err result)) - (error-msg "Compression failed") - (System/exit 1)))))} - - compress-skill - {:doc "Compress a single skill file (default: 2x ratio)\nUsage: bb compress-skill skills/libraries/data_validation/malli.md [--ratio 2]" - :task (let [args *command-line-args* - input-file (first args) - ratio (get-ratio-arg) - - ;; Create output path by adding .compressed before .md - output-file (str/replace input-file (re-pattern "\\.md$") ".compressed.md")] - - (when-not input-file - (throw (ex-info "Usage: bb compress-skill [--ratio N]" {}))) - - (when-not (fs/exists? input-file) - (error-msg (str "Skill file not found: " input-file)) - (System/exit 1)) - - (info-msg (str "Compressing " input-file " with " ratio "x ratio...")) - - (let [result (shell {:out :string - :err :string - :continue true} - (str "pipenv run python scripts/compress_prompt.py" - " --input " input-file - " --output " output-file - " --ratio " ratio))] - (if (zero? (:exit result)) - (do - (println (:out result)) - (success-msg (str "Compressed: " output-file))) - (do - (println (:err result)) - (error-msg "Compression failed") - (System/exit 1)))))} - migrate {:doc "Run database migrations" :task (do From 9c028db6e61f93d7d1ec10afc34e34a0fdbb12d7 Mon Sep 17 00:00:00 2001 From: Ivan Willig Date: Tue, 2 Dec 2025 19:27:57 -0500 Subject: [PATCH 4/4] Clean up readme --- deps.edn | 7 +- readme.md | 362 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 335 insertions(+), 34 deletions(-) diff --git a/deps.edn b/deps.edn index 668f3af..d47587c 100644 --- a/deps.edn +++ b/deps.edn @@ -8,17 +8,14 @@ funcool/lentes {:mvn/version "1.3.3"} clj-commons/clj-yaml {:mvn/version "1.0.29"} org.clj-commons/pretty {:mvn/version "3.6.7"} - dev.weavejester/hashp {:mvn/version "0.5.1"} io.github.paintparty/bling {:mvn/version "0.8.8"} - mvxcvi/puget {:mvn/version "1.3.4"} - + org.slf4j/slf4j-nop {:mvn/version "2.0.17"} ;; SQL database dependencies com.github.seancorfield/next.jdbc {:mvn/version "1.3.955"} com.github.seancorfield/honeysql {:mvn/version "2.7.1350"} dev.weavejester/ragtime.next-jdbc {:mvn/version "0.9.4"} - org.xerial/sqlite-jdbc {:mvn/version "3.47.1.0"} - com.github.clojure-lsp/clojure-lsp {:mvn/version "2025.08.25-14.21.46"}} + org.xerial/sqlite-jdbc {:mvn/version "3.47.1.0"}} :paths ["src" "classes" diff --git a/readme.md b/readme.md index a7191d0..dc70549 100644 --- a/readme.md +++ b/readme.md @@ -153,23 +153,29 @@ clojure -M:main db stats ### 4. Search for a skill (30 seconds) ```bash +# Search returns JSON with matching skills clojure -M:main skill search "validation" -# Returns JSON with matching skills -# Or format with jq -clojure -M:main skill search "validation" | jq '.skills[].name' -# Shows: malli, spec, schema... +# Format with jq to show just names +clojure -M:main skill search "validation" | jq -r '.skills[].name' +# Output: malli, spec, schema... + +# Use human-readable format +clojure -M:main -H skill search "validation" ``` ### 5. View a skill (30 seconds) ```bash +# Show skill (returns JSON with full content) clojure -M:main skill show malli -c libraries/data_validation -# Returns JSON with full skill content -# Extract just the content +# Extract just the markdown content clojure -M:main skill show malli | jq -r '.data.content' # Displays full Malli validation skill + +# Use human-readable format +clojure -M:main -H skill show malli ``` **Next steps:** @@ -253,11 +259,11 @@ clj-nrepl-eval --version git clone https://github.com/yourusername/clojure-skills.git cd clojure-skills -# Initialize the database -clojure -M:main init +# Initialize the database (creates schema) +clojure -M:main db init -# Sync skills to database (first time) -clojure -M:main sync +# Sync skills to database (loads all skills from filesystem) +clojure -M:main db sync # Build native binary (recommended for speed) bb build-cli @@ -273,58 +279,148 @@ The native binary will be created at `target/clojure-skills` and can be moved to ```bash clojure-skills db stats -# Should show: -# - 78 skills -# - 7 prompts -# - 29 categories -# - ~1018KB total size +# Should show (JSON output): +# { +# "type": "database-statistics", +# "database": { +# "skills": 78, +# "prompts": 7, +# "categories": 29, +# "total-size-bytes": 1018000, +# "total-tokens": 250000 +# } +# } + +# Or use human-readable format: +clojure-skills -H db stats ``` --- ## CLI Usage -The `clojure-skills` CLI outputs structured JSON by default, making it easy to pipe to `jq` for filtering and processing. +The `clojure-skills` CLI provides hierarchical subcommands for managing skills, prompts, and the database. All commands output structured JSON by default, making it easy to pipe to `jq` for filtering and processing. + +### Command Structure + +```bash +clojure-skills [global-options] [command-options] [arguments...] +``` + +**Main commands:** +- `db` - Database operations (init, sync, reset, stats) +- `skill` - Skill operations (search, list, show) +- `prompt` - Prompt operations (search, list, show, render) + +**Global options:** +- `-j, --json` - Output as JSON (default) +- `-H, --human` - Output in human-readable format +- `-?, --help` - Show help + +### Quick Reference + +**Most common commands:** + +```bash +# Search for skills +clojure-skills skill search "topic" + +# List skills in a category +clojure-skills skill list -c libraries/database + +# View a skill's content +clojure-skills skill show malli | jq -r '.data.content' + +# Search prompts +clojure-skills prompt search "agent" + +# Render a prompt as markdown +clojure-skills prompt render clojure_build + +# Database operations +clojure-skills db sync +clojure-skills db stats +``` ### Quick Start ```bash -# Get help +# Get help for any command clojure-skills --help +clojure-skills db --help +clojure-skills skill --help +clojure-skills prompt --help # Search for skills about a topic (returns JSON) +clojure-skills skill search "validation" clojure-skills skill search "validation" | jq '.count' # List all skills in a category +clojure-skills skill list -c libraries/database clojure-skills skill list -c libraries/database | jq '.skills[].name' # View a skill's full content (returns JSON) -clojure-skills skill show malli -c libraries/data_validation | jq '.data.content' +clojure-skills skill show malli -c libraries/data_validation +clojure-skills skill show malli | jq -r '.data.content' # Get database statistics +clojure-skills db stats clojure-skills db stats | jq '.database' + +# Render a prompt as plain markdown (new in v0.1.0) +clojure-skills prompt render clojure_build +clojure-skills prompt render clojure_skill_builder > my-prompt.md + +# Use human-readable output format +clojure-skills -H skill list +clojure-skills --human db stats ``` -**All commands output JSON** - pipe to `jq` for human-readable formatting or further processing. +**All commands output JSON by default** - pipe to `jq` for human-readable formatting or use `-H` flag for human-readable output. + +### Database Commands + +**Initialize and manage the database:** + +```bash +# Initialize database (first time setup) +clojure-skills db init + +# Sync skills and prompts from filesystem to database +clojure-skills db sync + +# Show database statistics +clojure-skills db stats + +# Reset database (WARNING: destructive - requires --force flag) +clojure-skills db reset --force +``` ### Searching Skills -**Basic search** - finds skills by content match (returns JSON): +**Full-text search using SQLite FTS5:** ```bash -# Search skills +# Search all skills (returns JSON) clojure-skills skill search "http server" # Search within a specific category clojure-skills skill search "query" -c libraries/database +clojure-skills skill search "validation" -c libraries/data_validation -# Limit results +# Limit number of results (default: 50) clojure-skills skill search "database" -n 10 +clojure-skills skill search "testing" --max-results 20 # Search prompts clojure-skills prompt search "agent" +clojure-skills prompt search "builder" ``` +**Search command options:** +- `-c, --category CATEGORY` - Filter by category (e.g., 'libraries/database') +- `-n, --max-results N` - Maximum results to return (default: 50) + **Example output (JSON):** ```json @@ -371,7 +467,7 @@ clojure-skills skill search "validation" | \ ### Listing Skills -**List all skills (returns JSON):** +**List all skills with metadata (returns JSON):** ```bash # Get all skills as JSON @@ -391,7 +487,8 @@ clojure-skills skill list | jq '[.skills[]."token-count"] | add' ```bash # Database skills -clojure-skills skill list -c libraries/database | jq '.skills' +clojure-skills skill list -c libraries/database +clojure-skills skill list --category libraries/database | jq '.skills' # Testing skills clojure-skills skill list -c testing | jq '.skills[].name' @@ -400,6 +497,9 @@ clojure-skills skill list -c testing | jq '.skills[].name' clojure-skills skill list -c language | jq '.skills' ``` +**List command options:** +- `-c, --category CATEGORY` - Filter by category + **Available categories:** ``` @@ -421,20 +521,95 @@ tooling/ - cider, clj-kondo, babashka **Show a skill's full content (returns JSON):** ```bash -# Basic usage (returns JSON) +# Basic usage - show skill by name (returns JSON) clojure-skills skill show malli +clojure-skills skill show next_jdbc # Specify category to avoid ambiguity clojure-skills skill show malli -c libraries/data_validation +clojure-skills skill show malli --category libraries/data_validation -# Extract just the content +# Extract just the content as markdown clojure-skills skill show malli | jq -r '.data.content' -# Get metadata +# Get metadata only clojure-skills skill show malli | jq '.data | {name, category, size: .size_bytes, tokens: .token_count}' + +# Extract specific fields +clojure-skills skill show malli | jq -r '.data.description' +clojure-skills skill show malli | jq '.data.token_count' +``` + +**Show command options:** +- `-c, --category CATEGORY` - Filter by category (useful when skill names are ambiguous) + +**Output is JSON with:** +- `.type` - Always "skill" +- `.data` - Skill object containing: + - `.content` - Full markdown content + - `.name` - Skill name + - `.category` - Category path + - `.size_bytes` - File size in bytes + - `.token_count` - Estimated token count + - `.created_at`, `.updated_at` - Timestamps + - `.file_hash` - SHA256 hash of content + +### Working with Prompts + +**Search prompts:** + +```bash +# Search all prompts +clojure-skills prompt search "agent" +clojure-skills prompt search "builder" + +# Limit results +clojure-skills prompt search "clojure" -n 5 +``` + +**List all prompts:** + +```bash +# Get all prompts as JSON +clojure-skills prompt list + +# Extract prompt names +clojure-skills prompt list | jq -r '.prompts[].name' + +# Count prompts +clojure-skills prompt list | jq '.count' +``` + +**Show prompt details (JSON):** + +```bash +# Show prompt with metadata and associated skills +clojure-skills prompt show clojure_build +clojure-skills prompt show clojure_skill_builder + +# Extract just the content +clojure-skills prompt show clojure_build | jq -r '.data.content' + +# View associated skills +clojure-skills prompt show clojure_build | jq '.data.skills' +``` + +**Render prompt as plain markdown:** + +```bash +# Render prompt with all skills composed together +clojure-skills prompt render clojure_build + +# Save to file +clojure-skills prompt render clojure_skill_builder > my-prompt.md + +# Combine with other tools +clojure-skills prompt render clojure_build | wc -l ``` -**Output is JSON with metadata and full markdown content in `.data.content`.** +**Difference between `show` and `render`:** +- `prompt show` - Returns JSON with metadata, content, and skill list +- `prompt render` - Returns plain markdown with all skills composed together (useful for copying to clipboard or saving to file) ### Database Statistics @@ -464,9 +639,39 @@ clojure-skills db stats | jq '{ - Configuration (database path, directories, settings) - Category breakdown with counts +### Output Formats + +**The CLI supports two output formats:** + +1. **JSON format** (default, `-j` or `--json`) + - Structured data for programmatic processing + - Easy to pipe to `jq` + - All fields available + +2. **Human-readable format** (`-H` or `--human`) + - Formatted for terminal display + - Tables and readable layout + - Useful for quick browsing + +**Examples:** + +```bash +# JSON output (default) +clojure-skills skill list +clojure-skills -j skill list # Explicit + +# Human-readable output +clojure-skills -H skill list +clojure-skills --human db stats + +# Format applies to all subcommands +clojure-skills -H skill search "validation" +clojure-skills -H prompt show clojure_build +``` + ### JSON Output and jq Integration -**All CLI commands output structured JSON** making it easy to process programmatically or pipe to `jq`. +**All CLI commands output structured JSON by default** making it easy to process programmatically or pipe to `jq`. **Common jq patterns:** @@ -486,6 +691,9 @@ clojure-skills skill list | jq '[.skills[]."token-count"] | add' # Format as table clojure-skills skill list | jq -r '.skills[] | "\(.name)\t\(.category)"' + +# Pretty print with colors +clojure-skills skill show malli | jq '.' ``` **Example: Find all database-related skills** @@ -495,6 +703,18 @@ clojure-skills skill list | \ jq '.skills[] | select(.category | contains("database")) | {name, tokens: ."token-count"}' ``` +**Example: Compare skill sizes** + +```bash +# Top 10 largest skills by token count +clojure-skills skill list | \ + jq -r '.skills | sort_by(."token-count") | reverse | .[0:10] | .[] | "\(."token-count")\t\(.name)"' + +# Skills over 3000 tokens +clojure-skills skill list | \ + jq '.skills[] | select(."token-count" > 3000) | {name, tokens: ."token-count"}' +``` + **Testing JSON output:** ```bash @@ -503,6 +723,31 @@ clojure-skills skill list | \ # Tests all commands work correctly with jq ``` +### CLI Command Reference + +**Complete command reference table:** + +| Command | Description | Options | +|---------|-------------|---------| +| **Database Commands** | +| `db init` | Initialize database with schema | None | +| `db sync` | Sync skills/prompts from filesystem | None | +| `db stats` | Show database statistics | None | +| `db reset --force` | Reset database (destructive) | `--force` (required) | +| **Skill Commands** | +| `skill search QUERY` | Search skills using FTS5 | `-c, --category`, `-n, --max-results` | +| `skill list` | List all skills | `-c, --category` | +| `skill show NAME` | Display skill content | `-c, --category` | +| **Prompt Commands** | +| `prompt search QUERY` | Search prompts using FTS5 | `-n, --max-results` | +| `prompt list` | List all prompts | None | +| `prompt show NAME` | Display prompt with metadata | None | +| `prompt render NAME` | Render prompt as plain markdown | None | +| **Global Options** | +| `-j, --json` | Output as JSON (default) | All commands | +| `-H, --human` | Output in human-readable format | All commands | +| `-?, --help` | Show help | All commands | + ### Database Management **Sync skills from filesystem:** @@ -510,12 +755,71 @@ clojure-skills skill list | \ ```bash # After adding or modifying skill files clojure-skills db sync + +# Check sync results +clojure-skills db stats ``` **Reset database (destructive):** ```bash +# Requires --force flag for safety clojure-skills db reset --force + +# Re-initialize after reset +clojure-skills db init +clojure-skills db sync +``` + +### Common CLI Issues + +**"No such skill found"** + +```bash +# Problem: Skill name might be ambiguous or in a different category +clojure-skills skill show http_kit +# ERROR: Multiple skills found with name 'http_kit' + +# Solution: Specify category +clojure-skills skill show http_kit -c http_servers +``` + +**"Database not initialized"** + +```bash +# Problem: Database hasn't been created yet +clojure-skills skill search "test" +# ERROR: Database file not found + +# Solution: Initialize and sync +clojure-skills db init +clojure-skills db sync +``` + +**"Empty search results"** + +```bash +# Problem: Search term too specific or no matches +clojure-skills skill search "nonexistent-library" +# Returns: {"count": 0, "skills": []} + +# Solution: Try broader search terms +clojure-skills skill search "database" +clojure-skills skill search "validation" +``` + +**"jq: parse error"** + +```bash +# Problem: Command doesn't output JSON +clojure-skills prompt render clojure_build | jq '.' +# ERROR: parse error (render outputs plain markdown, not JSON) + +# Solution: Use show instead for JSON output +clojure-skills prompt show clojure_build | jq '.' + +# Or don't pipe render to jq +clojure-skills prompt render clojure_build > output.md ``` ### Command Permissions