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
package/dist/cli.js CHANGED
@@ -1,59 +1,70 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  FileWriter,
4
+ runSync,
4
5
  syncCommand,
5
6
  syncFirebase,
6
7
  syncPostHog
7
- } from "./chunk-HUHDL7WV.js";
8
+ } from "./chunk-QGHJ45PL.js";
8
9
  import {
9
10
  linkAllUnprocessed,
10
11
  syncGitLab,
11
12
  syncLinear
12
- } from "./chunk-YJWTKFWX.js";
13
+ } from "./chunk-HENAABEL.js";
14
+ import {
15
+ syncGitHub
16
+ } from "./chunk-63AMINQC.js";
17
+ import {
18
+ GitManager
19
+ } from "./chunk-EYP6WMFF.js";
13
20
  import {
14
21
  DbManager
15
- } from "./chunk-XAEGXSEO.js";
22
+ } from "./chunk-74AHY7X6.js";
16
23
  import {
24
+ PLUGIN_REGISTRY,
17
25
  createJoWorkMcpServer
18
- } from "./chunk-7U3SXINY.js";
26
+ } from "./chunk-ATAUWJYD.js";
19
27
  import {
20
28
  GoalManager
21
- } from "./chunk-TN327MDF.js";
29
+ } from "./chunk-VX662YLA.js";
22
30
  import {
23
31
  readConfig,
24
32
  writeConfig
25
- } from "./chunk-JSTXMDXI.js";
33
+ } from "./chunk-FCFZCZHR.js";
26
34
  import {
27
35
  listCredentials,
28
36
  loadCredential,
29
37
  saveCredential
30
- } from "./chunk-XLYRHKG6.js";
38
+ } from "./chunk-54SD5GBF.js";
39
+ import {
40
+ readObjectContents
41
+ } from "./chunk-FX6Z3QHV.js";
31
42
  import {
32
43
  configPath,
33
44
  dbPath,
34
45
  fileRepoDir,
35
46
  joworkDir,
36
- logsDir
37
- } from "./chunk-ROIINI33.js";
38
- import {
39
- syncGitHub
40
- } from "./chunk-LS2AJM5A.js";
47
+ logsDir,
48
+ pluginsDir
49
+ } from "./chunk-4PIT2GZ4.js";
41
50
  import {
42
51
  syncFeishu,
43
52
  syncFeishuApprovals,
44
53
  syncFeishuDocs,
54
+ syncFeishuLinks,
45
55
  syncFeishuMeetings
46
- } from "./chunk-QMOFQX7X.js";
47
- import {
48
- createId
49
- } from "./chunk-JE6TOU7W.js";
50
- import {
51
- GitManager
52
- } from "./chunk-L5ZR7TSK.js";
56
+ } from "./chunk-DQW74UCN.js";
57
+ import "./chunk-RO3KK5RC.js";
53
58
  import {
54
59
  logError,
55
60
  logInfo
56
61
  } from "./chunk-MYDK7MWB.js";
62
+ import {
63
+ SyncContext
64
+ } from "./chunk-OXWWOKC7.js";
65
+ import {
66
+ createId
67
+ } from "./chunk-TFMF3EXE.js";
57
68
  import {
58
69
  __dirname
59
70
  } from "./chunk-UJ4KEHGZ.js";
@@ -91,7 +102,7 @@ function initCommand(program2) {
91
102
  default: true
92
103
  }]);
93
104
  if (continueSetup) {
94
- const { runSetupWizard } = await import("./setup-SYBQIL2O.js");
105
+ const { runSetupWizard } = await import("./setup-S2S2CHB2.js");
95
106
  await runSetupWizard();
96
107
  } else {
97
108
  console.log("");
@@ -513,20 +524,25 @@ async function runCompaction(sqlite, provider) {
513
524
  `SELECT id FROM memory_hot WHERE window_start = ? AND window_end = ?`
514
525
  ).get(window.start, window.end);
515
526
  if (existing) continue;
516
- const objects = sqlite.prepare(`
517
- SELECT ob.content FROM objects o
518
- JOIN object_bodies ob ON ob.object_id = o.id
527
+ const windowObjs = sqlite.prepare(`
528
+ SELECT o.id, o.file_path FROM objects o
519
529
  WHERE o.created_at >= ? AND o.created_at < ?
520
530
  ORDER BY o.created_at DESC
521
531
  LIMIT 100
522
532
  `).all(window.start, window.end);
523
- if (objects.length === 0) continue;
524
- const summary = await provider.summarize(objects.map((o) => o.content));
533
+ if (windowObjs.length === 0) continue;
534
+ const contentMap = readObjectContents(
535
+ sqlite,
536
+ windowObjs.map((o) => ({ id: o.id, filePath: o.file_path }))
537
+ );
538
+ const contentTexts = windowObjs.map((o) => contentMap.get(o.id)).filter((c) => !!c);
539
+ if (contentTexts.length === 0) continue;
540
+ const summary = await provider.summarize(contentTexts);
525
541
  const id = createId("hot");
526
542
  sqlite.prepare(`
527
543
  INSERT INTO memory_hot (id, window_start, window_end, summary, source_count, sources, created_at)
528
544
  VALUES (?, ?, ?, ?, ?, ?, ?)
529
- `).run(id, window.start, window.end, summary, objects.length, null, now);
545
+ `).run(id, window.start, window.end, summary, windowObjs.length, null, now);
530
546
  hotEntries++;
531
547
  }
