@tyvm/knowhow 0.0.128 → 0.0.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/package.json +5 -4
  2. package/scripts/cache-miss-debug.ts +365 -0
  3. package/src/agents/base/base.ts +159 -59
  4. package/src/agents/tools/execCommand.ts +26 -6
  5. package/src/agents/tools/index.ts +1 -0
  6. package/src/agents/tools/list.ts +2 -0
  7. package/src/agents/tools/minimal/callTool.ts +45 -0
  8. package/src/agents/tools/minimal/definitions.ts +50 -0
  9. package/src/agents/tools/minimal/index.ts +3 -0
  10. package/src/agents/tools/minimal/inspectTools.ts +27 -0
  11. package/src/agents/tools/skills.ts +79 -0
  12. package/src/chat/modules/AgentModule.ts +111 -9
  13. package/src/cli.ts +4 -0
  14. package/src/clients/anthropic.ts +59 -1
  15. package/src/clients/contextLimits.ts +9 -0
  16. package/src/clients/index.ts +6 -0
  17. package/src/clients/meta.ts +18 -0
  18. package/src/clients/openai.ts +28 -5
  19. package/src/clients/pricing/anthropic.ts +13 -0
  20. package/src/clients/pricing/fireworks.ts +18 -12
  21. package/src/clients/pricing/index.ts +1 -0
  22. package/src/clients/pricing/meta.ts +12 -0
  23. package/src/clients/pricing/openai.ts +16 -0
  24. package/src/clients/pricing/xai.ts +3 -0
  25. package/src/clients/types.ts +27 -4
  26. package/src/clients/xai.ts +2 -2
  27. package/src/commands/agent.ts +19 -0
  28. package/src/commands/behaviors.ts +47 -0
  29. package/src/commands/services.ts +7 -3
  30. package/src/commands/skills.ts +71 -0
  31. package/src/index.ts +2 -0
  32. package/src/plugins/PluginBase.ts +0 -2
  33. package/src/plugins/SkillsPlugin.ts +11 -124
  34. package/src/plugins/language.ts +0 -1
  35. package/src/plugins/plugins.ts +24 -13
  36. package/src/plugins/types.ts +8 -2
  37. package/src/processors/MinimalToolsMessageProcessor.ts +113 -0
  38. package/src/processors/TokenCompressor.ts +21 -4
  39. package/src/processors/index.ts +7 -1
  40. package/src/services/AgentSyncFs.ts +49 -0
  41. package/src/services/AgentSyncKnowhowWeb.ts +1 -1
  42. package/src/services/BehaviorsService.ts +432 -0
  43. package/src/services/KnowhowClient.ts +62 -1
  44. package/src/services/LazyToolsService.ts +12 -0
  45. package/src/services/MinimalToolsService.ts +167 -0
  46. package/src/services/SkillsService.ts +622 -0
  47. package/src/services/Tools.ts +11 -0
  48. package/src/services/index.ts +9 -2
  49. package/src/services/modules/types.ts +2 -0
  50. package/src/worker.ts +7 -0
  51. package/tests/processors/TokenCompressor.test.ts +45 -0
  52. package/tests/unit/services/MinimalToolsService.test.ts +424 -0
  53. package/ts_build/package.json +5 -4
  54. package/ts_build/src/agents/base/base.d.ts +13 -2
  55. package/ts_build/src/agents/base/base.js +91 -35
  56. package/ts_build/src/agents/base/base.js.map +1 -1
  57. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  58. package/ts_build/src/agents/tools/execCommand.js +18 -5
  59. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  60. package/ts_build/src/agents/tools/index.d.ts +1 -0
  61. package/ts_build/src/agents/tools/index.js +1 -0
  62. package/ts_build/src/agents/tools/index.js.map +1 -1
  63. package/ts_build/src/agents/tools/list.js +2 -0
  64. package/ts_build/src/agents/tools/list.js.map +1 -1
  65. package/ts_build/src/agents/tools/minimal/callTool.d.ts +5 -0
  66. package/ts_build/src/agents/tools/minimal/callTool.js +28 -0
  67. package/ts_build/src/agents/tools/minimal/callTool.js.map +1 -0
  68. package/ts_build/src/agents/tools/minimal/definitions.d.ts +4 -0
  69. package/ts_build/src/agents/tools/minimal/definitions.js +46 -0
  70. package/ts_build/src/agents/tools/minimal/definitions.js.map +1 -0
  71. package/ts_build/src/agents/tools/minimal/index.d.ts +3 -0
  72. package/ts_build/src/agents/tools/minimal/index.js +20 -0
  73. package/ts_build/src/agents/tools/minimal/index.js.map +1 -0
  74. package/ts_build/src/agents/tools/minimal/inspectTools.d.ts +2 -0
  75. package/ts_build/src/agents/tools/minimal/inspectTools.js +15 -0
  76. package/ts_build/src/agents/tools/minimal/inspectTools.js.map +1 -0
  77. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  78. package/ts_build/src/agents/tools/skills.js +62 -0
  79. package/ts_build/src/agents/tools/skills.js.map +1 -0
  80. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  81. package/ts_build/src/chat/modules/AgentModule.js +77 -2
  82. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  83. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  84. package/ts_build/src/cli.js +4 -0
  85. package/ts_build/src/cli.js.map +1 -1
  86. package/ts_build/src/clients/anthropic.d.ts +2 -0
  87. package/ts_build/src/clients/anthropic.js +35 -0
  88. package/ts_build/src/clients/anthropic.js.map +1 -1
  89. package/ts_build/src/clients/contextLimits.js +8 -0
  90. package/ts_build/src/clients/contextLimits.js.map +1 -1
  91. package/ts_build/src/clients/index.d.ts +1 -0
  92. package/ts_build/src/clients/index.js +6 -0
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/meta.d.ts +4 -0
  95. package/ts_build/src/clients/meta.js +15 -0
  96. package/ts_build/src/clients/meta.js.map +1 -0
  97. package/ts_build/src/clients/openai.d.ts +1 -0
  98. package/ts_build/src/clients/openai.js +25 -5
  99. package/ts_build/src/clients/openai.js.map +1 -1
  100. package/ts_build/src/clients/pricing/anthropic.d.ts +4 -0
  101. package/ts_build/src/clients/pricing/anthropic.js +12 -0
  102. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/pricing/fireworks.d.ts +2 -5
  104. package/ts_build/src/clients/pricing/fireworks.js +13 -11
  105. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  106. package/ts_build/src/clients/pricing/index.d.ts +1 -0
  107. package/ts_build/src/clients/pricing/index.js +5 -2
  108. package/ts_build/src/clients/pricing/index.js.map +1 -1
  109. package/ts_build/src/clients/pricing/meta.d.ts +6 -0
  110. package/ts_build/src/clients/pricing/meta.js +8 -0
  111. package/ts_build/src/clients/pricing/meta.js.map +1 -0
  112. package/ts_build/src/clients/pricing/openai.d.ts +3 -0
  113. package/ts_build/src/clients/pricing/openai.js +15 -0
  114. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  115. package/ts_build/src/clients/pricing/xai.d.ts +1 -0
  116. package/ts_build/src/clients/pricing/xai.js +3 -0
  117. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  118. package/ts_build/src/clients/types.d.ts +6 -1
  119. package/ts_build/src/clients/xai.js +1 -1
  120. package/ts_build/src/clients/xai.js.map +1 -1
  121. package/ts_build/src/commands/agent.js +16 -0
  122. package/ts_build/src/commands/agent.js.map +1 -1
  123. package/ts_build/src/commands/behaviors.d.ts +2 -0
  124. package/ts_build/src/commands/behaviors.js +43 -0
  125. package/ts_build/src/commands/behaviors.js.map +1 -0
  126. package/ts_build/src/commands/services.js +4 -2
  127. package/ts_build/src/commands/services.js.map +1 -1
  128. package/ts_build/src/commands/skills.d.ts +2 -0
  129. package/ts_build/src/commands/skills.js +61 -0
  130. package/ts_build/src/commands/skills.js.map +1 -0
  131. package/ts_build/src/index.d.ts +2 -0
  132. package/ts_build/src/index.js +5 -1
  133. package/ts_build/src/index.js.map +1 -1
  134. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  135. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  136. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  137. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  138. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  139. package/ts_build/src/plugins/language.js.map +1 -1
  140. package/ts_build/src/plugins/plugins.d.ts +4 -1
  141. package/ts_build/src/plugins/plugins.js +17 -11
  142. package/ts_build/src/plugins/plugins.js.map +1 -1
  143. package/ts_build/src/plugins/types.d.ts +3 -2
  144. package/ts_build/src/processors/MinimalToolsMessageProcessor.d.ts +7 -0
  145. package/ts_build/src/processors/MinimalToolsMessageProcessor.js +61 -0
  146. package/ts_build/src/processors/MinimalToolsMessageProcessor.js.map +1 -0
  147. package/ts_build/src/processors/TokenCompressor.js +5 -2
  148. package/ts_build/src/processors/TokenCompressor.js.map +1 -1
  149. package/ts_build/src/processors/index.d.ts +2 -1
  150. package/ts_build/src/processors/index.js +3 -1
  151. package/ts_build/src/processors/index.js.map +1 -1
  152. package/ts_build/src/services/AgentSyncFs.d.ts +2 -0
  153. package/ts_build/src/services/AgentSyncFs.js +30 -0
  154. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  155. package/ts_build/src/services/AgentSyncKnowhowWeb.js +1 -1
  156. package/ts_build/src/services/BehaviorsService.d.ts +46 -0
  157. package/ts_build/src/services/BehaviorsService.js +358 -0
  158. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  159. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  160. package/ts_build/src/services/KnowhowClient.js +20 -0
  161. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  162. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  163. package/ts_build/src/services/MinimalToolsService.d.ts +14 -0
  164. package/ts_build/src/services/MinimalToolsService.js +80 -0
  165. package/ts_build/src/services/MinimalToolsService.js.map +1 -0
  166. package/ts_build/src/services/SkillsService.d.ts +53 -0
  167. package/ts_build/src/services/SkillsService.js +478 -0
  168. package/ts_build/src/services/SkillsService.js.map +1 -0
  169. package/ts_build/src/services/Tools.d.ts +2 -0
  170. package/ts_build/src/services/Tools.js.map +1 -1
  171. package/ts_build/src/services/index.d.ts +4 -0
  172. package/ts_build/src/services/index.js +9 -2
  173. package/ts_build/src/services/index.js.map +1 -1
  174. package/ts_build/src/services/modules/index.d.ts +4 -2
  175. package/ts_build/src/services/modules/types.d.ts +2 -0
  176. package/ts_build/src/types.d.ts +8 -0
  177. package/ts_build/src/worker.js +5 -0
  178. package/ts_build/src/worker.js.map +1 -1
  179. package/ts_build/tests/processors/TokenCompressor.test.js +27 -0
  180. package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
  181. package/ts_build/tests/unit/services/MinimalToolsService.test.d.ts +1 -0
  182. package/ts_build/tests/unit/services/MinimalToolsService.test.js +320 -0
  183. package/ts_build/tests/unit/services/MinimalToolsService.test.js.map +1 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Meta Model API pricing (USD per 1M tokens)
