@tyvm/knowhow 0.0.129 → 0.0.131

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 (164) hide show
  1. package/package.json +4 -4
  2. package/src/agents/base/base.ts +40 -5
  3. package/src/agents/configurable/ConfigAgent.ts +0 -1
  4. package/src/agents/tools/index.ts +1 -0
  5. package/src/agents/tools/list.ts +2 -0
  6. package/src/agents/tools/skills.ts +79 -0
  7. package/src/agents/tools/startAgentTask.ts +17 -13
  8. package/src/ai.ts +55 -24
  9. package/src/chat/CliChatService.ts +7 -1
  10. package/src/chat/modules/AgentModule.ts +156 -15
  11. package/src/chat/renderer/PlainRenderer.ts +218 -0
  12. package/src/chat/renderer/index.ts +1 -0
  13. package/src/chat/renderer/loadRenderer.ts +2 -0
  14. package/src/chat.ts +2 -2
  15. package/src/cli.ts +10 -0
  16. package/src/clients/anthropic.ts +68 -2
  17. package/src/clients/fireworks.ts +10 -1
  18. package/src/clients/index.ts +5 -3
  19. package/src/clients/meta.ts +9 -0
  20. package/src/clients/openai.ts +28 -5
  21. package/src/clients/pricing/anthropic.ts +11 -0
  22. package/src/clients/pricing/fireworks.ts +12 -0
  23. package/src/clients/pricing/openai.ts +3 -0
  24. package/src/clients/types.ts +27 -4
  25. package/src/clients/xai.ts +2 -2
  26. package/src/commands/agent.ts +70 -18
  27. package/src/commands/agents.ts +556 -0
  28. package/src/commands/behaviors.ts +47 -0
  29. package/src/commands/hash.ts +124 -0
  30. package/src/commands/misc.ts +157 -3
  31. package/src/commands/services.ts +6 -2
  32. package/src/commands/skills.ts +107 -0
  33. package/src/config.ts +51 -1
  34. package/src/generate.ts +507 -0
  35. package/src/index.ts +34 -216
  36. package/src/plugins/PluginBase.ts +0 -2
  37. package/src/plugins/SkillsPlugin.ts +11 -124
  38. package/src/plugins/language.ts +0 -1
  39. package/src/plugins/plugins.ts +24 -13
  40. package/src/plugins/types.ts +8 -2
  41. package/src/services/BehaviorsService.ts +464 -0
  42. package/src/services/KnowhowClient.ts +61 -0
  43. package/src/services/SkillsService.ts +820 -0
  44. package/src/services/Tools.ts +2 -0
  45. package/src/services/index.ts +8 -2
  46. package/src/services/modules/types.ts +2 -0
  47. package/src/types.ts +36 -4
  48. package/ts_build/package.json +4 -4
  49. package/ts_build/src/agents/base/base.d.ts +9 -1
  50. package/ts_build/src/agents/base/base.js +29 -4
  51. package/ts_build/src/agents/base/base.js.map +1 -1
  52. package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
  53. package/ts_build/src/agents/tools/index.d.ts +1 -0
  54. package/ts_build/src/agents/tools/index.js +1 -0
  55. package/ts_build/src/agents/tools/index.js.map +1 -1
  56. package/ts_build/src/agents/tools/list.js +2 -0
  57. package/ts_build/src/agents/tools/list.js.map +1 -1
  58. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  59. package/ts_build/src/agents/tools/skills.js +62 -0
  60. package/ts_build/src/agents/tools/skills.js.map +1 -0
  61. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -2
  62. package/ts_build/src/agents/tools/startAgentTask.js +13 -12
  63. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  64. package/ts_build/src/ai.d.ts +10 -4
  65. package/ts_build/src/ai.js +27 -25
  66. package/ts_build/src/ai.js.map +1 -1
  67. package/ts_build/src/chat/CliChatService.d.ts +1 -1
  68. package/ts_build/src/chat/CliChatService.js +4 -1
  69. package/ts_build/src/chat/CliChatService.js.map +1 -1
  70. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  71. package/ts_build/src/chat/modules/AgentModule.js +130 -4
  72. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  73. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  74. package/ts_build/src/chat/renderer/PlainRenderer.d.ts +28 -0
  75. package/ts_build/src/chat/renderer/PlainRenderer.js +169 -0
  76. package/ts_build/src/chat/renderer/PlainRenderer.js.map +1 -0
  77. package/ts_build/src/chat/renderer/index.d.ts +1 -0
  78. package/ts_build/src/chat/renderer/index.js +3 -1
  79. package/ts_build/src/chat/renderer/index.js.map +1 -1
  80. package/ts_build/src/chat/renderer/loadRenderer.js +2 -0
  81. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -1
  82. package/ts_build/src/chat.d.ts +1 -1
  83. package/ts_build/src/chat.js +2 -2
  84. package/ts_build/src/chat.js.map +1 -1
  85. package/ts_build/src/cli.js +9 -0
  86. package/ts_build/src/cli.js.map +1 -1
  87. package/ts_build/src/clients/anthropic.d.ts +2 -0
  88. package/ts_build/src/clients/anthropic.js +39 -1
  89. package/ts_build/src/clients/anthropic.js.map +1 -1
  90. package/ts_build/src/clients/fireworks.js +7 -0
  91. package/ts_build/src/clients/fireworks.js.map +1 -1
  92. package/ts_build/src/clients/index.js +3 -1
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/meta.d.ts +2 -0
  95. package/ts_build/src/clients/meta.js +5 -0
  96. package/ts_build/src/clients/meta.js.map +1 -1
  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 +3 -0
  101. package/ts_build/src/clients/pricing/anthropic.js +6 -0
  102. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/pricing/fireworks.d.ts +1 -0
  104. package/ts_build/src/clients/pricing/fireworks.js +5 -1
  105. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  106. package/ts_build/src/clients/pricing/openai.js +3 -0
  107. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  108. package/ts_build/src/clients/types.d.ts +6 -1
  109. package/ts_build/src/clients/xai.js +1 -1
  110. package/ts_build/src/clients/xai.js.map +1 -1
  111. package/ts_build/src/commands/agent.js +58 -15
  112. package/ts_build/src/commands/agent.js.map +1 -1
  113. package/ts_build/src/commands/agents.d.ts +2 -0
  114. package/ts_build/src/commands/agents.js +460 -0
  115. package/ts_build/src/commands/agents.js.map +1 -0
  116. package/ts_build/src/commands/behaviors.d.ts +2 -0
  117. package/ts_build/src/commands/behaviors.js +43 -0
  118. package/ts_build/src/commands/behaviors.js.map +1 -0
  119. package/ts_build/src/commands/hash.d.ts +2 -0
  120. package/ts_build/src/commands/hash.js +117 -0
  121. package/ts_build/src/commands/hash.js.map +1 -0
  122. package/ts_build/src/commands/misc.js +108 -2
  123. package/ts_build/src/commands/misc.js.map +1 -1
  124. package/ts_build/src/commands/services.js +4 -2
  125. package/ts_build/src/commands/services.js.map +1 -1
  126. package/ts_build/src/commands/skills.d.ts +2 -0
  127. package/ts_build/src/commands/skills.js +85 -0
  128. package/ts_build/src/commands/skills.js.map +1 -0
  129. package/ts_build/src/config.js +34 -0
  130. package/ts_build/src/config.js.map +1 -1
  131. package/ts_build/src/generate.d.ts +16 -0
  132. package/ts_build/src/generate.js +304 -0
  133. package/ts_build/src/generate.js.map +1 -0
  134. package/ts_build/src/index.d.ts +3 -3
  135. package/ts_build/src/index.js +13 -118
  136. package/ts_build/src/index.js.map +1 -1
  137. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  138. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  139. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  140. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  141. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  142. package/ts_build/src/plugins/language.js.map +1 -1
  143. package/ts_build/src/plugins/plugins.d.ts +4 -1
  144. package/ts_build/src/plugins/plugins.js +17 -11
  145. package/ts_build/src/plugins/plugins.js.map +1 -1
  146. package/ts_build/src/plugins/types.d.ts +3 -2
  147. package/ts_build/src/services/BehaviorsService.d.ts +48 -0
  148. package/ts_build/src/services/BehaviorsService.js +382 -0
  149. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  150. package/ts_build/src/services/KnowhowClient.d.ts +8 -0
  151. package/ts_build/src/services/KnowhowClient.js +20 -0
  152. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  153. package/ts_build/src/services/SkillsService.d.ts +60 -0
  154. package/ts_build/src/services/SkillsService.js +641 -0
  155. package/ts_build/src/services/SkillsService.js.map +1 -0
  156. package/ts_build/src/services/Tools.d.ts +2 -0
  157. package/ts_build/src/services/Tools.js.map +1 -1
  158. package/ts_build/src/services/index.d.ts +3 -0
  159. package/ts_build/src/services/index.js +8 -2
  160. package/ts_build/src/services/index.js.map +1 -1
  161. package/ts_build/src/services/modules/index.d.ts +4 -2
  162. package/ts_build/src/services/modules/types.d.ts +2 -0
  163. package/ts_build/src/types.d.ts +14 -4
  164. package/ts_build/src/types.js.map +1 -1
