zubo 0.1.21 → 0.1.23

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 (54) hide show
  1. package/.playwright-mcp/console-2026-02-16T19-06-21-167Z.log +31 -0
  2. package/README.md +2 -1
  3. package/dashboard-chat.png +0 -0
  4. package/dashboard-followups.png +0 -0
  5. package/dashboard-history.png +0 -0
  6. package/dashboard-integrations.png +0 -0
  7. package/dashboard-knowledge-ok.png +0 -0
  8. package/dashboard-knowledge.png +0 -0
  9. package/dashboard-notes-add.png +0 -0
  10. package/dashboard-notes-improved.png +0 -0
  11. package/dashboard-notes.png +0 -0
  12. package/dashboard-overview.png +0 -0
  13. package/dashboard-preferences.png +0 -0
  14. package/dashboard-settings-fixed.png +0 -0
  15. package/dashboard-settings.png +0 -0
  16. package/dashboard-skills-ok.png +0 -0
  17. package/dashboard-skills.png +0 -0
  18. package/dashboard-todos-add.png +0 -0
  19. package/dashboard-todos-improved.png +0 -0
  20. package/dashboard-todos-item.png +0 -0
  21. package/dashboard-todos-priority-badge.png +0 -0
  22. package/dashboard-todos.png +0 -0
  23. package/dashboard-topics.png +0 -0
  24. package/docs/ROADMAP.md +12 -49
  25. package/migrations/024_personal_features.sql +96 -0
  26. package/package.json +1 -1
  27. package/site/docs/index.html +11 -0
  28. package/site/docs/skills.html +107 -0
  29. package/site/index.html +9 -1
  30. package/src/agent/context.ts +3 -3
  31. package/src/agent/delegate.ts +7 -2
  32. package/src/agent/loop.ts +6 -6
  33. package/src/agent/prompts.ts +49 -1
  34. package/src/agent/workflow-executor.ts +5 -1
  35. package/src/channels/dashboard.html.ts +558 -6
  36. package/src/channels/webchat.ts +305 -27
  37. package/src/llm/claude-code.ts +58 -17
  38. package/src/llm/codex.ts +59 -18
  39. package/src/start.ts +12 -0
  40. package/src/tools/builtin/diagnose.ts +19 -5
  41. package/src/tools/builtin/follow-ups.ts +189 -0
  42. package/src/tools/builtin/notes.ts +207 -0
  43. package/src/tools/builtin/preferences.ts +173 -0
  44. package/src/tools/builtin/todos.ts +270 -0
  45. package/src/tools/builtin/topics.ts +166 -0
  46. package/src/tools/mcp-client.ts +8 -0
  47. package/src/tools/permissions.ts +7 -0
  48. package/tests/agent/session.test.ts +43 -45
  49. package/tests/mcp-registry.test.ts +32 -35
  50. package/tests/personal-features.test.ts +1251 -0
  51. package/tests/skill-registry.test.ts +1 -7
  52. package/tests/db/export.test.ts +0 -219
  53. package/tests/session.test.ts +0 -58
  54. package/tests/tools/executor.test.ts +0 -150
@@ -1,13 +1,20 @@
1
- import { describe, test, expect, beforeEach } from "bun:test";
1
+ import { describe, test, expect, beforeEach, afterAll } from "bun:test";
2
2
  import { mock } from "bun:test";
3
+ import { writeFileSync, mkdtempSync, rmSync } from "fs";
4
+ import { join } from "path";
5
+ import { tmpdir } from "os";
3
6
 
4
7
  /**
5
8
  * Tests for src/tools/mcp-registry.ts
6
9
  *
7
- * We mock `fetch` to avoid real network calls and mock the file system / MCP
8
- * client modules that installFromRegistry and uninstallMcpServer depend on.
10
+ * We mock `fetch` to avoid real network calls and mock the config paths +
11
+ * MCP client modules. We do NOT mock `fs` — instead we use a real temp config file.
9
12
  */
10
13
 
14
+ // Create a temp directory for the config file
15
+ const testTmpDir = mkdtempSync(join(tmpdir(), "mcp-registry-test-"));
16
+ const testConfigPath = join(testTmpDir, "config.json");
17
+
11
18
  // Track fetch calls for assertions
12
19
  let fetchCalls: { url: string; options?: any }[] = [];
