jowork 0.3.6 → 0.3.8

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.
@@ -317,8 +317,8 @@ var FileWriter = class {
317
317
  existingHeaders = [...existing.matchAll(/^## .+/gm)].map((m) => m[0]);
318
318
  }
319
319
  const newMessages = messages.filter((m) => {
320
- const header2 = `## ${m.time} \u2014 ${m.sender}`;
321
- return !existingHeaders.includes(header2);
320
+ const header = `## ${m.time} \u2014 ${m.sender}`;
321
+ return !existingHeaders.includes(header);
322
322
  });
323
323
  if (newMessages.length === 0) return filePath;
324
324
  if (!existsSync(absPath)) {
@@ -381,69 +381,6 @@ ${sanitizeContent(m.content)}`).join("\n");
381
381
  };
382
382
 
383
383
  // src/commands/sync.ts
384
- var isTTY = process.stdout.isTTY;
385
- var c = {
386
- reset: isTTY ? "\x1B[0m" : "",
387
- bold: isTTY ? "\x1B[1m" : "",
388
- dim: isTTY ? "\x1B[2m" : "",
389
- green: isTTY ? "\x1B[32m" : "",
390
- yellow: isTTY ? "\x1B[33m" : "",
391
- red: isTTY ? "\x1B[31m" : "",
392
- cyan: isTTY ? "\x1B[36m" : "",
393
- gray: isTTY ? "\x1B[90m" : "",
394
- white: isTTY ? "\x1B[37m" : "",
395
- bgGreen: isTTY ? "\x1B[42m" : "",
396
- bgRed: isTTY ? "\x1B[41m" : "",
397
- bgYellow: isTTY ? "\x1B[43m" : ""
398
- };
399
- var icon = {
400
- ok: `${c.green}\u2713${c.reset}`,
401
- warn: `${c.yellow}\u26A0${c.reset}`,
402
- fail: `${c.red}\u2717${c.reset}`,
403
- skip: `${c.gray}\u25CB${c.reset}`,
404
- sync: `${c.cyan}\u21BB${c.reset}`,
405
- link: `${c.cyan}\u27E1${c.reset}`,
406
- git: `${c.gray}\u2387${c.reset}`
407
- };
408
- function header(text) {
409
- console.log("");
410
- console.log(` ${c.bold}${text}${c.reset}`);
411
- console.log(` ${c.dim}${"\u2500".repeat(Math.min(text.length + 4, 50))}${c.reset}`);
412
- }
413
- function progressBar(current, total, width = 20) {
414
- const pct = total > 0 ? current / total : 0;
415
- const filled = Math.round(pct * width);
416
- const empty = width - filled;
417
- const bar = `${c.green}${"\u2588".repeat(filled)}${c.gray}${"\u2591".repeat(empty)}${c.reset}`;
418
- return `${bar} ${c.dim}${current}/${total}${c.reset}`;
419
- }
420
- function sourceLabel(name) {
421
- const colors = {
422
- feishu: c.cyan,
423
- github: c.white,
424
- gitlab: c.yellow,
425
- linear: c.cyan,
426
- posthog: c.red,
427
- firebase: c.yellow
428
- };
429
- return `${colors[name] ?? c.white}${c.bold}${name}${c.reset}`;
430
- }
431
- function resultLine(ok, msg) {
432
- clearProgress();
433
- console.log(` ${ok ? icon.ok : icon.warn} ${msg}`);
434
- }
435
- function progressLine(msg) {
436
- if (!isTTY) return;
437
- process.stdout.write(`\r ${c.dim}\u22EF ${msg}${c.reset}\x1B[K`);
438
- }
439
- function clearProgress() {
440
- if (!isTTY) return;
441
- process.stdout.write("\r\x1B[K");
442
- }
443
- function elapsed(start) {
444
- const ms = Date.now() - start;
445
- return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
446
- }
447
384
  function getSyncErrorHint(source, error) {
448
385
  const is401 = /401|unauthorized|auth.*fail/i.test(error);
449
386
  const is403 = /403|forbidden/i.test(error);
@@ -473,7 +410,6 @@ async function runSync(sources) {
473
410
  db.ensureTables();
474
411
  const fileWriter = new FileWriter();
475
412
  const syncResults = [];
476
- const t0 = Date.now();
477
413
  let totalNew = 0;
478
414
  let gitManager = null;
479
415
  try {
@@ -481,27 +417,32 @@ async function runSync(sources) {
481
417
  await gitManager.init();
482
418
  } catch {
483
419
  }
484
- header(`Syncing ${sources.length} source${sources.length > 1 ? "s" : ""}`);
485
- for (let i = 0; i < sources.length; i++) {
486
- const source = sources[i];
420
+ const isTTY = process.stdout.isTTY;
421
+ let ui;
422
+ if (isTTY) {
423
+ const { renderSyncProgress } = await import("./render-TRVEKILS.js");
424
+ ui = renderSyncProgress(sources);
425
+ } else {
426
+ const { renderSyncProgressPlain } = await import("./render-TRVEKILS.js");
427
+ ui = renderSyncProgressPlain(sources);
428
+ }
429
+ for (const source of sources) {
487
430
  const cred = loadCredential(source);
488
431
  if (!cred) {
489
- console.log(` ${icon.skip} ${sourceLabel(source)} ${c.dim}no credentials, skipping${c.reset}`);
432
+ ui.updateSource(source, { status: "skipped" });
490
433
  continue;
491
434
  }
492
435
  const sourceStart = Date.now();
493
- console.log(` ${icon.sync} ${sourceLabel(source)} ${c.dim}syncing...${c.reset}`);
436
+ ui.updateSource(source, { status: "syncing", progress: "connecting..." });
494
437
  const logger = {
495
438
  info: (msg) => {
496
- progressLine(msg);
439
+ ui.updateSource(source, { progress: msg });
497
440
  },
498
441
  warn: (msg) => {
499
- clearProgress();
500
- resultLine(false, `${c.dim}${msg}${c.reset}`);
442
+ ui.updateSource(source, { progress: `\u26A0 ${msg}` });
501
443
  },
502
444
  error: (msg) => {
503
- clearProgress();
504
- console.error(` ${icon.fail} ${c.red}${msg}${c.reset}`);
445
+ ui.updateSource(source, { progress: `\u2717 ${msg}` });
505
446
  }
506
447
  };
507
448
  try {
@@ -509,105 +450,129 @@ async function runSync(sources) {
509
450
  case "feishu": {
510
451
  const feishuCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
511
452
  const result = await syncFeishu(feishuCtx, cred.data, logger);
512
- resultLine(true, `${result.newMessages} new messages from ${result.chats} chats`);
453
+ const parts = [`${result.newMessages} messages from ${result.chats} chats`];
513
454
  totalNew += result.newMessages;
514
455
  syncResults.push({ source: "feishu", newObjects: result.newMessages, label: "messages" });
515
456
  try {
516
457
  const mr = await syncFeishuMeetings(feishuCtx, cred.data, logger);
517
- if (mr.newObjects > 0) resultLine(true, `${mr.newObjects} calendar events`);
458
+ if (mr.newObjects > 0) parts.push(`${mr.newObjects} events`);
518
459
  syncResults.push({ source: "feishu/meetings", newObjects: mr.newObjects, label: "events" });
519
460
  } catch {
520
461
  }
521
462
  try {
522
463
  const dr = await syncFeishuDocs(feishuCtx, cred.data, logger);
523
- if (dr.newObjects > 0) resultLine(true, `${dr.newObjects} documents`);
464
+ if (dr.newObjects > 0) parts.push(`${dr.newObjects} docs`);
524
465
  syncResults.push({ source: "feishu/docs", newObjects: dr.newObjects, label: "docs" });
525
466
  } catch {
526
467
  }
527
468
  try {
528
469
  const ar = await syncFeishuApprovals(feishuCtx, cred.data, logger);
529
- if (ar.newObjects > 0) resultLine(true, `${ar.newObjects} approvals`);
470
+ if (ar.newObjects > 0) parts.push(`${ar.newObjects} approvals`);
530
471
  syncResults.push({ source: "feishu/approvals", newObjects: ar.newObjects, label: "approvals" });
531
472
  } catch {
532
473
  }
533
474
  try {
534
475
  const lr = await syncFeishuLinks(feishuCtx, cred.data, logger);
535
- if (lr.fetched > 0) resultLine(true, `${lr.fetched} link contents fetched (${lr.extracted} URLs found)`);
536
- else if (lr.extracted > 0) resultLine(false, `${lr.extracted} URLs found, ${lr.failed} failed to fetch`);
476
+ if (lr.fetched > 0) parts.push(`${lr.fetched} links`);
537
477
  syncResults.push({ source: "feishu/links", newObjects: lr.fetched, label: "links" });
538
478
  } catch {
539
479
  }
480
+ ui.updateSource(source, {
481
+ status: "done",
482
+ result: parts.join(", "),
483
+ elapsedMs: Date.now() - sourceStart
484
+ });
540
485
  break;
541
486
  }
542
487
  case "github": {
543
488
  const ghCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
544
489
  const r = await syncGitHub(ghCtx, cred.data, logger);
545
490
  const changeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
546
- const cloneInfo = r.clonedRepos > 0 ? ` | ${r.clonedRepos} repos cloned` : "";
547
- resultLine(true, `${r.repos} repos, ${r.prs} PRs, ${r.issues} issues ${c.dim}(${r.newObjects} new${changeInfo})${cloneInfo}${c.reset}`);
491
+ const cloneInfo = r.clonedRepos > 0 ? ` | ${r.clonedRepos} cloned` : "";
548
492
  totalNew += r.newObjects;
549
493
  syncResults.push({ source: "github", newObjects: r.newObjects + r.updatedObjects });
494
+ ui.updateSource(source, {
495
+ status: "done",
496
+ result: `${r.repos} repos, ${r.prs} PRs, ${r.issues} issues (${r.newObjects} new${changeInfo})${cloneInfo}`,
497
+ elapsedMs: Date.now() - sourceStart
498
+ });
550
499
  break;
551
500
  }
552
501
  case "gitlab": {
553
502
  const glCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
554
503
  const r = await syncGitLab(glCtx, cred.data, logger);
555
504
  const glChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
556
- resultLine(true, `${r.projects} projects, ${r.mrs} MRs, ${r.issues} issues ${c.dim}(${r.newObjects} new${glChangeInfo})${c.reset}`);
557
505
  totalNew += r.newObjects;
558
506
  syncResults.push({ source: "gitlab", newObjects: r.newObjects + r.updatedObjects });
507
+ ui.updateSource(source, {
508
+ status: "done",
509
+ result: `${r.projects} projects, ${r.mrs} MRs, ${r.issues} issues (${r.newObjects} new${glChangeInfo})`,
510
+ elapsedMs: Date.now() - sourceStart
511
+ });
559
512
  break;
560
513
  }
561
514
  case "linear": {
562
515
  const lnCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
563
516
  const r = await syncLinear(lnCtx, cred.data, logger);
564
517
  const lnChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
565
- resultLine(true, `${r.issues} issues ${c.dim}(${r.newObjects} new${lnChangeInfo})${c.reset}`);
566
518
  totalNew += r.newObjects;
567
519
  syncResults.push({ source: "linear", newObjects: r.newObjects + r.updatedObjects, label: "issues" });
520
+ ui.updateSource(source, {
521
+ status: "done",
522
+ result: `${r.issues} issues (${r.newObjects} new${lnChangeInfo})`,
523
+ elapsedMs: Date.now() - sourceStart
524
+ });
568
525
  break;
569
526
  }
570
527
  case "posthog": {
571
528
  const phCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
572
529
  const r = await syncPostHog(phCtx, cred.data, logger);
573
530
  const phChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
574
- resultLine(true, `${r.insights} insights, ${r.events} events ${c.dim}(${r.newObjects} new${phChangeInfo})${c.reset}`);
575
531
  totalNew += r.newObjects;
576
532
  syncResults.push({ source: "posthog", newObjects: r.newObjects + r.updatedObjects });
533
+ ui.updateSource(source, {
534
+ status: "done",
535
+ result: `${r.insights} insights, ${r.events} events (${r.newObjects} new${phChangeInfo})`,
536
+ elapsedMs: Date.now() - sourceStart
537
+ });
577
538
  break;
578
539
  }
579
540
  case "firebase": {
580
541
  const fbCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
581
542
  const r = await syncFirebase(fbCtx, cred.data, logger);
582
543
  const fbChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
583
- resultLine(true, `${r.events} events ${c.dim}(${r.newObjects} new${fbChangeInfo})${c.reset}`);
584
544
  totalNew += r.newObjects;
585
545
  syncResults.push({ source: "firebase", newObjects: r.newObjects + r.updatedObjects, label: "events" });
546
+ ui.updateSource(source, {
547
+ status: "done",
548
+ result: `${r.events} events (${r.newObjects} new${fbChangeInfo})`,
549
+ elapsedMs: Date.now() - sourceStart
550
+ });
586
551
  break;
587
552
  }
588
553
  default:
589
- console.log(` ${icon.skip} ${c.dim}unknown source${c.reset}`);
554
+ ui.updateSource(source, { status: "skipped" });
590
555
  }
591
- console.log(` ${c.dim}${elapsed(sourceStart)}${c.reset}`);
556
+ ui.setTotalNew(totalNew);
592
557
  } catch (err) {
593
558
  logError("sync", `Failed to sync ${source}`, { error: String(err) });
594
559
  const errStr = String(err);
595
- console.log(` ${icon.fail} ${c.red}sync failed${c.reset} ${c.dim}${errStr.slice(0, 60)}${c.reset}`);
596
560
  const hint = getSyncErrorHint(source, errStr);
597
- if (hint) {
598
- console.log(` ${c.yellow}Hint: ${hint}${c.reset}`);
599
- }
600
- }
601
- if (sources.length > 1) {
602
- console.log(` ${progressBar(i + 1, sources.length)}`);
561
+ ui.updateSource(source, {
562
+ status: "error",
563
+ error: hint ? `${errStr.slice(0, 50)} \u2014 ${hint}` : errStr.slice(0, 80),
564
+ elapsedMs: Date.now() - sourceStart
565
+ });
603
566
  }
604
567
  }
605
- console.log("");
606
- console.log(` ${icon.link} ${c.dim}extracting links...${c.reset}`);
568
+ ui.updatePhase("linking");
607
569
  const { processed, linksCreated } = linkAllUnprocessed(db.getSqlite());
608
570
  if (processed > 0) {
609
- resultLine(true, `${linksCreated} links from ${processed} objects`);
571
+ ui.setLinkResult(`${linksCreated} links from ${processed} objects`);
572
+ } else {
573
+ ui.setLinkResult("no new links");
610
574
  }
575
+ ui.updatePhase("backfilling");
611
576
  try {
612
577
  const sqlite = db.getSqlite();
613
578
  const { readObjectContent } = await import("./content-reader-VPGTR2SF.js");
@@ -618,7 +583,6 @@ async function runSync(sources) {
618
583
  LIMIT 500
619
584
  `).all();
620
585
  if (noFilePath.length > 0) {
621
- console.log(` ${icon.sync} ${c.dim}backfilling ${noFilePath.length} objects without files...${c.reset}`);
622
586
  let backfilled = 0;
623
587
  for (const obj of noFilePath) {
624
588
  try {
@@ -635,23 +599,22 @@ async function runSync(sources) {
635
599
  }
636
600
  }
637
601
  if (backfilled > 0) {
638
- resultLine(true, `${backfilled} files backfilled`);
602
+ ui.setBackfillResult(`${backfilled} files backfilled`);
639
603
  }
640
604
  }
641
605
  } catch {
642
606
  }
643
607
  db.close();
644
608
  if (gitManager) {
609
+ ui.updatePhase("committing");
645
610
  try {
646
611
  const sha = await gitManager.commitSync({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), sources: syncResults });
647
- if (sha) console.log(` ${icon.git} ${c.dim}committed ${sha.slice(0, 7)}${c.reset}`);
612
+ if (sha) ui.setGitResult(`committed ${sha.slice(0, 7)}`);
648
613
  } catch {
649
614
  }
650
615
  }
651
- console.log("");
652
- console.log(` ${c.bold}${c.green}Done${c.reset} ${c.dim}in ${elapsed(t0)}${c.reset}`);
653
- console.log(` ${c.bold}${totalNew}${c.reset} new objects synced from ${c.bold}${syncResults.length}${c.reset} sources`);
654
- console.log("");
616
+ ui.setTotalNew(totalNew);
617
+ ui.done();
655
618
  }
656
619
  function syncCommand(program) {
657
620
  program.command("sync").description("Sync data from connected sources").option("--source <source>", "Sync specific source only").action(async (opts) => {
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  syncCommand,
6
6
  syncFirebase,
7
7
  syncPostHog
8
- } from "./chunk-LMK2HS56.js";
8
+ } from "./chunk-NNCA4SWS.js";
9
9
  import {
10
10
  linkAllUnprocessed,
11
11
  syncGitLab,
@@ -102,7 +102,7 @@ function initCommand(program2) {
102
102
  default: true
103
103
  }]);
104
104
  if (continueSetup) {
105
- const { runSetupWizard } = await import("./setup-W6VQUVQU.js");
105
+ const { runSetupWizard } = await import("./setup-33MSPJXN.js");
106
106
  await runSetupWizard();
107
107
  } else {
108
108
  console.log("");
@@ -2793,7 +2793,7 @@ function isInstalled(packageName) {
2793
2793
  // src/cli.ts
2794
2794
  process.env["I18NEXT_DISABLE_BANNER"] = "1";
2795
2795
  var program = new Command();
2796
- program.name("jowork").description("AI Agent Infrastructure \u2014 let AI agents truly understand your work").version("0.3.6");
2796
+ program.name("jowork").description("AI Agent Infrastructure \u2014 let AI agents truly understand your work").version("0.3.8");
2797
2797
  initCommand(program);
2798
2798
  serveCommand(program);
2799
2799
  registerCommand(program);
@@ -2820,7 +2820,7 @@ program.action(async () => {
2820
2820
  const config = readConfig2();
2821
2821
  if (!config.initialized || !existsSync18(dbPath2())) {
2822
2822
  if (process.stdin.isTTY) {
2823
- const { runSetupWizard } = await import("./setup-W6VQUVQU.js");
2823
+ const { runSetupWizard } = await import("./setup-33MSPJXN.js");
2824
2824
  await runSetupWizard();
2825
2825
  } else {
2826
2826
  console.log("JoWork is not initialized. Run `jowork init` in an interactive terminal.");
@@ -0,0 +1,284 @@
1
+ import "./chunk-UJ4KEHGZ.js";
2
+
3
+ // src/tui/render.tsx
4
+ import React2 from "react";
5
+ import { render } from "ink";
6
+
7
+ // src/tui/SyncProgress.tsx
8
+ import { useState, useEffect } from "react";
9
+ import { Box, Text } from "ink";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ var SOURCE_COLORS = {
12
+ feishu: "cyan",
13
+ github: "white",
14
+ gitlab: "yellow",
15
+ linear: "cyan",
16
+ posthog: "red",
17
+ firebase: "yellow"
18
+ };
19
+ function sourceColor(name) {
20
+ return SOURCE_COLORS[name] ?? "white";
21
+ }
22
+ function ProgressBar({ value, width = 20 }) {
23
+ const filled = Math.round(Math.max(0, Math.min(1, value)) * width);
24
+ const empty = width - filled;
25
+ return /* @__PURE__ */ jsxs(Text, { children: [
26
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2588".repeat(filled) }),
27
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2591".repeat(empty) })
28
+ ] });
29
+ }
30
+ function StatusIcon({ status }) {
31
+ switch (status) {
32
+ case "done":
33
+ return /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" });
34
+ case "syncing":
35
+ return /* @__PURE__ */ jsx(Spinner, {});
36
+ case "error":
37
+ return /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717" });
38
+ case "skipped":
39
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB" });
40
+ case "waiting":
41
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u25CB" });
42
+ }
43
+ }
44
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
45
+ function Spinner() {
46
+ const [frame, setFrame] = useState(0);
47
+ useEffect(() => {
48
+ const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
49
+ return () => clearInterval(id);
50
+ }, []);
51
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] });
52
+ }
53
+ function ElapsedTime({ ms }) {
54
+ const text = ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
55
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: text });
56
+ }
57
+ function PhaseIcon({ phase }) {
58
+ switch (phase) {
59
+ case "linking":
60
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u27E1" });
61
+ case "backfilling":
62
+ return /* @__PURE__ */ jsx(Spinner, {});
63
+ case "committing":
64
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2387" });
65
+ default:
66
+ return null;
67
+ }
68
+ }
69
+ function SourceRow({ source }) {
70
+ const color = sourceColor(source.name);
71
+ const nameWidth = 10;
72
+ const padded = source.name.padEnd(nameWidth);
73
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
74
+ /* @__PURE__ */ jsx(Text, { children: " " }),
75
+ /* @__PURE__ */ jsx(StatusIcon, { status: source.status }),
76
+ /* @__PURE__ */ jsx(Text, { color, bold: true, children: padded }),
77
+ source.status === "syncing" && source.progress && /* @__PURE__ */ jsx(Text, { dimColor: true, children: source.progress }),
78
+ source.status === "done" && source.result && /* @__PURE__ */ jsx(Text, { children: source.result }),
79
+ source.status === "done" && source.elapsedMs != null && /* @__PURE__ */ jsx(ElapsedTime, { ms: source.elapsedMs }),
80
+ source.status === "error" && source.error && /* @__PURE__ */ jsx(Text, { color: "red", children: source.error.slice(0, 60) }),
81
+ source.status === "skipped" && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "no credentials, skipping" })
82
+ ] }) });
83
+ }
84
+ function SyncProgress({ state }) {
85
+ const [now, setNow] = useState(Date.now());
86
+ useEffect(() => {
87
+ if (state.phase === "done") return;
88
+ const id = setInterval(() => setNow(Date.now()), 200);
89
+ return () => clearInterval(id);
90
+ }, [state.phase]);
91
+ const elapsedMs = now - state.startTime;
92
+ const doneCount = state.sources.filter((s) => s.status === "done").length;
93
+ const total = state.sources.length;
94
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [
95
+ /* @__PURE__ */ jsxs(Box, { children: [
96
+ /* @__PURE__ */ jsx(Text, { children: " " }),
97
+ /* @__PURE__ */ jsxs(Text, { bold: true, children: [
98
+ "Syncing ",
99
+ total,
100
+ " source",
101
+ total > 1 ? "s" : ""
102
+ ] })
103
+ ] }),
104
+ /* @__PURE__ */ jsxs(Box, { children: [
105
+ /* @__PURE__ */ jsx(Text, { children: " " }),
106
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(40) })
107
+ ] }),
108
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingTop: 1, gap: 0, children: state.sources.map((source) => /* @__PURE__ */ jsx(SourceRow, { source }, source.name)) }),
109
+ total > 1 && /* @__PURE__ */ jsxs(Box, { paddingTop: 1, gap: 1, children: [
110
+ /* @__PURE__ */ jsx(Text, { children: " " }),
111
+ /* @__PURE__ */ jsx(ProgressBar, { value: doneCount / total }),
112
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
113
+ doneCount,
114
+ "/",
115
+ total
116
+ ] })
117
+ ] }),
118
+ (state.phase === "linking" || state.linkResult) && /* @__PURE__ */ jsxs(Box, { paddingTop: 1, gap: 1, children: [
119
+ /* @__PURE__ */ jsx(Text, { children: " " }),
120
+ /* @__PURE__ */ jsx(PhaseIcon, { phase: state.linkResult ? "done" : "linking" }),
121
+ state.linkResult ? /* @__PURE__ */ jsxs(Text, { children: [
122
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
123
+ " ",
124
+ state.linkResult
125
+ ] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "extracting links..." })
126
+ ] }),
127
+ state.backfillResult && /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
128
+ /* @__PURE__ */ jsx(Text, { children: " " }),
129
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
130
+ /* @__PURE__ */ jsx(Text, { children: state.backfillResult })
131
+ ] }),
132
+ state.gitResult && /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
133
+ /* @__PURE__ */ jsx(Text, { children: " " }),
134
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2387" }),
135
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: state.gitResult })
136
+ ] }),
137
+ state.phase === "done" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1, children: [
138
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
139
+ /* @__PURE__ */ jsx(Text, { children: " " }),
140
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "green", children: "Done" }),
141
+ /* @__PURE__ */ jsx(ElapsedTime, { ms: elapsedMs })
142
+ ] }),
143
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
144
+ /* @__PURE__ */ jsx(Text, { children: " " }),
145
+ /* @__PURE__ */ jsx(Text, { bold: true, children: state.totalNew }),
146
+ /* @__PURE__ */ jsx(Text, { children: "new objects synced from" }),
147
+ /* @__PURE__ */ jsx(Text, { bold: true, children: doneCount }),
148
+ /* @__PURE__ */ jsx(Text, { children: "sources" })
149
+ ] })
150
+ ] }),
151
+ state.phase !== "done" && /* @__PURE__ */ jsxs(Box, { paddingTop: 1, gap: 1, children: [
152
+ /* @__PURE__ */ jsx(Text, { children: " " }),
153
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "elapsed:" }),
154
+ /* @__PURE__ */ jsx(ElapsedTime, { ms: elapsedMs })
155
+ ] })
156
+ ] });
157
+ }
158
+
159
+ // src/tui/render.tsx
160
+ import { jsx as jsx2 } from "react/jsx-runtime";
161
+ function renderSyncProgress(sourceNames) {
162
+ const state = {
163
+ sources: sourceNames.map((name) => ({
164
+ name,
165
+ status: "waiting"
166
+ })),
167
+ totalNew: 0,
168
+ phase: "syncing",
169
+ startTime: Date.now()
170
+ };
171
+ let triggerUpdate;
172
+ function App() {
173
+ const [, setTick] = React2.useState(0);
174
+ triggerUpdate = () => setTick((t) => t + 1);
175
+ return /* @__PURE__ */ jsx2(SyncProgress, { state });
176
+ }
177
+ const instance = render(/* @__PURE__ */ jsx2(App, {}));
178
+ function rerender() {
179
+ triggerUpdate?.();
180
+ }
181
+ return {
182
+ updateSource(name, patch) {
183
+ const src = state.sources.find((s) => s.name === name);
184
+ if (src) {
185
+ Object.assign(src, patch);
186
+ rerender();
187
+ }
188
+ },
189
+ updatePhase(phase) {
190
+ state.phase = phase;
191
+ rerender();
192
+ },
193
+ setTotalNew(n) {
194
+ state.totalNew = n;
195
+ rerender();
196
+ },
197
+ setLinkResult(msg) {
198
+ state.linkResult = msg;
199
+ rerender();
200
+ },
201
+ setBackfillResult(msg) {
202
+ state.backfillResult = msg;
203
+ rerender();
204
+ },
205
+ setGitResult(msg) {
206
+ state.gitResult = msg;
207
+ rerender();
208
+ },
209
+ done() {
210
+ state.phase = "done";
211
+ rerender();
212
+ setTimeout(() => instance.unmount(), 100);
213
+ },
214
+ getState() {
215
+ return state;
216
+ }
217
+ };
218
+ }
219
+ function renderSyncProgressPlain(sourceNames) {
220
+ const state = {
221
+ sources: sourceNames.map((name) => ({
222
+ name,
223
+ status: "waiting"
224
+ })),
225
+ totalNew: 0,
226
+ phase: "syncing",
227
+ startTime: Date.now()
228
+ };
229
+ console.log(`
230
+ Syncing ${sourceNames.length} source${sourceNames.length > 1 ? "s" : ""}`);
231
+ console.log(` ${"\u2500".repeat(40)}`);
232
+ return {
233
+ updateSource(name, patch) {
234
+ const src = state.sources.find((s) => s.name === name);
235
+ if (!src) return;
236
+ Object.assign(src, patch);
237
+ if (patch.status === "syncing" && !patch.result) return;
238
+ if (patch.status === "done" && patch.result) {
239
+ const elapsed = src.elapsedMs != null ? ` (${src.elapsedMs < 1e3 ? `${src.elapsedMs}ms` : `${(src.elapsedMs / 1e3).toFixed(1)}s`})` : "";
240
+ console.log(` \u2713 ${name} ${patch.result}${elapsed}`);
241
+ }
242
+ if (patch.status === "error") {
243
+ console.log(` \u2717 ${name} ${patch.error ?? "sync failed"}`);
244
+ }
245
+ if (patch.status === "skipped") {
246
+ console.log(` \u25CB ${name} no credentials, skipping`);
247
+ }
248
+ },
249
+ updatePhase(phase) {
250
+ state.phase = phase;
251
+ },
252
+ setTotalNew(n) {
253
+ state.totalNew = n;
254
+ },
255
+ setLinkResult(msg) {
256
+ console.log(` \u2713 ${msg}`);
257
+ state.linkResult = msg;
258
+ },
259
+ setBackfillResult(msg) {
260
+ console.log(` \u2713 ${msg}`);
261
+ state.backfillResult = msg;
262
+ },
263
+ setGitResult(msg) {
264
+ console.log(` \u2387 ${msg}`);
265
+ state.gitResult = msg;
266
+ },
267
+ done() {
268
+ state.phase = "done";
269
+ const ms = Date.now() - state.startTime;
270
+ const elapsed = ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
271
+ console.log(`
272
+ Done in ${elapsed}`);
273
+ console.log(` ${state.totalNew} new objects synced
274
+ `);
275
+ },
276
+ getState() {
277
+ return state;
278
+ }
279
+ };
280
+ }
281
+ export {
282
+ renderSyncProgress,
283
+ renderSyncProgressPlain
284
+ };
@@ -212,7 +212,7 @@ async function runSetupWizard() {
212
212
  console.log("");
213
213
  console.log(zh ? ` \u6B63\u5728\u540C\u6B65 ${connectedSources.length} \u4E2A\u6570\u636E\u6E90...` : ` Syncing ${connectedSources.length} source${connectedSources.length > 1 ? "s" : ""}...`);
214
214
  console.log("");
215
- const { runSync } = await import("./sync-CIVY3XE6.js");
215
+ const { runSync } = await import("./sync-E46TEV3R.js");
216
216
  try {
217
217
  await runSync(connectedSources);
218
218
  } catch {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runSync,
3
3
  syncCommand
4
- } from "./chunk-LMK2HS56.js";
4
+ } from "./chunk-NNCA4SWS.js";
5
5
  import "./chunk-3IYVMICH.js";
