@tyvm/knowhow 0.0.108-dev.bd8f104-dev.bd8f104-dev.bd8f104 → 0.0.108-dev.c47492f

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 (53) hide show
  1. package/package.json +2 -2
  2. package/scripts/publish.sh +20 -4
  3. package/src/agents/base/base.ts +9 -0
  4. package/src/chat/CliChatService.ts +7 -1
  5. package/src/chat/renderer/CompactRenderer.ts +20 -0
  6. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  7. package/src/chat/renderer/FancyRenderer.ts +19 -0
  8. package/src/chat/renderer/types.ts +11 -0
  9. package/src/cli.ts +45 -21
  10. package/src/clients/types.ts +12 -4
  11. package/src/processors/JsonCompressor.ts +3 -3
  12. package/src/services/modules/index.ts +21 -17
  13. package/src/tunnel.ts +216 -0
  14. package/src/worker.ts +65 -336
  15. package/src/workers/auth/WsMiddleware.ts +99 -0
  16. package/src/workers/auth/authMiddleware.ts +104 -0
  17. package/src/workers/auth/types.ts +14 -2
  18. package/ts_build/package.json +2 -2
  19. package/ts_build/src/agents/base/base.js +10 -0
  20. package/ts_build/src/agents/base/base.js.map +1 -1
  21. package/ts_build/src/chat/CliChatService.js +10 -1
  22. package/ts_build/src/chat/CliChatService.js.map +1 -1
  23. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  24. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  25. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  26. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  27. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  28. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  29. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  30. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  31. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  32. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  33. package/ts_build/src/cli.js +17 -9
  34. package/ts_build/src/cli.js.map +1 -1
  35. package/ts_build/src/clients/types.d.ts +2 -2
  36. package/ts_build/src/processors/JsonCompressor.js +3 -3
  37. package/ts_build/src/processors/JsonCompressor.js.map +1 -1
  38. package/ts_build/src/services/modules/index.d.ts +30 -0
  39. package/ts_build/src/services/modules/index.js +9 -14
  40. package/ts_build/src/services/modules/index.js.map +1 -1
  41. package/ts_build/src/tunnel.d.ts +27 -0
  42. package/ts_build/src/tunnel.js +112 -0
  43. package/ts_build/src/tunnel.js.map +1 -0
  44. package/ts_build/src/worker.d.ts +1 -4
  45. package/ts_build/src/worker.js +38 -244
  46. package/ts_build/src/worker.js.map +1 -1
  47. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  48. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  49. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  50. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  51. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  52. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  53. package/ts_build/src/workers/auth/types.d.ts +8 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.108-dev.bd8f104-dev.bd8f104-dev.bd8f104",
3
+ "version": "0.0.108-dev.c47492f",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -55,7 +55,7 @@
55
55
  "@inquirer/editor": "^4.2.18",
56
56
  "@modelcontextprotocol/sdk": "^1.13.3",
57
57
  "@simplewebauthn/server": "^13.3.0",
58
- "@tyvm/knowhow-tunnel": "0.0.4",
58
+ "@tyvm/knowhow-tunnel": "0.0.5",
59
59
  "commander": "^14.0.0",
60
60
  "diff": "^5.2.0",
61
61
  "express": "^4.19.2",