13
20
  let fetchResponse: { ok: boolean; status: number; statusText: string; json: () => any } = {
@@ -17,10 +24,6 @@ let fetchResponse: { ok: boolean; status: number; statusText: string; json: () =
17
24
  json: () => [],
18
25
  };
19
26
 
20
- // Track file system operations
21
- let writtenConfig: string | null = null;
22
- let mockConfigData: any = { mcp: { servers: [] } };
23
-
24
27
  // Track MCP client operations
25
28
  let connectedServers: string[] = [];
26
29
  let disconnectedServers: string[] = [];
@@ -33,7 +36,7 @@ globalThis.fetch = async (input: any, init?: any) => {
33
36
  return fetchResponse as any;
34
37
  };
35
38
 
36
- // Mock dependencies
39
+ // Mock dependencies — but NOT `fs`!
37
40
  mock.module("../src/util/logger", () => ({
38
41
  logger: {
39
42
  debug: () => {},
@@ -45,19 +48,8 @@ mock.module("../src/util/logger", () => ({
45
48
 
46
49
  mock.module("../src/config/paths", () => ({
47
50
  paths: {
48
- config: "/tmp/zubo-test-config.json",
49
- },
50
- }));
51
-
52
- mock.module("fs", () => ({
53
- readFileSync: (path: string) => JSON.stringify(mockConfigData),
54
- writeFileSync: (path: string, content: string) => {
55
- writtenConfig = content;
51
+ config: testConfigPath,
56
52
  },
57
- existsSync: () => true,
58
- readdirSync: () => [],
59
- mkdirSync: () => {},
60
- unlinkSync: () => {},
61
53
  }));
62
54
 
63
55
  mock.module("../src/tools/mcp-client", () => ({
@@ -79,13 +71,21 @@ const {
79
71
  uninstallMcpServer,
80
72
  } = await import("../src/tools/mcp-registry");
81
73
 
74
+ afterAll(() => {
75
+ globalThis.fetch = originalFetch;
76
+ rmSync(testTmpDir, { recursive: true, force: true });
77
+ });
78
+
79
+ // Helper to write config file for install/uninstall tests
80
+ function writeTestConfig(data: any) {
81
+ writeFileSync(testConfigPath, JSON.stringify(data, null, 2));
82
+ }
83
+
82
84
  // ─── searchRegistry ─────────────────────────────────────────────────────────
83
85
 
84
86
  describe("mcp-registry: searchRegistry", () => {
85
87
  beforeEach(() => {
86
88
  fetchCalls = [];
87
- // Clear the module's internal cache by importing fresh — but since we
88
- // can't easily do that, we use unique queries to avoid stale cache hits.
89
89
  });
90
90
 
91
91
  test("calls registry API with encoded query", async () => {
@@ -234,8 +234,7 @@ describe("mcp-registry: installFromRegistry", () => {
234
234
  beforeEach(() => {
235
235
  fetchCalls = [];
236
236
  connectedServers = [];
237
- writtenConfig = null;
238
- mockConfigData = { mcp: { servers: [] } };
237
+ writeTestConfig({ mcp: { servers: [] } });
239
238
  });
240
239
 
241
240
  test("installs a server from registry into config", async () => {
@@ -257,9 +256,9 @@ describe("mcp-registry: installFromRegistry", () => {
257
256
  expect(result.name).toBe(uniqueName);
258
257
  expect(result.tools).toBe(3); // from our mock getMcpStatus
259
258
  expect(connectedServers).toContain(uniqueName);
260
- expect(writtenConfig).not.toBeNull();
261
259
 
262
- const savedConfig = JSON.parse(writtenConfig!);
260
+ // Verify config was written to disk
261
+ const savedConfig = JSON.parse(require("fs").readFileSync(testConfigPath, "utf-8"));
263
262
  const server = savedConfig.mcp.servers.find((s: any) => s.name === uniqueName);
264
263
  expect(server).toBeTruthy();
265
264
  expect(server.command).toBe("npx");
@@ -280,11 +279,11 @@ describe("mcp-registry: installFromRegistry", () => {
280
279
 
281
280
  test("replaces existing server entry with same name", async () => {
282
281
  const uniqueName = `replace-server-${Date.now()}`;
283
- mockConfigData = {
282
+ writeTestConfig({
284
283
  mcp: {
285
284
  servers: [{ name: uniqueName, command: "old-cmd", args: [], enabled: true }],
286
285
  },
287
- };
286
+ });
288
287
 
289
288
  fetchResponse = {
290
289
  ok: true,
@@ -299,7 +298,7 @@ describe("mcp-registry: installFromRegistry", () => {
299
298
 
300
299
  await installFromRegistry(uniqueName);
301
300
 
302
- const savedConfig = JSON.parse(writtenConfig!);
301
+ const savedConfig = JSON.parse(require("fs").readFileSync(testConfigPath, "utf-8"));
303
302
  const servers = savedConfig.mcp.servers.filter((s: any) => s.name === uniqueName);
304
303
  expect(servers).toHaveLength(1);
305
304
  expect(servers[0].command).toBe("new-cmd");
@@ -311,32 +310,30 @@ describe("mcp-registry: installFromRegistry", () => {
311
310
  describe("mcp-registry: uninstallMcpServer", () => {
312
311
  beforeEach(() => {
313
312
  disconnectedServers = [];
314
- writtenConfig = null;
315
313
  });
316
314
 
317
315
  test("disconnects and removes server from config", async () => {
318
- mockConfigData = {
316
+ writeTestConfig({
319
317
  mcp: {
320
318
  servers: [
321
319
  { name: "keep-this", command: "cmd1" },
322
320
  { name: "remove-this", command: "cmd2" },
323
321
  ],
324
322
  },
325
- };
323
+ });
326
324
 
327
325
  await uninstallMcpServer("remove-this");
328
326
 
329
327
  expect(disconnectedServers).toContain("remove-this");
330
- expect(writtenConfig).not.toBeNull();
331
328
 
332
- const savedConfig = JSON.parse(writtenConfig!);
329
+ const savedConfig = JSON.parse(require("fs").readFileSync(testConfigPath, "utf-8"));
333
330
  const remaining = savedConfig.mcp.servers.map((s: any) => s.name);
334
331
  expect(remaining).toContain("keep-this");
335
332
  expect(remaining).not.toContain("remove-this");
336
333
  });
337
334
 
338
335
  test("handles server not in config gracefully", async () => {
339
- mockConfigData = { mcp: { servers: [] } };
336
+ writeTestConfig({ mcp: { servers: [] } });
340
337
 
341
338
  await expect(uninstallMcpServer("nonexistent")).resolves.toBeUndefined();
342
339
  expect(disconnectedServers).toContain("nonexistent");