jowork 0.3.7 → 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,70 +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
- if (isTTY) process.stdout.write("\r\x1B[K");
433
- console.log(` ${ok ? icon.ok : icon.warn} ${msg}`);
434
- }
435
- var _currentSource = "";
436
- function progressLine(msg) {
437
- if (!isTTY) return;
438
- process.stdout.write(`\r ${icon.sync} ${_currentSource} ${c.dim}${msg}${c.reset}\x1B[K`);
439
- }
440
- function clearProgress() {
441
- if (!isTTY) return;
442
- process.stdout.write("\r\x1B[K");
443
- }
444
- function elapsed(start) {
445
- const ms = Date.now() - start;
446
- return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
447
- }
448
384
  function getSyncErrorHint(source, error) {
449
385
  const is401 = /401|unauthorized|auth.*fail/i.test(error);
450
386
  const is403 = /403|forbidden/i.test(error);
@@ -474,7 +410,6 @@ async function runSync(sources) {
474
410
  db.ensureTables();
475
411
  const fileWriter = new FileWriter();
476
412
  const syncResults = [];
477
- const t0 = Date.now();
478
413
  let totalNew = 0;
479
414
  let gitManager = null;
480
415
  try {
@@ -482,28 +417,32 @@ async function runSync(sources) {
482
417
  await gitManager.init();
483
418
  } catch {
484
419
  }
485
- header(`Syncing ${sources.length} source${sources.length > 1 ? "s" : ""}`);
486
- for (let i = 0; i < sources.length; i++) {
487
- 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) {
488
430
  const cred = loadCredential(source);
489
431
  if (!cred) {
490
- console.log(` ${icon.skip} ${sourceLabel(source)} ${c.dim}no credentials, skipping${c.reset}`);
432
+ ui.updateSource(source, { status: "skipped" });
491
433
  continue;
492
434
  }
493
435
  const sourceStart = Date.now();
494
- _currentSource = sourceLabel(source);
495
- progressLine("syncing...");
436
+ ui.updateSource(source, { status: "syncing", progress: "connecting..." });
496
437
  const logger = {
497
438
  info: (msg) => {
498
- progressLine(msg);
439
+ ui.updateSource(source, { progress: msg });
499
440
  },
500
441
  warn: (msg) => {
501
- clearProgress();
502
- resultLine(false, `${c.dim}${msg}${c.reset}`);
442
+ ui.updateSource(source, { progress: `\u26A0 ${msg}` });
503
443
  },
504
444
  error: (msg) => {
505
- clearProgress();
506
- console.error(` ${icon.fail} ${c.red}${msg}${c.reset}`);
445
+ ui.updateSource(source, { progress: `\u2717 ${msg}` });
507
446
  }
508
447
  };
509
448
  try {
@@ -511,106 +450,129 @@ async function runSync(sources) {
511
450
  case "feishu": {
512
451
  const feishuCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
513
452
  const result = await syncFeishu(feishuCtx, cred.data, logger);
514
- resultLine(true, `${result.newMessages} new messages from ${result.chats} chats`);
453
+ const parts = [`${result.newMessages} messages from ${result.chats} chats`];
515
454
  totalNew += result.newMessages;
516
455
  syncResults.push({ source: "feishu", newObjects: result.newMessages, label: "messages" });
517
456
  try {
518
457
  const mr = await syncFeishuMeetings(feishuCtx, cred.data, logger);
519
- if (mr.newObjects > 0) resultLine(true, `${mr.newObjects} calendar events`);
458
+ if (mr.newObjects > 0) parts.push(`${mr.newObjects} events`);
520
459
  syncResults.push({ source: "feishu/meetings", newObjects: mr.newObjects, label: "events" });
521
460
  } catch {
522
461
  }
523
462
  try {
524
463
  const dr = await syncFeishuDocs(feishuCtx, cred.data, logger);
525
- if (dr.newObjects > 0) resultLine(true, `${dr.newObjects} documents`);
464
+ if (dr.newObjects > 0) parts.push(`${dr.newObjects} docs`);
526
465
  syncResults.push({ source: "feishu/docs", newObjects: dr.newObjects, label: "docs" });
527
466
  } catch {
528
467
  }
529
468
  try {
530
469
  const ar = await syncFeishuApprovals(feishuCtx, cred.data, logger);
531
- if (ar.newObjects > 0) resultLine(true, `${ar.newObjects} approvals`);
470
+ if (ar.newObjects > 0) parts.push(`${ar.newObjects} approvals`);
532
471
  syncResults.push({ source: "feishu/approvals", newObjects: ar.newObjects, label: "approvals" });
533
472
  } catch {
534
473
  }
535
474
  try {
536
475
  const lr = await syncFeishuLinks(feishuCtx, cred.data, logger);
537
- if (lr.fetched > 0) resultLine(true, `${lr.fetched} link contents fetched (${lr.extracted} URLs found)`);
538
- 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`);
539
477
  syncResults.push({ source: "feishu/links", newObjects: lr.fetched, label: "links" });
540
478
  } catch {
541
479
  }
480
+ ui.updateSource(source, {
481
+ status: "done",
482
+ result: parts.join(", "),
483
+ elapsedMs: Date.now() - sourceStart
484
+ });
542
485
  break;
543
486
  }
544
487
  case "github": {
545
488
  const ghCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
546
489
  const r = await syncGitHub(ghCtx, cred.data, logger);
547
490
  const changeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
548
- const cloneInfo = r.clonedRepos > 0 ? ` | ${r.clonedRepos} repos cloned` : "";
549
- 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` : "";
550
492
  totalNew += r.newObjects;
551
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
+ });
552
499
  break;
553
500
  }
554
501
  case "gitlab": {
555
502
  const glCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
556
503
  const r = await syncGitLab(glCtx, cred.data, logger);
557
504
  const glChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
558
- resultLine(true, `${r.projects} projects, ${r.mrs} MRs, ${r.issues} issues ${c.dim}(${r.newObjects} new${glChangeInfo})${c.reset}`);
559
505
  totalNew += r.newObjects;
560
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
+ });
561
512
  break;
562
513
  }
563
514
  case "linear": {
564
515
  const lnCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
565
516
  const r = await syncLinear(lnCtx, cred.data, logger);
566
517
  const lnChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
567
- resultLine(true, `${r.issues} issues ${c.dim}(${r.newObjects} new${lnChangeInfo})${c.reset}`);
568
518
  totalNew += r.newObjects;
569
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
+ });
570
525
  break;
571
526
  }
572
527
  case "posthog": {
573
528
  const phCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
574
529
  const r = await syncPostHog(phCtx, cred.data, logger);
575
530
  const phChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
576
- resultLine(true, `${r.insights} insights, ${r.events} events ${c.dim}(${r.newObjects} new${phChangeInfo})${c.reset}`);
577
531
  totalNew += r.newObjects;
578
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
+ });
579
538
  break;