@@ -10,21 +10,37 @@ get_current_version() {
10
10
  version_nightly() {
11
11
  local version
12
12
  version=$(get_current_version)
13
+ # Strip any existing pre-release suffix (e.g. -dev.xxx or -nightly.xxx)
14
+ version=$(echo "$version" | sed 's/-.*$//')
13
15
  local stamp
14
16
  stamp=$(date -u +%Y%m%d)
15
17
  local pre="${version}-nightly.${stamp}"
16
- echo "Bumping version to: $pre"
17
- npm version "$pre" --no-git-tag-version
18
+ local current
19
+ current=$(get_current_version)
20
+ if [ "$current" = "$pre" ]; then
21
+ echo "Version already set to: $pre (no change needed)"
22
+ else
23
+ echo "Bumping version to: $pre"
24
+ npm version "$pre" --no-git-tag-version
25
+ fi
18
26
  }
19
27
 
20
28
  version_dev() {
21
29
  local version
22
30
  version=$(get_current_version)
31
+ # Strip any existing pre-release suffix (e.g. -dev.xxx or -nightly.xxx)
32
+ version=$(echo "$version" | sed 's/-.*$//')
23
33
  local hash
24
34
  hash=$(git rev-parse --short HEAD)
25
35
  local pre="${version}-dev.${hash}"
26
- echo "Bumping version to: $pre"
27
- npm version "$pre" --no-git-tag-version
36
+ local current
37
+ current=$(get_current_version)
38
+ if [ "$current" = "$pre" ]; then
39
+ echo "Version already set to: $pre (no change needed)"
40
+ else
41
+ echo "Bumping version to: $pre"
42
+ npm version "$pre" --no-git-tag-version
43
+ fi
28
44
  }
29
45
 
30
46
  case "$COMMAND" in
@@ -317,6 +317,15 @@ export abstract class BaseAgent implements IAgent {
317
317
  if (trimmed.startsWith("✅") || /^[\d\.\-\*]/.test(trimmed)) return true;
318
318
  }
319
319
 
320
+ // Detect JSON-wrapped finalAnswer output, e.g. {"answer":"..."} or {"finalAnswer":"..."}
321
+ try {
322
+ const parsed = JSON.parse(trimmed);
323
+ if (parsed && typeof parsed === "object") {
324
+ if (typeof parsed.answer === "string") return true;
325
+ if (typeof parsed.finalAnswer === "string") return true;
326
+ }
327
+ } catch (_) {}
328
+
320
329
  return false;
321
330
  }
322
331
 