6
6
  import "./chunk-L2SHMRM6.js";
7
7
  import "./chunk-EYP6WMFF.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jowork",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "AI Agent Infrastructure — let AI agents truly understand your work. Connect data sources, give agents awareness and goals. Local-first, one command.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -47,17 +47,23 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@hono/node-server": "^1.14.0",
50
+ "@json-render/core": "^0.15.0",
51
+ "@json-render/ink": "^0.15.0",
50
52
  "@modelcontextprotocol/sdk": "^1.27.0",
53
+ "@types/react": "^19.2.14",
51
54
  "better-sqlite3": "^12.6.2",
52
55
  "chalk": "^5.4.0",
53
56
  "commander": "^13.0.0",
54
57
  "croner": "^9.0.0",
55
58
  "drizzle-orm": "^0.44.0",
56
59
  "hono": "^4.7.0",
60
+ "ink": "^6.8.0",
57
61
  "inquirer": "^12.0.0",
58
62
  "ora": "^8.0.0",
59
63
  "pino": "^9.0.0",
60
64
  "pino-pretty": "^13.0.0",
65
+ "react": "^19.2.4",
66
+ "react-devtools-core": "^7.0.1",
61
67
  "simple-git": "^3.33.0",
62
68
  "ws": "^8.18.3",
63
69
  "zod": "^3.24.0"