580
539
  }
581
540
  case "firebase": {
582
541
  const fbCtx = new SyncContext(db.getSqlite(), logger, fileWriter);
583
542
  const r = await syncFirebase(fbCtx, cred.data, logger);
584
543
  const fbChangeInfo = r.updatedObjects > 0 ? `, ${r.updatedObjects} updated` : "";
585
- resultLine(true, `${r.events} events ${c.dim}(${r.newObjects} new${fbChangeInfo})${c.reset}`);
586
544
  totalNew += r.newObjects;
587
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
+ });
588
551
  break;
589
552
  }
590
553
  default:
591
- console.log(` ${icon.skip} ${c.dim}unknown source${c.reset}`);
554
+ ui.updateSource(source, { status: "skipped" });
592
555
  }
593
- clearProgress();
594
- console.log(` ${c.dim}${elapsed(sourceStart)}${c.reset}`);
556
+ ui.setTotalNew(totalNew);
595
557
  } catch (err) {
596
558
  logError("sync", `Failed to sync ${source}`, { error: String(err) });
597
559
  const errStr = String(err);
598
- console.log(` ${icon.fail} ${c.red}sync failed${c.reset} ${c.dim}${errStr.slice(0, 60)}${c.reset}`);
599
560
  const hint = getSyncErrorHint(source, errStr);
600
- if (hint) {
601
- console.log(` ${c.yellow}Hint: ${hint}${c.reset}`);
602
- }
603
- }
604
- if (sources.length > 1) {
605
- 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
+ });
606
566
  }
607
567
  }
608
- console.log("");
609
- console.log(` ${icon.link} ${c.dim}extracting links...${c.reset}`);
568
+ ui.updatePhase("linking");
610
569
  const { processed, linksCreated } = linkAllUnprocessed(db.getSqlite());
611
570
  if (processed > 0) {
612
- resultLine(true, `${linksCreated} links from ${processed} objects`);
571
+ ui.setLinkResult(`${linksCreated} links from ${processed} objects`);
572
+ } else {
573
+ ui.setLinkResult("no new links");
613
574
  }
575
+ ui.updatePhase("backfilling");
614
576
  try {
615
577
  const sqlite = db.getSqlite();
616
578
  const { readObjectContent } = await import("./content-reader-VPGTR2SF.js");
@@ -621,7 +583,6 @@ async function runSync(sources) {
621
583
  LIMIT 500
622
584
  `).all();
623
585
  if (noFilePath.length > 0) {
624
- console.log(` ${icon.sync} ${c.dim}backfilling ${noFilePath.length} objects without files...${c.reset}`);
625
586
  let backfilled = 0;
626
587
  for (const obj of noFilePath) {
627
588
  try {
@@ -638,23 +599,22 @@ async function runSync(sources) {
638
599
  }
639
600
  }
640
601
  if (backfilled > 0) {
641
- resultLine(true, `${backfilled} files backfilled`);
602
+ ui.setBackfillResult(`${backfilled} files backfilled`);
642
603
  }
643
604
  }
644
605
  } catch {
645
606
  }
646
607
  db.close();
647
608
  if (gitManager) {
609
+ ui.updatePhase("committing");
648
610
  try {
649
611
  const sha = await gitManager.commitSync({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), sources: syncResults });
650
- 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)}`);
651
613
  } catch {
652
614
  }
653
615
  }
654
- console.log("");
655
- console.log(` ${c.bold}${c.green}Done${c.reset} ${c.dim}in ${elapsed(t0)}${c.reset}`);
656
- console.log(` ${c.bold}${totalNew}${c.reset} new objects synced from ${c.bold}${syncResults.length}${c.reset} sources`);
657
- console.log("");
616
+ ui.setTotalNew(totalNew);
617
+ ui.done();
658
618
  }
659
619
  function syncCommand(program) {
660
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-KIETDRXF.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-5LFE6XDW.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.7");
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-5LFE6XDW.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-XUDNMTTA.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-KIETDRXF.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.7",
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"