532
548
  const goals = sqlite.prepare(
@@ -568,7 +584,7 @@ function serveCommand(program2) {
568
584
  program2.command("serve").description("Start MCP server (stdio mode for agents, or --daemon for background)").option("--daemon", "Run as background daemon with cron sync").action(async (opts) => {
569
585
  const resolvedDbPath = process.env["JOWORK_DB_PATH"] ?? dbPath();
570
586
  if (!existsSync2(resolvedDbPath)) {
571
- const { writeConfig: writeConfig2 } = await import("./config-AI6UIJJN.js");
587
+ const { writeConfig: writeConfig2 } = await import("./config-FH2XLN7A.js");
572
588
  const db = new DbManager(dbPath());
573
589
  db.ensureTables();
574
590
  db.close();
@@ -625,9 +641,9 @@ async function startDaemon() {
625
641
  const intervalMinutes = config.syncIntervalMinutes ?? 15;
626
642
  const cronExpr = `*/${intervalMinutes} * * * *`;
627
643
  daemonLog("info", "Daemon started", { pid: process.pid, intervalMinutes });
628
- await runSync();
644
+ await runSync2();
629
645
  const _syncJob = new Cron(cronExpr, async () => {
630
- await runSync();
646
+ await runSync2();
631
647
  });
632
648
  console.log(`Daemon started (PID ${process.pid})`);
633
649
  console.log(` Sync: every ${intervalMinutes} minutes`);
@@ -651,7 +667,7 @@ function shouldSkipSource(sqlite, source, config) {
651
667
  return false;
652
668
  }
653
669
  }
654
- async function runSync() {
670
+ async function runSync2() {
655
671
  const sources = listCredentials();
656
672
  if (sources.length === 0) {
657
673
  daemonLog("info", "No sources connected, skipping sync");
@@ -685,51 +701,63 @@ async function runSync() {
685
701
  try {
686
702
  switch (source) {
687
703
  case "feishu": {
688
- const r = await syncFeishu(sqlite, cred.data, daemonSyncLogger, fileWriter);
704
+ const feishuCtx = new SyncContext(sqlite, daemonSyncLogger, fileWriter);
705
+ const r = await syncFeishu(feishuCtx, cred.data, daemonSyncLogger);
689
706
  syncResults.push({ source: "feishu", newObjects: r.newMessages, label: "messages" });
690
707
  try {
691
- const mr = await syncFeishuMeetings(sqlite, cred.data, daemonSyncLogger, fileWriter);
708
+ const mr = await syncFeishuMeetings(feishuCtx, cred.data, daemonSyncLogger);
692
709
  syncResults.push({ source: "feishu/meetings", newObjects: mr.newObjects, label: "events" });
693
710
  } catch (e) {
694
711
  daemonLog("warn", `Feishu meetings sync: ${e}`);
695
712
  }
696
713
  try {
697
- const dr = await syncFeishuDocs(sqlite, cred.data, daemonSyncLogger, fileWriter);
714
+ const dr = await syncFeishuDocs(feishuCtx, cred.data, daemonSyncLogger);
698
715
  syncResults.push({ source: "feishu/docs", newObjects: dr.newObjects, label: "docs" });
699
716
  } catch (e) {
700
717
  daemonLog("warn", `Feishu docs sync: ${e}`);
701
718
  }
702
719
  try {
703
- const ar = await syncFeishuApprovals(sqlite, cred.data, daemonSyncLogger, fileWriter);
720
+ const ar = await syncFeishuApprovals(feishuCtx, cred.data, daemonSyncLogger);
704
721
  syncResults.push({ source: "feishu/approvals", newObjects: ar.newObjects, label: "approvals" });
705
722
  } catch (e) {
706
723
  daemonLog("warn", `Feishu approvals sync: ${e}`);
707
724
  }
725
+ try {
726
+ const lr = await syncFeishuLinks(feishuCtx, cred.data, daemonSyncLogger);
727
+ syncResults.push({ source: "feishu/links", newObjects: lr.fetched, label: "links" });
728
+ } catch (e) {
729
+ daemonLog("warn", `Feishu links sync: ${e}`);
730
+ }
708
731
  break;
709
732
  }
710
733
  case "github": {
711
- const r = await syncGitHub(sqlite, cred.data, daemonSyncLogger, fileWriter);
712
- syncResults.push({ source: "github", newObjects: r.newObjects });
734
+ const ghCtx = new SyncContext(sqlite, daemonSyncLogger, fileWriter);
735
+ const r = await syncGitHub(ghCtx, cred.data, daemonSyncLogger);
736
+ syncResults.push({ source: "github", newObjects: r.newObjects + r.updatedObjects });
713
737
  break;
714
738
  }
715
739
  case "gitlab": {
716
- const r = await syncGitLab(sqlite, cred.data, daemonSyncLogger, fileWriter);
717
- syncResults.push({ source: "gitlab", newObjects: r.newObjects });
740
+ const glCtx = new SyncContext(sqlite, daemonSyncLogger, fileWriter);
741
+ const r = await syncGitLab(glCtx, cred.data, daemonSyncLogger);
742
+ syncResults.push({ source: "gitlab", newObjects: r.newObjects + r.updatedObjects });
718
743
  break;
719
744
  }
720
745
  case "linear": {
721
- const r = await syncLinear(sqlite, cred.data, daemonSyncLogger, fileWriter);
722
- syncResults.push({ source: "linear", newObjects: r.newObjects, label: "issues" });
746
+ const lnCtx = new SyncContext(sqlite, daemonSyncLogger, fileWriter);
747
+ const r = await syncLinear(lnCtx, cred.data, daemonSyncLogger);
748
+ syncResults.push({ source: "linear", newObjects: r.newObjects + r.updatedObjects, label: "issues" });
723
749
  break;
724
750
  }
725
751
  case "posthog": {
726
- const r = await syncPostHog(sqlite, cred.data, daemonSyncLogger, fileWriter);
727
- syncResults.push({ source: "posthog", newObjects: r.newObjects });
752
+ const phCtx = new SyncContext(sqlite, daemonSyncLogger, fileWriter);
753
+ const r = await syncPostHog(phCtx, cred.data, daemonSyncLogger);
754
+ syncResults.push({ source: "posthog", newObjects: r.newObjects + r.updatedObjects });
728
755
  break;
729
756
  }
730
757
  case "firebase": {
731
- const r = await syncFirebase(sqlite, cred.data, daemonSyncLogger, fileWriter);
732
- syncResults.push({ source: "firebase", newObjects: r.newObjects, label: "events" });
758
+ const fbCtx = new SyncContext(sqlite, daemonSyncLogger, fileWriter);
759
+ const r = await syncFirebase(fbCtx, cred.data, daemonSyncLogger);
760
+ syncResults.push({ source: "firebase", newObjects: r.newObjects + r.updatedObjects, label: "events" });
733
761
  break;
734
762
  }
735
763
  default:
@@ -954,9 +982,8 @@ function statusCommand(program2) {
954
982
  let sourceRows = [];
955
983
  try {
956
984
  sourceRows = sqlite.prepare(`
957
- SELECT o.source, COUNT(*) as count, COALESCE(SUM(LENGTH(ob.content)), 0) as size
985
+ SELECT o.source, COUNT(*) as count, COALESCE(SUM(LENGTH(o.summary)), 0) as size
958
986
  FROM objects o
959
- LEFT JOIN object_bodies ob ON ob.object_id = o.id
960
987
  GROUP BY o.source
961
988
  ORDER BY o.source
962
989
  `).all();
@@ -1170,6 +1197,46 @@ function exportCommand(program2) {
1170
1197
  }
1171
1198
 
1172
1199
  // src/commands/connect.ts
1200
+ import { execFileSync } from "child_process";
1201
+ import { existsSync as existsSync7, writeFileSync as writeFileSync4, readFileSync as readFileSync4 } from "fs";
1202
+ import { join as join5 } from "path";
1203
+ var SYNCABLE_SOURCES = /* @__PURE__ */ new Set(["feishu", "github", "gitlab", "linear", "posthog", "firebase"]);
1204
+ async function autoSync(source) {
1205
+ if (!SYNCABLE_SOURCES.has(source)) return;
1206
+ console.log(`
1207
+ Running initial sync for ${source}...`);
1208
+ try {
1209
+ await runSync([source]);
1210
+ } catch {
1211
+ console.log(`\u26A0 Initial sync failed. You can retry with: jowork sync --source ${source}`);
1212
+ }
1213
+ }
1214
+ function autoInstallPlugin(source) {
1215
+ const pluginDef = PLUGIN_REGISTRY[source];
1216
+ if (!pluginDef) return;
1217
+ const dir = pluginsDir();
1218
+ const pkgJsonPath = join5(dir, "package.json");
1219
+ if (!existsSync7(pkgJsonPath)) {
1220
+ writeFileSync4(pkgJsonPath, JSON.stringify({
1221
+ name: "jowork-plugins",
1222
+ private: true,
1223
+ dependencies: {}
1224
+ }, null, 2));
1225
+ }
1226
+ const pkgJson = JSON.parse(readFileSync4(pkgJsonPath, "utf-8"));
1227
+ if (pkgJson.dependencies?.[pluginDef.package]) return;
1228
+ console.log(` Installing MCP plugin: ${pluginDef.package}...`);
1229
+ try {
1230
+ execFileSync("npm", ["install", "--save", pluginDef.package], {
1231
+ cwd: dir,
1232
+ stdio: "pipe",
1233
+ timeout: 6e4
1234
+ });
1235
+ console.log(` \u2713 ${pluginDef.name} MCP plugin installed`);
1236
+ } catch (err) {
1237
+ console.log(` \u26A0 Plugin install failed. You can install manually: cd ${dir} && npm install ${pluginDef.package}`);
1238
+ }
1239
+ }
1173
1240
  function connectCommand(program2) {
1174
1241
  program2.command("connect").description("Connect a data source").argument("<source>", "Data source: feishu, github, gitlab, linear, posthog, slack, telegram, firebase").option("--app-id <id>", "App ID (for Feishu)").option("--app-secret <secret>", "App Secret (for Feishu)").option("--token <token>", "Access token (for GitHub/GitLab)").option("--api-url <url>", "API base URL (for GitLab self-hosted)").option("--api-key <key>", "API key (for Linear/PostHog/Firebase)").option("--host <host>", "API host (for PostHog self-hosted)").option("--project-id <id>", "Project ID (for PostHog/Firebase)").option("--webhook-url <url>", "Webhook URL (for Slack)").option("--bot-token <token>", "Bot token (for Telegram)").option("--chat-id <id>", "Chat ID (for Telegram)").option("--property-id <id>", "Property ID (for Firebase GA4)").action(async (source, opts) => {
1175
1242
  switch (source) {
@@ -1231,6 +1298,7 @@ async function connectFeishu(opts) {
1231
1298
  const data = await res.json();
1232
1299
  if (data.code !== 0) {
1233
1300
  console.error(`Feishu auth failed: ${data.msg}`);
1301
+ console.error(" Hint: App ID/Secret invalid. Check at https://open.feishu.cn/app");
1234
1302
  process.exit(1);
1235
1303
  }
1236
1304
  console.log("\u2713 Feishu credentials verified");
@@ -1244,7 +1312,9 @@ async function connectFeishu(opts) {
1244
1312
  createdAt: Date.now(),
1245
1313
  updatedAt: Date.now()
1246
1314
  });
1247
- console.log("\u2713 Feishu connected. Run `jowork sync` to start syncing data.");
1315
+ console.log("\u2713 Feishu connected.");
1316
+ autoInstallPlugin("feishu");
1317
+ await autoSync("feishu");
1248
1318
  }
1249
1319
  async function connectGitHub(opts) {
1250
1320
  let token = opts.token;
@@ -1260,6 +1330,22 @@ async function connectGitHub(opts) {
1260
1330
  console.error("Error: GitHub token is required.");
1261
1331
  process.exit(1);
1262
1332
  }
1333
+ console.log("Verifying GitHub credentials...");
1334
+ try {
1335
+ const res = await fetch("https://api.github.com/user", {
1336
+ headers: { Authorization: `Bearer ${token}`, "User-Agent": "jowork" }
1337
+ });
1338
+ if (!res.ok) {
1339
+ console.error(`GitHub auth failed: HTTP ${res.status}`);
1340
+ console.error(" Hint: Token invalid or expired. Create a new one at https://github.com/settings/tokens");
1341
+ process.exit(1);
1342
+ }
1343
+ const user = await res.json();
1344
+ console.log(`\u2713 GitHub credentials verified (user: ${user.login})`);
1345
+ } catch (err) {
1346
+ console.error(`Network error: ${err}`);
1347
+ process.exit(1);
1348
+ }
1263
1349
  saveCredential("github", {
1264
1350
  type: "github",
1265
1351
  data: { token },
@@ -1267,6 +1353,8 @@ async function connectGitHub(opts) {
1267
1353
  updatedAt: Date.now()
1268
1354
  });
1269
1355
  console.log("\u2713 GitHub connected.");
1356
+ autoInstallPlugin("github");
1357
+ await autoSync("github");
1270
1358
  }
1271
1359
  async function connectGitLab(opts) {
1272
1360
  let token = opts.token;
@@ -1294,6 +1382,7 @@ async function connectGitLab(opts) {
1294
1382
  });
1295
1383
  if (!res.ok) {
1296
1384
  console.error(`GitLab auth failed: HTTP ${res.status}`);
1385
+ console.error(" Hint: Token invalid. Check at https://gitlab.com/-/profile/personal_access_tokens");
1297
1386
  process.exit(1);
1298
1387
  }
1299
1388
  const user = await res.json();
@@ -1310,7 +1399,9 @@ async function connectGitLab(opts) {
1310
1399
  createdAt: Date.now(),
1311
1400
  updatedAt: Date.now()
1312
1401
  });
1313
- console.log("\u2713 GitLab connected. Run `jowork sync` to start syncing data.");
1402
+ console.log("\u2713 GitLab connected.");
1403
+ autoInstallPlugin("gitlab");
1404
+ await autoSync("gitlab");
1314
1405
  }
1315
1406
  async function connectLinear(opts) {
1316
1407
  let apiKey = opts.apiKey;
@@ -1338,11 +1429,13 @@ async function connectLinear(opts) {
1338
1429
  });
1339
1430
  if (!res.ok) {
1340
1431
  console.error(`Linear auth failed: HTTP ${res.status}`);
1432
+ console.error(" Hint: API key invalid. Get one at https://linear.app/settings/api");
1341
1433
  process.exit(1);
1342
1434
  }
1343
1435
  const data = await res.json();
1344
1436
  if (data.errors?.length) {
1345
1437
  console.error(`Linear auth failed: ${data.errors[0].message}`);
1438
+ console.error(" Hint: API key invalid. Get one at https://linear.app/settings/api");
1346
1439
  process.exit(1);
1347
1440
  }
1348
1441
  console.log(`\u2713 Linear credentials verified (user: ${data.data?.viewer?.name})`);
@@ -1356,7 +1449,9 @@ async function connectLinear(opts) {
1356
1449
  createdAt: Date.now(),
1357
1450
  updatedAt: Date.now()
1358
1451
  });
1359
- console.log("\u2713 Linear connected. Run `jowork sync` to start syncing data.");
1452
+ console.log("\u2713 Linear connected.");
1453
+ autoInstallPlugin("linear");
1454
+ await autoSync("linear");
1360
1455
  }
1361
1456
  async function connectPostHog(opts) {
1362
1457
  let apiKey = opts.apiKey;
@@ -1379,7 +1474,11 @@ async function connectPostHog(opts) {
1379
1474
  const res = await fetch(`${host}/api/projects/${projectId}/`, {
1380
1475
  headers: { Authorization: `Bearer ${apiKey}` }
1381
1476
  });
1382
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1477
+ if (!res.ok) {
1478
+ console.error(`PostHog auth failed: HTTP ${res.status}`);
1479
+ console.error(" Hint: API key invalid. Get one at https://app.posthog.com/project/settings");
1480
+ process.exit(1);
1481
+ }
1383
1482
  console.log("\u2713 PostHog credentials verified");
1384
1483
  } catch (err) {
1385
1484
  console.error(`PostHog verification failed: ${err}`);
@@ -1391,7 +1490,9 @@ async function connectPostHog(opts) {
1391
1490
  createdAt: Date.now(),
1392
1491
  updatedAt: Date.now()
1393
1492
  });
1394
- console.log("\u2713 PostHog connected. Run `jowork sync` to start syncing data.");
1493
+ console.log("\u2713 PostHog connected.");
1494
+ autoInstallPlugin("posthog");
1495
+ await autoSync("posthog");
1395
1496
  }
1396
1497
  async function connectSlack(opts) {
1397
1498
  let webhookUrl = opts.webhookUrl;
@@ -1461,22 +1562,40 @@ async function connectFirebase(opts) {
1461
1562
  console.error("Error: API key required.");
1462
1563
  process.exit(1);
1463
1564
  }
1464
- const data = { projectId, apiKey };
1465
- if (propertyId) data.propertyId = propertyId;
1565
+ const propId = propertyId || projectId;
1566
+ console.log("Verifying Firebase/GA4 credentials...");
1567
+ try {
1568
+ const res = await fetch(
1569
+ `https://analyticsdata.googleapis.com/v1beta/properties/${propId}/metadata?key=${apiKey}`
1570
+ );
1571
+ if (!res.ok) {
1572
+ console.error(`Firebase/GA4 auth failed: HTTP ${res.status}`);
1573
+ console.error(" Hint: Check your API key and Property ID at https://console.cloud.google.com");
1574
+ process.exit(1);
1575
+ }
1576
+ console.log("\u2713 Firebase/GA4 credentials verified");
1577
+ } catch (err) {
1578
+ console.error(`Network error: ${err}`);
1579
+ process.exit(1);
1580
+ }
1581
+ const credData = { projectId, apiKey };
1582
+ if (propertyId) credData.propertyId = propertyId;
1466
1583
  saveCredential("firebase", {
1467
1584
  type: "firebase",
1468
- data,
1585
+ data: credData,
1469
1586
  createdAt: Date.now(),
1470
1587
  updatedAt: Date.now()
1471
1588
  });
1472
- console.log("\u2713 Firebase connected. Run `jowork sync` to start syncing data.");
1589
+ console.log("\u2713 Firebase connected.");
1590
+ autoInstallPlugin("firebase");
1591
+ await autoSync("firebase");
1473
1592
  }
1474
1593
 
1475
1594
  // src/commands/search.ts
1476
- import { existsSync as existsSync7 } from "fs";
1595
+ import { existsSync as existsSync8 } from "fs";
1477
1596
  function searchCommand(program2) {
1478
1597
  program2.command("search").description("Search across all synced data").argument("<query>", "Search keywords").option("--source <source>", "Filter by source").option("--limit <n>", "Max results", "20").action(async (query, opts) => {
1479
- if (!existsSync7(dbPath())) {
1598
+ if (!existsSync8(dbPath())) {
1480
1599
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1481
1600
  process.exit(1);
1482
1601
  }
@@ -1627,8 +1746,8 @@ function goalCommand(program2) {
1627
1746
  }
1628
1747
 
1629
1748
  // src/commands/install-service.ts
1630
- import { writeFileSync as writeFileSync4, existsSync as existsSync8, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
1631
- import { join as join5 } from "path";
1749
+ import { writeFileSync as writeFileSync5, existsSync as existsSync9, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
1750
+ import { join as join6 } from "path";
1632
1751
  var HOME2 = process.env["HOME"] ?? "";
1633
1752
  function installServiceCommand(program2) {
1634
1753
  program2.command("install-service").description("Generate system service config (macOS LaunchAgent / Linux systemd)").option("--uninstall", "Remove the service").action(async (opts) => {
@@ -1642,9 +1761,9 @@ function installServiceCommand(program2) {
1642
1761
  });
1643
1762
  }
1644
1763
  function installLaunchAgent(uninstall) {
1645
- const plistPath = join5(HOME2, "Library", "LaunchAgents", "work.jowork.daemon.plist");
1764
+ const plistPath = join6(HOME2, "Library", "LaunchAgents", "work.jowork.daemon.plist");
1646
1765
  if (uninstall) {
1647
- if (existsSync8(plistPath)) {
1766
+ if (existsSync9(plistPath)) {
1648
1767
  unlinkSync2(plistPath);
1649
1768
  console.log(`\u2713 Removed ${plistPath}`);
1650
1769
  console.log(" Run: launchctl unload work.jowork.daemon");
@@ -1655,7 +1774,7 @@ function installLaunchAgent(uninstall) {
1655
1774
  }
1656
1775
  const joworkBin = process.argv[0];
1657
1776
  const joworkScript = process.argv[1];
1658
- const logsPath = join5(joworkDir(), "logs");
1777
+ const logsPath = join6(joworkDir(), "logs");
1659
1778
  mkdirSync3(logsPath, { recursive: true });
1660
1779
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
1661
1780
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -1687,7 +1806,7 @@ function installLaunchAgent(uninstall) {
1687
1806
  </dict>
1688
1807
  </dict>
1689
1808
  </plist>`;
1690
- writeFileSync4(plistPath, plist);
1809
+ writeFileSync5(plistPath, plist);
1691
1810
  console.log(`\u2713 LaunchAgent written to ${plistPath}`);
1692
1811
  console.log("");
1693
1812
  console.log("To start:");
@@ -1698,10 +1817,10 @@ function installLaunchAgent(uninstall) {
1698
1817
  console.log(" jowork install-service --uninstall");
1699
1818
  }
1700
1819
  function installSystemd(uninstall) {
1701
- const serviceDir = join5(HOME2, ".config", "systemd", "user");
1702
- const servicePath = join5(serviceDir, "jowork.service");
1820
+ const serviceDir = join6(HOME2, ".config", "systemd", "user");
1821
+ const servicePath = join6(serviceDir, "jowork.service");
1703
1822
  if (uninstall) {
1704
- if (existsSync8(servicePath)) {
1823
+ if (existsSync9(servicePath)) {
1705
1824
  unlinkSync2(servicePath);
1706
1825
  console.log(`\u2713 Removed ${servicePath}`);
1707
1826
  console.log(" Run: systemctl --user disable jowork");
@@ -1725,7 +1844,7 @@ RestartSec=10
1725
1844
  [Install]
1726
1845
  WantedBy=default.target
1727
1846
  `;
1728
- writeFileSync4(servicePath, service);
1847
+ writeFileSync5(servicePath, service);
1729
1848
  console.log(`\u2713 Systemd service written to ${servicePath}`);
1730
1849
  console.log("");
1731
1850
  console.log("To start:");
@@ -1734,10 +1853,11 @@ WantedBy=default.target
1734
1853
  }
1735
1854
 
1736
1855
  // src/commands/gc.ts
1737
- import { existsSync as existsSync9, statSync as statSync2 } from "fs";
1856
+ import { existsSync as existsSync10, statSync as statSync2, unlinkSync as unlinkSync3 } from "fs";
1857
+ import { join as join7 } from "path";
1738
1858
  function gcCommand(program2) {
1739
1859
  program2.command("gc").description("Garbage collect: remove old data, vacuum database").option("--retention-days <days>", "Override retention days (0 = keep all)").option("--dry-run", "Show what would be deleted without deleting").action(async (opts) => {
1740
- if (!existsSync9(dbPath())) {
1860
+ if (!existsSync10(dbPath())) {
1741
1861
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1742
1862
  process.exit(1);
1743
1863
  }
@@ -1749,31 +1869,40 @@ function gcCommand(program2) {
1749
1869
  const sqlite = db.getSqlite();
1750
1870
  const sizeBefore = statSync2(dbPath()).size;
1751
1871
  console.log(`Database size: ${(sizeBefore / 1024 / 1024).toFixed(2)} MB`);
1752
- let deletedBodies = 0;
1753
1872
  let deletedObjects = 0;
1873
+ let deletedFiles = 0;
1754
1874
  if (retentionDays > 0) {
1755
1875
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
1756
1876
  if (dryRun) {
1757
1877
  const count = sqlite.prepare(
1758
- `SELECT COUNT(*) as c FROM object_bodies ob
1759
- JOIN objects o ON o.id = ob.object_id
1760
- WHERE o.created_at < ?`
1878
+ `SELECT COUNT(*) as c FROM objects WHERE created_at < ?`
1761
1879
  ).get(cutoff).c;
1762
- console.log(`Would delete ${count} object bodies older than ${retentionDays} days`);
1880
+ console.log(`Would delete ${count} objects older than ${retentionDays} days`);
1763
1881
  } else {
1764
- const result = sqlite.prepare(
1765
- `DELETE FROM object_bodies WHERE object_id IN (
1766
- SELECT id FROM objects WHERE created_at < ?
1767
- )`
1768
- ).run(cutoff);
1769
- deletedBodies = result.changes;
1770
- const objResult = sqlite.prepare(
1771
- `DELETE FROM objects WHERE created_at < ?
1772
- AND id NOT IN (SELECT object_id FROM object_bodies)
1882
+ const repoDir = fileRepoDir();
1883
+ const oldObjects = sqlite.prepare(
1884
+ `SELECT id, file_path FROM objects WHERE created_at < ?
1773
1885
  AND id NOT IN (SELECT source_object_id FROM object_links)`
1774
- ).run(cutoff);
1775
- deletedObjects = objResult.changes;
1776
- console.log(`Deleted ${deletedBodies} old object bodies, ${deletedObjects} orphan objects`);
1886
+ ).all(cutoff);
1887
+ for (const obj of oldObjects) {
1888
+ if (obj.file_path) {
1889
+ try {
1890
+ unlinkSync3(join7(repoDir, obj.file_path));
1891
+ deletedFiles++;
1892
+ } catch {
1893
+ }
1894
+ }
1895
+ }
1896
+ if (oldObjects.length > 0) {
1897
+ const ids = oldObjects.map((o) => o.id);
1898
+ for (let i = 0; i < ids.length; i += 100) {
1899
+ const batch = ids.slice(i, i + 100);
1900
+ const placeholders = batch.map(() => "?").join(",");
1901
+ sqlite.prepare(`DELETE FROM objects WHERE id IN (${placeholders})`).run(...batch);
1902
+ }
1903
+ deletedObjects = oldObjects.length;
1904
+ }
1905
+ console.log(`Deleted ${deletedObjects} old objects, ${deletedFiles} files from disk`);
1777
1906
  }
1778
1907
  } else {
1779
1908
  console.log("Retention: keep all (set retentionDays in config or use --retention-days)");
@@ -1785,8 +1914,8 @@ function gcCommand(program2) {
1785
1914
  const saved = sizeBefore - sizeAfter;
1786
1915
  console.log(`VACUUM complete: ${(sizeAfter / 1024 / 1024).toFixed(2)} MB (saved ${(saved / 1024).toFixed(0)} KB)`);
1787
1916
  logInfo("gc", "Garbage collection complete", {
1788
- deletedBodies,
1789
1917
  deletedObjects,
1918
+ deletedFiles,
1790
1919
  sizeBefore,
1791
1920
  sizeAfter,
1792
1921
  savedBytes: saved
@@ -1804,18 +1933,18 @@ function gcCommand(program2) {
1804
1933
  }
1805
1934
 
1806
1935
  // src/commands/device-sync.ts
1807
- import { existsSync as existsSync11, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
1936
+ import { existsSync as existsSync12, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
1808
1937
 
1809
1938
  // src/sync/device-sync.ts
1810
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
1811
- import { join as join6 } from "path";
1939
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync11 } from "fs";
1940
+ import { join as join8 } from "path";
1812
1941
  function getDeviceId() {
1813
- const idPath = join6(joworkDir(), "device-id");
1814
- if (existsSync10(idPath)) {
1815
- return readFileSync4(idPath, "utf-8").trim();
1942
+ const idPath = join8(joworkDir(), "device-id");
1943
+ if (existsSync11(idPath)) {
1944
+ return readFileSync5(idPath, "utf-8").trim();
1816
1945
  }
1817
1946
  const id = createId("dev");
1818
- writeFileSync5(idPath, id);
1947
+ writeFileSync6(idPath, id);
1819
1948
  return id;
1820
1949
  }
1821
1950
  function getUnsyncedChanges(sqlite, limit = 100) {
@@ -1910,7 +2039,7 @@ function importSyncBundle(sqlite, bundleJson) {
1910
2039
  function deviceSyncCommand(program2) {
1911
2040
  const sync = program2.command("device-sync").description("Sync data between devices");
1912
2041
  sync.command("status").description("Show device sync status").action(async () => {
1913
- if (!existsSync11(dbPath())) {
2042
+ if (!existsSync12(dbPath())) {
1914
2043
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1915
2044
  process.exit(1);
1916
2045
  }
@@ -1925,7 +2054,7 @@ function deviceSyncCommand(program2) {
1925
2054
  db.close();
1926
2055
  });
1927
2056
  sync.command("export").description("Export unsynced changes to a file").argument("<file>", "Output file path").action(async (file) => {
1928
- if (!existsSync11(dbPath())) {
2057
+ if (!existsSync12(dbPath())) {
1929
2058
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1930
2059
  process.exit(1);
1931
2060
  }
@@ -1933,24 +2062,24 @@ function deviceSyncCommand(program2) {
1933
2062
  db.ensureTables();
1934
2063
  const sqlite = db.getSqlite();
1935
2064
  const bundle = exportSyncBundle(sqlite);
1936
- writeFileSync6(file, bundle);
2065
+ writeFileSync7(file, bundle);
1937
2066
  const parsed = JSON.parse(bundle);
1938
2067
  console.log(`Exported ${parsed.changes.length} changes to ${file}`);
1939
2068
  markSynced(sqlite, parsed.changes.map((c) => c.id));
1940
2069
  db.close();
1941
2070
  });
1942
2071
  sync.command("import").description("Import changes from another device").argument("<file>", "Input file path").action(async (file) => {
1943
- if (!existsSync11(file)) {
2072
+ if (!existsSync12(file)) {
1944
2073
  console.error(`File not found: ${file}`);
1945
2074
  process.exit(1);
1946
2075
  }
1947
- if (!existsSync11(dbPath())) {
2076
+ if (!existsSync12(dbPath())) {
1948
2077
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1949
2078
  process.exit(1);
1950
2079
  }
1951
2080
  const db = new DbManager(dbPath());
1952
2081
  db.ensureTables();
1953
- const bundleJson = readFileSync5(file, "utf-8");
2082
+ const bundleJson = readFileSync6(file, "utf-8");
1954
2083
  const result = importSyncBundle(db.getSqlite(), bundleJson);
1955
2084
  console.log(`Applied ${result.applied}, conflicts ${result.conflicts}, skipped ${result.skipped}`);
1956
2085
  db.close();
@@ -1958,15 +2087,15 @@ function deviceSyncCommand(program2) {
1958
2087
  }
1959
2088
 
1960
2089
  // src/commands/dashboard.ts
1961
- import { existsSync as existsSync12 } from "fs";
2090
+ import { existsSync as existsSync13 } from "fs";
1962
2091
  import { exec } from "child_process";
1963
2092
  function dashboardCommand(program2) {
1964
2093
  program2.command("dashboard").description("Open the JoWork companion dashboard in your browser").option("-p, --port <port>", "Port number (default: 18801)").option("--no-open", "Do not auto-open browser").action(async (opts) => {
1965
- if (!existsSync12(dbPath())) {
2094
+ if (!existsSync13(dbPath())) {
1966
2095
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1967
2096
  process.exit(1);
1968
2097
  }
1969
- const { startDashboard } = await import("./server-5GVWN2NB.js");
2098
+ const { startDashboard } = await import("./server-WEADPUST.js");
1970
2099
  const port = opts.port ? parseInt(opts.port, 10) : void 0;
1971
2100
  const dashboard = await startDashboard({ port });
1972
2101
  const url = `http://127.0.0.1:${dashboard.port}`;
@@ -1991,12 +2120,12 @@ function dashboardCommand(program2) {
1991
2120
  }
1992
2121
 
1993
2122
  // src/commands/log.ts
1994
- import { existsSync as existsSync13 } from "fs";
1995
- import { join as join7 } from "path";
2123
+ import { existsSync as existsSync14 } from "fs";
2124
+ import { join as join9 } from "path";
1996
2125
  function logCommand(program2) {
1997
2126
  program2.command("log").description("Show data sync history").option("-n, --limit <n>", "Number of entries", "20").action(async (opts) => {
1998
2127
  const repoDir = fileRepoDir();
1999
- if (!existsSync13(join7(repoDir, ".git"))) {
2128
+ if (!existsSync14(join9(repoDir, ".git"))) {
2000
2129
  console.log("No sync history yet. Run `jowork sync` first.");
2001
2130
  return;
2002
2131
  }
@@ -2014,12 +2143,12 @@ function logCommand(program2) {
2014
2143
  }
2015
2144
 
2016
2145
  // src/commands/push.ts
2017
- import { existsSync as existsSync14 } from "fs";
2018
- import { join as join9 } from "path";
2146
+ import { existsSync as existsSync15 } from "fs";
2147
+ import { join as join11 } from "path";
2019
2148
 
2020
2149
  // src/sync/push-back.ts
2021
- import { readFileSync as readFileSync6 } from "fs";
2022
- import { join as join8 } from "path";
2150
+ import { readFileSync as readFileSync7 } from "fs";
2151
+ import { join as join10 } from "path";
2023
2152
  function parseFilePath(filePath) {
2024
2153
  const githubMatch = filePath.match(
2025
2154
  /^github\/([^/]+)\/(issues|pulls)\/(\d+)\.md$/
@@ -2087,7 +2216,7 @@ async function pushChanges(repoDir) {
2087
2216
  for (const file of modifiedFiles) {
2088
2217
  const parsed = parseFilePath(file);
2089
2218
  if (!parsed) continue;
2090
- const content = readFileSync6(join8(repoDir, file), "utf-8");
2219
+ const content = readFileSync7(join10(repoDir, file), "utf-8");
2091
2220
  const fm = parseFrontmatter(content);
2092
2221
  const body = getBody(content);
2093
2222
  try {
@@ -2325,7 +2454,7 @@ async function pushLinear(parsed, fm, _body, file) {
2325
2454
  function pushCommand(program2) {
2326
2455
  program2.command("push").description("Push local file edits back to data sources (GitHub, GitLab, Linear)").option("--dry-run", "Show what would be pushed without making API calls").action(async (opts) => {
2327
2456
  const repoDir = fileRepoDir();
2328
- if (!existsSync14(join9(repoDir, ".git"))) {
2457
+ if (!existsSync15(join11(repoDir, ".git"))) {
2329
2458
  console.error(
2330
2459
  "No data repo found. Run `jowork sync` first to create it."
2331
2460
  );
@@ -2333,7 +2462,7 @@ function pushCommand(program2) {
2333
2462
  }
2334
2463
  console.log("Detecting local changes...");
2335
2464
  if (opts.dryRun) {
2336
- const { GitManager: GitManager2 } = await import("./git-manager-N35XSG4Y.js");
2465
+ const { GitManager: GitManager2 } = await import("./git-manager-RVWV2GSV.js");
2337
2466
  const gm = new GitManager2(repoDir);
2338
2467
  const status = await gm.getStatus();
2339
2468
  const modified = status.modified;
@@ -2400,8 +2529,8 @@ function configCommand(program2) {
2400
2529
  }
2401
2530
 
2402
2531
  // src/commands/setup-skill.ts
2403
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, readFileSync as readFileSync7 } from "fs";
2404
- import { join as join10 } from "path";
2532
+ import { existsSync as existsSync16, mkdirSync as mkdirSync4, writeFileSync as writeFileSync8, readFileSync as readFileSync8 } from "fs";
2533
+ import { join as join12 } from "path";
2405
2534
  import { execSync as execSync2 } from "child_process";
2406
2535
  var HOME3 = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
2407
2536
  var SKILL_CONTENT = `---
@@ -2499,34 +2628,34 @@ function setupSkillCommand(program2) {
2499
2628
  console.log(" JoWork Setup");
2500
2629
  console.log(" ============");
2501
2630
  console.log("");
2502
- const skillDir = join10(HOME3, ".claude", "skills", "jowork");
2631
+ const skillDir = join12(HOME3, ".claude", "skills", "jowork");
2503
2632
  console.log(" Installing skill to ~/.claude/skills/jowork/ ...");
2504
2633
  mkdirSync4(skillDir, { recursive: true });
2505
- writeFileSync7(join10(skillDir, "SKILL.md"), SKILL_CONTENT);
2506
- writeFileSync7(join10(skillDir, "VERSION"), "0.2.0\n");
2634
+ writeFileSync8(join12(skillDir, "SKILL.md"), SKILL_CONTENT);
2635
+ writeFileSync8(join12(skillDir, "VERSION"), "0.2.0\n");
2507
2636
  console.log(" \u2713 Skill installed (/jowork)");
2508
2637
  const jDir = joworkDir();
2509
- if (existsSync15(join10(jDir, "config.json"))) {
2638
+ if (existsSync16(join12(jDir, "config.json"))) {
2510
2639
  console.log(" \u2713 Database already initialized");
2511
2640
  } else {
2512
2641
  mkdirSync4(jDir, { recursive: true });
2513
- mkdirSync4(join10(jDir, "data"), { recursive: true });
2514
- mkdirSync4(join10(jDir, "logs"), { recursive: true });
2515
- mkdirSync4(join10(jDir, "credentials"), { recursive: true });
2642
+ mkdirSync4(join12(jDir, "data"), { recursive: true });
2643
+ mkdirSync4(join12(jDir, "logs"), { recursive: true });
2644
+ mkdirSync4(join12(jDir, "credentials"), { recursive: true });
2516
2645
  const db = new DbManager(dbPath());
2517
2646
  db.ensureTables();
2518
2647
  db.close();
2519
- writeFileSync7(
2520
- join10(jDir, "config.json"),
2648
+ writeFileSync8(
2649
+ join12(jDir, "config.json"),
2521
2650
  JSON.stringify({ version: "0.1.0", initialized: true, connectors: {} }, null, 2)
2522
2651
  );
2523
2652
  console.log(" \u2713 Database initialized");
2524
2653
  }
2525
- const claudeJson = join10(HOME3, ".claude.json");
2654
+ const claudeJson = join12(HOME3, ".claude.json");
2526
2655
  let mcpRegistered = false;
2527
- if (existsSync15(claudeJson)) {
2656
+ if (existsSync16(claudeJson)) {
2528
2657
  try {
2529
- const content = JSON.parse(readFileSync7(claudeJson, "utf-8"));
2658
+ const content = JSON.parse(readFileSync8(claudeJson, "utf-8"));
2530
2659
  if (content.mcpServers?.jowork) mcpRegistered = true;
2531
2660
  } catch {
2532
2661
  }
@@ -2539,7 +2668,7 @@ function setupSkillCommand(program2) {
2539
2668
  console.log(" \u2713 MCP server registered with Claude Code");
2540
2669
  } catch {
2541
2670
  try {
2542
- const cliPath = join10(__dirname, "..", "cli.js");
2671
+ const cliPath = join12(__dirname, "..", "cli.js");
2543
2672
  execSync2(`node "${cliPath}" register claude-code`, { stdio: "pipe" });
2544
2673
  console.log(" \u2713 MCP server registered with Claude Code");
2545
2674
  } catch {
@@ -2556,7 +2685,7 @@ function setupSkillCommand(program2) {
2556
2685
  }
2557
2686
  } catch {
2558
2687
  }
2559
- const { listCredentials: listCredentials2 } = await import("./credential-store-ZRZCSRPC.js");
2688
+ const { listCredentials: listCredentials2 } = await import("./credential-store-OS5ZY4OW.js");
2560
2689
  const connectedSources = listCredentials2();
2561
2690
  console.log("");
2562
2691
  console.log(" ============");
@@ -2611,6 +2740,49 @@ function setupSkillCommand(program2) {
2611
2740
  });
2612
2741
  }
2613
2742
 
2743
+ // src/commands/plugin.ts
2744
+ import { execFileSync as execFileSync2 } from "child_process";
2745
+ import { existsSync as existsSync17 } from "fs";
2746
+ import { join as join13 } from "path";
2747
+ function pluginCommand(program2) {
2748
+ const cmd = program2.command("plugin").description("Manage MCP plugin packages");
2749
+ cmd.command("list").description("List available and installed MCP plugins").action(() => {
2750
+ console.log("\nMCP Plugins\n");
2751
+ for (const [source, def] of Object.entries(PLUGIN_REGISTRY)) {
2752
+ const cred = loadCredential(def.connectorName);
2753
+ const installed = isInstalled(def.package);
2754
+ const status = !cred ? "\u25CB not connected" : installed ? "\u2713 installed" : "\u26A0 connected but not installed";
2755
+ console.log(` ${source.padEnd(10)} ${def.name.padEnd(16)} ${def.package}`);
2756
+ console.log(` ${"".padEnd(10)} ${status}`);
2757
+ }
2758
+ console.log("");
2759
+ });
2760
+ cmd.command("update").description("Update all installed MCP plugin packages").action(() => {
2761
+ const dir = pluginsDir();
2762
+ const pkgJsonPath = join13(dir, "package.json");
2763
+ if (!existsSync17(pkgJsonPath)) {
2764
+ console.log("No plugins installed.");
2765
+ return;
2766
+ }
2767
+ console.log("Updating MCP plugins...");
2768
+ try {
2769
+ const output = execFileSync2("npm", ["update"], {
2770
+ cwd: dir,
2771
+ encoding: "utf-8",
2772
+ timeout: 12e4
2773
+ });
2774
+ if (output.trim()) console.log(output.trim());
2775
+ console.log("\u2713 Plugins updated.");
2776
+ } catch (err) {
2777
+ console.error(`\u2717 Update failed: ${err}`);
2778
+ }
2779
+ });
2780
+ }
2781
+ function isInstalled(packageName) {
2782
+ const modulePath = join13(pluginsDir(), "node_modules", packageName);
2783
+ return existsSync17(modulePath);
2784
+ }
2785
+
2614
2786
  // src/cli.ts
2615
2787
  process.env["I18NEXT_DISABLE_BANNER"] = "1";
2616
2788
  var program = new Command();
@@ -2633,20 +2805,21 @@ logCommand(program);
2633
2805
  pushCommand(program);
2634
2806
  configCommand(program);
2635
2807
  setupSkillCommand(program);
2808
+ pluginCommand(program);
2636
2809
  program.action(async () => {
2637
- const { existsSync: existsSync16 } = await import("fs");
2638
- const { dbPath: dbPath2 } = await import("./paths-JXOMBYIT.js");
2639
- const { readConfig: readConfig2 } = await import("./config-AI6UIJJN.js");
2810
+ const { existsSync: existsSync18 } = await import("fs");
2811
+ const { dbPath: dbPath2 } = await import("./paths-FFRET6F7.js");
2812
+ const { readConfig: readConfig2 } = await import("./config-FH2XLN7A.js");
2640
2813
  const config = readConfig2();
2641
- if (!config.initialized || !existsSync16(dbPath2())) {
2814
+ if (!config.initialized || !existsSync18(dbPath2())) {
2642
2815
  if (process.stdin.isTTY) {
2643
- const { runSetupWizard } = await import("./setup-SYBQIL2O.js");
2816
+ const { runSetupWizard } = await import("./setup-S2S2CHB2.js");
2644
2817
  await runSetupWizard();
2645
2818
  } else {
2646
2819
  console.log("JoWork is not initialized. Run `jowork init` in an interactive terminal.");
2647
2820
  }
2648
2821
  } else {
2649
- const { listCredentials: listCredentials2 } = await import("./credential-store-ZRZCSRPC.js");
2822
+ const { listCredentials: listCredentials2 } = await import("./credential-store-OS5ZY4OW.js");
2650
2823
  const creds = listCredentials2();
2651
2824
  console.log(`JoWork v0.1.0 \u2014 ${creds.length} data source${creds.length !== 1 ? "s" : ""} connected`);
2652
2825
  console.log("");