jowork 0.2.4 → 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 (38) 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-QGHJ45PL.js +661 -0
  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 +338 -149
  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-IDQDPCEJ.js → setup-S2S2CHB2.js} +91 -32
  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-AIXKXEYS.js +0 -547
  33. package/dist/chunk-L5ZR7TSK.js +0 -82
  34. package/dist/chunk-LS2AJM5A.js +0 -163
  35. package/dist/chunk-QMOFQX7X.js +0 -612
  36. package/dist/chunk-YJWTKFWX.js +0 -451
  37. package/dist/github-SHWUFNYB.js +0 -10
  38. package/dist/sync-7V54N62M.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-AIXKXEYS.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-IDQDPCEJ.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,8 +584,11 @@ 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
- console.error("Error: JoWork not initialized. Run `jowork init` first.");
572
- process.exit(1);
587
+ const { writeConfig: writeConfig2 } = await import("./config-FH2XLN7A.js");
588
+ const db = new DbManager(dbPath());
589
+ db.ensureTables();
590
+ db.close();
591
+ writeConfig2({ version: "0.1.0", initialized: true, connectors: {} });
573
592
  }
574
593
  if (opts.daemon) {
575
594
  await startDaemon();
@@ -622,9 +641,9 @@ async function startDaemon() {
622
641
  const intervalMinutes = config.syncIntervalMinutes ?? 15;
623
642
  const cronExpr = `*/${intervalMinutes} * * * *`;
624
643
  daemonLog("info", "Daemon started", { pid: process.pid, intervalMinutes });
625
- await runSync();
644
+ await runSync2();
626
645
  const _syncJob = new Cron(cronExpr, async () => {
627
- await runSync();
646
+ await runSync2();
628
647
  });
629
648
  console.log(`Daemon started (PID ${process.pid})`);
630
649
  console.log(` Sync: every ${intervalMinutes} minutes`);
@@ -648,7 +667,7 @@ function shouldSkipSource(sqlite, source, config) {
648
667
  return false;
649
668
  }
650
669
  }
651
- async function runSync() {
670
+ async function runSync2() {
652
671
  const sources = listCredentials();
653
672
  if (sources.length === 0) {
654
673
  daemonLog("info", "No sources connected, skipping sync");
@@ -682,51 +701,63 @@ async function runSync() {
682
701
  try {
683
702
  switch (source) {
684
703
  case "feishu": {
685
- 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);
686
706
  syncResults.push({ source: "feishu", newObjects: r.newMessages, label: "messages" });
687
707
  try {
688
- const mr = await syncFeishuMeetings(sqlite, cred.data, daemonSyncLogger, fileWriter);
708
+ const mr = await syncFeishuMeetings(feishuCtx, cred.data, daemonSyncLogger);
689
709
  syncResults.push({ source: "feishu/meetings", newObjects: mr.newObjects, label: "events" });
690
710
  } catch (e) {
691
711
  daemonLog("warn", `Feishu meetings sync: ${e}`);
692
712
  }
693
713
  try {
694
- const dr = await syncFeishuDocs(sqlite, cred.data, daemonSyncLogger, fileWriter);
714
+ const dr = await syncFeishuDocs(feishuCtx, cred.data, daemonSyncLogger);
695
715
  syncResults.push({ source: "feishu/docs", newObjects: dr.newObjects, label: "docs" });
696
716
  } catch (e) {
697
717
  daemonLog("warn", `Feishu docs sync: ${e}`);
698
718
  }
699
719
  try {
700
- const ar = await syncFeishuApprovals(sqlite, cred.data, daemonSyncLogger, fileWriter);
720
+ const ar = await syncFeishuApprovals(feishuCtx, cred.data, daemonSyncLogger);
701
721
  syncResults.push({ source: "feishu/approvals", newObjects: ar.newObjects, label: "approvals" });
702
722
  } catch (e) {
703
723
  daemonLog("warn", `Feishu approvals sync: ${e}`);
704
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
+ }
705
731
  break;
706
732
  }
707
733
  case "github": {
708
- const r = await syncGitHub(sqlite, cred.data, daemonSyncLogger, fileWriter);
709
- 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 });
710
737
  break;
711
738
  }
712
739
  case "gitlab": {
713
- const r = await syncGitLab(sqlite, cred.data, daemonSyncLogger, fileWriter);
714
- 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 });
715
743
  break;
716
744
  }
717
745
  case "linear": {
718
- const r = await syncLinear(sqlite, cred.data, daemonSyncLogger, fileWriter);
719
- 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" });
720
749
  break;
721
750
  }
722
751
  case "posthog": {
723
- const r = await syncPostHog(sqlite, cred.data, daemonSyncLogger, fileWriter);
724
- 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 });
725
755
  break;
726
756
  }
