@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.
- package/package.json +4 -4
- package/src/agents/base/base.ts +40 -5
- package/src/agents/configurable/ConfigAgent.ts +0 -1
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -0
- package/src/agents/tools/skills.ts +79 -0
- 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 +156 -15
- 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 +10 -0
- package/src/clients/anthropic.ts +68 -2
- package/src/clients/fireworks.ts +10 -1
- package/src/clients/index.ts +5 -3
- package/src/clients/meta.ts +9 -0
- package/src/clients/openai.ts +28 -5
- package/src/clients/pricing/anthropic.ts +11 -0
- package/src/clients/pricing/fireworks.ts +12 -0
- package/src/clients/pricing/openai.ts +3 -0
- package/src/clients/types.ts +27 -4
- package/src/clients/xai.ts +2 -2
- package/src/commands/agent.ts +70 -18
- package/src/commands/agents.ts +556 -0
- package/src/commands/behaviors.ts +47 -0
- package/src/commands/hash.ts +124 -0
- package/src/commands/misc.ts +157 -3
- package/src/commands/services.ts +6 -2
- package/src/commands/skills.ts +107 -0
- package/src/config.ts +51 -1
- package/src/generate.ts +507 -0
- package/src/index.ts +34 -216
- package/src/plugins/PluginBase.ts +0 -2
- package/src/plugins/SkillsPlugin.ts +11 -124
- package/src/plugins/language.ts +0 -1
- package/src/plugins/plugins.ts +24 -13
- package/src/plugins/types.ts +8 -2
- package/src/services/BehaviorsService.ts +464 -0
- package/src/services/KnowhowClient.ts +61 -0
- package/src/services/SkillsService.ts +820 -0
- package/src/services/Tools.ts +2 -0
- package/src/services/index.ts +8 -2
- package/src/services/modules/types.ts +2 -0
- package/src/types.ts +36 -4
- package/ts_build/package.json +4 -4
- package/ts_build/src/agents/base/base.d.ts +9 -1
- package/ts_build/src/agents/base/base.js +29 -4
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/skills.d.ts +33 -0
- package/ts_build/src/agents/tools/skills.js +62 -0
- package/ts_build/src/agents/tools/skills.js.map +1 -0
- 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.d.ts +2 -0
- package/ts_build/src/chat/modules/AgentModule.js +130 -4
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +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 +9 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +2 -0
- package/ts_build/src/clients/anthropic.js +39 -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 +3 -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/openai.d.ts +1 -0
- package/ts_build/src/clients/openai.js +25 -5
- package/ts_build/src/clients/openai.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/clients/pricing/openai.js +3 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +6 -1
- package/ts_build/src/clients/xai.js +1 -1
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/agent.js +58 -15
- 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/behaviors.d.ts +2 -0
- package/ts_build/src/commands/behaviors.js +43 -0
- package/ts_build/src/commands/behaviors.js.map +1 -0
- package/ts_build/src/commands/hash.d.ts +2 -0
- package/ts_build/src/commands/hash.js +117 -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/services.js +4 -2
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/commands/skills.d.ts +2 -0
- package/ts_build/src/commands/skills.js +85 -0
- package/ts_build/src/commands/skills.js.map +1 -0
- package/ts_build/src/config.js +34 -0
- 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 +3 -3
- package/ts_build/src/index.js +13 -118
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/plugins/PluginBase.d.ts +0 -2
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
- package/ts_build/src/plugins/SkillsPlugin.js +6 -135
- package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +4 -1
- package/ts_build/src/plugins/plugins.js +17 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/types.d.ts +3 -2
- package/ts_build/src/services/BehaviorsService.d.ts +48 -0
- package/ts_build/src/services/BehaviorsService.js +382 -0
- package/ts_build/src/services/BehaviorsService.js.map +1 -0
- package/ts_build/src/services/KnowhowClient.d.ts +8 -0
- package/ts_build/src/services/KnowhowClient.js +20 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/SkillsService.d.ts +60 -0
- package/ts_build/src/services/SkillsService.js +641 -0
- package/ts_build/src/services/SkillsService.js.map +1 -0
- package/ts_build/src/services/Tools.d.ts +2 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/index.d.ts +3 -0
- package/ts_build/src/services/index.js +8 -2
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +4 -2
- package/ts_build/src/services/modules/types.d.ts +2 -0
- package/ts_build/src/types.d.ts +14 -4
- package/ts_build/src/types.js.map +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import * as crypto from "crypto";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import { globSync } from "glob";
|
|
5
|
+
import { getHashes, saveHashes } from "../hashes";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compute an MD5 hash over the sorted contents of all matched files.
|
|
9
|
+
* Uses file content only (not mtime) for stability across CI clones.
|
|
10
|
+
*/
|
|
11
|
+
function hashFiles(files: string[]): string {
|
|
12
|
+
const hash = crypto.createHash("md5");
|
|
13
|
+
const sorted = [...files].sort();
|
|
14
|
+
for (const f of sorted) {
|
|
15
|
+
if (!fs.existsSync(f)) continue;
|
|
16
|
+
const content = fs.readFileSync(f);
|
|
17
|
+
hash.update(f);
|
|
18
|
+
hash.update(content);
|
|
19
|
+
}
|
|
20
|
+
return hash.digest("hex");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function addHashCommand(program: Command): void {
|
|
24
|
+
program
|
|
25
|
+
.command("hash")
|
|
26
|
+
.description(
|
|
27
|
+
"Check whether a named set of input files has changed since last saved. " +
|
|
28
|
+
"Exits 0 (unchanged) or 1 (changed / first run). " +
|
|
29
|
+
"Use with shell short-circuit: (knowhow hash ... || run-build-step)"
|
|
30
|
+
)
|
|
31
|
+
.requiredOption(
|
|
32
|
+
"--name <name>",
|
|
33
|
+
"Unique name for this hash slot (e.g. 'build', 'generate-api'). Becomes the top-level key in .hashes.json."
|
|
34
|
+
)
|
|
35
|
+
.requiredOption(
|
|
36
|
+
"--input <glob>",
|
|
37
|
+
"Glob pattern(s) of input files to hash. Separate multiple globs with commas."
|
|
38
|
+
)
|
|
39
|
+
.option(
|
|
40
|
+
"--output <paths>",
|
|
41
|
+
"Optional output file(s) — if any are missing the check always returns changed (comma-separated)."
|
|
42
|
+
)
|
|
43
|
+
.option(
|
|
44
|
+
"--save",
|
|
45
|
+
"Save the current hash after a successful build step."
|
|
46
|
+
)
|
|
47
|
+
.option("--verbose", "Print what files were found and the computed hash")
|
|
48
|
+
.action(
|
|
49
|
+
async (opts: {
|
|
50
|
+
name: string;
|
|
51
|
+
input: string;
|
|
52
|
+
output?: string;
|
|
53
|
+
save?: boolean;
|
|
54
|
+
verbose?: boolean;
|
|
55
|
+
}) => {
|
|
56
|
+
const hashes = await getHashes();
|
|
57
|
+
|
|
58
|
+
const inputGlobs = opts.input.split(",").map((g) => g.trim());
|
|
59
|
+
const outputPaths = opts.output
|
|
60
|
+
? opts.output.split(",").map((p) => p.trim())
|
|
61
|
+
: [];
|
|
62
|
+
|
|
63
|
+
// Expand input globs
|
|
64
|
+
const inputFiles: string[] = [];
|
|
65
|
+
for (const pattern of inputGlobs) {
|
|
66
|
+
const matches = globSync(pattern, { nodir: true });
|
|
67
|
+
inputFiles.push(...matches);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (opts.verbose) {
|
|
71
|
+
console.error(`[knowhow hash] name=${opts.name}`);
|
|
72
|
+
console.error(
|
|
73
|
+
`[knowhow hash] input files (${inputFiles.length}): ${inputFiles.slice(0, 10).join(", ")}${inputFiles.length > 10 ? "…" : ""}`
|
|
74
|
+
);
|
|
75
|
+
if (outputPaths.length) {
|
|
76
|
+
console.error(`[knowhow hash] output files: ${outputPaths.join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const currentHash = hashFiles(inputFiles);
|
|
81
|
+
|
|
82
|
+
// --save mode: record into .hashes.json under hashes[name][inputGlob] = hash
|
|
83
|
+
if (opts.save) {
|
|
84
|
+
if (!hashes[opts.name]) hashes[opts.name] = {};
|
|
85
|
+
hashes[opts.name][opts.input] = currentHash;
|
|
86
|
+
await saveHashes(hashes);
|
|
87
|
+
if (opts.verbose) {
|
|
88
|
+
console.error(`[knowhow hash] saved hash ${currentHash} for "${opts.name}"`);
|
|
89
|
+
}
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check mode: if any output file is missing, always report changed
|
|
94
|
+
for (const outPath of outputPaths) {
|
|
95
|
+
if (!fs.existsSync(outPath)) {
|
|
96
|
+
if (opts.verbose) {
|
|
97
|
+
console.error(`[knowhow hash] output file missing: ${outPath} → changed`);
|
|
98
|
+
}
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const storedHash: string | undefined = hashes[opts.name]?.[opts.input];
|
|
104
|
+
|
|
105
|
+
if (opts.verbose) {
|
|
106
|
+
console.error(
|
|
107
|
+
`[knowhow hash] stored=${storedHash ?? "(none)"} current=${currentHash}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (storedHash && storedHash === currentHash) {
|
|
112
|
+
if (opts.verbose) {
|
|
113
|
+
console.error(`[knowhow hash] ✓ unchanged — skipping`);
|
|
114
|
+
}
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (opts.verbose) {
|
|
119
|
+
console.error(`[knowhow hash] ✗ changed — rebuild needed`);
|
|
120
|
+
}
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
package/src/commands/misc.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { Command } from "commander";
|
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
3
|
import { version } from "../../package.json";
|
|
4
4
|
import { logger } from "../logger";
|
|
5
|
-
import {
|
|
5
|
+
import { embed, upload, download, purge } from "../index";
|
|
6
|
+
import { generate, buildWaves, normalizeInputPattern, GenerateOptions } from "../generate";
|
|
6
7
|
import { init } from "../config";
|
|
7
8
|
import { login } from "../login";
|
|
8
9
|
import { KnowhowSimpleClient } from "../services/KnowhowClient";
|
|
@@ -53,13 +54,166 @@ export function addGenerateCommand(program: Command): void {
|
|
|
53
54
|
program
|
|
54
55
|
.command("generate")
|
|
55
56
|
.description("Generate documentation")
|
|
56
|
-
.
|
|
57
|
+
.option(
|
|
58
|
+
"--name <name>",
|
|
59
|
+
"Only run the generation source with this exact `name`"
|
|
60
|
+
)
|
|
61
|
+
.option(
|
|
62
|
+
"--filter <substring>",
|
|
63
|
+
"Only run generation sources whose name/input/output contains this substring"
|
|
64
|
+
)
|
|
65
|
+
.option(
|
|
66
|
+
"--sync-fs",
|
|
67
|
+
"Enable filesystem sync for agent-driven sources (creates .knowhow/processes/agents/<taskId>/ so tasks appear in `knowhow agents list`)"
|
|
68
|
+
)
|
|
69
|
+
.option(
|
|
70
|
+
"--force",
|
|
71
|
+
"Skip hash checks and regenerate all matching sources regardless of whether inputs/outputs have changed"
|
|
72
|
+
)
|
|
73
|
+
.option(
|
|
74
|
+
"--concurrency <n>",
|
|
75
|
+
"Maximum number of sources to run in parallel within a dependency wave (default: 3)",
|
|
76
|
+
(v) => parseInt(v, 10)
|
|
77
|
+
)
|
|
78
|
+
.option(
|
|
79
|
+
"--plan",
|
|
80
|
+
"Show the execution plan (dependency waves, skip/run status) without actually generating anything"
|
|
81
|
+
)
|
|
82
|
+
.action(async (options: {
|
|
83
|
+
name?: string;
|
|
84
|
+
filter?: string;
|
|
85
|
+
syncFs?: boolean;
|
|
86
|
+
force?: boolean;
|
|
87
|
+
concurrency?: number;
|
|
88
|
+
plan?: boolean;
|
|
89
|
+
}) => {
|
|
57
90
|
const { setupServices } = await import("./services");
|
|
58
91
|
await setupServices();
|
|
59
|
-
|
|
92
|
+
|
|
93
|
+
if (options.plan) {
|
|
94
|
+
await showGeneratePlan(options);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await generate({
|
|
99
|
+
name: options.name,
|
|
100
|
+
filter: options.filter,
|
|
101
|
+
syncFs: options.syncFs,
|
|
102
|
+
force: options.force,
|
|
103
|
+
concurrency: options.concurrency,
|
|
104
|
+
});
|
|
60
105
|
});
|
|
61
106
|
}
|
|
62
107
|
|
|
108
|
+
async function showGeneratePlan(options: {
|
|
109
|
+
name?: string;
|
|
110
|
+
filter?: string;
|
|
111
|
+
force?: boolean;
|
|
112
|
+
concurrency?: number;
|
|
113
|
+
}): Promise<void> {
|
|
114
|
+
const { getConfig } = await import("../config");
|
|
115
|
+
const { getHashes, checkNoFilesChanged } = await import("../hashes");
|
|
116
|
+
const { globSync } = await import("glob");
|
|
117
|
+
const crypto = await import("crypto");
|
|
118
|
+
const { loadPrompt } = await import("../config");
|
|
119
|
+
|
|
120
|
+
const config = await getConfig();
|
|
121
|
+
const allSources = config.sources ?? [];
|
|
122
|
+
|
|
123
|
+
// Filter sources
|
|
124
|
+
const sources = allSources.filter((s) => {
|
|
125
|
+
if (options.name) return s.name === options.name;
|
|
126
|
+
if (options.filter) {
|
|
127
|
+
const needle = options.filter.toLowerCase();
|
|
128
|
+
return [s.name, s.input, s.output].filter(Boolean).some((v) => v!.toLowerCase().includes(needle));
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (sources.length === 0) {
|
|
134
|
+
console.warn("No generation sources matched the filter.");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const concurrency = options.concurrency ?? 3;
|
|
139
|
+
const waves = buildWaves(sources);
|
|
140
|
+
const hashes = await getHashes();
|
|
141
|
+
|
|
142
|
+
console.log(`\n📋 Generation Plan (concurrency: ${concurrency})\n`);
|
|
143
|
+
console.log(` Total sources : ${sources.length}`);
|
|
144
|
+
console.log(` Waves : ${waves.length}`);
|
|
145
|
+
console.log("");
|
|
146
|
+
|
|
147
|
+
for (let wi = 0; wi < waves.length; wi++) {
|
|
148
|
+
const wave = waves[wi];
|
|
149
|
+
console.log(`┌─ Wave ${wi + 1}/${waves.length} (${wave.length} source${wave.length !== 1 ? "s" : ""} in parallel)`);
|
|
150
|
+
|
|
151
|
+
for (const source of wave) {
|
|
152
|
+
// Determine skip/run status via hash check
|
|
153
|
+
let status = "▶ run ";
|
|
154
|
+
let reason = "";
|
|
155
|
+
|
|
156
|
+
if (!options.force) {
|
|
157
|
+
try {
|
|
158
|
+
const prompt = await loadPrompt(source.prompt);
|
|
159
|
+
const promptHash = crypto.createHash("md5").update(prompt || "").digest("hex");
|
|
160
|
+
let filesToCheck: string[];
|
|
161
|
+
|
|
162
|
+
if (source.output.endsWith("/")) {
|
|
163
|
+
// For multi-output we check the input files individually;
|
|
164
|
+
// just check one representative glob match to indicate staleness
|
|
165
|
+
const files = globSync(normalizeInputPattern(source.input || ""));
|
|
166
|
+
filesToCheck = files.length > 0 ? [files[0], source.output] : [source.output];
|
|
167
|
+
} else {
|
|
168
|
+
const files = globSync(normalizeInputPattern(source.input || ""));
|
|
169
|
+
filesToCheck = [source.output, ...files];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const noChanges = await checkNoFilesChanged(filesToCheck, promptHash, hashes);
|
|
173
|
+
if (noChanges) {
|
|
174
|
+
status = "⏭ skip";
|
|
175
|
+
reason = " (no changes)";
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
// If hash check fails just mark as run
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
reason = " (--force)";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const deps = source.dependsOn?.length ? ` [depends: ${source.dependsOn.join(", ")}]` : "";
|
|
185
|
+
const name = source.name ? `${source.name}` : "(unnamed)";
|
|
186
|
+
const arrow = `${source.input || "?"} → ${source.output || "?"}`;
|
|
187
|
+
console.log(`│ ${status} ${name.padEnd(30)} ${arrow}${reason}${deps}`);
|
|
188
|
+
}
|
|
189
|
+
console.log("└" + "─".repeat(60));
|
|
190
|
+
console.log("");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const runCount = (await Promise.all(
|
|
194
|
+
waves.flat().map(async (source) => {
|
|
195
|
+
if (options.force) return true;
|
|
196
|
+
try {
|
|
197
|
+
const prompt = await loadPrompt(source.prompt);
|
|
198
|
+
const promptHash = crypto.createHash("md5").update(prompt || "").digest("hex");
|
|
199
|
+
const files = globSync(normalizeInputPattern(source.input || ""));
|
|
200
|
+
const filesToCheck = source.output.endsWith("/")
|
|
201
|
+
? (files.length > 0 ? [files[0], source.output] : [source.output])
|
|
202
|
+
: [source.output, ...files];
|
|
203
|
+
const noChanges = await checkNoFilesChanged(filesToCheck, promptHash, hashes);
|
|
204
|
+
return !noChanges;
|
|
205
|
+
} catch {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
)).filter(Boolean).length;
|
|
210
|
+
|
|
211
|
+
console.log(` Would run : ${runCount} source${runCount !== 1 ? "s" : ""}`);
|
|
212
|
+
console.log(` Would skip : ${sources.length - runCount} source${sources.length - runCount !== 1 ? "s" : ""}`);
|
|
213
|
+
console.log("");
|
|
214
|
+
console.log("Run `knowhow generate` to execute, or add --force to skip hash checks.\n");
|
|
215
|
+
}
|
|
216
|
+
|
|
63
217
|
export function addEmbedCommands(program: Command): void {
|
|
64
218
|
program
|
|
65
219
|
.command("embed")
|
package/src/commands/services.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as allTools from "../agents/tools";
|
|
|
3
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
|
-
|
|
76
|
+
Behaviors,
|
|
77
|
+
Events,
|
|
74
78
|
});
|
|
75
79
|
|
|
76
80
|
return { Tools, Clients };
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
|
|
72
|
+
skills
|
|
73
|
+
.command("upload")
|
|
74
|
+
.description(
|
|
75
|
+
"Upload locally installed skills from .knowhow/skills/ to the backend as behaviors (isSkill: true)"
|
|
76
|
+
)
|
|
77
|
+
.option("--global", "Upload from global ~/.knowhow/skills/")
|
|
78
|
+
.option(
|
|
79
|
+
"--install-dir <dir>",
|
|
80
|
+
"Custom install directory to upload from (overrides default and --global)"
|
|
81
|
+
)
|
|
82
|
+
.action(async (opts: { global?: boolean; installDir?: string }) => {
|
|
83
|
+
const svc = new SkillsService({
|
|
84
|
+
global: opts.global,
|
|
85
|
+
installDir: opts.installDir,
|
|
86
|
+
});
|
|
87
|
+
await svc.upload();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
skills
|
|
91
|
+
.command("download")
|
|
92
|
+
.description(
|
|
93
|
+
"Download skill behaviors from the backend and write them as SKILL.md files into .knowhow/skills/<name>/SKILL.md"
|
|
94
|
+
)
|
|
95
|
+
.option("--global", "Download into global ~/.knowhow/skills/")
|
|
96
|
+
.option(
|
|
97
|
+
"--install-dir <dir>",
|
|
98
|
+
"Custom install directory (overrides default and --global)"
|
|
99
|
+
)
|
|
100
|
+
.action(async (opts: { global?: boolean; installDir?: string }) => {
|
|
101
|
+
const svc = new SkillsService({
|
|
102
|
+
global: opts.global,
|
|
103
|
+
installDir: opts.installDir,
|
|
104
|
+
});
|
|
105
|
+
await svc.download();
|
|
106
|
+
});
|
|
107
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -7,6 +7,9 @@ import { promisify } from "util";
|
|
|
7
7
|
import { KNOWHOW_API_URL } from "./services/KnowhowClient";
|
|
8
8
|
import {
|
|
9
9
|
Config,
|
|
10
|
+
SourceRef,
|
|
11
|
+
GenerationSource,
|
|
12
|
+
EmbedSource,
|
|
10
13
|
Language,
|
|
11
14
|
AssistantConfig,
|
|
12
15
|
Models,
|
|
@@ -270,6 +273,51 @@ export function getConfigSync() {
|
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
let loggedWarning = false;
|
|
276
|
+
/**
|
|
277
|
+
* Checks whether an item in a sources/embedSources array is a SourceRef
|
|
278
|
+
* (i.e. `{ from: "..." }`) rather than a real source entry.
|
|
279
|
+
*/
|
|
280
|
+
function isSourceRef(item: unknown): item is SourceRef {
|
|
281
|
+
return (
|
|
282
|
+
typeof item === "object" &&
|
|
283
|
+
item !== null &&
|
|
284
|
+
"from" in item &&
|
|
285
|
+
typeof (item as any).from === "string"
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Resolves `{ from: "./path/to/file.json" }` entries in `sources` and
|
|
291
|
+
* `embedSources` arrays. Each referenced file must contain a JSON array of
|
|
292
|
+
* source entries. Paths are resolved relative to `.knowhow/`.
|
|
293
|
+
*/
|
|
294
|
+
function resolveSourceRefs<T>(
|
|
295
|
+
items: (T | SourceRef)[] | undefined,
|
|
296
|
+
label: string
|
|
297
|
+
): T[] {
|
|
298
|
+
if (!items) return [];
|
|
299
|
+
const resolved: T[] = [];
|
|
300
|
+
for (const item of items) {
|
|
301
|
+
if (isSourceRef(item)) {
|
|
302
|
+
const refPath = path.resolve(".knowhow", item.from);
|
|
303
|
+
try {
|
|
304
|
+
const raw = fs.readFileSync(refPath, "utf8");
|
|
305
|
+
const arr = JSON.parse(raw);
|
|
306
|
+
if (!Array.isArray(arr)) {
|
|
307
|
+
console.warn(`⚠️ ${label} ref "${item.from}" did not contain a JSON array — skipping.`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
resolved.push(...(arr as T[]));
|
|
311
|
+
} catch (e) {
|
|
312
|
+
console.warn(`⚠️ Could not load ${label} ref "${item.from}": ${(e as Error).message}`);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
resolved.push(item as T);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return resolved;
|
|
319
|
+
}
|
|
320
|
+
|
|
273
321
|
export async function getConfig() {
|
|
274
322
|
if (!fs.existsSync(".knowhow/knowhow.json")) {
|
|
275
323
|
if (!loggedWarning) {
|
|
@@ -285,7 +333,9 @@ export async function getConfig() {
|
|
|
285
333
|
try {
|
|
286
334
|
const config = await readFile(".knowhow/knowhow.json", "utf8");
|
|
287
335
|
const parsedConfig = JSON.parse(config);
|
|
288
|
-
|
|
336
|
+
// Resolve any { from: "..." } SourceRef entries in sources / embedSources
|
|
337
|
+
parsedConfig.sources = resolveSourceRefs<GenerationSource>(parsedConfig.sources, "sources");
|
|
338
|
+
parsedConfig.embedSources = resolveSourceRefs<EmbedSource>(parsedConfig.embedSources, "embedSources");
|
|
289
339
|
return parsedConfig as Config;
|
|
290
340
|
} catch (error) {
|
|
291
341
|
console.error("Error reading .knowhow/knowhow.json:", error);
|