@@ -0,0 +1,464 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import * as os from "os";
4
+ import { KnowhowSimpleClient } from "./KnowhowClient";
5
+ import { createHash } from "crypto";
6
+ import { getConfig } from "../config";
7
+
8
+ const BEHAVIORS_DIR = ".knowhow/behaviors";
9
+ const LOCAL_SKILLS_DIRS = [".agents/skills", ".knowhow/skills"];
10
+ const GLOBAL_SKILLS_DIR = path.join(os.homedir(), ".knowhow", "skills");
11
+
12
+ export interface Behavior {
13
+ id?: string;
14
+ name: string;
15
+ description: string;
16
+ instructions: string;
17
+ textTrigger?: string;
18
+ semanticTriggerText?: string;
19
+ mcpServers?: string;
20
+ tools?: string;
21
+ embeddings?: string;
22
+ model?: string;
23
+ toolsDisabled?: boolean;
24
+ isPublic?: boolean;
25
+ isSkill?: boolean;
26
+ platform?: boolean;
27
+ }
28
+
29
+ /**
30
+ * In-memory singleton for behaviors/skills.
31
+ * Call initFromDisk() once at startup; tools then read from memory.
32
+ */
33
+ export class BehaviorsService {
34
+ private behaviors: Behavior[] = [];
35
+ private skills: Behavior[] = [];
36
+ public client = new KnowhowSimpleClient();
37
+
38
+ /**
39
+ * Load behaviors (and derive skills) from disk into memory.
40
+ * @param filePaths - optional explicit file paths to load; defaults to all files in BEHAVIORS_DIR
41
+ */
42
+ initFromDisk(filePaths?: string[]): void {
43
+ if (filePaths && filePaths.length > 0) {
44
+ const loaded: Behavior[] = [];
45
+ for (const fp of filePaths) {
46
+ try {
47
+ const raw = fs.readFileSync(fp, "utf-8");
48
+ const behavior = fp.endsWith(".md")
49
+ ? this.parseMdBehavior(raw)
50
+ : (JSON.parse(raw) as Behavior);
51
+ loaded.push(behavior);
52
+ } catch {
53
+ // skip malformed files
54
+ }
55
+ }
56
+ this.behaviors = loaded;
57
+ } else {
58
+ this.behaviors = this.loadLocal();
59
+ }
60
+
61
+ this.skills = this.behaviors.filter((b) => b.isSkill === true);
62
+ }
63
+
64
+ async initFromConfig() {
65
+ // Load skills/behaviors from disk into memory (respects config.skills file list if set)
66
+ let config: { skills?: string[] } = {};
67
+ try {
68
+ config = await getConfig();
69
+ } catch {
70
+ /* no config file */
71
+ }
72
+ const skills = config.skills || [];
73
+ this.initFromDisk(skills);
74
+ }
75
+
76
+ /** Initialize only skills subset into memory. */
77
+ initSkills(skills: Behavior[]): void {
78
+ this.skills = skills;
79
+ }
80
+
81
+ initBehaviors(behaviors: Behavior[]): void {
82
+ this.behaviors = behaviors;
83
+ }
84
+
85
+ /** Return all in-memory behaviors. */
86
+ getBehaviors(): Behavior[] {
87
+ return this.behaviors;
88
+ }
89
+
90
+ /** Return all in-memory skills (behaviors where isSkill === true). */
91
+ listSkills(): Behavior[] {
92
+ return this.skills;
93
+ }
94
+
95
+ /** Find a skill by name (exact then partial match). Returns undefined if not found. */
96
+ findSkill(name: string): Behavior | undefined {
97
+ const lower = name.toLowerCase();
98
+ return (
99
+ this.skills.find((s) => s.name.toLowerCase() === lower) ??
100
+ this.skills.find(
101
+ (s) =>
102
+ s.name.toLowerCase().includes(lower) ||
103
+ lower.includes(s.name.toLowerCase())
104
+ )
105
+ );
106
+ }
107
+
108
+ /** Singleton instance — import this everywhere instead of constructing BehaviorService directly. */
109
+
110
+ /**
111
+ * Load all behaviors from local disk (.knowhow/behaviors/).
112
+ * Supports both .json and .md files.
113
+ * Also scans .agents/skills/ and .knowhow/skills/ for SKILL.md files,
114
+ * treating them as skills (isSkill: true) regardless of frontmatter.
115
+ */
116
+ loadLocal(opts: { skillsOnly?: boolean } = {}): Behavior[] {
117
+ const results: Behavior[] = [];
118
+
119
+ // Load from .knowhow/behaviors/ (existing behaviors/skills with explicit isSkill flag)
120
+ if (fs.existsSync(BEHAVIORS_DIR)) {
121
+ const files = fs
122
+ .readdirSync(BEHAVIORS_DIR)
123
+ .filter((f) => f.endsWith(".json") || f.endsWith(".md"));
124
+
125
+ for (const file of files) {
126
+ try {
127
+ const filePath = path.join(BEHAVIORS_DIR, file);
128
+ const raw = fs.readFileSync(filePath, "utf-8");
129
+ const behavior = file.endsWith(".md")
130
+ ? this.parseMdBehavior(raw)
131
+ : (JSON.parse(raw) as Behavior);
132
+ if (opts.skillsOnly && !behavior.isSkill) continue;
133
+ results.push(behavior);
134
+ } catch {
135
+ // skip malformed files
136
+ }
137
+ }
138
+ }
139
+
140
+ // Load from .agents/skills/<name>/SKILL.md and .knowhow/skills/<name>/SKILL.md
141
+ // Also load from global ~/.knowhow/skills/<name>/SKILL.md
142
+ // Files here are always treated as skills (isSkill: true)
143
+ const allSkillsDirs = [
144
+ ...LOCAL_SKILLS_DIRS,
145
+ GLOBAL_SKILLS_DIR,
146
+ ];
147
+ for (const skillsDir of allSkillsDirs) {
148
+ if (!fs.existsSync(skillsDir)) continue;
149
+
150
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ if (!entry.isDirectory()) continue;
153
+ const skillFile = path.join(skillsDir, entry.name, "SKILL.md");
154
+ if (!fs.existsSync(skillFile)) continue;
155
+ try {
156
+ const raw = fs.readFileSync(skillFile, "utf-8");
157
+ const behavior = this.parseMdBehavior(raw, { isSkill: true });
158
+ results.push(behavior);
159
+ } catch {
160
+ // skip malformed files
161
+ }
162
+ }
163
+ }
164
+
165
+ return results;
166
+ }
167
+
168
+ /**
169
+ * Match a behavior from local disk by textTrigger or name prefix.
170
+ * Does NOT hit the network.
171
+ */
172
+ matchBehaviorLocal(userInput: string): Behavior | null {
173
+ const behaviors = this.loadLocal();
174
+ const lower = userInput.toLowerCase();
175
+
176
+ // Try exact text trigger match first
177
+ for (const b of behaviors) {
178
+ if (b.textTrigger && lower.startsWith(b.textTrigger.toLowerCase())) {
179
+ return b;
180
+ }
181
+ }
182
+
183
+ // Try partial text trigger match
184
+ for (const b of behaviors) {
185
+ if (b.textTrigger && lower.includes(b.textTrigger.toLowerCase())) {
186
+ return b;
187
+ }
188
+ }
189
+
190
+ return null;
191
+ }
192
+
193
+ /**
194
+ * Find a behavior from local disk by its ID field.
195
+ */
196
+ findBehaviorById(id: string): Behavior | null {
197
+ const behaviors = this.loadLocal();
198
+ return behaviors.find((b) => b.id === id) ?? null;
199
+ }
200
+
201
+ /**
202
+ * Load a behavior directly from a file path (.json or .md).
203
+ */
204
+ loadBehaviorFromFile(filePath: string): Behavior | null {
205
+ const resolved = path.isAbsolute(filePath)
206
+ ? filePath
207
+ : path.resolve(process.cwd(), filePath);
208
+
209
+ if (!fs.existsSync(resolved)) {
210
+ return null;
211
+ }
212
+
213
+ try {
214
+ const raw = fs.readFileSync(resolved, "utf-8");
215
+ if (resolved.endsWith(".md")) {
216
+ return this.parseMdBehavior(raw);
217
+ } else {
218
+ return JSON.parse(raw) as Behavior;
219
+ }
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+
225
+ async list(opts: { skillsOnly?: boolean; includeInternal?: boolean } = {}) {
226
+ const behaviors = opts.skillsOnly
227
+ ? await this.client.getOrgSkills(opts.includeInternal ?? true)
228
+ : await this.client.getOrgBehaviors();
229
+
230
+ const items = behaviors.data;
231
+ if (items.length === 0) {
232
+ console.log("No behaviors found.");
233
+ return;
234
+ }
235
+
236
+ for (const b of items) {
237
+ const type = b.isSkill ? "[skill]" : "[behavior]";
238
+ const platformLabel = b.platform ? " [platform]" : "";
239
+ console.log(`${type}${platformLabel} ${b.name} — ${b.description || ""}`);
240
+ }
241
+ }
242
+
243
+ async download(
244
+ opts: {
245
+ skillsOnly?: boolean;
246
+ includeInternal?: boolean;
247
+ md?: boolean;
248
+ } = {}
249
+ ) {
250
+ fs.mkdirSync(BEHAVIORS_DIR, { recursive: true });
251
+
252
+ const behaviors = opts.skillsOnly
253
+ ? await this.client.getOrgSkills(opts.includeInternal ?? true)
254
+ : await this.client.getOrgBehaviors();
255
+
256
+ const items = behaviors.data;
257
+ let created = 0;
258
+ let updated = 0;
259
+ let unchanged = 0;
260
+
261
+ for (const b of items) {
262
+ const name = (b.name as string) || "unnamed";
263
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
264
+ const filePath = path.join(
265
+ BEHAVIORS_DIR,
266
+ `${slug}.${opts.md ? "md" : "json"}`
267
+ );
268
+
269
+ if (opts.md) {
270
+ const frontmatterLines: string[] = [
271
+ "---",
272
+ `name: ${name}`,
273
+ `description: ${(b.description as string) || ""}`,
274
+ ];
275
+ if (b.model) frontmatterLines.push(`model: ${b.model}`);
276
+ if (b.textTrigger)
277
+ frontmatterLines.push(`textTrigger: ${b.textTrigger}`);
278
+ if (b.semanticTriggerText)
279
+ frontmatterLines.push(
280
+ `semanticTriggerText: ${b.semanticTriggerText}`
281
+ );
282
+ if (b.isSkill !== undefined)
283
+ frontmatterLines.push(`isSkill: ${b.isSkill}`);
284
+ if (b.isPublic !== undefined)
285
+ frontmatterLines.push(`isPublic: ${b.isPublic}`);
286
+ if (b.mcpServers) frontmatterLines.push(`mcpServers: ${b.mcpServers}`);
287
+ if (b.tools) frontmatterLines.push(`tools: ${b.tools}`);
288
+ if (b.embeddings) frontmatterLines.push(`embeddings: ${b.embeddings}`);
289
+ frontmatterLines.push(`id: ${b.id}`);
290
+ frontmatterLines.push("---");
291
+
292
+ const content = `${frontmatterLines.join("\n")}\n\n${
293
+ (b.instructions as string) || ""
294
+ }`;
295
+ const changed = this.writeIfChanged(filePath, content);
296
+ if (changed === "created") {
297
+ created++;
298
+ console.log(`✨ Created: ${filePath}`);
299
+ } else if (changed === "updated") {
300
+ updated++;
301
+ console.log(`📝 Updated: ${filePath}`);
302
+ } else {
303
+ unchanged++;
304
+ }
305
+ } else {
306
+ const data: Behavior = {
307
+ id: b.id as string,
308
+ name,
309
+ description: (b.description as string) || "",
310
+ instructions: (b.instructions as string) || "",
311
+ textTrigger: b.textTrigger as string | undefined,
312
+ semanticTriggerText: b.semanticTriggerText as string | undefined,
313
+ mcpServers: b.mcpServers as string | undefined,
314
+ tools: b.tools as string | undefined,
315
+ embeddings: b.embeddings as string | undefined,
316
+ model: b.model as string | undefined,
317
+ toolsDisabled: b.toolsDisabled as boolean | undefined,
318
+ isPublic: b.isPublic as boolean | undefined,
319
+ isSkill: b.isSkill as boolean | undefined,
320
+ platform: b.platform as boolean | undefined,
321
+ };
322
+ const content = JSON.stringify(data, null, 2);
323
+ const changed = this.writeIfChanged(filePath, content);
324
+ if (changed === "created") {
325
+ created++;
326
+ console.log(`✨ Created: ${filePath}`);
327
+ } else if (changed === "updated") {
328
+ updated++;
329
+ console.log(`📝 Updated: ${filePath}`);
330
+ } else {
331
+ unchanged++;
332
+ }
333
+ }
334
+ }
335
+
336
+ console.log(
337
+ `\nSync complete: ${created} created, ${updated} updated, ${unchanged} unchanged → ${BEHAVIORS_DIR}/`
338
+ );
339
+ }
340
+
341
+ /**
342
+ * Write content to filePath only if it differs from what's there already.
343
+ * Returns "created" | "updated" | "unchanged".
344
+ */
345
+ private writeIfChanged(
346
+ filePath: string,
347
+ content: string
348
+ ): "created" | "updated" | "unchanged" {
349
+ const hash = (s: string) => createHash("sha256").update(s).digest("hex");
350
+
351
+ if (!fs.existsSync(filePath)) {
352
+ fs.writeFileSync(filePath, content);
353
+ return "created";
354
+ }
355
+ const existing = fs.readFileSync(filePath, "utf-8");
356
+ if (hash(existing) !== hash(content)) {
357
+ fs.writeFileSync(filePath, content);
358
+ return "updated";
359
+ }
360
+ return "unchanged";
361
+ }
362
+
363
+ parseMdBehavior(content: string, defaults: Partial<Behavior> = {}): Behavior {
364
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
365
+ if (!match)
366
+ throw new Error("Invalid behavior markdown: missing frontmatter");
367
+
368
+ const meta: Record<string, string> = {};
369
+ for (const line of match[1].split("\n")) {
370
+ const colonIdx = line.indexOf(":");
371
+ if (colonIdx > 0) {
372
+ const key = line.slice(0, colonIdx).trim();
373
+ const val = line.slice(colonIdx + 1).trim();
374
+ meta[key] = val;
375
+ }
376
+ }
377
+
378
+ return {
379
+ id: meta.id,
380
+ name: meta.name || "",
381
+ description: meta.description || "",
382
+ instructions: match[2].trim(),
383
+ textTrigger: meta.textTrigger || undefined,
384
+ semanticTriggerText: meta.semanticTriggerText || undefined,
385
+ model: meta.model || undefined,
386
+ mcpServers: meta.mcpServers || undefined,
387
+ tools: meta.tools || undefined,
388
+ embeddings: meta.embeddings || undefined,
389
+ isSkill: meta.isSkill === "true" || defaults.isSkill === true,
390
+ isPublic: meta.isPublic === "true",
391
+ };
392
+ }
393
+
394
+ async upload() {
395
+ if (!fs.existsSync(BEHAVIORS_DIR)) {
396
+ console.error(
397
+ "No .knowhow/behaviors/ directory found. Run 'knowhow behaviors download' first."
398
+ );
399
+ return;
400
+ }
401
+
402
+ const files = fs
403
+ .readdirSync(BEHAVIORS_DIR)
404
+ .filter((f) => f.endsWith(".json") || f.endsWith(".md"));
405
+
406
+ for (const file of files) {
407
+ const filePath = path.join(BEHAVIORS_DIR, file);
408
+ const rawContent = fs.readFileSync(filePath, "utf-8");
409
+
410
+ let data: Behavior;
411
+ if (file.endsWith(".md")) {
412
+ data = this.parseMdBehavior(rawContent);
413
+ } else {
414
+ data = JSON.parse(rawContent) as Behavior;
415
+ }
416
+
417
+ if (data.id) {
418
+ await this.client.updateOrgBehavior(
419
+ data.id,
420
+ data as unknown as Record<string, unknown>
421
+ );
422
+ console.log(`Updated: ${data.name}`);
423
+ } else {
424
+ await this.client.createOrgBehavior(
425
+ data as unknown as Record<string, unknown>
426
+ );
427
+ console.log(`Created: ${data.name}`);
428
+ }
429
+ }
430
+
431
+ console.log(`Uploaded ${files.length} behaviors.`);
432
+ }
433
+
434
+ /**
435
+ * Try to match a behavior by text trigger.
436
+ * Returns the first matched behavior or null.
437
+ */
438
+ async matchBehavior(userInput: string): Promise<Behavior | null> {
439
+ try {
440
+ const result = await this.client.getTriggeredBehaviors(userInput);
441
+ const triggered = result.data.triggered;
442
+ if (triggered && triggered.length > 0) {
443
+ const b = triggered[0];
444
+ return {
445
+ id: b.id as string,
446
+ name: (b.name as string) || "",
447
+ description: (b.description as string) || "",
448
+ instructions: (b.instructions as string) || "",
449
+ textTrigger: b.textTrigger as string | undefined,
450
+ semanticTriggerText: b.semanticTriggerText as string | undefined,
451
+ model: b.model as string | undefined,
452
+ mcpServers: b.mcpServers as string | undefined,
453
+ tools: b.tools as string | undefined,
454
+ embeddings: b.embeddings as string | undefined,
455
+ isSkill: b.isSkill as boolean | undefined,
456
+ isPublic: b.isPublic as boolean | undefined,
457
+ };
458
+ }
459
+ } catch {
460
+ // Not authenticated or backend unavailable — skip behavior matching
461
+ }
462
+ return null;
463
+ }
464
+ }
@@ -799,4 +799,65 @@ export class KnowhowSimpleClient {
799
799
  );
800
800
  return response.data;
801
801
  }