3
+ * Source: Meta Model API docs — https://api.meta.ai
4
+ * New accounts start with $20 in free credits.
5
+ */
6
+ export const MetaTextPricing: Record<string, { input: number; output: number; cached_input?: number }> = {
7
+ "muse-spark-1.1": { input: 1.25, output: 4.25, cached_input: 0.15 },
8
+ };
9
+
10
+ // Web search grounding is billed per 1,000 search queries ($2.50/1,000),
11
+ // not per-token, so it isn't represented in the per-token pricing map above.
12
+ export const MetaWebSearchPricingPer1000Queries = 2.5;
@@ -16,6 +16,9 @@ import {
16
16
  // ─── Model IDs ────────────────────────────────────────────────────────────────
17
17
 
18
18
  export const OpenAiModels = {
19
+ GPT_56_Sol: "gpt-5.6-sol",
20
+ GPT_56_Terra: "gpt-5.6-terra",
21
+ GPT_56_Luna: "gpt-5.6-luna",
19
22
  GPT_55: "gpt-5.5",
20
23
  GPT_55_Pro: "gpt-5.5-pro",
21
24
  GPT_54: "gpt-5.4",
@@ -95,6 +98,9 @@ export const OpenAiEmbeddingModels: Record<string, string> = {
95
98
 
96
99
  export const OpenAiReasoningModels: string[] = [
97
100
  OpenAiModels.GPT_5,
101
+ OpenAiModels.GPT_56_Sol,
102
+ OpenAiModels.GPT_56_Terra,
103
+ OpenAiModels.GPT_56_Luna,
98
104
  OpenAiModels.GPT_5_Pro,
99
105
  OpenAiModels.GPT_5_Mini,
100
106
  OpenAiModels.GPT_5_Nano,
@@ -125,6 +131,9 @@ export const OpenAiChatModels: string[] = [
125
131
  OpenAiModels.GPT_41,
126
132
  OpenAiModels.GPT_41_Mini,
127
133
  OpenAiModels.GPT_41_Nano,
134
+ OpenAiModels.GPT_56_Sol,
135
+ OpenAiModels.GPT_56_Terra,
136
+ OpenAiModels.GPT_56_Luna,
128
137
  OpenAiModels.GPT_54,
129
138
  OpenAiModels.GPT_54_Mini,
130
139
  OpenAiModels.GPT_55,
@@ -158,6 +167,9 @@ export const OpenAiResponsesOnlyModels: string[] = [
158
167
  OpenAiModels.GPT_55_Pro,
159
168
  OpenAiModels.GPT_55,
160
169
  OpenAiModels.GPT_5_Pro,
170
+ OpenAiModels.GPT_56_Sol,
171
+ OpenAiModels.GPT_56_Terra,
172
+ OpenAiModels.GPT_56_Luna,
161
173
  OpenAiModels.o1,
162
174
  OpenAiModels.o1_Pro,
163
175
  ];
@@ -218,6 +230,10 @@ export const OpenAiSearchModels: string[] = [
218
230
  // ─── Pricing (USD per 1M tokens) ──────────────────────────────────────────────
219
231
 
220
232
  export const OpenAiTextPricing: Record<string, ModelPricing> = {
233
+ // gpt-5.6 series — short context / long context pricing
234
+ [OpenAiModels.GPT_56_Sol]: { input: 5.0, cached_input: 0.5, cache_write: 6.25, output: 30.0, input_gt_200k: 10.0, output_gt_200k: 45.0 },
235
+ [OpenAiModels.GPT_56_Terra]: { input: 2.5, cached_input: 0.25, cache_write: 3.125, output: 15.0, input_gt_200k: 5.0, output_gt_200k: 22.5 },
236
+ [OpenAiModels.GPT_56_Luna]: { input: 1.0, cached_input: 0.10, cache_write: 1.25, output: 6.0, input_gt_200k: 2.0, output_gt_200k: 9.0 },
221
237
  [OpenAiModels.GPT_55]: { input: 5.0, cached_input: 0.5, output: 30.0 },
222
238
  [OpenAiModels.GPT_55_Pro]: { input: 30.0, cached_input: 0, output: 180.0, reasoningLevels: ["medium", "high", "xhigh"] },
223
239
  [OpenAiModels.GPT_54]: { input: 2.5, cached_input: 0.25, output: 15.0 },
@@ -10,6 +10,7 @@ export const XaiModels = {
10
10
  Grok_4_20_Reasoning: "grok-4.20-0309-reasoning",
11
11
  Grok_4_20_NonReasoning: "grok-4.20-0309-non-reasoning",
12
12
  Grok_4_20_MultiAgent: "grok-4.20-multi-agent-0309",
13
+ Grok4_5: "grok-4.5",
13
14
  Grok4_1_Fast_Reasoning: "grok-4-1-fast-reasoning",
14
15
  Grok4_1_Fast_NonReasoning: "grok-4-1-fast-non-reasoning",
15
16
  GrokCodeFast: "grok-code-fast-1",
@@ -44,6 +45,7 @@ export const XaiTextModels: string[] = [
44
45
  XaiModels.Grok_4_20_Reasoning, XaiModels.Grok_4_20_NonReasoning,
45
46
  XaiModels.Grok_4_20_MultiAgent,
46
47
  XaiModels.Grok4_1_Fast_Reasoning, XaiModels.Grok4_1_Fast_NonReasoning,
48
+ XaiModels.Grok4_5,
47
49
  XaiModels.GrokCodeFast, XaiModels.Grok4,
48
50
  XaiModels.Grok3Beta, XaiModels.Grok3MiniBeta, XaiModels.Grok3FastBeta, XaiModels.Grok3MiniFastBeta,
49
51
  ];
@@ -87,6 +89,7 @@ export const XaiTextPricing: Record<string, ModelPricing> = {
87
89
  [XaiModels.Grok_4_20_MultiAgent]: { input: 2.0, cache_hit: 0.20, output: 6.0, reasoningLevels: ["low", "medium", "high", "xhigh"], useResponsesApi: true },
88
90
  [XaiModels.Grok4_1_Fast_Reasoning]: { input: 0.2, cache_hit: 0.05, output: 0.5, useResponsesApi: true },
89
91
  [XaiModels.Grok4_1_Fast_NonReasoning]: { input: 0.2, cache_hit: 0.05, output: 0.5, useResponsesApi: true },
92
+ [XaiModels.Grok4_5]: { input: 2.0, cache_hit: 0.50, output: 6.0 },
90
93
  [XaiModels.GrokCodeFast]: { input: 0.2, cache_hit: 0.02, output: 1.5 },
91
94
  [XaiModels.Grok4]: { input: 3.0, output: 15.0 },
92
95
  [XaiModels.Grok3Beta]: { input: 3.0, output: 15.0 },
@@ -86,10 +86,26 @@ export interface CompletionOptions extends RetryOptions {
86
86
  tools?: Tool[];
87
87
  tool_choice?: "auto" | "none";
88
88
  max_tokens?: number;
89
- /** Reasoning effort level for models that support it.
90
- * Maps to: OpenAI reasoning_effort, xAI reasoning.effort, Gemini thinkingLevel/thinkingBudget, Anthropic thinking budget.
91
- * "low" = minimal thinking, "medium" = balanced, "high" = maximum reasoning */
92
- reasoning_effort?: "low" | "medium" | "high";
89
+ /**
90
+ * Reasoning effort level for models that support it.
91
+ * Maps to: OpenAI reasoning_effort/reasoning.effort, xAI reasoning.effort,
92
+ * Gemini thinkingLevel/thinkingBudget, Anthropic thinking mode.
93
+ *
94
+ * "none" = disable thinking entirely (Anthropic: thinking.type="disabled",
95
+ * OpenAI: reasoning.effort="none"). Only supported by models that
96
+ * allow disabling thinking (e.g. claude-sonnet-5).
97
+ * "low" = minimal thinking
98
+ * "medium" = balanced (default for most models)
99
+ * "high" = maximum reasoning
100
+ */
101
+ reasoning_effort?: "none" | "low" | "medium" | "high";
102
+ /**
103
+ * When true, request a summarized view of the model's thinking process.
104
+ * - Anthropic: thinking.display = "summarized"
105
+ * - OpenAI: reasoning.summary = "auto"
106
+ * Defaults to false (no summary displayed).
107
+ */
108
+ reasoning_summary?: boolean;
93
109
  /**
94
110
  * When true, hints to the client that this task is long-running and it should
95
111
  * use a long-TTL cache where available.
@@ -115,6 +131,13 @@ export interface TokenUsage {
115
131
  output_tokens?: number;
116
132
  /** Convenience total (prompt + completion) */
117
133
  total_tokens?: number;
134
+ /** Breakdown of output tokens — thinking/reasoning tokens are billed as output
135
+ * but should NOT be counted toward the visible-content heuristics. */
136
+ output_tokens_details?: {
137
+ /** Anthropic: thinking_tokens, OpenAI: reasoning_tokens */
138
+ thinking_tokens?: number;
139
+ reasoning_tokens?: number;
140
+ };
118
141
  /** Cache details */
119
142
  prompt_tokens_details?: {
120
143
  /** Tokens served from the prompt cache (reduces cost) */
@@ -76,9 +76,9 @@ export class GenericXAIClient implements GenericClient {
76
76
  model: options.model,
77
77
  messages: xaiMessages,
78
78
  max_tokens: options.max_tokens,
79
- ...(XaiReasoningModels.includes(options.model) && options.reasoning_effort && {
79
+ ...(XaiReasoningModels.includes(options.model) && options.reasoning_effort && options.reasoning_effort !== "none" && {
80
80
  // grok-3-mini models support reasoning_effort: "low" | "medium" | "high"
81
- reasoning_effort: options.reasoning_effort,
81
+ reasoning_effort: options.reasoning_effort as "low" | "medium" | "high",
82
82
  }),
83
83
  ...(options.tools && {
84
84
  tools: options.tools,
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { readPromptFile } from "../ai";
3
3
  import { AgentModule } from "../chat/modules/AgentModule";
4
+ import { BehaviorsService, Behavior } from "../services/BehaviorsService";
4
5
  import { AskModule } from "../chat/modules/AskModule";
5
6
  import { SearchModule } from "../chat/modules/SearchModule";
6
7
  import { SessionsModule } from "../chat/modules/SessionsModule";
@@ -131,11 +132,29 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
131
132
  }
132
133
 
133
134
  await agentModule.initialize(chatService);
135
+
136
+ // Match a behavior from local disk (.knowhow/behaviors/)
137
+ let behaviorSystemPrompt: string | undefined;
138
+ let behaviorModel: string | undefined;
139
+ const behaviorsSvc = new BehaviorsService();
140
+ const matchedBehavior = behaviorsSvc.matchBehaviorLocal(input);
141
+ if (matchedBehavior) {
142
+ console.log(`🎯 Matched behavior: ${matchedBehavior.name}`);
143
+ if (matchedBehavior.instructions) {
144
+ behaviorSystemPrompt = matchedBehavior.instructions;
145
+ }
146
+ if (matchedBehavior.model) {
147
+ behaviorModel = matchedBehavior.model;
148
+ }
149
+ }
150
+
134
151
  const { taskCompleted } = await agentModule.setupAgent({
135
152
  ...options,
136
153
  input,
137
154
  maxTimeLimit: parseInt(options.maxTimeLimit, 10),
138
155
  maxSpendLimit: parseFloat(options.maxSpendLimit),
156
+ ...(behaviorSystemPrompt ? { systemPrompt: behaviorSystemPrompt } : {}),
157
+ ...(behaviorModel ? { model: behaviorModel } : {}),
139
158
  run: true,
140
159
  });
141
160
  await taskCompleted;
@@ -0,0 +1,47 @@
1
+ import { Command } from "commander";
2
+ import { BehaviorsService } from "../services/BehaviorsService";
3
+
4
+ export function addBehaviorsCommand(program: Command): void {
5
+ const behaviors = program
6
+ .command("behaviors")
7
+ .description("Manage org behaviors from the CLI");
8
+
9
+ behaviors
10
+ .command("list")
11
+ .description("List behaviors from backend")
12
+ .option("--skills-only", "Only show skill behaviors")
13
+ .option("--include-internal", "Include platform skills")
14
+ .action(async (opts) => {
15
+ const svc = new BehaviorsService();
16
+ await svc.list({
17
+ skillsOnly: opts.skillsOnly,
18
+ includeInternal: opts.includeInternal,
19
+ });
20
+ });
21
+
22
+ behaviors
23
+ .command("download")
24
+ .description("Download behaviors from backend to .knowhow/behaviors/")
25
+ .option("--skills-only", "Only download skill behaviors")
26
+ .option("--include-internal", "Include platform skills")
27
+ .option(
28
+ "--md",
29
+ "Save as Markdown files with YAML frontmatter instead of JSON"
30
+ )
31
+ .action(async (opts) => {
32
+ const svc = new BehaviorsService();
33
+ await svc.download({
34
+ skillsOnly: opts.skillsOnly,
35
+ includeInternal: opts.includeInternal,
36
+ md: opts.md,
37
+ });
38
+ });
39
+
40
+ behaviors
41
+ .command("upload")
42
+ .description("Upload behaviors from .knowhow/behaviors/ to backend")
43
+ .action(async () => {
44
+ const svc = new BehaviorsService();
45
+ await svc.upload();
46
+ });
47
+ }
@@ -1,8 +1,9 @@
1
1
  import { includedTools } from "../agents/tools/list";
2
2
  import * as allTools from "../agents/tools";
3
- import { LazyToolsService, services } from "../services";
3
+ import { LazyToolsService, services, MinimalToolsService } from "../services";
4
4
  import { agents } from "../agents";
5
5
  import { ModulesService } from "../services/modules";
6
+ import { getConfig } from "../config";
6
7
 
7
8
  /**
8
9
  * Shared service setup used by commands that need full services (chat, agent, worker, etc.)
@@ -17,12 +18,14 @@ export async function setupServices() {
17
18
  Plugins,
18
19
  Events,
19
20
  MediaProcessor,
21
+ Behaviors,
20
22
  } = services();
21
23
 
22
-
23
24
  // cli uses LazyTools to keep context slim
24
25
  const Tools = new LazyToolsService();
26
+ await Behaviors.initFromConfig();
25
27
 
28
+ // Give LazyToolsService the same context that ToolService had
26
29
  Tools.setContext({
27
30
  ...AllTools.getContext(),
28
31
  });
@@ -70,7 +73,8 @@ export async function setupServices() {
70
73
  Clients,
71
74
  Tools,
72
75
  MediaProcessor,
73
- Events
76
+ Behaviors,
77
+ Events,
74
78
  });
75
79
 
76
80
  return { Tools, Clients };
@@ -0,0 +1,71 @@
1
+ import { Command } from "commander";
2
+ import { SkillsService } from "../services/SkillsService";
3
+
4
+ export function addSkillsCommand(program: Command): void {
5
+ const skills = program
6
+ .command("skills")
7
+ .description("Manage agent skills from the skills.sh ecosystem");
8
+
9
+ skills
10
+ .command("add <package>")
11
+ .description(
12
+ "Add a skill. Accepts:\n" +
13
+ " owner/repo@skill-name\n" +
14
+ " https://github.com/owner/repo (with --skill <name>)\n" +
15
+ " https://github.com/owner/repo@skill-name"
16
+ )
17
+ .option("--skill <name>", "Skill name when using a GitHub URL as the package argument")
18
+ .option("--global", "Install skill globally to ~/.knowhow/skills/")
19
+ .option(
20
+ "--install-dir <dir>",
21
+ "Custom install directory (overrides default and --global)"
22
+ )
23
+ .action(async (pkg: string, opts: { skill?: string; global?: boolean; installDir?: string }) => {
24
+ const svc = new SkillsService({
25
+ global: opts.global,
26
+ installDir: opts.installDir,
27
+ });
28
+ await svc.add(pkg, opts.skill);
29
+ });
30
+
31
+ skills
32
+ .command("remove <skill>")
33
+ .alias("rm")
34
+ .description("Remove an installed skill by name")
35
+ .option("--global", "Remove from global ~/.knowhow/skills/")
36
+ .action((skillName: string, opts: { global?: boolean }) => {
37
+ const svc = new SkillsService({ global: opts.global });
38
+ svc.remove(skillName);
39
+ });
40
+
41
+ skills
42
+ .command("list")
43
+ .alias("ls")
44
+ .description("List installed skills from the lock file")
45
+ .option("--global", "List skills from global ~/.knowhow/skills/")
46
+ .action((opts: { global?: boolean }) => {
47
+ const svc = new SkillsService({ global: opts.global });
48
+ svc.list();
49
+ });
50
+
51
+ skills
52
+ .command("install")
53
+ .description(
54
+ "Install all skills from skills-lock.json (restores from lock file)"
55
+ )
56
+ .option("--global", "Install from global ~/.knowhow/skills/skills-lock.json")
57
+ .action(async (opts: { global?: boolean }) => {
58
+ const svc = new SkillsService({ global: opts.global });
59
+ await svc.install();
60
+ });
61
+
62
+ skills
63
+ .command("update [skills...]")
64
+ .alias("upgrade")
65
+ .description("Update installed skills to their latest versions")
66
+ .option("--global", "Update skills in global ~/.knowhow/skills/")
67
+ .action(async (skillNames: string[], opts: { global?: boolean }) => {
68
+ const svc = new SkillsService({ global: opts.global });
69
+ await svc.update(skillNames);
70
+ });
71
+ }
package/src/index.ts CHANGED
@@ -60,6 +60,8 @@ export { ConversionService } from "./services/conversion/ConversionService";
60
60
  // Export plugin types for external plugins
61
61
  export { PluginBase } from "./plugins/PluginBase";
62
62
  export { PluginMeta, Plugin, PluginContext } from "./plugins/types";
63
+ export { PluginService } from "./plugins/plugins";
64
+ export { SkillsPlugin } from "./plugins/SkillsPlugin";
63
65
  // Export embedding types
64
66
  export { MinimalEmbedding, Embeddable } from "./types";
65
67
 
@@ -1,4 +1,3 @@
1
- import { MinimalEmbedding } from "../types";
2
1
  import { Plugin, PluginContext, PluginMeta } from "./types";
3
2
 
4
3
  export abstract class PluginBase implements Plugin {
@@ -66,7 +65,6 @@ export abstract class PluginBase implements Plugin {
66
65
  /** Mandatory plugin actions ---------------------------------------- */
67
66
  /* ------------------------------------------------------------------ */
68
67
  abstract call(input?: string): Promise<string>;
69
- abstract embed(input: string): Promise<MinimalEmbedding[]>;
70
68
  }
71
69
 
72
70
  export { PluginMeta, Plugin } from "./types";
@@ -1,33 +1,11 @@
1
- import { promises as fs, Dirent } from "fs";
2
- import * as path from "path";
3
1
  import { PluginBase, PluginMeta } from "./PluginBase";
4
2
  import { Plugin, PluginContext } from "./types";
5
- import { MinimalEmbedding } from "../types";
6
- import { getConfig } from "../config";
7
-
8
- interface SkillMeta {
9
- name: string;
10
- description: string;
11
- filePath: string;
12
- }
13
-
14
- function parseFrontmatter(content: string): Record<string, string> {
15
- const match = content.match(/^---\n([\s\S]*?)\n---/);
16
- if (!match) return {};
17
- const result: Record<string, string> = {};
18
- for (const line of match[1].split("\n")) {
19
- const [key, ...rest] = line.split(":");
20
- if (key && rest.length) result[key.trim()] = rest.join(":").trim();
21
- }
22
- return result;
23
- }
24
3
 
25
4
  export class SkillsPlugin extends PluginBase implements Plugin {
26
5
  static readonly meta: PluginMeta = {
27
6
  key: "skills",
28
7
  name: "Skills Plugin",
29
- description:
30
- "Scans configured skills directories for SKILL.md files and provides skill content to agents",
8
+ description: "Lists available skills",
31
9
  requires: [],
32
10
  };
33
11
 
@@ -37,114 +15,23 @@ export class SkillsPlugin extends PluginBase implements Plugin {
37
15
  super(context);
38
16
  }
39
17
 
40
- async call(input?: string): Promise<string> {
41
- const result = await this.embed(input || "");
42
- return result.map((e) => e.text).join("\n\n");
43
- }
44
-
45
- async embed(userPrompt: string): Promise<MinimalEmbedding[]> {
46
- const config = await getConfig();
47
- const skillDirs: string[] = (config as any).skills || [];
48
-
49
- if (skillDirs.length === 0) {
50
- return [];
51
- }
52
-
53
- const skills = await this.scanSkills(skillDirs);
18
+ /**
19
+ * Returns a summary list of in-memory skills (name + description).
20
+ * This is called by the plugin system to inject context into agent prompts.
21
+ * Skills must be loaded into memory first via Behaviors.initFromDisk() at startup.
22
+ */
23
+ async call(_input?: string): Promise<string> {
24
+ const { Behaviors } = this.context;
25
+ const skills = Behaviors.listSkills();
54
26
 
55
27
  if (skills.length === 0) {
56
- return [];
57
- }
58
-
59
- // Check if any skill name appears in the prompt
60
- const matchedSkills = skills.filter((skill) =>
61
- userPrompt.toLowerCase().includes(skill.name.toLowerCase())
62
- );
63
-
64
- if (matchedSkills.length > 0) {
65
- // Return full skill content for matched skills
66
- const results: MinimalEmbedding[] = [];
67
- for (const skill of matchedSkills) {
68
- try {
69
- const content = await fs.readFile(skill.filePath, "utf-8");
70
- results.push({
71
- id: skill.filePath,
72
- text: `## Skill: ${skill.name}\nFile: ${skill.filePath}\n\n${content}`,
73
- metadata: { filePath: skill.filePath },
74
- });
75
- } catch {
76
- // Skip unreadable files
77
- }
78
- }
79
- return results;
80
- }
81
-
82
- // Return skill discovery summary
83
- const summary = this.buildSkillSummary(skills);
84
- return [
85
- {
86
- id: "skills:summary",
87
- text: summary,
88
- metadata: {},
89
- },
90
- ];
91
- }
92
-
93
- private async scanSkills(dirs: string[]): Promise<SkillMeta[]> {
94
- const skills: SkillMeta[] = [];
95
- for (const dir of dirs) {
96
- const resolvedDir = dir.replace(/^~/, process.env.HOME || "");
97
- try {
98
- const found = await this.findSkillFiles(resolvedDir);
99
- skills.push(...found);
100
- } catch {
101
- // Skip directories that don't exist or can't be read
102
- }
103
- }
104
- return skills;
105
- }
106
-
107
- private async findSkillFiles(dir: string): Promise<SkillMeta[]> {
108
- const skills: SkillMeta[] = [];
109
- let entries: Dirent[];
110
- try {
111
- entries = await fs.readdir(dir, { withFileTypes: true });
112
- } catch {
113
- return skills;
114
- }
115
-
116
- for (const entry of entries) {
117
- const fullPath = path.join(dir, entry.name);
118
- if (entry.isDirectory()) {
119
- const nested = await this.findSkillFiles(fullPath);
120
- skills.push(...nested);
121
- } else if (entry.isFile() && entry.name === "SKILL.md") {
122
- try {
123
- const content = await fs.readFile(fullPath, "utf-8");
124
- const frontmatter = parseFrontmatter(content);
125
- const name = frontmatter.name;
126
- const description = frontmatter.description || "";
127
- if (name) {
128
- skills.push({ name, description, filePath: fullPath });
129
- }
130
- } catch {
131
- // Skip unreadable files
132
- }
133
- }
28
+ return "No skills available";
134
29
  }
135
30
 
136
- return skills;
137
- }
138
-
139
- private buildSkillSummary(skills: SkillMeta[]): string {
140
31
  const lines = ["Available skills:"];
141
32
  for (const skill of skills) {
142
- lines.push(`- ${skill.name} (${skill.filePath}): ${skill.description}`);
33
+ lines.push(`- ${skill.name}: ${skill.description}`);
143
34
  }
144
- lines.push("");
145
- lines.push(
146
- "To use a skill, reference its name in your request and I will load the full instructions."
147
- );
148
35
  return lines.join("\n");
149
36
  }
150
37
  }
@@ -6,7 +6,6 @@ import { getConfig, getLanguageConfig } from "../config";
6
6
  import { getEnabledPlugins } from "../types";
7
7
  import { PluginBase, PluginMeta } from "./PluginBase";
8
8
  import { Plugin, PluginContext } from "./types";
9
- import { PluginService } from "./plugins";
10
9
 
11
10
  /**
12
11
  * Language Plugin with event-driven context loading
@@ -14,24 +14,12 @@ import { SkillsPlugin } from "./SkillsPlugin";
14
14
  import { EventService } from "../services/EventService";
15
15
 
16
16
  export class PluginService {
17
- private pluginMap = new Map<string, Plugin>();
17
+ protected pluginMap = new Map<string, Plugin>();
18
18
  private events?: EventService;
19
19
 
20
20
  constructor(context: PluginContext) {
21
21
  this.events = context.Events;
22
22
  context.Plugins = this;
23
-
24
- // Register migrated PluginBase plugins
25
- this.pluginMap.set("embeddings", new EmbeddingPlugin(context));
26
- this.pluginMap.set("vim", new VimPlugin(context));
27
- this.pluginMap.set("linter", new LinterPlugin(context));
28
- this.pluginMap.set("language", new LanguagePlugin(context));
29
- this.pluginMap.set("url", new UrlPlugin(context));
30
- this.pluginMap.set("git", new GitPlugin(context));
31
- this.pluginMap.set("tmux", new TmuxPlugin(context));
32
- this.pluginMap.set("agents-md", new AgentsMdPlugin(context));
33
- this.pluginMap.set("exec", new ExecPlugin(context));
34
- this.pluginMap.set("skills", new SkillsPlugin(context));
35
23
  }
36
24
 
37
25
  /* -------- lifecycle helpers ------------------------------------ */
@@ -146,3 +134,26 @@ export class PluginService {
146
134
  return newPlugin.embed ? newPlugin.embed(userInput) : [];
147
135
  }
148
136
  }
137
+
138
+ /**
139
+ * CliPluginService extends PluginService with the full set of default plugins
140
+ * used by the Knowhow CLI. Use this class when setting up agents for CLI usage.
141
+ * Use the base PluginService when you want a clean slate to register your own plugins.
142
+ */
143
+ export class CliPluginService extends PluginService {
144
+ constructor(context: PluginContext) {
145
+ super(context);
146
+
147
+ // Register CLI default plugins
148
+ this.pluginMap.set("embeddings", new EmbeddingPlugin(context));
149
+ this.pluginMap.set("vim", new VimPlugin(context));
150
+ this.pluginMap.set("linter", new LinterPlugin(context));
151
+ this.pluginMap.set("language", new LanguagePlugin(context));
152
+ this.pluginMap.set("url", new UrlPlugin(context));
153
+ this.pluginMap.set("git", new GitPlugin(context));
154
+ this.pluginMap.set("tmux", new TmuxPlugin(context));
155
+ this.pluginMap.set("agents-md", new AgentsMdPlugin(context));
156
+ this.pluginMap.set("exec", new ExecPlugin(context));
157
+ this.pluginMap.set("skills", new SkillsPlugin(context));
158
+ }
159
+ }
@@ -1,5 +1,10 @@
1
1
  import { Embeddable, MinimalEmbedding } from "../types";
2
- import { AgentService, EventService, ToolsService } from "../services";
2
+ import {
3
+ AgentService,
4
+ BehaviorsService,
5
+ EventService,
6
+ ToolsService,
7
+ } from "../services";
3
8
  import { AIClient } from "../clients";
4
9
  import { PluginService } from "./plugins";
5
10
 
@@ -13,7 +18,7 @@ export interface PluginMeta {
13
18
  export interface Plugin {
14
19
  callMany(userInput?: string): Promise<string>;
15
20
  call(userInput?: string): Promise<string>;
16
- embed(userInput?: string): Promise<MinimalEmbedding[]>;
21
+ embed?(userInput?: string): Promise<MinimalEmbedding[]>;
17
22
  enable(): void;
18
23
  disable(): void;
19
24
  isEnabled(): boolean;
@@ -27,4 +32,5 @@ export interface PluginContext {
27
32
  Clients?: AIClient;
28
33
  Tools?: ToolsService;
29
34
  Plugins?: PluginService;
35
+ Behaviors?: BehaviorsService;
30
36
  }