jowork 0.2.5 → 0.3.0

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/dist/{chunk-ROIINI33.js → chunk-4PIT2GZ4.js} +13 -1
  2. package/dist/{chunk-XLYRHKG6.js → chunk-54SD5GBF.js} +1 -1
  3. package/dist/chunk-63AMINQC.js +156 -0
  4. package/dist/{chunk-XAEGXSEO.js → chunk-74AHY7X6.js} +4 -0
  5. package/dist/{chunk-7U3SXINY.js → chunk-ATAUWJYD.js} +320 -50
  6. package/dist/chunk-DQW74UCN.js +671 -0
  7. package/dist/chunk-EYP6WMFF.js +153 -0
  8. package/dist/{chunk-JSTXMDXI.js → chunk-FCFZCZHR.js} +1 -1
  9. package/dist/chunk-FX6Z3QHV.js +34 -0
  10. package/dist/chunk-HENAABEL.js +419 -0
  11. package/dist/chunk-OXWWOKC7.js +201 -0
  12. package/dist/{chunk-HUHDL7WV.js → chunk-QGHJ45PL.js} +276 -199
  13. package/dist/chunk-RO3KK5RC.js +132 -0
  14. package/dist/{chunk-JE6TOU7W.js → chunk-TFMF3EXE.js} +2 -7
  15. package/dist/{chunk-TN327MDF.js → chunk-VX662YLA.js} +3 -3
  16. package/dist/cli.js +308 -135
  17. package/dist/{config-AI6UIJJN.js → config-FH2XLN7A.js} +2 -2
  18. package/dist/content-reader-VPGTR2SF.js +10 -0
  19. package/dist/context-ZNI3WOB7.js +10 -0
  20. package/dist/{credential-store-ZRZCSRPC.js → credential-store-OS5ZY4OW.js} +2 -2
  21. package/dist/{feishu-A6YVFKEN.js → feishu-XW5T6ER2.js} +8 -3
  22. package/dist/{git-manager-N35XSG4Y.js → git-manager-RVWV2GSV.js} +2 -1
  23. package/dist/github-PQKAYTLO.js +11 -0
  24. package/dist/{paths-JXOMBYIT.js → paths-FFRET6F7.js} +7 -3
  25. package/dist/{server-5GVWN2NB.js → server-WEADPUST.js} +59 -66
  26. package/dist/{setup-SYBQIL2O.js → setup-S2S2CHB2.js} +76 -30
  27. package/dist/sync-SRLFR5NA.js +21 -0
  28. package/dist/transport.js +6 -4
  29. package/package.json +1 -1
  30. package/src/dashboard/public/app.js +34 -8
  31. package/src/dashboard/public/style.css +14 -0
  32. package/dist/chunk-L5ZR7TSK.js +0 -82
  33. package/dist/chunk-LS2AJM5A.js +0 -163
  34. package/dist/chunk-QMOFQX7X.js +0 -612
  35. package/dist/chunk-YJWTKFWX.js +0 -451
  36. package/dist/github-SHWUFNYB.js +0 -10
  37. package/dist/sync-KDSPGY4A.js +0 -18
@@ -1,27 +1,268 @@
1
1
  import {
2
2
  GoalManager
3
- } from "./chunk-TN327MDF.js";
3
+ } from "./chunk-VX662YLA.js";
4
+ import {
5
+ loadCredential
6
+ } from "./chunk-54SD5GBF.js";
7
+ import {
8
+ readObjectContent
9
+ } from "./chunk-FX6Z3QHV.js";
10
+ import {
11
+ logError,
12
+ logInfo
13
+ } from "./chunk-MYDK7MWB.js";
4
14
  import {
5
15
  buildFtsQuery,
6
16
  connectorConfigs,
7
17
  createId,
8
18
  detectSourceFromQuery,
9
19
  memories,
10
- objectBodies,
11
20
  objects
12
- } from "./chunk-JE6TOU7W.js";
13
- import {
14
- logInfo
15
- } from "./chunk-MYDK7MWB.js";
21
+ } from "./chunk-TFMF3EXE.js";
16
22
 
17
23
  // src/mcp/server.ts