@@ -265,7 +265,13 @@ export class CliChatService implements ChatService {
265
265
  if (this.context.voiceMode) {
266
266
  value = await voiceToText();
267
267
  } else if (this.context.multilineMode) {
268
- value = await editor({ message: prompt });
268
+ const renderer = this.context.renderer;
269
+ if (renderer) renderer.pause();
270
+ try {
271
+ value = await editor({ message: prompt });
272
+ } finally {
273
+ if (renderer) renderer.resume();
274
+ }
269
275
  this.context.multilineMode = false; // Disable after use like original
270
276
  } else {
271
277
  // Use saved input history for scrollback (InputQueueManager handles reverse access)
@@ -72,6 +72,8 @@ function colorFor(n: string) {
72
72
  export class CompactRenderer implements AgentRenderer {
73
73
  private activeTaskId: string | undefined;
74
74
  private emitter = new EventEmitter();
75
+ private paused = false;
76
+ private bufferedEvents: RenderEvent[] = [];
75
77
 
76
78
  setActiveTaskId(id: string | undefined): void {
77
79
  this.activeTaskId = id;
@@ -110,8 +112,26 @@ export class CompactRenderer implements AgentRenderer {
110
112
  this.emitter.on("agentDone", handler);
111
113
  }
112
114
 
115
+ pause(): void {
116
+ this.paused = true;
117
+ }
118
+
119
+ resume(): void {
120
+ this.paused = false;
121
+ const buffered = this.bufferedEvents.splice(0);
122
+ for (const event of buffered) {
123
+ this.render(event);
124
+ }
125
+ }
126
+
113
127
  render(event: RenderEvent): void {
114
128
  if (!this.isActiveTask(event.taskId)) return;
129
+
130
+ if (this.paused) {
131
+ this.bufferedEvents.push(event);
132
+ return;
133
+ }
134
+
115
135
  this.emitter.emit(event.type, event);
116
136
  switch (event.type) {
117
137
  case "log":
@@ -17,6 +17,8 @@ import { EventEmitter } from "events";
17
17
  export class ConsoleRenderer implements AgentRenderer {
18
18
  private activeTaskId: string | undefined;
19
19
  private emitter = new EventEmitter();
20
+ private paused = false;
21
+ private bufferedEvents: RenderEvent[] = [];
20
22
 
21
23
  setActiveTaskId(taskId: string | undefined): void {
22
24
  this.activeTaskId = taskId;
@@ -56,9 +58,26 @@ export class ConsoleRenderer implements AgentRenderer {
56
58
  this.emitter.on("agentDone", handler);
57
59
  }
58
60
 
61
+ pause(): void {
62
+ this.paused = true;
63
+ }
64
+
65
+ resume(): void {
66
+ this.paused = false;
67
+ const buffered = this.bufferedEvents.splice(0);
68
+ for (const event of buffered) {
69
+ this.render(event);
70
+ }
71
+ }
72
+
59
73
  render(event: RenderEvent): void {
60
74
  if (!this.isActiveTask(event.taskId)) return;
61
75
 
76
+ if (this.paused) {
77
+ this.bufferedEvents.push(event);
78
+ return;
79
+ }
80
+
62
81
  switch (event.type) {
63
82
  case "log":
64
83
  this.emitter.emit("log", event);
@@ -194,6 +194,8 @@ const toolTimers = new Map<string, number>();
194
194
  export class FancyRenderer implements AgentRenderer {
195
195
  private activeTaskId: string | undefined;
196
196
  private emitter = new EventEmitter();
197
+ private paused = false;
198
+ private bufferedEvents: RenderEvent[] = [];
197
199
 
198
200
  setActiveTaskId(taskId: string | undefined): void {
199
201
  this.activeTaskId = taskId;
@@ -227,9 +229,26 @@ export class FancyRenderer implements AgentRenderer {
227
229
  this.emitter.on("agentDone", handler);
228
230
  }
229
231
 
232
+ pause(): void {
233
+ this.paused = true;
234
+ }
235
+
236
+ resume(): void {
237
+ this.paused = false;
238
+ const buffered = this.bufferedEvents.splice(0);
239
+ for (const event of buffered) {
240
+ this.render(event);
241
+ }
242
+ }
243
+
230
244
  render(event: RenderEvent): void {
231
245
  if (!this.isActiveTask(event.taskId)) return;
232
246
 
247
+ if (this.paused) {
248
+ this.bufferedEvents.push(event);
249
+ return;
250
+ }
251
+
233
252
  switch (event.type) {
234
253
  case "log":
235
254
  this.emitter.emit("log", event);
@@ -80,6 +80,17 @@ export interface AgentRenderer {
80
80
  setActiveTaskId(taskId: string | undefined): void;
81
81
  getActiveTaskId(): string | undefined;
82
82
 
83
+ /**
84
+ * Pause rendering - buffer any incoming events instead of printing them.
85
+ * Useful when an interactive UI (e.g. editor for /multi) takes over the terminal.
86
+ */
87
+ pause(): void;
88
+
89
+ /**
90
+ * Resume rendering - flush any buffered events and resume normal output.
91
+ */
92
+ resume(): void;
93
+
83
94
  /**
84
95
  * Replay the last N render events (used by /logs command).
85
96
  * If count is not provided, shows last 10.
package/src/cli.ts CHANGED
@@ -14,7 +14,8 @@ import { includedTools } from "./agents/tools/list";
14
14
  import * as allTools from "./agents/tools";
15
15
  import { LazyToolsService, services } from "./services";
16
16
  import { login } from "./login";
17
- import { worker, tunnel } from "./worker";
17
+ import { worker } from "./worker";
18
+ import { TUNNEL_MINIMAL_TOOLS } from "./tunnel";
18
19
  import { fileSync } from "./fileSync";
19
20
  import { KnowhowSimpleClient } from "./services/KnowhowClient";
20
21
  import { ModulesService } from "./services/modules";
@@ -44,23 +45,28 @@ import { CliChatService } from "./chat/CliChatService";
44
45
  // Without this, a single failing MCP server (e.g. expired Notion token) will
45
46
  // crash the entire CLI with an unhandled rejection.
46
47
  process.on("unhandledRejection", (reason: unknown) => {
47
- const message =
48
- reason instanceof Error ? reason.message : String(reason);
48
+ const message = reason instanceof Error ? reason.message : String(reason);
49
49
  // Only warn — don't exit. The MCP connect errors are recoverable;
50
50
  // the server will simply be unavailable but others continue working.
51
- console.warn(
52
- `⚠ Unhandled MCP/async error (non-fatal): ${message}`
53
- );
51
+ console.warn(`⚠ Unhandled MCP/async error (non-fatal): ${message}`);
54
52
  });
55
53
 
56
54
  async function setupServices() {
57
- const { Agents, Mcp, Clients, Tools: OldTools } = services();
55
+ const {
56
+ Agents,
57
+ Mcp,
58
+ Clients,
59
+ Tools: AllTools,
60
+ Embeddings,
61
+ Plugins,
62
+ MediaProcessor,
63
+ } = services();
58
64
  const Tools = new LazyToolsService(); // eslint-disable-line no-shadow
59
65
 
60
66
  // Load modules from config first so module-provided tools/agents/plugins are available
61
67
  // We need to wireup the LazyTools to be connected to the same singletons that are in services()
62
68
  Tools.setContext({
63
- ...OldTools.getContext(),
69
+ ...AllTools.getContext(),
64
70
  });
65
71
 
66
72
  // Build the AgentContext with the fully-populated LazyToolsService so every
@@ -107,16 +113,17 @@ async function setupServices() {
107
113
  const modulesService = new ModulesService();
108
114
  await modulesService.loadModulesFromConfig({
109
115
  Agents,
110
- Embeddings: services().Embeddings,
111
- Plugins: services().Plugins,
116
+ Embeddings,
117
+ Plugins,
112
118
  Clients,
119
+
113
120
  // Use LazyToolsService so module-provided tools are visible to agents and scripts
114
- Tools: Tools as any,
115
- MediaProcessor: services().MediaProcessor,
121
+ Tools,
122
+ MediaProcessor,
116
123
  });
117
124
 
118
- // Return both LazyToolsService (for agents) and OldTools (plain ToolsService with all tools for scripts)
119
- return { Tools, Clients, PlainTools: OldTools };
125
+ // Return both LazyToolsService (for agents) and AllTools (plain ToolsService with all tools for scripts)
126
+ return { Tools, Clients };
120
127
  }
121
128
 
122
129
  // Utility function to read from stdin
@@ -528,14 +535,25 @@ async function main() {
528
535
  program
529
536
  .command("cloudworker")
530
537
  .description("Create or sync a cloud worker with your local knowhow config")
531
- .option("--create", "Create a new cloud worker with synced config and files")
532
- .option("--push <uid>", "Push/sync local config and files to an existing cloud worker")
533
- .option("--pull <id>", "Pull the latest workerConfigJson from a cloud worker and update local config")
538
+ .option(
539
+ "--create",
540
+ "Create a new cloud worker with synced config and files"
541
+ )
542
+ .option(
543
+ "--push <uid>",
544
+ "Push/sync local config and files to an existing cloud worker"
545
+ )
546
+ .option(
547
+ "--pull <id>",
548
+ "Pull the latest workerConfigJson from a cloud worker and update local config"
549
+ )
534
550
  .option("--name <name>", "Name for the cloud worker (used with --create)")
535
551
  .option("--dry-run", "Print what would be synced without doing it")
536
552
  .action(async (options) => {
537
553
  try {
538
- const { cloudWorker, pullCloudWorkerConfig } = await import("./cloudWorker");
554
+ const { cloudWorker, pullCloudWorkerConfig } = await import(
555
+ "./cloudWorker"
556
+ );
539
557
  if (options.pull) {
540
558
  await pullCloudWorkerConfig({ id: options.pull });
541
559
  } else {
@@ -550,7 +568,9 @@ async function main() {
550
568
  program
551
569
  .command("tunnel")
552
570
  .description(
553
- "Start tunnel-only mode: expose local ports to the cloud without registering any tools"
571
+ "Start a minimal worker with tunnel enabled: exposes local ports to the cloud. " +
572
+ "Registers essential tools (unlock, lock, listAllowedPorts) so the backend is aware of the worker and ports. " +
573
+ "If passkey auth is configured, the tunnel is locked until unlocked via tool call or WebSocket auth protocol."
554
574
  )
555
575
  .option(
556
576
  "--share",
@@ -558,10 +578,14 @@ async function main() {
558
578
  )
559
579
  .option("--unshare", "Make this tunnel private (only you can use it)")
560
580
  .action(async (options) => {
561
- await tunnel(options);
581
+ console.log("🌐 Starting tunnel (minimal worker) mode...");
582
+ console.log(` Tools: ${TUNNEL_MINIMAL_TOOLS.join(", ")}`);
583
+ await worker({
584
+ ...options,
585
+ allowedTools: TUNNEL_MINIMAL_TOOLS,
586
+ });
562
587
  });
563
588
 
564
-
565
589
  program
566
590
  .command("script")
567
591
  .description("Run a local tool script file using the executeScript sandbox")
@@ -1,4 +1,10 @@
1
- export type ModelModality = "completion" | "embedding" | "image" | "audio" | "video" | "transcription";
1
+ export type ModelModality =
2
+ | "completion"
3
+ | "embedding"
4
+ | "image"
5
+ | "audio"
6
+ | "video"
7
+ | "transcription";
2
8
 
3
9
  export type MessageContent =
4
10
  | { type: "text"; text: string }
@@ -8,7 +14,7 @@ export type MessageContent =
8
14
 
9
15
  export interface Message {
10
16
  role: "system" | "user" | "assistant" | "tool";
11
- content?: string | MessageContent[];
17
+ content?: string | MessageContent[] | null;
12
18
 
13
19
  name?: string;
14
20
  tool_call_id?: string;
@@ -16,7 +22,7 @@ export interface Message {
16
22
  }
17
23
 
18
24
  export interface OutputMessage extends Message {
19
- content: string;
25
+ content?: string | null;
20
26
  }
21
27
 
22
28
  export interface ToolProp {
@@ -301,7 +307,9 @@ export interface GenericClient {
301
307
  * When modality is provided, return only models for that modality (static list).
302
308
  * When omitted, return ALL models (backward compat — may do a live API call).
303
309
  */
304
- getModels(modality?: ModelModality): Promise<{ id: string; modality?: ModelModality[] }[]>;
310
+ getModels(
311
+ modality?: ModelModality
312
+ ): Promise<{ id: string; modality?: ModelModality[] }[]>;
305
313
  /**
306
314
  * Returns the context window limit and compression threshold for a given model,
307
315
  * or undefined if the model is not known to this client.
@@ -382,7 +382,7 @@ export class JsonCompressor {
382
382
  i + currentChunk.length - 1
383
383
  }]\nPreview: ${chunkString.substring(0, 100)}...\n[Use ${
384
384
  this.toolName
385
- } tool with key "${key}" to retrieve this chunk]`;
385
+ } tool with key "${key}" to retrieve this chunk]\n[TIP: try jqToolResponse,grepToolResponse,tailToolResponse to filter/search/map without repeated ${this.toolName} calls - especially useful for JSON data]`;
386
386
  finalArray.unshift(stub); // Add stub to the start of our final result.
387
387
 
388
388
  currentChunk = [];
@@ -453,7 +453,7 @@ export class JsonCompressor {
453
453
  result
454
454
  ).join(", ")}\nPreview: ${objectAsString.substring(0, 200)}...\n[Use ${
455
455
  this.toolName
456
- } tool with key "${key}" to retrieve full content]`;
456
+ } tool with key "${key}" to retrieve full content]\n[TIP: try jqToolResponse,grepToolResponse,tailToolResponse to filter/search/map without repeated ${this.toolName} calls - especially useful for JSON data]`;
457
457
  }
458
458
  return result;
459
459
  }
@@ -486,7 +486,7 @@ export class JsonCompressor {
486
486
  200
487
487
  )}...\n[Use ${
488
488
  this.toolName
489
- } tool with key "${key}" to retrieve full content]`;
489
+ } tool with key "${key}" to retrieve full content]\n[TIP: try jqToolResponse,grepToolResponse,tailToolResponse to filter/search/map without repeated ${this.toolName} calls - especially useful for JSON data]`;
490
490
  }
491
491
  return obj;
492
492
  }
@@ -5,20 +5,21 @@ import { services } from "../";
5
5
  import * as path from "path";
6
6
 
7
7
  export class ModulesService {
8
+ async getDefaultContext() {
9
+ return { ...services() };
10
+ }
11
+
12
+ async overrideDefaultContext(overrides: Partial<ModuleContext>) {
13
+ const defaultContext = await this.getDefaultContext();
14
+ return { ...defaultContext, ...overrides };
15
+ }
16
+
8
17
  async loadModulesFromConfig(context?: ModuleContext) {
9
18
  const config = await getConfig();
10
19
 
11
20
  // If no context provided, fall back to global singletons
12
21
  if (!context) {
13
- const { Clients, Plugins, Agents, Tools, Embeddings, MediaProcessor } = services();
14
- context = {
15
- Agents,
16
- Embeddings,
17
- Plugins,
18
- Clients,
19
- Tools,
20
- MediaProcessor,
21
- };
22
+ context = { ...(await this.getDefaultContext()) };
22
23
  }
23
24
 
24
25
  // Use the toolsService from context
@@ -43,9 +44,13 @@ export class ModulesService {
43
44
  : modulePath;
44
45
  const rawModule = require(resolvedPath);
45
46
  const importedModule = (rawModule.default || rawModule) as KnowhowModule;
46
- console.log(`🔌 Loading module: ${modulePath} (resolved: ${resolvedPath})`);
47
+ console.log(
48
+ `🔌 Loading module: ${modulePath} (resolved: ${resolvedPath})`
49
+ );
47
50
  await importedModule.init({ config, cwd: process.cwd(), context });
48
- console.log(`✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`);
51
+ console.log(
52
+ `✅ Module initialized: ${modulePath} (tools: ${importedModule.tools.length}, agents: ${importedModule.agents.length}, plugins: ${importedModule.plugins.length}, clients: ${importedModule.clients.length})`
53
+ );
49
54
 
50
55
  for (const agent of importedModule.agents) {
51
56
  agentService.registerAgent(agent);
@@ -58,13 +63,12 @@ export class ModulesService {
58
63
 
59
64
  for (const plugin of importedModule.plugins) {
60
65
  const pluginContext = {
61
- Agents: agentService,
62
- Clients: clients,
63
- Tools: toolsService,
64
- Plugins: pluginService,
65
- ...(context.MediaProcessor ? { MediaProcessor: context.MediaProcessor } : {}),
66
+ ...context,
66
67
  };
67
- pluginService.registerPlugin(plugin.name, new plugin.plugin(pluginContext as any));
68
+ pluginService.registerPlugin(
69
+ plugin.name,
70
+ new plugin.plugin(pluginContext as any)
71
+ );
68
72
  }
69
73
 
70
74
  for (const client of importedModule.clients) {