727
757
  case "firebase": {
728
- const r = await syncFirebase(sqlite, cred.data, daemonSyncLogger, fileWriter);
729
- 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" });
730
761
  break;
731
762
  }
732
763
  default:
@@ -783,7 +814,16 @@ async function runSync() {
783
814
  // src/commands/register.ts
784
815
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
785
816
  import { join as join2 } from "path";
817
+ import { execSync } from "child_process";
786
818
  var HOME = process.env["HOME"] ?? "";
819
+ function getMcpCommand() {
820
+ try {
821
+ execSync("which jowork", { stdio: "ignore" });
822
+ return { command: "jowork", args: ["serve"] };
823
+ } catch {
824
+ return { command: "npx", args: ["-y", "jowork@latest", "serve"] };
825
+ }
826
+ }
787
827
  function registerCommand(program2) {
788
828
  program2.command("register").description("Register JoWork MCP server with an AI agent engine").argument("<engine>", "Engine to register with: claude-code, codex, openclaw").action(async (engine) => {
789
829
  switch (engine) {
@@ -820,9 +860,10 @@ function registerClaudeCode() {
820
860
  }
821
861
  }
822
862
  if (!config.mcpServers) config.mcpServers = {};
863
+ const mcp = getMcpCommand();
823
864
  config.mcpServers["jowork"] = {
824
- command: "jowork",
825
- args: ["serve"],
865
+ command: mcp.command,
866
+ args: mcp.args,
826
867
  env: { JOWORK_ENGINE: "claude-code" }
827
868
  };
828
869
  writeFileSync2(configPath2, JSON.stringify(config, null, 2));
@@ -849,9 +890,10 @@ function registerOpenClaw() {
849
890
  }
850
891
  }
851
892
  if (!config["mcpServers"]) config["mcpServers"] = {};
893
+ const mcp = getMcpCommand();
852
894
  config["mcpServers"]["jowork"] = {
853
- command: "jowork",
854
- args: ["serve"],
895
+ command: mcp.command,
896
+ args: mcp.args,
855
897
  env: { JOWORK_ENGINE: "openclaw" }
856
898
  };
857
899
  writeFileSync2(configPath2, JSON.stringify(config, null, 2));
@@ -876,10 +918,12 @@ function registerCodex() {
876
918
  console.log("\u2713 JoWork already registered with Codex");
877
919
  return;
878
920
  }
921
+ const mcp = getMcpCommand();
922
+ const argsToml = mcp.args.map((a) => `"${a}"`).join(", ");
879
923
  const mcpEntry = `
880
924
  [mcp_servers.jowork]
881
- command = "jowork"
882
- args = ["serve"]
925
+ command = "${mcp.command}"
926
+ args = [${argsToml}]
883
927
 
884
928
  [mcp_servers.jowork.env]
885
929
  JOWORK_ENGINE = "codex"
@@ -938,9 +982,8 @@ function statusCommand(program2) {
938
982
  let sourceRows = [];
939
983
  try {
940
984
  sourceRows = sqlite.prepare(`
941
- 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
942
986
  FROM objects o
943
- LEFT JOIN object_bodies ob ON ob.object_id = o.id
944
987
  GROUP BY o.source
945
988
  ORDER BY o.source
946
989
  `).all();
@@ -1154,6 +1197,46 @@ function exportCommand(program2) {
1154
1197
  }
1155
1198
 
1156
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
+ }
1157
1240
  function connectCommand(program2) {
1158
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) => {
1159
1242
  switch (source) {
@@ -1215,6 +1298,7 @@ async function connectFeishu(opts) {
1215
1298
  const data = await res.json();
1216
1299
  if (data.code !== 0) {
1217
1300
  console.error(`Feishu auth failed: ${data.msg}`);
1301
+ console.error(" Hint: App ID/Secret invalid. Check at https://open.feishu.cn/app");
1218
1302
  process.exit(1);
1219
1303
  }
1220
1304
  console.log("\u2713 Feishu credentials verified");
@@ -1228,7 +1312,9 @@ async function connectFeishu(opts) {
1228
1312
  createdAt: Date.now(),
1229
1313
  updatedAt: Date.now()
1230
1314
  });
1231
- 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");
1232
1318
  }
1233
1319
  async function connectGitHub(opts) {
1234
1320
  let token = opts.token;
@@ -1244,6 +1330,22 @@ async function connectGitHub(opts) {
1244
1330
  console.error("Error: GitHub token is required.");
1245
1331
  process.exit(1);
1246
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
+ }
1247
1349
  saveCredential("github", {
1248
1350
  type: "github",
1249
1351
  data: { token },
@@ -1251,6 +1353,8 @@ async function connectGitHub(opts) {
1251
1353
  updatedAt: Date.now()
1252
1354
  });
1253
1355
  console.log("\u2713 GitHub connected.");
1356
+ autoInstallPlugin("github");
1357
+ await autoSync("github");
1254
1358
  }
1255
1359
  async function connectGitLab(opts) {
1256
1360
  let token = opts.token;
@@ -1278,6 +1382,7 @@ async function connectGitLab(opts) {
1278
1382
  });
1279
1383
  if (!res.ok) {
1280
1384
  console.error(`GitLab auth failed: HTTP ${res.status}`);
1385
+ console.error(" Hint: Token invalid. Check at https://gitlab.com/-/profile/personal_access_tokens");
1281
1386
  process.exit(1);
1282
1387
  }
1283
1388
  const user = await res.json();
@@ -1294,7 +1399,9 @@ async function connectGitLab(opts) {
1294
1399
  createdAt: Date.now(),
1295
1400
  updatedAt: Date.now()
1296
1401
  });
1297
- 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");
1298
1405
  }
1299
1406
  async function connectLinear(opts) {
1300
1407
  let apiKey = opts.apiKey;
@@ -1322,11 +1429,13 @@ async function connectLinear(opts) {
1322
1429
  });
1323
1430
  if (!res.ok) {
1324
1431
  console.error(`Linear auth failed: HTTP ${res.status}`);
1432
+ console.error(" Hint: API key invalid. Get one at https://linear.app/settings/api");
1325
1433
  process.exit(1);
1326
1434
  }
1327
1435
  const data = await res.json();
1328
1436
  if (data.errors?.length) {
1329
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");
1330
1439
  process.exit(1);
1331
1440
  }
1332
1441
  console.log(`\u2713 Linear credentials verified (user: ${data.data?.viewer?.name})`);
@@ -1340,7 +1449,9 @@ async function connectLinear(opts) {
1340
1449
  createdAt: Date.now(),
1341
1450
  updatedAt: Date.now()
1342
1451
  });
1343
- 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");
1344
1455
  }
1345
1456
  async function connectPostHog(opts) {
1346
1457
  let apiKey = opts.apiKey;
@@ -1363,7 +1474,11 @@ async function connectPostHog(opts) {
1363
1474
  const res = await fetch(`${host}/api/projects/${projectId}/`, {
1364
1475
  headers: { Authorization: `Bearer ${apiKey}` }
1365
1476
  });
1366
- 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
+ }
1367
1482
  console.log("\u2713 PostHog credentials verified");
1368
1483
  } catch (err) {
1369
1484
  console.error(`PostHog verification failed: ${err}`);
@@ -1375,7 +1490,9 @@ async function connectPostHog(opts) {
1375
1490
  createdAt: Date.now(),
1376
1491
  updatedAt: Date.now()
1377
1492
  });
1378
- 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");
1379
1496
  }
1380
1497
  async function connectSlack(opts) {
1381
1498
  let webhookUrl = opts.webhookUrl;
@@ -1445,22 +1562,40 @@ async function connectFirebase(opts) {
1445
1562
  console.error("Error: API key required.");
1446
1563
  process.exit(1);
1447
1564
  }
1448
- const data = { projectId, apiKey };
1449
- 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;
1450
1583
  saveCredential("firebase", {
1451
1584
  type: "firebase",
1452
- data,
1585
+ data: credData,
1453
1586
  createdAt: Date.now(),
1454
1587
  updatedAt: Date.now()
1455
1588
  });
1456
- 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");
1457
1592
  }
1458
1593
 
1459
1594
  // src/commands/search.ts
1460
- import { existsSync as existsSync7 } from "fs";
1595
+ import { existsSync as existsSync8 } from "fs";
1461
1596
  function searchCommand(program2) {
1462
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) => {
1463
- if (!existsSync7(dbPath())) {
1598
+ if (!existsSync8(dbPath())) {
1464
1599
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1465
1600
  process.exit(1);
1466
1601
  }
@@ -1611,8 +1746,8 @@ function goalCommand(program2) {
1611
1746
  }
1612
1747
 
1613
1748
  // src/commands/install-service.ts
1614
- import { writeFileSync as writeFileSync4, existsSync as existsSync8, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
1615
- 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";
1616
1751
  var HOME2 = process.env["HOME"] ?? "";
1617
1752
  function installServiceCommand(program2) {
1618
1753
  program2.command("install-service").description("Generate system service config (macOS LaunchAgent / Linux systemd)").option("--uninstall", "Remove the service").action(async (opts) => {
@@ -1626,9 +1761,9 @@ function installServiceCommand(program2) {
1626
1761
  });
1627
1762
  }
1628
1763
  function installLaunchAgent(uninstall) {
1629
- const plistPath = join5(HOME2, "Library", "LaunchAgents", "work.jowork.daemon.plist");
1764
+ const plistPath = join6(HOME2, "Library", "LaunchAgents", "work.jowork.daemon.plist");
1630
1765
  if (uninstall) {
1631
- if (existsSync8(plistPath)) {
1766
+ if (existsSync9(plistPath)) {
1632
1767
  unlinkSync2(plistPath);
1633
1768
  console.log(`\u2713 Removed ${plistPath}`);
1634
1769
  console.log(" Run: launchctl unload work.jowork.daemon");
@@ -1639,7 +1774,7 @@ function installLaunchAgent(uninstall) {
1639
1774
  }
1640
1775
  const joworkBin = process.argv[0];
1641
1776
  const joworkScript = process.argv[1];
1642
- const logsPath = join5(joworkDir(), "logs");
1777
+ const logsPath = join6(joworkDir(), "logs");
1643
1778
  mkdirSync3(logsPath, { recursive: true });
1644
1779
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
1645
1780
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -1671,7 +1806,7 @@ function installLaunchAgent(uninstall) {
1671
1806
  </dict>
1672
1807
  </dict>
1673
1808
  </plist>`;
1674
- writeFileSync4(plistPath, plist);
1809
+ writeFileSync5(plistPath, plist);
1675
1810
  console.log(`\u2713 LaunchAgent written to ${plistPath}`);
1676
1811
  console.log("");
1677
1812
  console.log("To start:");
@@ -1682,10 +1817,10 @@ function installLaunchAgent(uninstall) {
1682
1817
  console.log(" jowork install-service --uninstall");
1683
1818
  }
1684
1819
  function installSystemd(uninstall) {
1685
- const serviceDir = join5(HOME2, ".config", "systemd", "user");
1686
- const servicePath = join5(serviceDir, "jowork.service");
1820
+ const serviceDir = join6(HOME2, ".config", "systemd", "user");
1821
+ const servicePath = join6(serviceDir, "jowork.service");
1687
1822
  if (uninstall) {
1688
- if (existsSync8(servicePath)) {
1823
+ if (existsSync9(servicePath)) {
1689
1824
  unlinkSync2(servicePath);
1690
1825
  console.log(`\u2713 Removed ${servicePath}`);
1691
1826
  console.log(" Run: systemctl --user disable jowork");
@@ -1709,7 +1844,7 @@ RestartSec=10
1709
1844
  [Install]
1710
1845
  WantedBy=default.target
1711
1846
  `;
1712
- writeFileSync4(servicePath, service);
1847
+ writeFileSync5(servicePath, service);
1713
1848
  console.log(`\u2713 Systemd service written to ${servicePath}`);
1714
1849
  console.log("");
1715
1850
  console.log("To start:");
@@ -1718,10 +1853,11 @@ WantedBy=default.target
1718
1853
  }
1719
1854
 
1720
1855
  // src/commands/gc.ts
1721
- 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";
1722
1858
  function gcCommand(program2) {
1723
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) => {
1724
- if (!existsSync9(dbPath())) {
1860
+ if (!existsSync10(dbPath())) {
1725
1861
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1726
1862
  process.exit(1);
1727
1863
  }
@@ -1733,31 +1869,40 @@ function gcCommand(program2) {
1733
1869
  const sqlite = db.getSqlite();
1734
1870
  const sizeBefore = statSync2(dbPath()).size;
1735
1871
  console.log(`Database size: ${(sizeBefore / 1024 / 1024).toFixed(2)} MB`);
1736
- let deletedBodies = 0;
1737
1872
  let deletedObjects = 0;
1873
+ let deletedFiles = 0;
1738
1874
  if (retentionDays > 0) {
1739
1875
  const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
1740
1876
  if (dryRun) {
1741
1877
  const count = sqlite.prepare(
1742
- `SELECT COUNT(*) as c FROM object_bodies ob
1743
- JOIN objects o ON o.id = ob.object_id
1744
- WHERE o.created_at < ?`
1878
+ `SELECT COUNT(*) as c FROM objects WHERE created_at < ?`
1745
1879
  ).get(cutoff).c;
1746
- console.log(`Would delete ${count} object bodies older than ${retentionDays} days`);
1880
+ console.log(`Would delete ${count} objects older than ${retentionDays} days`);
1747
1881
  } else {
1748
- const result = sqlite.prepare(
1749
- `DELETE FROM object_bodies WHERE object_id IN (
1750
- SELECT id FROM objects WHERE created_at < ?
1751
- )`
1752
- ).run(cutoff);
1753
- deletedBodies = result.changes;
1754
- const objResult = sqlite.prepare(
1755
- `DELETE FROM objects WHERE created_at < ?
1756
- 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 < ?
1757
1885
  AND id NOT IN (SELECT source_object_id FROM object_links)`
1758
- ).run(cutoff);
1759
- deletedObjects = objResult.changes;
1760
- 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`);
1761
1906
  }
1762
1907
  } else {
1763
1908
  console.log("Retention: keep all (set retentionDays in config or use --retention-days)");
@@ -1769,8 +1914,8 @@ function gcCommand(program2) {
1769
1914
  const saved = sizeBefore - sizeAfter;
1770
1915
  console.log(`VACUUM complete: ${(sizeAfter / 1024 / 1024).toFixed(2)} MB (saved ${(saved / 1024).toFixed(0)} KB)`);
1771
1916
  logInfo("gc", "Garbage collection complete", {
1772
- deletedBodies,
1773
1917
  deletedObjects,
1918
+ deletedFiles,
1774
1919
  sizeBefore,
1775
1920
  sizeAfter,
1776
1921
  savedBytes: saved
@@ -1788,18 +1933,18 @@ function gcCommand(program2) {
1788
1933
  }
1789
1934
 
1790
1935
  // src/commands/device-sync.ts
1791
- 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";
1792
1937
 
1793
1938
  // src/sync/device-sync.ts
1794
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
1795
- 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";
1796
1941
  function getDeviceId() {
1797
- const idPath = join6(joworkDir(), "device-id");
1798
- if (existsSync10(idPath)) {
1799
- return readFileSync4(idPath, "utf-8").trim();
1942
+ const idPath = join8(joworkDir(), "device-id");
1943
+ if (existsSync11(idPath)) {
1944
+ return readFileSync5(idPath, "utf-8").trim();
1800
1945
  }
1801
1946
  const id = createId("dev");
1802
- writeFileSync5(idPath, id);
1947
+ writeFileSync6(idPath, id);
1803
1948
  return id;
1804
1949
  }
1805
1950
  function getUnsyncedChanges(sqlite, limit = 100) {
@@ -1894,7 +2039,7 @@ function importSyncBundle(sqlite, bundleJson) {
1894
2039
  function deviceSyncCommand(program2) {
1895
2040
  const sync = program2.command("device-sync").description("Sync data between devices");
1896
2041
  sync.command("status").description("Show device sync status").action(async () => {
1897
- if (!existsSync11(dbPath())) {
2042
+ if (!existsSync12(dbPath())) {
1898
2043
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1899
2044
  process.exit(1);
1900
2045
  }
@@ -1909,7 +2054,7 @@ function deviceSyncCommand(program2) {
1909
2054
  db.close();
1910
2055
  });
1911
2056
  sync.command("export").description("Export unsynced changes to a file").argument("<file>", "Output file path").action(async (file) => {
1912
- if (!existsSync11(dbPath())) {
2057
+ if (!existsSync12(dbPath())) {
1913
2058
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1914
2059
  process.exit(1);
1915
2060
  }
@@ -1917,24 +2062,24 @@ function deviceSyncCommand(program2) {
1917
2062
  db.ensureTables();
1918
2063
  const sqlite = db.getSqlite();
1919
2064
  const bundle = exportSyncBundle(sqlite);
1920
- writeFileSync6(file, bundle);
2065
+ writeFileSync7(file, bundle);
1921
2066
  const parsed = JSON.parse(bundle);
1922
2067
  console.log(`Exported ${parsed.changes.length} changes to ${file}`);
1923
2068
  markSynced(sqlite, parsed.changes.map((c) => c.id));
1924
2069
  db.close();
1925
2070
  });
1926
2071
  sync.command("import").description("Import changes from another device").argument("<file>", "Input file path").action(async (file) => {
1927
- if (!existsSync11(file)) {
2072
+ if (!existsSync12(file)) {
1928
2073
  console.error(`File not found: ${file}`);
1929
2074
  process.exit(1);
1930
2075
  }
1931
- if (!existsSync11(dbPath())) {
2076
+ if (!existsSync12(dbPath())) {
1932
2077
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1933
2078
  process.exit(1);
1934
2079
  }
1935
2080
  const db = new DbManager(dbPath());
1936
2081
  db.ensureTables();
1937
- const bundleJson = readFileSync5(file, "utf-8");
2082
+ const bundleJson = readFileSync6(file, "utf-8");
1938
2083
  const result = importSyncBundle(db.getSqlite(), bundleJson);
1939
2084
  console.log(`Applied ${result.applied}, conflicts ${result.conflicts}, skipped ${result.skipped}`);
1940
2085
  db.close();
@@ -1942,15 +2087,15 @@ function deviceSyncCommand(program2) {
1942
2087
  }
1943
2088
 
1944
2089
  // src/commands/dashboard.ts
1945
- import { existsSync as existsSync12 } from "fs";
2090
+ import { existsSync as existsSync13 } from "fs";
1946
2091
  import { exec } from "child_process";
1947
2092
  function dashboardCommand(program2) {
1948
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) => {
1949
- if (!existsSync12(dbPath())) {
2094
+ if (!existsSync13(dbPath())) {
1950
2095
  console.error("Error: JoWork not initialized. Run `jowork init` first.");
1951
2096
  process.exit(1);
1952
2097
  }
1953
- const { startDashboard } = await import("./server-5GVWN2NB.js");
2098
+ const { startDashboard } = await import("./server-WEADPUST.js");
1954
2099
  const port = opts.port ? parseInt(opts.port, 10) : void 0;
1955
2100
  const dashboard = await startDashboard({ port });
1956
2101
  const url = `http://127.0.0.1:${dashboard.port}`;
@@ -1975,12 +2120,12 @@ function dashboardCommand(program2) {
1975
2120
  }
1976
2121
 
1977
2122
  // src/commands/log.ts
1978
- import { existsSync as existsSync13 } from "fs";
1979
- import { join as join7 } from "path";
2123
+ import { existsSync as existsSync14 } from "fs";
2124
+ import { join as join9 } from "path";
1980
2125
  function logCommand(program2) {
1981
2126
  program2.command("log").description("Show data sync history").option("-n, --limit <n>", "Number of entries", "20").action(async (opts) => {
1982
2127
  const repoDir = fileRepoDir();
1983
- if (!existsSync13(join7(repoDir, ".git"))) {
2128
+ if (!existsSync14(join9(repoDir, ".git"))) {
1984
2129
  console.log("No sync history yet. Run `jowork sync` first.");
1985
2130
  return;
1986
2131
  }
@@ -1998,12 +2143,12 @@ function logCommand(program2) {
1998
2143
  }
1999
2144
 
2000
2145
  // src/commands/push.ts
2001
- import { existsSync as existsSync14 } from "fs";
2002
- import { join as join9 } from "path";
2146
+ import { existsSync as existsSync15 } from "fs";
2147
+ import { join as join11 } from "path";
2003
2148
 
2004
2149
  // src/sync/push-back.ts
2005
- import { readFileSync as readFileSync6 } from "fs";
2006
- import { join as join8 } from "path";
2150
+ import { readFileSync as readFileSync7 } from "fs";
2151
+ import { join as join10 } from "path";
2007
2152
  function parseFilePath(filePath) {
2008
2153
  const githubMatch = filePath.match(
2009
2154
  /^github\/([^/]+)\/(issues|pulls)\/(\d+)\.md$/
@@ -2071,7 +2216,7 @@ async function pushChanges(repoDir) {
2071
2216
  for (const file of modifiedFiles) {
2072
2217
  const parsed = parseFilePath(file);
2073
2218
  if (!parsed) continue;
2074
- const content = readFileSync6(join8(repoDir, file), "utf-8");
2219
+ const content = readFileSync7(join10(repoDir, file), "utf-8");
2075
2220
  const fm = parseFrontmatter(content);
2076
2221
  const body = getBody(content);
2077
2222
  try {
@@ -2309,7 +2454,7 @@ async function pushLinear(parsed, fm, _body, file) {
2309
2454
  function pushCommand(program2) {
2310
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) => {
2311
2456
  const repoDir = fileRepoDir();
2312
- if (!existsSync14(join9(repoDir, ".git"))) {
2457
+ if (!existsSync15(join11(repoDir, ".git"))) {
2313
2458
  console.error(
2314
2459
  "No data repo found. Run `jowork sync` first to create it."
2315
2460
  );
@@ -2317,7 +2462,7 @@ function pushCommand(program2) {
2317
2462
  }
2318
2463
  console.log("Detecting local changes...");
2319
2464
  if (opts.dryRun) {
2320
- const { GitManager: GitManager2 } = await import("./git-manager-N35XSG4Y.js");
2465
+ const { GitManager: GitManager2 } = await import("./git-manager-RVWV2GSV.js");
2321
2466
  const gm = new GitManager2(repoDir);
2322
2467
  const status = await gm.getStatus();
2323
2468
  const modified = status.modified;
@@ -2384,9 +2529,9 @@ function configCommand(program2) {
2384
2529
  }
2385
2530
 
2386
2531
  // src/commands/setup-skill.ts
2387
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, readFileSync as readFileSync7 } from "fs";
2388
- import { join as join10 } from "path";
2389
- import { execSync } from "child_process";
2532
+ import { existsSync as existsSync16, mkdirSync as mkdirSync4, writeFileSync as writeFileSync8, readFileSync as readFileSync8 } from "fs";
2533
+ import { join as join12 } from "path";
2534
+ import { execSync as execSync2 } from "child_process";
2390
2535
  var HOME3 = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "";
2391
2536
  var SKILL_CONTENT = `---
2392
2537
  name: jowork
@@ -2483,34 +2628,34 @@ function setupSkillCommand(program2) {
2483
2628
  console.log(" JoWork Setup");
2484
2629
  console.log(" ============");
2485
2630
  console.log("");
2486
- const skillDir = join10(HOME3, ".claude", "skills", "jowork");
2631
+ const skillDir = join12(HOME3, ".claude", "skills", "jowork");
2487
2632
  console.log(" Installing skill to ~/.claude/skills/jowork/ ...");
2488
2633
  mkdirSync4(skillDir, { recursive: true });
2489
- writeFileSync7(join10(skillDir, "SKILL.md"), SKILL_CONTENT);
2490
- 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");
2491
2636
  console.log(" \u2713 Skill installed (/jowork)");
2492
2637
  const jDir = joworkDir();
2493
- if (existsSync15(join10(jDir, "config.json"))) {
2638
+ if (existsSync16(join12(jDir, "config.json"))) {
2494
2639
  console.log(" \u2713 Database already initialized");
2495
2640
  } else {
2496
2641
  mkdirSync4(jDir, { recursive: true });
2497
- mkdirSync4(join10(jDir, "data"), { recursive: true });
2498
- mkdirSync4(join10(jDir, "logs"), { recursive: true });
2499
- 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 });
2500
2645
  const db = new DbManager(dbPath());
2501
2646
  db.ensureTables();
2502
2647
  db.close();
2503
- writeFileSync7(
2504
- join10(jDir, "config.json"),
2648
+ writeFileSync8(
2649
+ join12(jDir, "config.json"),
2505
2650
  JSON.stringify({ version: "0.1.0", initialized: true, connectors: {} }, null, 2)
2506
2651
  );
2507
2652
  console.log(" \u2713 Database initialized");
2508
2653
  }
2509
- const claudeJson = join10(HOME3, ".claude.json");
2654
+ const claudeJson = join12(HOME3, ".claude.json");
2510
2655
  let mcpRegistered = false;
2511
- if (existsSync15(claudeJson)) {
2656
+ if (existsSync16(claudeJson)) {
2512
2657
  try {
2513
- const content = JSON.parse(readFileSync7(claudeJson, "utf-8"));
2658
+ const content = JSON.parse(readFileSync8(claudeJson, "utf-8"));
2514
2659
  if (content.mcpServers?.jowork) mcpRegistered = true;
2515
2660
  } catch {
2516
2661
  }
@@ -2519,12 +2664,12 @@ function setupSkillCommand(program2) {
2519
2664
  console.log(" \u2713 MCP server registered with Claude Code");
2520
2665
  } else {
2521
2666
  try {
2522
- execSync("jowork register claude-code", { stdio: "pipe" });
2667
+ execSync2("jowork register claude-code", { stdio: "pipe" });
2523
2668
  console.log(" \u2713 MCP server registered with Claude Code");
2524
2669
  } catch {
2525
2670
  try {
2526
- const cliPath = join10(__dirname, "..", "cli.js");
2527
- execSync(`node "${cliPath}" register claude-code`, { stdio: "pipe" });
2671
+ const cliPath = join12(__dirname, "..", "cli.js");
2672
+ execSync2(`node "${cliPath}" register claude-code`, { stdio: "pipe" });
2528
2673
  console.log(" \u2713 MCP server registered with Claude Code");
2529
2674
  } catch {
2530
2675
  console.log(" \u26A0 Could not auto-register. Run: jowork register claude-code");
@@ -2532,15 +2677,15 @@ function setupSkillCommand(program2) {
2532
2677
  }
2533
2678
  }
2534
2679
  try {
2535
- execSync("which codex", { stdio: "pipe" });
2680
+ execSync2("which codex", { stdio: "pipe" });
2536
2681
  try {
2537
- execSync("jowork register codex", { stdio: "pipe" });
2682
+ execSync2("jowork register codex", { stdio: "pipe" });
2538
2683
  console.log(" \u2713 Registered with Codex");
2539
2684
  } catch {
2540
2685
  }
2541
2686
  } catch {
2542
2687
  }
2543
- const { listCredentials: listCredentials2 } = await import("./credential-store-ZRZCSRPC.js");
2688
+ const { listCredentials: listCredentials2 } = await import("./credential-store-OS5ZY4OW.js");
2544
2689
  const connectedSources = listCredentials2();
2545
2690
  console.log("");
2546
2691
  console.log(" ============");
@@ -2573,13 +2718,13 @@ function setupSkillCommand(program2) {
2573
2718
  console.log(" Detected GITHUB_PERSONAL_ACCESS_TOKEN in environment.");
2574
2719
  console.log(" Connecting GitHub automatically...");
2575
2720
  try {
2576
- execSync('jowork connect github --token "$GITHUB_PERSONAL_ACCESS_TOKEN"', {
2721
+ execSync2('jowork connect github --token "$GITHUB_PERSONAL_ACCESS_TOKEN"', {
2577
2722
  stdio: "pipe",
2578
2723
  env: process.env
2579
2724
  });
2580
2725
  console.log(" \u2713 GitHub connected! Running initial sync...");
2581
2726
  try {
2582
- const output = execSync("jowork sync --source github", {
2727
+ const output = execSync2("jowork sync --source github", {
2583
2728
  encoding: "utf-8",
2584
2729
  timeout: 3e4,
2585
2730
  env: process.env
@@ -2595,6 +2740,49 @@ function setupSkillCommand(program2) {
2595
2740
  });
2596
2741
  }
2597
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
+
2598
2786
  // src/cli.ts
2599
2787
  process.env["I18NEXT_DISABLE_BANNER"] = "1";
2600
2788
  var program = new Command();
@@ -2617,20 +2805,21 @@ logCommand(program);
2617
2805
  pushCommand(program);
2618
2806
  configCommand(program);
2619
2807
  setupSkillCommand(program);
2808
+ pluginCommand(program);
2620
2809
  program.action(async () => {
2621
- const { existsSync: existsSync16 } = await import("fs");
2622
- const { dbPath: dbPath2 } = await import("./paths-JXOMBYIT.js");
2623
- 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");
2624
2813
  const config = readConfig2();
2625
- if (!config.initialized || !existsSync16(dbPath2())) {
2814
+ if (!config.initialized || !existsSync18(dbPath2())) {
2626
2815
  if (process.stdin.isTTY) {
2627
- const { runSetupWizard } = await import("./setup-IDQDPCEJ.js");
2816
+ const { runSetupWizard } = await import("./setup-S2S2CHB2.js");
2628
2817
  await runSetupWizard();
2629
2818
  } else {
2630
2819
  console.log("JoWork is not initialized. Run `jowork init` in an interactive terminal.");
2631
2820
  }
2632
2821
  } else {
2633
- const { listCredentials: listCredentials2 } = await import("./credential-store-ZRZCSRPC.js");
2822
+ const { listCredentials: listCredentials2 } = await import("./credential-store-OS5ZY4OW.js");
2634
2823
  const creds = listCredentials2();
2635
2824
  console.log(`JoWork v0.1.0 \u2014 ${creds.length} data source${creds.length !== 1 ? "s" : ""} connected`);
2636
2825
  console.log("");