802
+
803
+ // ============================================
804
+ // Org Behaviors Methods
805
+ // ============================================
806
+
807
+ /**
808
+ * List all behaviors for the current org
809
+ */
810
+ async getOrgBehaviors() {
811
+ await this.checkJwt();
812
+ return http.get<Record<string, unknown>[]>(
813
+ `${this.baseUrl}/api/org-behaviors`,
814
+ { headers: this.headers }
815
+ );
816
+ }
817
+
818
+ /**
819
+ * List all skills (isSkill=true) for the current org
820
+ */
821
+ async getOrgSkills(includeInternal = true) {
822
+ await this.checkJwt();
823
+ return http.get<Record<string, unknown>[]>(
824
+ `${this.baseUrl}/api/org-behaviors/skills?includeInternal=${includeInternal}`,
825
+ { headers: this.headers }
826
+ );
827
+ }
828
+
829
+ /**
830
+ * Create a new org behavior
831
+ */
832
+ async createOrgBehavior(data: Record<string, unknown>) {
833
+ await this.checkJwt();
834
+ return http.post<Record<string, unknown>>(
835
+ `${this.baseUrl}/api/org-behaviors`,
836
+ data,
837
+ { headers: this.headers }
838
+ );
839
+ }
840
+
841
+ /**
842
+ * Update an existing org behavior by ID
843
+ */
844
+ async updateOrgBehavior(id: string, data: Record<string, unknown>) {
845
+ await this.checkJwt();
846
+ return http.put<Record<string, unknown>>(
847
+ `${this.baseUrl}/api/org-behaviors/${id}`,
848
+ data,
849
+ { headers: this.headers }
850
+ );
851
+ }
852
+
853
+ /**
854
+ * Get triggered behaviors for a given user input string
855
+ */
856
+ async getTriggeredBehaviors(userInput: string) {
857
+ await this.checkJwt();
858
+ return http.get<{ triggered: Record<string, unknown>[]; trigger_type: string }>(
859
+ `${this.baseUrl}/api/org-behaviors/triggered?userInput=${encodeURIComponent(userInput)}`,
860
+ { headers: this.headers }
861
+ );
862
+ }
802
863
  }