@tyvm/knowhow 0.0.130 → 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 (97) hide show
  1. package/package.json +1 -1
  2. package/src/agents/configurable/ConfigAgent.ts +0 -1
  3. package/src/agents/tools/startAgentTask.ts +17 -13
  4. package/src/ai.ts +55 -24
  5. package/src/chat/CliChatService.ts +7 -1
  6. package/src/chat/modules/AgentModule.ts +72 -14
  7. package/src/chat/renderer/PlainRenderer.ts +218 -0
  8. package/src/chat/renderer/index.ts +1 -0
  9. package/src/chat/renderer/loadRenderer.ts +2 -0
  10. package/src/chat.ts +2 -2
  11. package/src/cli.ts +6 -0
  12. package/src/clients/anthropic.ts +9 -1
  13. package/src/clients/fireworks.ts +10 -1
  14. package/src/clients/index.ts +5 -3
  15. package/src/clients/meta.ts +9 -0
  16. package/src/clients/pricing/anthropic.ts +11 -0
  17. package/src/clients/pricing/fireworks.ts +12 -0
  18. package/src/commands/agent.ts +53 -20
  19. package/src/commands/agents.ts +556 -0
  20. package/src/commands/hash.ts +124 -0
  21. package/src/commands/misc.ts +157 -3
  22. package/src/commands/skills.ts +36 -0
  23. package/src/config.ts +51 -1
  24. package/src/generate.ts +507 -0
  25. package/src/index.ts +32 -216
  26. package/src/services/BehaviorsService.ts +32 -0
  27. package/src/services/SkillsService.ts +198 -0
  28. package/src/types.ts +36 -4
  29. package/ts_build/package.json +1 -1
  30. package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
  31. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -2
  32. package/ts_build/src/agents/tools/startAgentTask.js +13 -12
  33. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  34. package/ts_build/src/ai.d.ts +10 -4
  35. package/ts_build/src/ai.js +27 -25
  36. package/ts_build/src/ai.js.map +1 -1
  37. package/ts_build/src/chat/CliChatService.d.ts +1 -1
  38. package/ts_build/src/chat/CliChatService.js +4 -1
  39. package/ts_build/src/chat/CliChatService.js.map +1 -1
  40. package/ts_build/src/chat/modules/AgentModule.js +61 -4
  41. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  42. package/ts_build/src/chat/renderer/PlainRenderer.d.ts +28 -0
  43. package/ts_build/src/chat/renderer/PlainRenderer.js +169 -0
  44. package/ts_build/src/chat/renderer/PlainRenderer.js.map +1 -0
  45. package/ts_build/src/chat/renderer/index.d.ts +1 -0
  46. package/ts_build/src/chat/renderer/index.js +3 -1
  47. package/ts_build/src/chat/renderer/index.js.map +1 -1
  48. package/ts_build/src/chat/renderer/loadRenderer.js +2 -0
  49. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -1
  50. package/ts_build/src/chat.d.ts +1 -1
  51. package/ts_build/src/chat.js +2 -2
  52. package/ts_build/src/chat.js.map +1 -1
  53. package/ts_build/src/cli.js +5 -0
  54. package/ts_build/src/cli.js.map +1 -1
  55. package/ts_build/src/clients/anthropic.js +4 -1
  56. package/ts_build/src/clients/anthropic.js.map +1 -1
  57. package/ts_build/src/clients/fireworks.js +7 -0
  58. package/ts_build/src/clients/fireworks.js.map +1 -1
  59. package/ts_build/src/clients/index.js +3 -1
  60. package/ts_build/src/clients/index.js.map +1 -1
  61. package/ts_build/src/clients/meta.d.ts +2 -0
  62. package/ts_build/src/clients/meta.js +5 -0
  63. package/ts_build/src/clients/meta.js.map +1 -1
  64. package/ts_build/src/clients/pricing/anthropic.d.ts +3 -0
  65. package/ts_build/src/clients/pricing/anthropic.js +6 -0
  66. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  67. package/ts_build/src/clients/pricing/fireworks.d.ts +1 -0
  68. package/ts_build/src/clients/pricing/fireworks.js +5 -1
  69. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  70. package/ts_build/src/commands/agent.js +44 -17
  71. package/ts_build/src/commands/agent.js.map +1 -1
  72. package/ts_build/src/commands/agents.d.ts +2 -0
  73. package/ts_build/src/commands/agents.js +460 -0
  74. package/ts_build/src/commands/agents.js.map +1 -0
  75. package/ts_build/src/commands/hash.d.ts +2 -0
  76. package/ts_build/src/commands/hash.js +117 -0
  77. package/ts_build/src/commands/hash.js.map +1 -0
  78. package/ts_build/src/commands/misc.js +108 -2
  79. package/ts_build/src/commands/misc.js.map +1 -1
  80. package/ts_build/src/commands/skills.js +24 -0
  81. package/ts_build/src/commands/skills.js.map +1 -1
  82. package/ts_build/src/config.js +34 -0
  83. package/ts_build/src/config.js.map +1 -1
  84. package/ts_build/src/generate.d.ts +16 -0
  85. package/ts_build/src/generate.js +304 -0
  86. package/ts_build/src/generate.js.map +1 -0
  87. package/ts_build/src/index.d.ts +1 -3
  88. package/ts_build/src/index.js +9 -118
  89. package/ts_build/src/index.js.map +1 -1
  90. package/ts_build/src/services/BehaviorsService.d.ts +2 -0
  91. package/ts_build/src/services/BehaviorsService.js +24 -0
  92. package/ts_build/src/services/BehaviorsService.js.map +1 -1
  93. package/ts_build/src/services/SkillsService.d.ts +7 -0
  94. package/ts_build/src/services/SkillsService.js +163 -0
  95. package/ts_build/src/services/SkillsService.js.map +1 -1
  96. package/ts_build/src/types.d.ts +14 -4
  97. package/ts_build/src/types.js.map +1 -1
@@ -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 { generate, embed, upload, download, purge } from "../index";
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
- .action(async () => {
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
- await generate();
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")
@@ -68,4 +68,40 @@ export function addSkillsCommand(program: Command): void {
68
68
  const svc = new SkillsService({ global: opts.global });
69
69
  await svc.update(skillNames);
70
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
+ });
71
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);