18
24
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
- import { z } from "zod";
25
+ import { z as z2 } from "zod";
20
26
  import Database from "better-sqlite3";
21
27
  import { drizzle } from "drizzle-orm/better-sqlite3";
22
28
  import { like, eq, or, desc } from "drizzle-orm";
23
29
  import { existsSync, readFileSync } from "fs";
24
30
  import { join } from "path";
31
+
32
+ // src/mcp/proxy.ts
33
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
34
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
35
+ import { z } from "zod";
36
+ var PLUGIN_REGISTRY = {
37
+ feishu: {
38
+ package: "@larksuiteoapi/lark-mcp",
39
+ name: "Feishu (Lark)",
40
+ command: "npx",
41
+ args: ["-y", "@larksuiteoapi/lark-mcp", "--app-id", "{{appId}}", "--app-secret", "{{appSecret}}"],
42
+ credentialMap: { appId: "appId", appSecret: "appSecret" },
43
+ connectorName: "feishu"
44
+ },
45
+ figma: {
46
+ package: "@anthropic/claude-code-figma-mcp",
47
+ name: "Figma",
48
+ command: "npx",
49
+ args: ["-y", "@anthropic/claude-code-figma-mcp"],
50
+ credentialMap: { FIGMA_ACCESS_TOKEN: "token" },
51
+ connectorName: "figma"
52
+ },
53
+ github: {
54
+ package: "@modelcontextprotocol/server-github",
55
+ name: "GitHub",
56
+ command: "npx",
57
+ args: ["-y", "@modelcontextprotocol/server-github"],
58
+ credentialMap: { GITHUB_PERSONAL_ACCESS_TOKEN: "token" },
59
+ connectorName: "github"
60
+ },
61
+ linear: {
62
+ package: "@anthropic/claude-code-linear-mcp",
63
+ name: "Linear",
64
+ command: "npx",
65
+ args: ["-y", "@anthropic/claude-code-linear-mcp"],
66
+ credentialMap: { LINEAR_API_KEY: "apiKey" },
67
+ connectorName: "linear"
68
+ },
69
+ gitlab: {
70
+ package: "@modelcontextprotocol/server-gitlab",
71
+ name: "GitLab",
72
+ command: "npx",
73
+ args: ["-y", "@modelcontextprotocol/server-gitlab"],
74
+ credentialMap: { GITLAB_PERSONAL_ACCESS_TOKEN: "token", GITLAB_API_URL: "apiUrl" },
75
+ connectorName: "gitlab"
76
+ }
77
+ };
78
+ var MAX_RETRIES = 3;
79
+ var SPAWN_TIMEOUT_MS = 3e4;
80
+ var McpProxyManager = class {
81
+ plugins = /* @__PURE__ */ new Map();
82
+ nativeToolNames;
83
+ constructor(nativeToolNames) {
84
+ this.nativeToolNames = new Set(nativeToolNames);
85
+ }
86
+ /**
87
+ * Discover which plugins can be activated based on available credentials.
88
+ * Does NOT spawn any processes — just identifies what's available.
89
+ */
90
+ discoverPlugins() {
91
+ const available = [];
92
+ for (const [source, def] of Object.entries(PLUGIN_REGISTRY)) {
93
+ const cred = loadCredential(def.connectorName);
94
+ if (cred) {
95
+ this.plugins.set(source, {
96
+ def,
97
+ client: null,
98
+ transport: null,
99
+ tools: /* @__PURE__ */ new Map(),
100
+ status: "idle",
101
+ failures: 0
102
+ });
103
+ available.push(source);
104
+ }
105
+ }
106
+ return available;
107
+ }
108
+ /**
109
+ * Register proxied tools on the JoWork MCP server.
110
+ * Creates a catch-all tool handler that routes to the correct plugin subprocess.
111
+ */
112
+ registerProxyTools(server) {
113
+ for (const [source, plugin] of this.plugins) {
114
+ const toolName = `${source}_proxy`;
115
+ server.tool(
116
+ toolName,
117
+ {
118
+ tool_name: z.string().describe(`The tool to call on the ${plugin.def.name} MCP server (without prefix)`),
119
+ arguments: z.record(z.unknown()).optional().describe("Arguments to pass to the tool")
120
+ },
121
+ async ({ tool_name, arguments: args }) => {
122
+ try {
123
+ const result = await this.callPluginTool(source, tool_name, args ?? {});
124
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
125
+ } catch (err) {
126
+ return { content: [{ type: "text", text: `Error calling ${source}/${tool_name}: ${err}` }], isError: true };
127
+ }
128
+ }
129
+ );
130
+ }
131
+ }
132
+ /**
133
+ * Call a tool on a plugin, spawning the subprocess if needed.
134
+ */
135
+ async callPluginTool(source, toolName, args) {
136
+ const plugin = this.plugins.get(source);
137
+ if (!plugin) throw new Error(`Plugin not found: ${source}`);
138
+ if (plugin.status === "unhealthy") throw new Error(`Plugin ${source} is unhealthy after ${MAX_RETRIES} failures`);
139
+ if (!plugin.client || plugin.status === "idle") {
140
+ await this.startPlugin(source);
141
+ }
142
+ if (!plugin.client) throw new Error(`Failed to start plugin: ${source}`);
143
+ const result = await plugin.client.callTool({ name: toolName, arguments: args });
144
+ return result;
145
+ }
146
+ /**
147
+ * Start a plugin subprocess and discover its tools.
148
+ */
149
+ async startPlugin(source) {
150
+ const plugin = this.plugins.get(source);
151
+ if (!plugin) return;
152
+ plugin.status = "starting";
153
+ const cred = loadCredential(plugin.def.connectorName);
154
+ if (!cred) {
155
+ plugin.status = "unhealthy";
156
+ throw new Error(`No credentials for ${source}`);
157
+ }
158
+ const env = { ...process.env };
159
+ for (const [envVar, credKey] of Object.entries(plugin.def.credentialMap)) {
160
+ const value = cred.data[credKey];
161
+ if (value) env[envVar] = value;
162
+ }
163
+ const args = plugin.def.args.map((arg) => {
164
+ const match = arg.match(/^\{\{(\w+)\}\}$/);
165
+ if (match) {
166
+ return cred.data[match[1]] ?? arg;
167
+ }
168
+ return arg;
169
+ });
170
+ try {
171
+ const transport = new StdioClientTransport({
172
+ command: plugin.def.command,
173
+ args,
174
+ env
175
+ });
176
+ const client = new Client({ name: `jowork-proxy-${source}`, version: "1.0.0" });
177
+ const connectPromise = client.connect(transport);
178
+ const timeoutPromise = new Promise(
179
+ (_, reject) => setTimeout(() => reject(new Error(`Spawn timeout for ${source}`)), SPAWN_TIMEOUT_MS)
180
+ );
181
+ await Promise.race([connectPromise, timeoutPromise]);
182
+ const toolsResult = await client.listTools();
183
+ plugin.tools.clear();
184
+ for (const tool of toolsResult.tools) {
185
+ if (this.nativeToolNames.has(tool.name)) {
186
+ logInfo("proxy", `Skipping conflicting tool: ${source}/${tool.name} (native tool has priority)`);
187
+ continue;
188
+ }
189
+ plugin.tools.set(tool.name, {
190
+ description: tool.description ?? "",
191
+ inputSchema: tool.inputSchema
192
+ });
193
+ }
194
+ plugin.client = client;
195
+ plugin.transport = transport;
196
+ plugin.status = "ready";
197
+ plugin.failures = 0;
198
+ logInfo("proxy", `Plugin ${source} started: ${plugin.tools.size} tools available`);
199
+ } catch (err) {
200
+ plugin.failures++;
201
+ if (plugin.failures >= MAX_RETRIES) {
202
+ plugin.status = "unhealthy";
203
+ logError("proxy", `Plugin ${source} unhealthy after ${MAX_RETRIES} failures: ${err}`);
204
+ } else {
205
+ plugin.status = "idle";
206
+ logError("proxy", `Plugin ${source} start failed (attempt ${plugin.failures}/${MAX_RETRIES}): ${err}`);
207
+ }
208
+ throw err;
209
+ }
210
+ }
211
+ /** Shut down all plugin subprocesses. */
212
+ async shutdown() {
213
+ for (const [source, plugin] of this.plugins) {
214
+ if (plugin.client) {
215
+ try {
216
+ await plugin.client.close();
217
+ } catch {
218
+ logError("proxy", `Failed to close plugin ${source}`);
219
+ }
220
+ plugin.client = null;
221
+ plugin.transport = null;
222
+ plugin.status = "idle";
223
+ }
224
+ }
225
+ }
226
+ /** Get status of all plugins. */
227
+ getStatus() {
228
+ return Array.from(this.plugins.entries()).map(([source, plugin]) => ({
229
+ source,
230
+ name: plugin.def.name,
231
+ status: plugin.status,
232
+ tools: plugin.tools.size
233
+ }));
234
+ }
235
+ /** List all available proxied tools across all plugins. */
236
+ listAllTools() {
237
+ const tools = [];
238
+ for (const [source, plugin] of this.plugins) {
239
+ for (const [name, tool] of plugin.tools) {
240
+ tools.push({ source, name: `${source}__${name}`, description: tool.description });
241
+ }
242
+ }
243
+ return tools;
244
+ }
245
+ };
246
+
247
+ // src/mcp/server.ts
248
+ var JOWORK_MCP_TOOLS = [
249
+ "search_data",
250
+ "list_sources",
251
+ "fetch_content",
252
+ "fetch_doc_map",
253
+ "fetch_chunk",
254
+ "read_memory",
255
+ "write_memory",
256
+ "search_memory",
257
+ "get_environment",
258
+ "get_goals",
259
+ "get_metrics",
260
+ "update_goal",
261
+ "push_to_channel",
262
+ "get_hot_context",
263
+ "get_briefing",
264
+ "sync_now"
265
+ ];
25
266
  var STALE_THRESHOLD_MS = 60 * 60 * 1e3;
