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
|
|
321
|
-
return !existingHeaders.includes(
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
432
|
+
ui.updateSource(source, { status: "skipped" });
|
|
490
433
|
continue;
|
|
491
434
|
}
|
|
492
435
|
const sourceStart = Date.now();
|
|
493
|
-
|
|
436
|
+
ui.updateSource(source, { status: "syncing", progress: "connecting..." });
|
|
494
437
|
const logger = {
|
|
495
438
|
info: (msg) => {
|
|
496
|
-
|
|
439
|
+
ui.updateSource(source, { progress: msg });
|
|
497
440
|
},
|
|
498
441
|
warn: (msg) => {
|
|
499
|
-
|
|
500
|
-
resultLine(false, `${c.dim}${msg}${c.reset}`);
|
|
442
|
+
ui.updateSource(source, { progress: `\u26A0 ${msg}` });
|
|
501
443
|
},
|
|
502
444
|
error: (msg) => {
|
|
503
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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}
|
|
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
|
-
|
|
554
|
+
ui.updateSource(source, { status: "skipped" });
|
|
590
555
|
}
|
|
591
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
612
|
+
if (sha) ui.setGitResult(`committed ${sha.slice(0, 7)}`);
|
|
648
613
|
} catch {
|
|
649
614
|
}
|
|
650
615
|
}
|
|
651
|
-
|
|
652
|
-
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
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-
|
|
215
|
+
const { runSync } = await import("./sync-E46TEV3R.js");
|
|
216
216
|
try {
|
|
217
217
|
await runSync(connectedSources);
|
|
218
218
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jowork",
|
|
3
|
-
"version": "0.3.
|
|
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"
|