@tyvm/knowhow 0.0.130 → 0.0.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/agents/configurable/ConfigAgent.ts +0 -1
- package/src/agents/tools/startAgentTask.ts +17 -13
- package/src/ai.ts +55 -24
- package/src/chat/CliChatService.ts +7 -1
- package/src/chat/modules/AgentModule.ts +72 -14
- package/src/chat/renderer/PlainRenderer.ts +218 -0
- package/src/chat/renderer/index.ts +1 -0
- package/src/chat/renderer/loadRenderer.ts +2 -0
- package/src/chat.ts +2 -2
- package/src/cli.ts +23 -0
- package/src/clients/anthropic.ts +9 -1
- package/src/clients/fireworks.ts +10 -1
- package/src/clients/index.ts +6 -3
- package/src/clients/meta.ts +9 -0
- package/src/clients/pricing/anthropic.ts +11 -0
- package/src/clients/pricing/fireworks.ts +12 -0
- package/src/commands/agent.ts +53 -20
- package/src/commands/agents.ts +556 -0
- package/src/commands/hash.ts +170 -0
- package/src/commands/misc.ts +157 -3
- package/src/commands/skills.ts +36 -0
- package/src/config.ts +54 -2
- package/src/generate.ts +507 -0
- package/src/index.ts +32 -216
- package/src/services/BehaviorsService.ts +32 -0
- package/src/services/SkillsService.ts +198 -0
- package/src/types.ts +36 -4
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
- package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -2
- package/ts_build/src/agents/tools/startAgentTask.js +13 -12
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/ai.d.ts +10 -4
- package/ts_build/src/ai.js +27 -25
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/chat/CliChatService.d.ts +1 -1
- package/ts_build/src/chat/CliChatService.js +4 -1
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +61 -4
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/renderer/PlainRenderer.d.ts +28 -0
- package/ts_build/src/chat/renderer/PlainRenderer.js +169 -0
- package/ts_build/src/chat/renderer/PlainRenderer.js.map +1 -0
- package/ts_build/src/chat/renderer/index.d.ts +1 -0
- package/ts_build/src/chat/renderer/index.js +3 -1
- package/ts_build/src/chat/renderer/index.js.map +1 -1
- package/ts_build/src/chat/renderer/loadRenderer.js +2 -0
- package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -1
- package/ts_build/src/chat.d.ts +1 -1
- package/ts_build/src/chat.js +2 -2
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.js +16 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +4 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/fireworks.js +7 -0
- package/ts_build/src/clients/fireworks.js.map +1 -1
- package/ts_build/src/clients/index.js +5 -1
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/meta.d.ts +2 -0
- package/ts_build/src/clients/meta.js +5 -0
- package/ts_build/src/clients/meta.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +3 -0
- package/ts_build/src/clients/pricing/anthropic.js +6 -0
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +1 -0
- package/ts_build/src/clients/pricing/fireworks.js +5 -1
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/commands/agent.js +44 -17
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/agents.d.ts +2 -0
- package/ts_build/src/commands/agents.js +460 -0
- package/ts_build/src/commands/agents.js.map +1 -0
- package/ts_build/src/commands/hash.d.ts +2 -0
- package/ts_build/src/commands/hash.js +150 -0
- package/ts_build/src/commands/hash.js.map +1 -0
- package/ts_build/src/commands/misc.js +108 -2
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/commands/skills.js +24 -0
- package/ts_build/src/commands/skills.js.map +1 -1
- package/ts_build/src/config.js +37 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/generate.d.ts +16 -0
- package/ts_build/src/generate.js +304 -0
- package/ts_build/src/generate.js.map +1 -0
- package/ts_build/src/index.d.ts +1 -3
- package/ts_build/src/index.js +9 -118
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/services/BehaviorsService.d.ts +2 -0
- package/ts_build/src/services/BehaviorsService.js +24 -0
- package/ts_build/src/services/BehaviorsService.js.map +1 -1
- package/ts_build/src/services/SkillsService.d.ts +7 -0
- package/ts_build/src/services/SkillsService.js +163 -0
- package/ts_build/src/services/SkillsService.js.map +1 -1
- package/ts_build/src/types.d.ts +14 -4
- package/ts_build/src/types.js.map +1 -1
package/src/index.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { summarizeFiles, summarizeFile } from "./ai";
|
|
2
|
-
import {
|
|
3
|
-
saveAllFileHashes,
|
|
4
|
-
saveHashes,
|
|
5
|
-
getHashes,
|
|
6
|
-
checkNoFilesChanged,
|
|
7
|
-
} from "./hashes";
|
|
2
|
+
import type { AgentOptions } from "./ai";
|
|
8
3
|
import * as fs from "fs";
|
|
9
4
|
import * as path from "path";
|
|
10
5
|
import * as crypto from "crypto";
|
|
11
|
-
import { promisify } from "util";
|
|
12
6
|
import { globSync } from "glob";
|
|
13
7
|
|
|
14
8
|
import { Prompts } from "./prompts";
|
|
@@ -37,12 +31,25 @@ import {
|
|
|
37
31
|
getConfiguredEmbeddingMap,
|
|
38
32
|
} from "./embeddings";
|
|
39
33
|
|
|
40
|
-
import { abort } from "process";
|
|
41
34
|
import { convertToText } from "./conversion";
|
|
42
35
|
import { knowhowMcpClient } from "./services/Mcp";
|
|
43
36
|
import { services } from "./services/";
|
|
44
37
|
import { Models } from "./types";
|
|
45
38
|
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Re-export generate pipeline (moved to src/generate.ts)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
export {
|
|
43
|
+
generate,
|
|
44
|
+
GenerateOptions,
|
|
45
|
+
buildWaves,
|
|
46
|
+
normalizeInputPattern,
|
|
47
|
+
withOutputTarget,
|
|
48
|
+
writeAgentOrSummaryOutput,
|
|
49
|
+
handleMultiOutputGeneration,
|
|
50
|
+
handleSingleOutputGeneration,
|
|
51
|
+
} from "./generate";
|
|
52
|
+
|
|
46
53
|
export * as clients from "./clients";
|
|
47
54
|
export * as agents from "./agents";
|
|
48
55
|
export * as services from "./services";
|
|
@@ -65,8 +72,11 @@ export { SkillsPlugin } from "./plugins/SkillsPlugin";
|
|
|
65
72
|
// Export embedding types
|
|
66
73
|
export { MinimalEmbedding, Embeddable } from "./types";
|
|
67
74
|
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Embed
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
68
79
|
export async function embed() {
|
|
69
|
-
// load config
|
|
70
80
|
const config = await getConfig();
|
|
71
81
|
const ignorePattern = await getIgnorePattern();
|
|
72
82
|
|
|
@@ -74,7 +84,6 @@ export async function embed() {
|
|
|
74
84
|
config.embeddingModel || EmbeddingModels.openai.EmbeddingAda2;
|
|
75
85
|
|
|
76
86
|
if (!config.embedSources) {
|
|
77
|
-
// No embeddings configured
|
|
78
87
|
return;
|
|
79
88
|
}
|
|
80
89
|
|
|
@@ -83,6 +92,10 @@ export async function embed() {
|
|
|
83
92
|
}
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Purge
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
86
99
|
export async function purge(globPath: string) {
|
|
87
100
|
const files = globSync(globPath);
|
|
88
101
|
const embeddings = await getConfiguredEmbeddingMap();
|
|
@@ -109,6 +122,10 @@ export async function purge(globPath: string) {
|
|
|
109
122
|
}
|
|
110
123
|
}
|
|
111
124
|
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Upload
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
112
129
|
export async function upload() {
|
|
113
130
|
const config = await getConfig();
|
|
114
131
|
const { AwsS3, Embeddings, knowhowApiClient } = services();
|
|
@@ -126,10 +143,6 @@ export async function upload() {
|
|
|
126
143
|
}
|
|
127
144
|
const items = JSON.parse(await readFile(source.output, "utf8"));
|
|
128
145
|
const { name: embeddingName } = path.parse(source.output);
|
|
129
|
-
const data = {
|
|
130
|
-
embeddingName,
|
|
131
|
-
items,
|
|
132
|
-
};
|
|
133
146
|
|
|
134
147
|
if (Embeddings.hasResolver(source.remoteType) && source.remoteType !== "knowhow") {
|
|
135
148
|
console.log(
|
|
@@ -144,7 +157,6 @@ export async function upload() {
|
|
|
144
157
|
if (!source.remoteId) {
|
|
145
158
|
throw new Error("remoteId is required for knowhow uploads");
|
|
146
159
|
}
|
|
147
|
-
// Warn if the local embeddingModel differs from the one stored on the backend
|
|
148
160
|
try {
|
|
149
161
|
const remoteEmbedding = await knowhowApiClient.getOrgEmbedding(source.remoteId);
|
|
150
162
|
const localModel = config.embeddingModel || EmbeddingModels.openai.EmbeddingAda2;
|
|
@@ -159,12 +171,11 @@ export async function upload() {
|
|
|
159
171
|
);
|
|
160
172
|
}
|
|
161
173
|
} catch (e) {
|
|
162
|
-
// Non-fatal
|
|
174
|
+
// Non-fatal
|
|
163
175
|
}
|
|
164
176
|
const url = await knowhowApiClient.getPresignedUploadUrl(source);
|
|
165
177
|
console.log("Uploading to", url);
|
|
166
178
|
await AwsS3.uploadToPresignedUrl(url, source.output);
|
|
167
|
-
// Sync config metadata back to the backend DB
|
|
168
179
|
await knowhowApiClient.updateEmbeddingMetadata(source.remoteId, {
|
|
169
180
|
inputGlob: source.input,
|
|
170
181
|
outputPath: source.output,
|
|
@@ -183,201 +194,9 @@ export async function upload() {
|
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
196
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
* - Standard glob patterns (e.g. "src/**\/*.ts")
|
|
190
|
-
* - Brace expansion (e.g. "{src/a.ts,src/b.ts}")
|
|
191
|
-
* - Comma-separated file paths (e.g. "src/a.ts,src/b.ts") — auto-converted to brace expansion
|
|
192
|
-
* - Mixed comma-separated list with globs (e.g. "src/a.ts,src/commands/**\/*.ts")
|
|
193
|
-
*/
|
|
194
|
-
function normalizeInputPattern(input: string): string {
|
|
195
|
-
// If it already has braces, use as-is (already brace-expanded)
|
|
196
|
-
if (input.includes("{")) {
|
|
197
|
-
return input;
|
|
198
|
-
}
|
|
199
|
-
// If it contains commas, treat as comma-separated list and wrap in braces
|
|
200
|
-
// This also handles the mixed case: "src/a.ts,src/commands/**/*.ts"
|
|
201
|
-
if (input.includes(",")) {
|
|
202
|
-
const parts = input.split(",").map((p) => p.trim());
|
|
203
|
-
return `{${parts.join(",")}}`;
|
|
204
|
-
}
|
|
205
|
-
return input;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export async function generate(): Promise<void> {
|
|
209
|
-
const config = await getConfig();
|
|
210
|
-
for (const source of config.sources) {
|
|
211
|
-
console.log("Generating", source.input, "to", source.output);
|
|
212
|
-
if (source.kind === "file" || !source.kind) {
|
|
213
|
-
const files = globSync(normalizeInputPattern(source.input));
|
|
214
|
-
const prompt = await loadPrompt(source.prompt);
|
|
215
|
-
|
|
216
|
-
if (source.output.endsWith("/")) {
|
|
217
|
-
await handleMultiOutputGeneration(
|
|
218
|
-
source.model,
|
|
219
|
-
source.input,
|
|
220
|
-
files,
|
|
221
|
-
prompt,
|
|
222
|
-
source.output,
|
|
223
|
-
source.outputExt,
|
|
224
|
-
source.outputName,
|
|
225
|
-
source.kind,
|
|
226
|
-
source.agent
|
|
227
|
-
);
|
|
228
|
-
} else {
|
|
229
|
-
await handleSingleOutputGeneration(
|
|
230
|
-
source.model,
|
|
231
|
-
files,
|
|
232
|
-
prompt,
|
|
233
|
-
source.output,
|
|
234
|
-
source.kind,
|
|
235
|
-
source.agent
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
} else {
|
|
239
|
-
await handleAllKindsGeneration(source);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async function handleAllKindsGeneration(source: GenerationSource) {
|
|
245
|
-
const { Plugins } = services();
|
|
246
|
-
const { kind, input } = source;
|
|
247
|
-
if (Plugins.isPlugin(kind)) {
|
|
248
|
-
const data = await Plugins.call(kind, input);
|
|
249
|
-
if (source.output.endsWith("/")) {
|
|
250
|
-
throw new Error(`Plugin ${kind} can only output to a single file`);
|
|
251
|
-
}
|
|
252
|
-
await writeFile(source.output, data);
|
|
253
|
-
}
|
|
254
|
-
return handleFileKindGeneration(source);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async function handleFileKindGeneration(source: GenerationSource) {
|
|
258
|
-
const prompt = await loadPrompt(source.prompt);
|
|
259
|
-
const files = globSync(normalizeInputPattern(source.input));
|
|
260
|
-
console.log("Analyzing files: ", files);
|
|
261
|
-
|
|
262
|
-
if (source.output.endsWith("/")) {
|
|
263
|
-
await handleMultiOutputGeneration(
|
|
264
|
-
source.model,
|
|
265
|
-
source.input,
|
|
266
|
-
files,
|
|
267
|
-
prompt,
|
|
268
|
-
source.output,
|
|
269
|
-
source.outputExt,
|
|
270
|
-
source.outputName,
|
|
271
|
-
source.kind,
|
|
272
|
-
source.agent
|
|
273
|
-
);
|
|
274
|
-
} else {
|
|
275
|
-
await handleSingleOutputGeneration(
|
|
276
|
-
source.model,
|
|
277
|
-
files,
|
|
278
|
-
prompt,
|
|
279
|
-
source.output,
|
|
280
|
-
source.kind,
|
|
281
|
-
source.agent
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
export async function handleMultiOutputGeneration(
|
|
286
|
-
model: string,
|
|
287
|
-
inputPattern: string,
|
|
288
|
-
files: string[],
|
|
289
|
-
prompt: string,
|
|
290
|
-
output: string,
|
|
291
|
-
outputExt = "mdx",
|
|
292
|
-
outputName?: string,
|
|
293
|
-
kind?: string,
|
|
294
|
-
agent?: string
|
|
295
|
-
) {
|
|
296
|
-
// get the hash of the prompt
|
|
297
|
-
const promptHash = crypto.createHash("md5").update(prompt).digest("hex");
|
|
298
|
-
|
|
299
|
-
// get the files matching the input pattern
|
|
300
|
-
const hashes = await getHashes();
|
|
301
|
-
|
|
302
|
-
const inputPath = inputPattern.includes("**")
|
|
303
|
-
? inputPattern.split("**")[0]
|
|
304
|
-
: "";
|
|
305
|
-
|
|
306
|
-
for (const file of files) {
|
|
307
|
-
// get the hash of the file
|
|
308
|
-
const fileContent = await convertToText(file);
|
|
309
|
-
const fileHash = crypto.createHash("md5").update(fileContent).digest("hex");
|
|
310
|
-
|
|
311
|
-
if (!hashes[file]) {
|
|
312
|
-
hashes[file] = { promptHash: "", fileHash: "" };
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// write the summary to the output file
|
|
316
|
-
const { name, ext, dir } = path.parse(file);
|
|
317
|
-
const nestedFolder = inputPath ? (dir + "/").replace(inputPath, "") : "";
|
|
318
|
-
const outputFolder = path.join(output, nestedFolder);
|
|
319
|
-
|
|
320
|
-
if (!fs.existsSync(outputFolder)) {
|
|
321
|
-
fs.mkdirSync(outputFolder, { recursive: true });
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const outputFileName = outputName || name;
|
|
325
|
-
const outputFile = path.join(
|
|
326
|
-
outputFolder,
|
|
327
|
-
outputFileName + "." + outputExt
|
|
328
|
-
);
|
|
329
|
-
console.log({ dir, inputPath, nestedFolder, outputFile });
|
|
330
|
-
|
|
331
|
-
const toCheck = [file, outputFile];
|
|
332
|
-
const noChanges = await checkNoFilesChanged(toCheck, promptHash, hashes);
|
|
333
|
-
if (noChanges) {
|
|
334
|
-
console.log("Skipping file", file, "because it hasn't changed");
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// summarize the file
|
|
339
|
-
console.log("Summarizing", file);
|
|
340
|
-
const summary = prompt
|
|
341
|
-
? await summarizeFile(file, prompt, model, agent)
|
|
342
|
-
: fileContent;
|
|
343
|
-
|
|
344
|
-
console.log("Writing summary to", outputFile);
|
|
345
|
-
await writeFile(outputFile, summary);
|
|
346
|
-
|
|
347
|
-
await saveAllFileHashes(toCheck, promptHash);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export async function handleSingleOutputGeneration(
|
|
352
|
-
model: string,
|
|
353
|
-
files: string[],
|
|
354
|
-
prompt: string,
|
|
355
|
-
outputFile: string,
|
|
356
|
-
kind?: string,
|
|
357
|
-
agent?: string
|
|
358
|
-
) {
|
|
359
|
-
const hashes = await getHashes();
|
|
360
|
-
const promptHash = crypto.createHash("md5").update(prompt).digest("hex");
|
|
361
|
-
|
|
362
|
-
const filesToCheck = [outputFile, ...files];
|
|
363
|
-
const noChanges = await checkNoFilesChanged(filesToCheck, promptHash, hashes);
|
|
364
|
-
if (noChanges) {
|
|
365
|
-
console.log(`Skipping ${files.length} files because they haven't changed`);
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
console.log("Summarizing", files.length, "files");
|
|
370
|
-
const summary = prompt
|
|
371
|
-
? await summarizeFiles(files, prompt, model, agent)
|
|
372
|
-
: (await Promise.all(files.map(convertToText))).join("\n\n");
|
|
373
|
-
|
|
374
|
-
const fileHash = crypto.createHash("md5").update(summary).digest("hex");
|
|
375
|
-
|
|
376
|
-
console.log("Writing summary to", outputFile);
|
|
377
|
-
await writeFile(outputFile, summary);
|
|
378
|
-
|
|
379
|
-
await saveAllFileHashes(filesToCheck, promptHash);
|
|
380
|
-
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Download
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
381
200
|
|
|
382
201
|
export async function download() {
|
|
383
202
|
const config = await getConfig();
|
|
@@ -421,10 +240,7 @@ export async function download() {
|
|
|
421
240
|
"to",
|
|
422
241
|
destinationPath
|
|
423
242
|
);
|
|
424
|
-
const preSignedUrl = await knowhowApiClient.getPresignedDownloadUrl(
|
|
425
|
-
source
|
|
426
|
-
);
|
|
427
|
-
// Ensure output directory exists
|
|
243
|
+
const preSignedUrl = await knowhowApiClient.getPresignedDownloadUrl(source);
|
|
428
244
|
const outputDir = path.dirname(destinationPath);
|
|
429
245
|
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
|
|
430
246
|
await AwsS3.downloadFromPresignedUrl(preSignedUrl, destinationPath);
|
|
@@ -190,6 +190,38 @@ export class BehaviorsService {
|
|
|
190
190
|
return null;
|
|
191
191
|
}
|
|
192
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
|
+
|
|
193
225
|
async list(opts: { skillsOnly?: boolean; includeInternal?: boolean } = {}) {
|
|
194
226
|
const behaviors = opts.skillsOnly
|
|
195
227
|
? await this.client.getOrgSkills(opts.includeInternal ?? true)
|
|
@@ -3,6 +3,7 @@ import * as path from "path";
|
|
|
3
3
|
import * as os from "os";
|
|
4
4
|
import { createHash } from "crypto";
|
|
5
5
|
import * as https from "https";
|
|
6
|
+
import { KnowhowSimpleClient } from "./KnowhowClient";
|
|
6
7
|
|
|
7
8
|
// Default local install dir (relative to cwd)
|
|
8
9
|
const LOCAL_SKILLS_DIR = ".knowhow/skills";
|
|
@@ -236,6 +237,7 @@ export class SkillsService {
|
|
|
236
237
|
private cwd: string;
|
|
237
238
|
private isGlobal: boolean;
|
|
238
239
|
private customInstallDir?: string;
|
|
240
|
+
public client = new KnowhowSimpleClient();
|
|
239
241
|
|
|
240
242
|
constructor(opts: SkillsServiceOptions = {}) {
|
|
241
243
|
this.cwd = opts.cwd ?? process.cwd();
|
|
@@ -619,4 +621,200 @@ export class SkillsService {
|
|
|
619
621
|
`\nDone: ${updated} updated, ${unchanged} unchanged, ${failed} failed.`
|
|
620
622
|
);
|
|
621
623
|
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Upload locally installed skills (from .knowhow/skills/<name>/SKILL.md) to the backend as behaviors.
|
|
627
|
+
* Each skill is upserted: created if it has no id in frontmatter, updated if it does.
|
|
628
|
+
*/
|
|
629
|
+
async upload(): Promise<void> {
|
|
630
|
+
const installDir = this.getInstallDir();
|
|
631
|
+
|
|
632
|
+
if (!fs.existsSync(installDir)) {
|
|
633
|
+
console.error(
|
|
634
|
+
`No skills directory found at ${installDir}. Run 'knowhow skills add <ref>' first.`
|
|
635
|
+
);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const entries = fs.readdirSync(installDir, { withFileTypes: true });
|
|
640
|
+
const skillDirs = entries.filter((e) => e.isDirectory());
|
|
641
|
+
|
|
642
|
+
if (skillDirs.length === 0) {
|
|
643
|
+
console.log("No skill directories found. Nothing to upload.");
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
let uploaded = 0;
|
|
648
|
+
let failed = 0;
|
|
649
|
+
|
|
650
|
+
for (const dir of skillDirs) {
|
|
651
|
+
const skillFile = path.join(installDir, dir.name, "SKILL.md");
|
|
652
|
+
if (!fs.existsSync(skillFile)) {
|
|
653
|
+
console.warn(` ⚠ ${dir.name}: no SKILL.md found, skipping.`);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const raw = fs.readFileSync(skillFile, "utf-8");
|
|
659
|
+
const behavior = this.parseSkillMd(raw, dir.name);
|
|
660
|
+
|
|
661
|
+
// Always mark as a skill
|
|
662
|
+
const payload: Record<string, unknown> = {
|
|
663
|
+
...behavior,
|
|
664
|
+
isSkill: true,
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
if (behavior.id) {
|
|
668
|
+
await this.client.updateOrgBehavior(behavior.id as string, payload);
|
|
669
|
+
console.log(` 📝 Updated: ${behavior.name} (${behavior.id as string})`);
|
|
670
|
+
} else {
|
|
671
|
+
const result = await this.client.createOrgBehavior(payload);
|
|
672
|
+
const created = result.data as Record<string, unknown>;
|
|
673
|
+
if (created.id) {
|
|
674
|
+
// Write the assigned id back into the SKILL.md frontmatter so future uploads update instead of create
|
|
675
|
+
const withId = this.injectIdIntoSkillMd(raw, created.id as string);
|
|
676
|
+
fs.writeFileSync(skillFile, withId, "utf-8");
|
|
677
|
+
console.log(` ✨ Created: ${behavior.name} (id: ${created.id})`);
|
|
678
|
+
} else {
|
|
679
|
+
console.log(` ✨ Created: ${behavior.name}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
uploaded++;
|
|
683
|
+
} catch (e) {
|
|
684
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
685
|
+
console.error(` ❌ ${dir.name}: ${msg}`);
|
|
686
|
+
failed++;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
console.log(`\nUploaded ${uploaded} skill(s). Failed: ${failed}.`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Download skill behaviors from the backend and write them as SKILL.md files
|
|
695
|
+
* into the skills install directory (.knowhow/skills/<name>/SKILL.md).
|
|
696
|
+
*/
|
|
697
|
+
async download(): Promise<void> {
|
|
698
|
+
const installDir = this.getInstallDir();
|
|
699
|
+
const result = await this.client.getOrgSkills(false); // false = exclude platform/internal skills
|
|
700
|
+
const skills = result.data as Record<string, unknown>[];
|
|
701
|
+
|
|
702
|
+
if (skills.length === 0) {
|
|
703
|
+
console.log("No skills found on backend.");
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
let created = 0;
|
|
708
|
+
let updated = 0;
|
|
709
|
+
let unchanged = 0;
|
|
710
|
+
|
|
711
|
+
for (const skill of skills) {
|
|
712
|
+
const name = ((skill.name as string) || "unnamed")
|
|
713
|
+
.toLowerCase()
|
|
714
|
+
.replace(/[^a-z0-9-]+/g, "-");
|
|
715
|
+
const skillDir = path.join(installDir, name);
|
|
716
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
717
|
+
|
|
718
|
+
const skillFile = path.join(skillDir, "SKILL.md");
|
|
719
|
+
const content = this.serializeSkillMd(skill);
|
|
720
|
+
|
|
721
|
+
const hash = (s: string) => createHash("sha256").update(s).digest("hex");
|
|
722
|
+
if (!fs.existsSync(skillFile)) {
|
|
723
|
+
fs.writeFileSync(skillFile, content, "utf-8");
|
|
724
|
+
console.log(` ✨ Created: ${skillFile}`);
|
|
725
|
+
created++;
|
|
726
|
+
} else {
|
|
727
|
+
const existing = fs.readFileSync(skillFile, "utf-8");
|
|
728
|
+
if (hash(existing) !== hash(content)) {
|
|
729
|
+
fs.writeFileSync(skillFile, content, "utf-8");
|
|
730
|
+
console.log(` 📝 Updated: ${skillFile}`);
|
|
731
|
+
updated++;
|
|
732
|
+
} else {
|
|
733
|
+
unchanged++;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
console.log(
|
|
739
|
+
`\nSync complete: ${created} created, ${updated} updated, ${unchanged} unchanged → ${installDir}/`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Parse a SKILL.md file into a behavior object.
|
|
745
|
+
* Falls back to using the directory name if name is missing from frontmatter.
|
|
746
|
+
*/
|
|
747
|
+
private parseSkillMd(content: string, fallbackName: string): Record<string, unknown> {
|
|
748
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
749
|
+
if (!match) {
|
|
750
|
+
return { name: fallbackName, description: "", instructions: content.trim(), isSkill: true };
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const meta: Record<string, string> = {};
|
|
754
|
+
for (const line of match[1].split("\n")) {
|
|
755
|
+
const colonIdx = line.indexOf(":");
|
|
756
|
+
if (colonIdx > 0) {
|
|
757
|
+
const key = line.slice(0, colonIdx).trim();
|
|
758
|
+
const val = line.slice(colonIdx + 1).trim();
|
|
759
|
+
meta[key] = val;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return {
|
|
764
|
+
id: meta.id || undefined,
|
|
765
|
+
name: meta.name || fallbackName,
|
|
766
|
+
description: meta.description || "",
|
|
767
|
+
instructions: match[2].trim(),
|
|
768
|
+
textTrigger: meta.textTrigger || undefined,
|
|
769
|
+
semanticTriggerText: meta.semanticTriggerText || undefined,
|
|
770
|
+
model: meta.model || undefined,
|
|
771
|
+
mcpServers: meta.mcpServers || undefined,
|
|
772
|
+
tools: meta.tools || undefined,
|
|
773
|
+
embeddings: meta.embeddings || undefined,
|
|
774
|
+
isSkill: true,
|
|
775
|
+
isPublic: meta.isPublic === "true",
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Serialize a backend behavior record as a SKILL.md string.
|
|
781
|
+
*/
|
|
782
|
+
private serializeSkillMd(skill: Record<string, unknown>): string {
|
|
783
|
+
const lines: string[] = ["---"];
|
|
784
|
+
lines.push(`name: ${skill.name || ""}`);
|
|
785
|
+
lines.push(`description: ${skill.description || ""}`);
|
|
786
|
+
if (skill.model) lines.push(`model: ${skill.model}`);
|
|
787
|
+
if (skill.textTrigger) lines.push(`textTrigger: ${skill.textTrigger}`);
|
|
788
|
+
if (skill.semanticTriggerText) lines.push(`semanticTriggerText: ${skill.semanticTriggerText}`);
|
|
789
|
+
if (skill.mcpServers) lines.push(`mcpServers: ${skill.mcpServers}`);
|
|
790
|
+
if (skill.tools) lines.push(`tools: ${skill.tools}`);
|
|
791
|
+
if (skill.embeddings) lines.push(`embeddings: ${skill.embeddings}`);
|
|
792
|
+
if (skill.isPublic !== undefined) lines.push(`isPublic: ${skill.isPublic}`);
|
|
793
|
+
if (skill.id) lines.push(`id: ${skill.id}`);
|
|
794
|
+
lines.push("---");
|
|
795
|
+
lines.push("");
|
|
796
|
+
lines.push(((skill.instructions as string) || "").trim());
|
|
797
|
+
lines.push("");
|
|
798
|
+
return lines.join("\n");
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Inject or replace the `id:` field in a SKILL.md frontmatter.
|
|
803
|
+
*/
|
|
804
|
+
private injectIdIntoSkillMd(content: string, id: string): string {
|
|
805
|
+
const match = content.match(/^(---\n[\s\S]*?\n---\n?)([\s\S]*)$/);
|
|
806
|
+
if (!match) return content;
|
|
807
|
+
|
|
808
|
+
let frontmatter = match[1];
|
|
809
|
+
const body = match[2];
|
|
810
|
+
|
|
811
|
+
if (/^id:/m.test(frontmatter)) {
|
|
812
|
+
frontmatter = frontmatter.replace(/^id:.*$/m, `id: ${id}`);
|
|
813
|
+
} else {
|
|
814
|
+
// Insert before closing ---
|
|
815
|
+
frontmatter = frontmatter.replace(/\n---\n?$/, `\nid: ${id}\n---\n`);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return frontmatter + body;
|
|
819
|
+
}
|
|
622
820
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,23 +1,55 @@
|
|
|
1
1
|
import { AssistantTool } from "openai/resources/beta";
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Flexible hash store. Each key is either:
|
|
6
|
+
* - a file path (legacy): { fileHash, promptHash, [promptHash]: fileHash }
|
|
7
|
+
* - a named build slot (new): { [inputKey: string]: hashValue }
|
|
8
|
+
*
|
|
9
|
+
* Both formats use the same shape: an object of string→string pairs.
|
|
10
|
+
* `fileHash` and `promptHash` are just conventional keys in the legacy format.
|
|
11
|
+
*/
|
|
4
12
|
export type Hashes = {
|
|
5
|
-
[
|
|
6
|
-
[
|
|
7
|
-
promptHash: string;
|
|
8
|
-
fileHash: string;
|
|
13
|
+
[key: string]: {
|
|
14
|
+
[inputKey: string]: string;
|
|
9
15
|
};
|
|
10
16
|
};
|
|
11
17
|
|
|
18
|
+
/**
|
|
19
|
+
* A reference to an external JSON file containing an array of GenerationSource
|
|
20
|
+
* or EmbedSource entries. Used in knowhow.json `sources` / `embedSources` arrays
|
|
21
|
+
* to split a large config across multiple files:
|
|
22
|
+
* { "from": "./generate/autodoc.json" }
|
|
23
|
+
*/
|
|
24
|
+
export type SourceRef = { from: string };
|
|
25
|
+
|
|
12
26
|
export type GenerationSource = {
|
|
13
27
|
model?: string;
|
|
14
28
|
agent?: string;
|
|
29
|
+
name?: string;
|
|
15
30
|
input: string;
|
|
16
31
|
output: string;
|
|
17
32
|
prompt: string;
|
|
18
33
|
kind?: string;
|
|
19
34
|
outputExt?: string;
|
|
20
35
|
outputName?: string;
|
|
36
|
+
/** Pass to runAgentTask: enable .knowhow/processes/agents/<taskId> directory */
|
|
37
|
+
syncFs?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Deterministic task ID for this source (used with syncFs).
|
|
40
|
+
* Defaults to "generate:<sanitized-output>" when syncFs is enabled.
|
|
41
|
+
*/
|
|
42
|
+
taskId?: string;
|
|
43
|
+
/** Time limit in minutes for the agent (optional) */
|
|
44
|
+
maxTimeLimit?: number;
|
|
45
|
+
/** Cost limit in dollars for the agent (optional) */
|
|
46
|
+
maxSpendLimit?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Names of other GenerationSource entries (by `name` field) that must
|
|
49
|
+
* complete before this source runs. Used for explicit dependency ordering
|
|
50
|
+
* when automatic I/O overlap detection is insufficient.
|
|
51
|
+
*/
|
|
52
|
+
dependsOn?: string[];
|
|
21
53
|
};
|
|
22
54
|
|
|
23
55
|
export type EmbedSource = {
|
package/ts_build/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfigAgent.js","sourceRoot":"","sources":["../../../../src/agents/configurable/ConfigAgent.ts"],"names":[],"mappings":";;;AAAA,uCAAuD;
|
|
1
|
+
{"version":3,"file":"ConfigAgent.js","sourceRoot":"","sources":["../../../../src/agents/configurable/ConfigAgent.ts"],"names":[],"mappings":";;;AAAA,uCAAuD;AAKvD,MAAa,WAAY,SAAQ,gBAAS;IAIpB;IAHpB,IAAI,CAAS;IACb,WAAW,CAAS;IAEpB,YAAoB,MAAiB,EAAE,OAAqB;QAC1D,KAAK,CAAC,OAAO,CAAC,CAAC;QADG,WAAM,GAAN,MAAM,CAAW;QAEnC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,mBAAmB,CAAC;YACvB,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE;SACnD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,OAAO;YACL,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;YACrD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE;SACxB,CAAC;IACjB,CAAC;CACF;AAlBD,kCAkBC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from "../../clients/types";
|
|
2
|
-
interface StartAgentTaskParams {
|
|
2
|
+
export interface StartAgentTaskParams {
|
|
3
3
|
messageId?: string;
|
|
4
4
|
syncFs?: boolean;
|
|
5
5
|
taskId?: string;
|
|
@@ -10,7 +10,7 @@ interface StartAgentTaskParams {
|
|
|
10
10
|
agentName?: string;
|
|
11
11
|
maxTimeLimit?: number;
|
|
12
12
|
maxSpendLimit?: number;
|
|
13
|
+
waitForCompletion?: boolean;
|
|
13
14
|
}
|
|
14
15
|
export declare function startAgentTask(params: StartAgentTaskParams): Promise<string>;
|
|
15
16
|
export declare const startAgentTaskDefinition: Tool;
|
|
16
|
-
export {};
|