26
267
  function escapeLike(input) {
27
268
  return input.replace(/[%_\\]/g, "\\$&");
@@ -59,7 +300,15 @@ function createJoWorkMcpServer(opts) {
59
300
  const db = drizzle(sqlite);
60
301
  const goalManager = new GoalManager(sqlite);
61
302
  const server = new McpServer({ name: "jowork", version: "0.1.0" });
303
+ const proxyManager = new McpProxyManager([...JOWORK_MCP_TOOLS]);
304
+ const availablePlugins = proxyManager.discoverPlugins();
305
+ if (availablePlugins.length > 0) {
306
+ logInfo("mcp", `Proxy plugins available: ${availablePlugins.join(", ")}`);
307
+ proxyManager.registerProxyTools(server);
308
+ }
62
309
  server.server.onclose = () => {
310
+ proxyManager.shutdown().catch(() => {
311
+ });
63
312
  if (ownsSqlite) {
64
313
  try {
65
314
  sqlite.close();
@@ -90,23 +339,33 @@ function createJoWorkMcpServer(opts) {
90
339
  const freshness = getDataFreshness(source);
91
340
  if (!freshness.isStale) return false;
92
341
  try {
93
- const { loadCredential, listCredentials } = await import("./credential-store-ZRZCSRPC.js");
342
+ const { loadCredential: loadCredential2, listCredentials } = await import("./credential-store-OS5ZY4OW.js");
94
343
  const sources = source ? [source] : listCredentials();
95
344
  if (sources.length === 0) return false;
96
345
  logInfo("mcp", `Auto-syncing stale data (${freshness.hint})`, { source });
97
346
  for (const src of sources) {
98
- const cred = loadCredential(src);
347
+ const cred = loadCredential2(src);
99
348
  if (!cred) continue;
100
349
  try {
101
350
  switch (src) {
102
351
  case "feishu": {
103
- const { syncFeishu } = await import("./feishu-A6YVFKEN.js");
104
- await syncFeishu(sqlite, cred.data);
352
+ const { syncFeishu } = await import("./feishu-XW5T6ER2.js");
353
+ const { SyncContext } = await import("./context-ZNI3WOB7.js");
354
+ const feishuCtx = new SyncContext(sqlite, { info: () => {
355
+ }, warn: () => {
356
+ }, error: () => {
357
+ } });
358
+ await syncFeishu(feishuCtx, cred.data);
105
359
  break;
106
360
  }
107
361
  case "github": {
108
- const { syncGitHub } = await import("./github-SHWUFNYB.js");
109
- await syncGitHub(sqlite, cred.data);
362
+ const { syncGitHub } = await import("./github-PQKAYTLO.js");
363
+ const { SyncContext } = await import("./context-ZNI3WOB7.js");
364
+ const ghCtx = new SyncContext(sqlite, { info: () => {
365
+ }, warn: () => {
366
+ }, error: () => {
367
+ } });
368
+ await syncGitHub(ghCtx, cred.data);
110
369
  break;
111
370
  }
112
371
  }
@@ -122,10 +381,10 @@ function createJoWorkMcpServer(opts) {
122
381
  server.tool(
123
382
  "search_data",
124
383
  {
125
- query: z.string().describe("Keywords to search across all synced data. JoWork auto-syncs if data is stale (>1 hour old)."),
126
- source: z.string().optional().describe("Filter by data source: github, feishu, gitlab, notion, slack, local"),
127
- path: z.string().optional().describe("Filter local files by path prefix (only applies to source=local)"),
128
- limit: z.number().optional().default(20).describe("Max results (default 20)")
384
+ query: z2.string().describe("Keywords to search across all synced data. JoWork auto-syncs if data is stale (>1 hour old)."),
385
+ source: z2.string().optional().describe("Filter by data source: github, feishu, gitlab, notion, slack, local"),
386
+ path: z2.string().optional().describe("Filter local files by path prefix (only applies to source=local)"),
387
+ limit: z2.number().optional().default(20).describe("Max results (default 20)")
129
388
  },
130
389
  async ({ query, source, path, limit }) => {
131
390
  await autoSyncIfStale(source);
@@ -225,18 +484,18 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
225
484
  server.tool(
226
485
  "fetch_content",
227
486
  {
228
- uri: z.string().describe("Object URI from search_data results")
487
+ uri: z2.string().describe("Object URI from search_data results")
229
488
  },
230
489
  async ({ uri }) => {
231
490
  const obj = db.select().from(objects).where(eq(objects.uri, uri)).get();
232
491
  if (!obj) {
233
492
  return { content: [{ type: "text", text: `Object not found: ${uri}` }] };
234
493
  }
235
- const body = db.select().from(objectBodies).where(eq(objectBodies.objectId, obj.id)).get();
494
+ const body = readObjectContent(sqlite, obj.id, obj.filePath);
236
495
  return {
237
496
  content: [{
238
497
  type: "text",
239
- text: JSON.stringify({ ...obj, body: body?.content ?? null, contentType: body?.contentType ?? null }, null, 2)
498
+ text: JSON.stringify({ ...obj, body: body ?? null }, null, 2)
240
499
  }]
241
500
  };
242
501
  }
@@ -244,7 +503,7 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
244
503
  server.tool(
245
504
  "fetch_doc_map",
246
505
  {
247
- id: z.string().describe("Object ID from search_data results")
506
+ id: z2.string().describe("Object ID from search_data results")
248
507
  },
249
508
  async ({ id }) => {
250
509
  const row = sqlite.prepare(`SELECT doc_map, title FROM objects WHERE id = ?`).get(id);
@@ -258,8 +517,8 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
258
517
  server.tool(
259
518
  "fetch_chunk",
260
519
  {
261
- id: z.string().describe("Object ID"),
262
- idx: z.number().describe("Chunk index (0-based, see fetch_doc_map output for available indices)")
520
+ id: z2.string().describe("Object ID"),
521
+ idx: z2.number().describe("Chunk index (0-based, see fetch_doc_map output for available indices)")
263
522
  },
264
523
  async ({ id, idx }) => {
265
524
  const chunk = sqlite.prepare(
@@ -276,8 +535,8 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
276
535
  server.tool(
277
536
  "read_memory",
278
537
  {
279
- query: z.string().describe("Search keywords -- matches against title, content, and tags"),
280
- limit: z.number().optional().default(10).describe("Max results")
538
+ query: z2.string().describe("Search keywords -- matches against title, content, and tags"),
539
+ limit: z2.number().optional().default(10).describe("Max results")
281
540
  },
282
541
  async ({ query, limit }) => {
283
542
  const pattern = `%${escapeLike(query)}%`;
@@ -302,10 +561,10 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
302
561
  server.tool(
303
562
  "write_memory",
304
563
  {
305
- title: z.string().describe("Short descriptive title for the memory"),
306
- content: z.string().describe("Memory content -- what to remember"),
307
- tags: z.array(z.string()).optional().describe('Tags for categorization (e.g. ["decision", "preference"])'),
308
- scope: z.enum(["personal", "team"]).optional().default("personal").describe("Scope")
564
+ title: z2.string().describe("Short descriptive title for the memory"),
565
+ content: z2.string().describe("Memory content -- what to remember"),
566
+ tags: z2.array(z2.string()).optional().describe('Tags for categorization (e.g. ["decision", "preference"])'),
567
+ scope: z2.enum(["personal", "team"]).optional().default("personal").describe("Scope")
309
568
  },
310
569
  async ({ title, content, tags, scope }) => {
311
570
  const now = Date.now();
@@ -358,8 +617,8 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
358
617
  server.tool(
359
618
  "search_memory",
360
619
  {
361
- query: z.string().describe("Search query -- uses full-text search with time-weighted ranking"),
362
- limit: z.number().optional().default(10).describe("Max results")
620
+ query: z2.string().describe("Search query -- uses full-text search with time-weighted ranking"),
621
+ limit: z2.number().optional().default(10).describe("Max results")
363
622
  },
364
623
  async ({ query, limit }) => {
365
624
  const t0 = Date.now();
@@ -567,7 +826,7 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
567
826
  server.tool(
568
827
  "get_goals",
569
828
  {
570
- status: z.string().optional().describe("Filter: active, paused, completed")
829
+ status: z2.string().optional().describe("Filter: active, paused, completed")
571
830
  },
572
831
  async ({ status }) => {
573
832
  const goals = goalManager.listGoals({ status });
@@ -577,7 +836,7 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
577
836
  server.tool(
578
837
  "get_metrics",
579
838
  {
580
- goal_id: z.string().optional().describe("Goal ID (shows all active if omitted)")
839
+ goal_id: z2.string().optional().describe("Goal ID (shows all active if omitted)")
581
840
  },
582
841
  async ({ goal_id }) => {
583
842
  const query = goal_id ? `SELECT s.*, m.threshold, m.comparison, m.current, m.met, g.title as goal_title
@@ -596,10 +855,10 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
596
855
  server.tool(
597
856
  "update_goal",
598
857
  {
599
- goal_id: z.string().describe("Goal ID"),
600
- title: z.string().optional().describe("New title"),
601
- description: z.string().optional().describe("New description"),
602
- status: z.enum(["active", "paused", "completed"]).optional().describe("New status")
858
+ goal_id: z2.string().describe("Goal ID"),
859
+ title: z2.string().optional().describe("New title"),
860
+ description: z2.string().optional().describe("New description"),
861
+ status: z2.enum(["active", "paused", "completed"]).optional().describe("New status")
603
862
  },
604
863
  async ({ goal_id, title, description, status }) => {
605
864
  const existing = goalManager.getGoal(goal_id);
@@ -630,9 +889,9 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
630
889
  server.tool(
631
890
  "push_to_channel",
632
891
  {
633
- channel: z.string().describe("Channel: feishu, slack, telegram"),
634
- target: z.string().describe("Target ID (chat_id for feishu, channel for slack)"),
635
- message: z.string().describe("Message content")
892
+ channel: z2.string().describe("Channel: feishu, slack, telegram"),
893
+ target: z2.string().describe("Target ID (chat_id for feishu, channel for slack)"),
894
+ message: z2.string().describe("Message content")
636
895
  },
637
896
  async ({ channel, target, message }) => {
638
897
  if (!checkPushRateLimit(`${channel}:${target}`)) {
@@ -724,7 +983,7 @@ Showing ${rows.length} results (summaries only). Use fetch_content with a specif
724
983
  server.tool(
725
984
  "get_hot_context",
726
985
  {
727
- hours: z.number().optional().default(24).describe("Time window in hours (default 24, max 72)")
986
+ hours: z2.number().optional().default(24).describe("Time window in hours (default 24, max 72)")
728
987
  },
729
988
  async ({ hours }) => {
730
989
  const h = Math.min(hours, 72);
@@ -783,7 +1042,7 @@ ${hot.summary}
783
1042
  parts.push(`- ${s.source}: ${s.count} objects (last sync: ${ago} min ago${staleTag})`);
784
1043
  }
785
1044
  try {
786
- const { listCredentials } = await import("./credential-store-ZRZCSRPC.js");
1045
+ const { listCredentials } = await import("./credential-store-OS5ZY4OW.js");
787
1046
  const connected = listCredentials();
788
1047
  const synced = new Set(sources.map((s) => s.source));
789
1048
  const neverSynced = connected.filter((c) => !synced.has(c));
@@ -813,32 +1072,42 @@ ${hot.summary}
813
1072
  server.tool(
814
1073
  "sync_now",
815
1074
  {
816
- source: z.string().optional().describe("Sync a specific source (feishu, github, etc.) or omit for all. Call this directly when data is stale \u2014 do NOT ask the user for permission to sync.")
1075
+ source: z2.string().optional().describe("Sync a specific source (feishu, github, etc.) or omit for all. Call this directly when data is stale \u2014 do NOT ask the user for permission to sync.")
817
1076
  },
818
1077
  async ({ source }) => {
819
1078
  const freshness = getDataFreshness(source);
820
1079
  try {
821
- const { loadCredential, listCredentials } = await import("./credential-store-ZRZCSRPC.js");
1080
+ const { loadCredential: loadCredential2, listCredentials } = await import("./credential-store-OS5ZY4OW.js");
822
1081
  const sources = source ? [source] : listCredentials();
823
1082
  if (sources.length === 0) {
824
1083
  return { content: [{ type: "text", text: "No data sources connected. Run `jowork connect <source>` first." }] };
825
1084
  }
826
1085
  const results = [];
827
1086
  for (const src of sources) {
828
- const cred = loadCredential(src);
1087
+ const cred = loadCredential2(src);
829
1088
  if (!cred) continue;
830
1089
  try {
831
1090
  switch (src) {
832
1091
  case "feishu": {
833
- const { syncFeishu } = await import("./feishu-A6YVFKEN.js");
834
- const r = await syncFeishu(sqlite, cred.data);
1092
+ const { syncFeishu } = await import("./feishu-XW5T6ER2.js");
1093
+ const { SyncContext } = await import("./context-ZNI3WOB7.js");
1094
+ const feishuCtx = new SyncContext(sqlite, { info: () => {
1095
+ }, warn: () => {
1096
+ }, error: () => {
1097
+ } });
1098
+ const r = await syncFeishu(feishuCtx, cred.data);
835
1099
  results.push(`feishu: ${r.newMessages} new messages`);
836
1100
  break;
837
1101
  }
838
1102
  case "github": {
839
- const { syncGitHub } = await import("./github-SHWUFNYB.js");
840
- const r = await syncGitHub(sqlite, cred.data);
841
- results.push(`github: ${r.newObjects} new objects`);
1103
+ const { syncGitHub } = await import("./github-PQKAYTLO.js");
1104
+ const { SyncContext } = await import("./context-ZNI3WOB7.js");
1105
+ const ghCtx = new SyncContext(sqlite, { info: () => {
1106
+ }, warn: () => {
1107
+ }, error: () => {
1108
+ } });
1109
+ const r = await syncGitHub(ghCtx, cred.data);
1110
+ results.push(`github: ${r.newObjects} new, ${r.updatedObjects} updated`);
842
1111
  break;
843
1112
  }
844
1113
  default:
@@ -892,5 +1161,6 @@ Previous data was ${freshness.hint}.` }] };
892
1161
  }
893
1162
 
894
1163
  export {
1164
+ PLUGIN_REGISTRY,
895
1165
  createJoWorkMcpServer
896
1166
  };