@tyvm/knowhow 0.0.60 → 0.0.61

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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/agents/setup/setup.ts +9 -2
  3. package/src/agents/tools/textSearch.ts +4 -1
  4. package/src/chat/CliChatService.ts +5 -6
  5. package/src/chat/modules/SystemModule.ts +1 -1
  6. package/src/clients/anthropic.ts +12 -0
  7. package/src/config.ts +5 -0
  8. package/src/plugins/language.ts +4 -0
  9. package/src/services/Mcp.ts +1 -0
  10. package/src/services/Tools.ts +5 -3
  11. package/src/types.ts +1 -0
  12. package/src/utils/InputQueueManager.ts +119 -95
  13. package/ts_build/package.json +1 -1
  14. package/ts_build/src/agents/setup/setup.js +9 -2
  15. package/ts_build/src/agents/setup/setup.js.map +1 -1
  16. package/ts_build/src/agents/tools/textSearch.js +2 -1
  17. package/ts_build/src/agents/tools/textSearch.js.map +1 -1
  18. package/ts_build/src/chat/CliChatService.d.ts +1 -1
  19. package/ts_build/src/chat/CliChatService.js +6 -6
  20. package/ts_build/src/chat/CliChatService.js.map +1 -1
  21. package/ts_build/src/chat/modules/SystemModule.js +1 -1
  22. package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
  23. package/ts_build/src/clients/anthropic.js +12 -0
  24. package/ts_build/src/clients/anthropic.js.map +1 -1
  25. package/ts_build/src/config.js +5 -0
  26. package/ts_build/src/config.js.map +1 -1
  27. package/ts_build/src/plugins/language.js +4 -0
  28. package/ts_build/src/plugins/language.js.map +1 -1
  29. package/ts_build/src/services/Mcp.js.map +1 -1
  30. package/ts_build/src/services/Tools.js +1 -1
  31. package/ts_build/src/services/Tools.js.map +1 -1
  32. package/ts_build/src/types.d.ts +1 -0
  33. package/ts_build/src/types.js +1 -0
  34. package/ts_build/src/types.js.map +1 -1
  35. package/ts_build/src/utils/InputQueueManager.d.ts +4 -1
  36. package/ts_build/src/utils/InputQueueManager.js +93 -78
  37. package/ts_build/src/utils/InputQueueManager.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -38,15 +38,22 @@ export class SetupAgent extends BaseAgent {
38
38
 
39
39
  Always ask the user to approve what you're going to do to the config, that way you can get feedback via askHuman before modifying the config
40
40
 
41
+ After using askHuman and them providing their feedback of what you'd like to do, only follow what they say. We want to make the minimum set of changes to the config.
42
+
43
+ For codebase embeddings you don't want to use prompt, as that'd embed a transformation of the code, you want to embed the actual source, so don't use prompt.
44
+ For embeddings prompt would only be used for generating an embedding from transformed data, like if you wanted to summarize a transcript and make embeddings from the summary, then you'd use prompt on the embeddings, otherwise you should not need it.
45
+
41
46
  When setting up the language plugin for a user you should come up with phrases they're likely to say, like frontend/backend/schema etc that will signal we should load in guides or rules for that type of task. You should put any of your rules/analses in .knowhow/docs and the language plugin should reference those.
42
47
 
48
+ The language plugin can only read in files, not directories, so do not add entries to language plugin unless you've first written some markdown files to load in as guidance. The files loaded by the language plugin should give quick tips to any unusual things about the project, commands that should be run to rebuild any auto-generated code, quirks about codebase behavior etc.
49
+
43
50
  If a user is vauge about setting up, you should give them some options of what all you could help them setup with a brief explanation of what those setups would enable.
44
51
 
45
52
  Only suggest embeddings that include a folder path with many elements, ie src/**/*.ts, never suggest entries with one element
46
53
 
47
- If a user is requesting help with setting up a coding project, you can look at their package.json to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
54
+ If a user is requesting help with setting up a coding project, you can look at their package.json, or language specific config to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
48
55
 
49
- If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole codebaseand linter commands if possible. Try not do dig too deep if they want fast, just get the highest impact features setup
56
+ If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole codebase and linter commands if possible. Try not do dig too deep if they want fast, just get the highest impact features setup
50
57
 
51
58
  `,
52
59
  },
@@ -3,7 +3,10 @@ import { execCommand } from "./execCommand";
3
3
 
4
4
  export async function textSearch(searchTerm) {
5
5
  try {
6
- const command = `ag -m 3 -Q "${searchTerm}"`;
6
+ // Escape the search term for safe shell usage
7
+ // Replace single quotes with '\'' which closes quote, adds escaped quote, reopens quote
8
+ const escapedTerm = searchTerm.replace(/'/g, "'\\''");
9
+ const command = `ag -m 3 -Q '${escapedTerm}'`;
7
10
  const output = await execCommand(command);
8
11
  return output;
9
12
  } catch (err) {
@@ -61,8 +61,8 @@ export class CliChatService implements ChatService {
61
61
  try {
62
62
  if (fs.existsSync(this.historyFile)) {
63
63
  const historyData = fs.readFileSync(this.historyFile, "utf8");
64
- const chatHistory: ChatHistory = JSON.parse(historyData);
65
- this.inputHistory = chatHistory.inputs || [];
64
+ const parsedHistory: ChatHistory = JSON.parse(historyData);
65
+ this.inputHistory = parsedHistory.inputs || [];
66
66
  }
67
67
  } catch (error) {
68
68
  console.error("Error loading input history:", error);
@@ -81,11 +81,11 @@ export class CliChatService implements ChatService {
81
81
  fs.mkdirSync(dir, { recursive: true });
82
82
  }
83
83
 
84
- const chatHistory: ChatHistory = {
84
+ const inputHistory: ChatHistory = {
85
85
  inputs: this.inputHistory,
86
86
  };
87
87
 
88
- fs.writeFileSync(this.historyFile, JSON.stringify(chatHistory, null, 2));
88
+ fs.writeFileSync(this.historyFile, JSON.stringify(inputHistory, null, 2));
89
89
  } catch (error) {
90
90
  console.error("Error saving input history:", error);
91
91
  }
@@ -154,6 +154,7 @@ export class CliChatService implements ChatService {
154
154
 
155
155
  async processInput(input: string): Promise<boolean> {
156
156
  // Note: Input is added to history via setOnNewHistoryEntry callback when user presses Enter
157
+ // Note: this actually sends all commands to modules, first to service takes it
157
158
 
158
159
  // Check if input is a command
159
160
  if (input.startsWith("/")) {
@@ -198,7 +199,6 @@ export class CliChatService implements ChatService {
198
199
  async getInput(
199
200
  prompt: string = "> ",
200
201
  options: string[] = [],
201
- chatHistory: any[] = []
202
202
  ): Promise<string> {
203
203
  if (this.context.inputMethod) {
204
204
  return await this.context.inputMethod.getInput(prompt);
@@ -277,7 +277,6 @@ export class CliChatService implements ChatService {
277
277
  const input = await this.getInput(
278
278
  promptText,
279
279
  commandNames,
280
- this.chatHistory
281
280
  );
282
281
 
283
282
  if (input.trim() === "") {
@@ -28,7 +28,7 @@ export class SystemModule extends BaseChatModule {
28
28
  },
29
29
  {
30
30
  name: "clear",
31
- description: "Clear chat history",
31
+ description: "Clear chat history - AI will not remember previous messages",
32
32
  handler: this.handleClearCommand.bind(this),
33
33
  },
34
34
  ];
@@ -325,12 +325,24 @@ export class GenericAnthropicClient implements GenericClient {
325
325
 
326
326
  pricesPerMillion() {
327
327
  return {
328
+ [Models.anthropic.Opus4_6]: {
329
+ input: 5.0,
330
+ cache_write: 6.25,
331
+ cache_hit: 0.5,
332
+ output: 25.0,
333
+ },
328
334
  [Models.anthropic.Opus4_5]: {
329
335
  input: 5.0,
330
336
  cache_write: 6.25,
331
337
  cache_hit: 0.5,
332
338
  output: 25.0,
333
339
  },
340
+ [Models.anthropic.Opus4_1]: {
341
+ input: 15.0,
342
+ cache_write: 18.75,
343
+ cache_hit: 1.5,
344
+ output: 75.0,
345
+ },
334
346
  [Models.anthropic.Opus4]: {
335
347
  input: 15.0,
336
348
  cache_write: 18.75,
package/src/config.ts CHANGED
@@ -52,6 +52,11 @@ const defaultConfig = {
52
52
  prompt: "BasicEmbeddingExplainer",
53
53
  chunkSize: 2000,
54
54
  },
55
+ {
56
+ input: "src/**/*.ts",
57
+ output: ".knowhow/embeddings/code.json",
58
+ chunkSize: 2000,
59
+ },
55
60
  ],
56
61
  embeddingModel: EmbeddingModels.openai.EmbeddingAda2,
57
62
 
@@ -96,6 +96,10 @@ export class LanguagePlugin extends PluginBase implements Plugin {
96
96
  if (!exists) {
97
97
  return { filePath, content: `File ${filePath} does not exist` };
98
98
  }
99
+ const stat = await fileStat(filePath);
100
+ if (stat.isDirectory()) {
101
+ throw new Error(`Cannot read directories: ${filePath}`);
102
+ }
99
103
  const content = (await readFile(filePath, "utf8")).toString();
100
104
  return { filePath, content };
101
105
  })
@@ -84,6 +84,7 @@ export class McpService {
84
84
  const token = fs.readFileSync(mcp.authorization_token_file, "utf-8");
85
85
  mcp.authorization_token = token.trim();
86
86
  }
87
+
87
88
  return new StreamableHTTPClientTransport(new URL(mcp.url), {
88
89
  requestInit: {
89
90
  headers: {
@@ -348,7 +348,7 @@ export class ToolsService {
348
348
 
349
349
  const originalFunc = this.originalFunctions[name];
350
350
 
351
- // Check for overrides first
351
+ // Overrides basically replace the function entirely
352
352
  const matchingOverride = this.findMatchingOverride(name);
353
353
  if (matchingOverride) {
354
354
  // Create a wrapper function that calls the override with correct arguments
@@ -361,7 +361,9 @@ export class ToolsService {
361
361
  return;
362
362
  }
363
363
 
364
- // Check for wrappers
364
+ // Wrappers are like russian dolls,
365
+ // each wrapper gets the previous function as its "inner" function to call, and can modify arguments and return value as needed
366
+ // the first wrapper gets the original function, the second wrapper gets the first wrapper as its inner function, etc
365
367
  const wrappers = this.findMatchingWrappers(name);
366
368
  if (wrappers.length > 0) {
367
369
  let wrappedFunction = originalFunc;
@@ -369,7 +371,7 @@ export class ToolsService {
369
371
  // Apply wrappers in priority order
370
372
  for (const wrapperReg of wrappers) {
371
373
  const innerFunc = wrappedFunction;
372
- wrappedFunction = ((args: any) => {
374
+ wrappedFunction = ((...args: any[]) => {
373
375
  const toolDefinition = this.getTool(name);
374
376
  return wrapperReg.wrapper(innerFunc, args, toolDefinition);
375
377
  }).bind(this);
package/src/types.ts CHANGED
@@ -142,6 +142,7 @@ export type ChatInteraction = {
142
142
 
143
143
  export const Models = {
144
144
  anthropic: {
145
+ Opus4_6: "claude-opus-4-6",
145
146
  Opus4_5: "claude-opus-4-5-20251101",
146
147
  Opus4: "claude-opus-4-20250514",
147
148
  Opus4_1: "claude-opus-4-1-20250805",
@@ -12,7 +12,9 @@ type AskOptions = {
12
12
 
13
13
  export class InputQueueManager {
14
14
  private stack: AskOptions[] = [];
15
- private rl: readline.Interface | null = null;
15
+ private static instance: InputQueueManager | null = null;
16
+ private static rl: readline.Interface | null = null;
17
+ private static keypressListenerSetup = false;
16
18
 
17
19
  // We keep one "live" buffer shared across stacked questions
18
20
  // (so typing is preserved when questions change)
@@ -31,6 +33,11 @@ export class InputQueueManager {
31
33
  // This allows CliChatService to update inputHistory immediately
32
34
  private onNewEntry?: OnNewHistoryEntry;
33
35
 
36
+ constructor() {
37
+ // Store the current instance as the singleton
38
+ InputQueueManager.instance = this;
39
+ }
40
+
34
41
  /**
35
42
  * Set a callback to be notified when user enters a new history entry.
36
43
  * This allows the caller to update their history source immediately.
@@ -40,9 +47,9 @@ export class InputQueueManager {
40
47
  }
41
48
 
42
49
  private ensureRl(): readline.Interface {
43
- if (this.rl) return this.rl;
50
+ if (InputQueueManager.rl) return InputQueueManager.rl;
44
51
 
45
- this.rl = readline.createInterface({
52
+ InputQueueManager.rl = readline.createInterface({
46
53
  input: process.stdin,
47
54
  output: process.stdout,
48
55
  terminal: true,
@@ -74,7 +81,7 @@ export class InputQueueManager {
74
81
  });
75
82
 
76
83
  // When user presses Enter, buffer the line for paste detection
77
- this.rl.on("line", (line) => {
84
+ InputQueueManager.rl.on("line", (line) => {
78
85
  const current = this.peek();
79
86
  if (!current) return;
80
87
 
@@ -84,25 +91,32 @@ export class InputQueueManager {
84
91
  // Already in paste mode, add to buffer
85
92
  this.pasteBuffer.push(line);
86
93
  clearTimeout(this.pasteTimeout);
87
- this.pasteTimeout = setTimeout(() => this.flushPasteBuffer(), this.PASTE_DELAY_MS);
94
+ this.pasteTimeout = setTimeout(
95
+ () => this.flushPasteBuffer(),
96
+ this.PASTE_DELAY_MS
97
+ );
88
98
  return;
89
99
  }
90
100
 
91
101
  // Start paste detection mode - buffer this line and wait to see if more come
92
102
  this.pasteBuffer.push(line);
93
- this.pasteTimeout = setTimeout(() => this.flushPasteBuffer(), this.PASTE_DELAY_MS);
103
+ this.pasteTimeout = setTimeout(
104
+ () => this.flushPasteBuffer(),
105
+ this.PASTE_DELAY_MS
106
+ );
94
107
  });
95
108
 
96
- this.rl.on("close", () => {
109
+ InputQueueManager.rl.on("close", () => {
97
110
  // Flush any remaining paste buffer on close
98
111
  if (this.pasteTimeout) {
99
112
  clearTimeout(this.pasteTimeout);
100
113
  this.flushPasteBuffer();
114
+ this.historyIndex = -1;
101
115
  }
102
116
  });
103
117
 
104
118
  // Handle Ctrl+C (readline SIGINT)
105
- this.rl.on("SIGINT", () => {
119
+ InputQueueManager.rl.on("SIGINT", () => {
106
120
  // If there's an active question, cancel it (like Esc)
107
121
  if (this.stack.length > 0) {
108
122
  const cancelled = this.stack.pop();
@@ -120,91 +134,98 @@ export class InputQueueManager {
120
134
 
121
135
  // Capture keypresses for ESC + history nav while still using readline.
122
136
  // Tab is handled by rl completer.
123
- readline.emitKeypressEvents(process.stdin);
124
- if (process.stdin.isTTY) process.stdin.setRawMode(true);
125
-
126
- process.stdin.on("keypress", (_str, key) => {
127
- // Handle Ctrl+C in raw mode (some terminals deliver this here instead of SIGINT)
128
- if (key?.ctrl && key?.name === "c") {
129
- if (this.stack.length > 0) {
130
- const cancelled = this.stack.pop();
131
- cancelled?.resolve("");
132
- this.currentLine = "";
133
- }
134
- this.close();
135
- process.exit(0);
136
- }
137
-
138
- // If RL is closed or nothing to ask, ignore
139
- if (!this.rl || this.stack.length === 0) return;
140
-
141
- // Keep our buffer in sync with readline's live line
142
- this.syncFromReadline();
143
-
144
- // Any "real typing" should exit history mode
145
- // (we'll treat left/right as not exiting; you can tweak)
146
- const exitsHistoryMode =
147
- key &&
148
- (key.name === "return" ||
149
- key.name === "enter" ||
150
- key.name === "backspace" ||
151
- (key.sequence &&
152
- key.sequence.length === 1 &&
153
- !key.ctrl &&
154
- !key.meta));
155
- if (exitsHistoryMode && this.historyIndex !== -1) {
156
- this.historyIndex = -1;
157
- this.savedLineBeforeHistory = "";
158
- }
159
-
160
- // Custom Up/Down history navigation using only passed-in history
161
- if (key?.name === "up") {
162
- const history = this.getHistory();
163
- if (history.length === 0) return;
164
-
165
- if (this.historyIndex === -1) {
166
- // entering history mode: remember current typed text
167
- this.savedLineBeforeHistory = this.currentLine;
137
+ // Only set up keypress listener once to avoid multiple handlers
138
+ if (!InputQueueManager.keypressListenerSetup) {
139
+ InputQueueManager.keypressListenerSetup = true;
140
+ readline.emitKeypressEvents(process.stdin);
141
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
142
+
143
+ process.stdin.on("keypress", (_str, key) => {
144
+ // Handle Ctrl+C in raw mode (some terminals deliver this here instead of SIGINT)
145
+ const instance = InputQueueManager.instance;
146
+ if (!instance) return;
147
+
148
+ if (key?.ctrl && key?.name === "c") {
149
+ if (instance.stack.length > 0) {
150
+ const cancelled = instance.stack.pop();
151
+ cancelled?.resolve("");
152
+ instance.currentLine = "";
153
+ }
154
+ instance.close();
155
+ process.exit(0);
168
156
  }
169
157
 
170
- if (this.historyIndex < history.length - 1) {
171
- this.historyIndex++;
172
- const next =
173
- history[history.length - 1 - this.historyIndex] ?? "";
174
- this.replaceLine(next);
175
- this.currentLine = next;
158
+ // If RL is closed or nothing to ask, ignore
159
+ if (!InputQueueManager.rl || instance.stack.length === 0) return;
160
+
161
+ // Keep our buffer in sync with readline's live line
162
+ instance.syncFromReadline();
163
+
164
+ // Any "real typing" should exit history mode
165
+ // (we'll treat left/right as not exiting; you can tweak)
166
+ const exitsHistoryMode =
167
+ key &&
168
+ (key.name === "return" ||
169
+ key.name === "enter" ||
170
+ key.name === "backspace" ||
171
+ (key.sequence &&
172
+ key.sequence.length === 1 &&
173
+ !key.ctrl &&
174
+ !key.meta));
175
+ if (exitsHistoryMode && instance.historyIndex !== -1) {
176
+ instance.historyIndex = -1;
177
+ instance.savedLineBeforeHistory = "";
176
178
  }
177
- return;
178
- }
179
-
180
- if (key?.name === "down") {
181
- const history = this.getHistory();
182
- if (history.length === 0) return;
183
179
 
184
- if (this.historyIndex > 0) {
185
- this.historyIndex--;
186
- const next =
187
- history[history.length - 1 - this.historyIndex] ?? "";
188
- this.replaceLine(next);
189
- this.currentLine = next;
180
+ // Custom Up/Down history navigation using only passed-in history
181
+ if (key?.name === "up") {
182
+ const history = instance.getHistory();
183
+ if (history.length === 0) return;
184
+
185
+ if (instance.historyIndex === -1) {
186
+ // entering history mode: remember current typed text
187
+ instance.savedLineBeforeHistory = instance.currentLine;
188
+ }
189
+
190
+ if (instance.historyIndex < history.length - 1) {
191
+ instance.historyIndex++;
192
+ const index = history.length - 1 - instance.historyIndex;
193
+ const next = history[index] ?? "";
194
+ instance.replaceLine(next);
195
+ instance.currentLine = next;
196
+ }
190
197
  return;
191
198
  }
192
199
 
193
- if (this.historyIndex === 0) {
194
- // leave history mode, restore what user was typing
195
- this.historyIndex = -1;
196
- const restore = this.savedLineBeforeHistory ?? "";
197
- this.savedLineBeforeHistory = "";
198
- this.replaceLine(restore);
199
- this.currentLine = restore;
200
+ if (key?.name === "down") {
201
+ const history = instance.getHistory();
202
+ if (history.length === 0) return;
203
+
204
+ if (instance.historyIndex > 0) {
205
+ instance.historyIndex--;
206
+ const index = history.length - 1 - instance.historyIndex;
207
+ const next = history[index] ?? "";
208
+ instance.replaceLine(next);
209
+ instance.currentLine = next;
210
+ return;
211
+ }
212
+
213
+ if (instance.historyIndex === 0) {
214
+ // leave history mode, restore what user was typing
215
+ instance.historyIndex = -1;
216
+ const restore = instance.savedLineBeforeHistory ?? "";
217
+ instance.savedLineBeforeHistory = "";
218
+ instance.replaceLine(restore);
219
+ instance.currentLine = restore;
220
+ return;
221
+ }
222
+
200
223
  return;
201
224
  }
225
+ });
226
+ }
202
227
 
203
- return;
204
- }
205
- });
206
-
207
- return this.rl;
228
+ return InputQueueManager.rl;
208
229
  }
209
230
 
210
231
  private flushPasteBuffer(): void {
@@ -238,6 +259,7 @@ export class InputQueueManager {
238
259
  async ask(question: string, options: string[] = [], history: string[] = []) {
239
260
  return new Promise<string>((resolve) => {
240
261
  this.stack.push({ question, options, history, resolve });
262
+ this.historyIndex = -1; // reset history nav when a new question is asked
241
263
 
242
264
  const rl = this.ensureRl();
243
265
 
@@ -260,8 +282,8 @@ export class InputQueueManager {
260
282
  }
261
283
 
262
284
  private syncFromReadline(): void {
263
- if (!this.rl) return;
264
- this.currentLine = (this.rl as any).line ?? "";
285
+ if (!InputQueueManager.rl) return;
286
+ this.currentLine = (InputQueueManager.rl as any).line ?? "";
265
287
  }
266
288
 
267
289
  private sanitizeHistoryEntry(value: string): string {
@@ -290,13 +312,13 @@ export class InputQueueManager {
290
312
  }
291
313
 
292
314
  private render(): void {
293
- if (!this.rl) return;
315
+ if (!InputQueueManager.rl) return;
294
316
  const current = this.peek();
295
317
  if (!current) return;
296
318
 
297
319
  // Make prompt be the question (readline manages wrapping/cursor)
298
- this.rl.setPrompt(current.question);
299
- this.rl.prompt(true);
320
+ InputQueueManager.rl.setPrompt(current.question);
321
+ InputQueueManager.rl.prompt(true);
300
322
  }
301
323
 
302
324
  private renderTopOrClose(): void {
@@ -311,17 +333,17 @@ export class InputQueueManager {
311
333
 
312
334
  this.render();
313
335
  this.replaceLine(this.currentLine);
314
- this.rl?.prompt(true);
336
+ InputQueueManager.rl?.prompt(true);
315
337
  }
316
338
 
317
339
  private replaceLine(next: string): void {
318
- if (!this.rl) return;
340
+ if (!InputQueueManager.rl) return;
319
341
 
320
342
  const safe = this.sanitizeHistoryEntry(next);
321
343
 
322
344
  // Clear current line and write next input without affecting terminal scrollback
323
- this.rl.write(null, { ctrl: true, name: "u" }); // Ctrl+U clears the line
324
- if (safe) this.rl.write(safe);
345
+ InputQueueManager.rl.write(null, { ctrl: true, name: "u" }); // Ctrl+U clears the line
346
+ if (safe) InputQueueManager.rl.write(safe);
325
347
  }
326
348
 
327
349
  /**
@@ -343,9 +365,11 @@ export class InputQueueManager {
343
365
  }
344
366
 
345
367
  private close(): void {
346
- if (!this.rl) return;
347
- this.rl.close();
348
- this.rl = null;
368
+ if (!InputQueueManager.rl) return;
369
+ InputQueueManager.rl.close();
370
+ InputQueueManager.rl = null;
371
+ // Note: We don't reset keypressListenerSetup because the listener stays attached to process.stdin
372
+ // and will continue to work for the next readline interface
349
373
 
350
374
  if (process.stdin.isTTY) {
351
375
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -39,15 +39,22 @@ class SetupAgent extends base_1.BaseAgent {
39
39
 
40
40
  Always ask the user to approve what you're going to do to the config, that way you can get feedback via askHuman before modifying the config
41
41
 
42
+ After using askHuman and them providing their feedback of what you'd like to do, only follow what they say. We want to make the minimum set of changes to the config.
43
+
44
+ For codebase embeddings you don't want to use prompt, as that'd embed a transformation of the code, you want to embed the actual source, so don't use prompt.
45
+ For embeddings prompt would only be used for generating an embedding from transformed data, like if you wanted to summarize a transcript and make embeddings from the summary, then you'd use prompt on the embeddings, otherwise you should not need it.
46
+
42
47
  When setting up the language plugin for a user you should come up with phrases they're likely to say, like frontend/backend/schema etc that will signal we should load in guides or rules for that type of task. You should put any of your rules/analses in .knowhow/docs and the language plugin should reference those.
43
48
 
49
+ The language plugin can only read in files, not directories, so do not add entries to language plugin unless you've first written some markdown files to load in as guidance. The files loaded by the language plugin should give quick tips to any unusual things about the project, commands that should be run to rebuild any auto-generated code, quirks about codebase behavior etc.
50
+
44
51
  If a user is vauge about setting up, you should give them some options of what all you could help them setup with a brief explanation of what those setups would enable.
45
52
 
46
53
  Only suggest embeddings that include a folder path with many elements, ie src/**/*.ts, never suggest entries with one element
47
54
 
48
- If a user is requesting help with setting up a coding project, you can look at their package.json to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
55
+ If a user is requesting help with setting up a coding project, you can look at their package.json, or language specific config to setup the lintCommands so that we get feedback on file edits, and embeddings for the source code as those two features are the highest impact
49
56
 
50
- If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole codebaseand linter commands if possible. Try not do dig too deep if they want fast, just get the highest impact features setup
57
+ If the user just says setup fast, try to get a general idea of the project file structure and setup one source code embedding for the whole codebase and linter commands if possible. Try not do dig too deep if they want fast, just get the highest impact features setup
51
58
 
52
59
  `,
53
60
  },
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../src/agents/setup/setup.ts"],"names":[],"mappings":";;;;;;AACA,uCAAuD;AACvD,2CAA6C;AAC7C,gGAAiE;AACjE,iCAAkC;AAElC,MAAa,UAAW,SAAQ,gBAAS;IACvC,IAAI,GAAG,OAAO,CAAC;IACf,WAAW,GAAG,4CAA4C,CAAC;IAE3D,YAAY,OAAqB;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,mBAAmB,CAAC;YACvB,EAAE,KAAK,EAAE,WAAM,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE;YAC1D;gBACE,KAAK,EAAE,WAAM,CAAC,MAAM,CAAC,WAAW;gBAChC,QAAQ,EAAE,QAAQ;aACnB;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,GAAG,oBAAW;;;;yCAIU,+BAAc;;;;;;;;;;;;;;;;;;;;SAoB9C;aACF;YACD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE;SACxB,CAAC;IACjB,CAAC;CACF;AAjDD,gCAiDC"}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../../src/agents/setup/setup.ts"],"names":[],"mappings":";;;;;;AACA,uCAAuD;AACvD,2CAA6C;AAC7C,gGAAiE;AACjE,iCAAkC;AAElC,MAAa,UAAW,SAAQ,gBAAS;IACvC,IAAI,GAAG,OAAO,CAAC;IACf,WAAW,GAAG,4CAA4C,CAAC;IAE3D,YAAY,OAAqB;QAC/B,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,mBAAmB,CAAC;YACvB,EAAE,KAAK,EAAE,WAAM,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE;YAC1D;gBACE,KAAK,EAAE,WAAM,CAAC,MAAM,CAAC,WAAW;gBAChC,QAAQ,EAAE,QAAQ;aACnB;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,SAAiB;QACxC,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,GAAG,oBAAW;;;;yCAIU,+BAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2B9C;aACF;YACD,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE;SACxB,CAAC;IACjB,CAAC;CACF;AAxDD,gCAwDC"}
@@ -5,7 +5,8 @@ const embeddings_1 = require("../../embeddings");
5
5
  const execCommand_1 = require("./execCommand");
6
6
  async function textSearch(searchTerm) {
7
7
  try {
8
- const command = `ag -m 3 -Q "${searchTerm}"`;
8
+ const escapedTerm = searchTerm.replace(/'/g, "'\\''");
9
+ const command = `ag -m 3 -Q '${escapedTerm}'`;
9
10
  const output = await (0, execCommand_1.execCommand)(command);
10
11
  return output;
11
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"textSearch.js","sourceRoot":"","sources":["../../../../src/agents/tools/textSearch.ts"],"names":[],"mappings":";;;AAAA,iDAA2D;AAC3D,+CAA4C;AAErC,KAAK,UAAU,UAAU,CAAC,UAAU;IACzC,IAAI;QACF,MAAM,OAAO,GAAG,eAAe,UAAU,GAAG,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAW,EAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;KACf;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CACT,mEAAmE,CACpE,CAAC;QACF,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,IAAA,oCAAuB,GAAE,CAAC;QACnD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAC9C,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CACvD,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;KAChB;AACH,CAAC;AAjBD,gCAiBC"}
1
+ {"version":3,"file":"textSearch.js","sourceRoot":"","sources":["../../../../src/agents/tools/textSearch.ts"],"names":[],"mappings":";;;AAAA,iDAA2D;AAC3D,+CAA4C;AAErC,KAAK,UAAU,UAAU,CAAC,UAAU;IACzC,IAAI;QAGF,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,eAAe,WAAW,GAAG,CAAC;QAC9C,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAW,EAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;KACf;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,GAAG,CACT,mEAAmE,CACpE,CAAC;QACF,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,IAAA,oCAAuB,GAAE,CAAC;QACnD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAC9C,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CACvD,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC;KAChB;AACH,CAAC;AApBD,gCAoBC"}
@@ -27,7 +27,7 @@ export declare class CliChatService implements ChatService {
27
27
  processInput(input: string): Promise<boolean>;
28
28
  enableMode(name: string): void;
29
29
  disableMode(name: string): void;
30
- getInput(prompt?: string, options?: string[], chatHistory?: any[]): Promise<string>;
30
+ getInput(prompt?: string, options?: string[]): Promise<string>;
31
31
  clearHistory(): void;
32
32
  getChatHistory(): ChatInteraction[];
33
33
  getInputHistory(): string[];
@@ -43,8 +43,8 @@ class CliChatService {
43
43
  try {
44
44
  if (fs_1.default.existsSync(this.historyFile)) {
45
45
  const historyData = fs_1.default.readFileSync(this.historyFile, "utf8");
46
- const chatHistory = JSON.parse(historyData);
47
- this.inputHistory = chatHistory.inputs || [];
46
+ const parsedHistory = JSON.parse(historyData);
47
+ this.inputHistory = parsedHistory.inputs || [];
48
48
  }
49
49
  }
50
50
  catch (error) {
@@ -58,10 +58,10 @@ class CliChatService {
58
58
  if (!fs_1.default.existsSync(dir)) {
59
59
  fs_1.default.mkdirSync(dir, { recursive: true });
60
60
  }
61
- const chatHistory = {
61
+ const inputHistory = {
62
62
  inputs: this.inputHistory,
63
63
  };
64
- fs_1.default.writeFileSync(this.historyFile, JSON.stringify(chatHistory, null, 2));
64
+ fs_1.default.writeFileSync(this.historyFile, JSON.stringify(inputHistory, null, 2));
65
65
  }
66
66
  catch (error) {
67
67
  console.error("Error saving input history:", error);
@@ -143,7 +143,7 @@ class CliChatService {
143
143
  mode.active = false;
144
144
  }
145
145
  }
146
- async getInput(prompt = "> ", options = [], chatHistory = []) {
146
+ async getInput(prompt = "> ", options = []) {
147
147
  if (this.context.inputMethod) {
148
148
  return await this.context.inputMethod.getInput(prompt);
149
149
  }
@@ -197,7 +197,7 @@ class CliChatService {
197
197
  ? `\nAsk knowhow ${this.context.currentAgent}: `
198
198
  : `\nAsk knowhow: `;
199
199
  try {
200
- const input = await this.getInput(promptText, commandNames, this.chatHistory);
200
+ const input = await this.getInput(promptText, commandNames);
201
201
  if (input.trim() === "") {
202
202
  continue;
203
203
  }