cograph 0.1.14 → 0.1.16

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  Client,
4
4
  CographError
5
- } from "./chunk-IHKVOXYV.js";
5
+ } from "./chunk-K7B4PPX2.js";
6
6
  import "./chunk-7VVBEUZQ.js";
7
7
 
8
8
  // src/shell.ts
@@ -59,6 +59,11 @@ function showCommands() {
59
59
  ["/kg delete <name>", "Delete a KG (irreversible)"],
60
60
  ["/types [query]", "List types in the current KG (with entity counts)"],
61
61
  ["/type <name>", "Drill into one type \u2014 attributes, relationships, samples"],
62
+ ["/type <name> --system", "\u2026also include auto-attached system attributes"],
63
+ ["/enrich <Type> <attr> ...", "Plan + run an enrichment job (interactive)"],
64
+ ["/enrich watch <job_id>", "Live progress for a running job"],
65
+ ["/enrich jobs", "List recent enrichment jobs"],
66
+ ["/enrich review <job_id>", "Walk through conflicts and accept/reject"],
62
67
  ["/login", "Re-authenticate (browser)"],
63
68
  ["/status", "Show graph stats"],
64
69
  ["/reset", "Clear the current KG"],
@@ -376,17 +381,21 @@ async function resolveTypeName(client, kg, rl, input) {
376
381
  return null;
377
382
  }
378
383
  async function cmdType(client, kg, rl, input) {
379
- if (!input.trim()) {
380
- stdout.write(` ${YELLOW}Usage:${RESET} /type <name>
384
+ const tokens = splitArgs(input.trim());
385
+ const includeSystem = tokens.includes("--system");
386
+ const nameTokens = tokens.filter((t) => t !== "--system");
387
+ const nameInput = nameTokens.join(" ").trim();
388
+ if (!nameInput) {
389
+ stdout.write(` ${YELLOW}Usage:${RESET} /type <name> [--system]
381
390
  `);
382
391
  return;
383
392
  }
384
- const name = await resolveTypeName(client, kg, rl, input);
393
+ const name = await resolveTypeName(client, kg, rl, nameInput);
385
394
  if (!name) return;
386
395
  const sp = startSpinner(`Loading ${name}...`);
387
396
  let usage;
388
397
  try {
389
- usage = await client.typeUsage(kg, name);
398
+ usage = await client.typeUsage(kg, name, { includeSystem });
390
399
  } catch (err) {
391
400
  sp.stop();
392
401
  if (err instanceof CographError) printError(err.message);
@@ -396,6 +405,9 @@ async function cmdType(client, kg, rl, input) {
396
405
  sp.stop();
397
406
  const total = usage.entity_count;
398
407
  const pct = (n) => total > 0 ? `${Math.round(n / total * 100).toString().padStart(3)}%` : " \u2014";
408
+ const relNames = new Set(usage.relationships.map((r) => r.name));
409
+ const attrLitByName = new Map(usage.attributes.map((a) => [a.name, a]));
410
+ const litOnlyAttrs = usage.attributes.filter((a) => !relNames.has(a.name));
399
411
  stdout.write("\n");
400
412
  stdout.write(
401
413
  ` ${BOLD}${usage.name}${RESET} ${DIM}${fmtNum(total)} entities${RESET}
@@ -409,25 +421,25 @@ async function cmdType(client, kg, rl, input) {
409
421
  stdout.write(` ${DIM}subClassOf ${usage.parent_type}${RESET}
410
422
  `);
411
423
  }
412
- if (usage.attributes.length > 0) {
424
+ if (litOnlyAttrs.length > 0) {
413
425
  stdout.write(
414
426
  `
415
- ${BOLD}Attributes (${usage.attributes.length})${RESET}
427
+ ${BOLD}Attributes (${litOnlyAttrs.length})${RESET}
416
428
  `
417
429
  );
418
430
  const nameW = Math.max(
419
- ...usage.attributes.map((a) => a.name.length + 1),
431
+ ...litOnlyAttrs.map((a) => a.name.length + 1),
420
432
  8
421
433
  );
422
434
  const typeW = Math.max(
423
- ...usage.attributes.map((a) => a.datatype.length),
435
+ ...litOnlyAttrs.map((a) => a.datatype.length),
424
436
  8
425
437
  );
426
438
  const cntW = Math.max(
427
- ...usage.attributes.map((a) => fmtNum(a.count).length),
439
+ ...litOnlyAttrs.map((a) => fmtNum(a.count).length),
428
440
  4
429
441
  );
430
- for (const a of usage.attributes) {
442
+ for (const a of litOnlyAttrs) {
431
443
  const dotName = `.${a.name}`;
432
444
  stdout.write(
433
445
  ` ${CYAN}${dotName.padEnd(nameW)}${RESET} ${DIM}${a.datatype.padEnd(typeW)}${RESET} ${fmtNum(a.count).padStart(cntW)} ${DIM}(${pct(a.count)})${RESET}
@@ -452,8 +464,10 @@ async function cmdType(client, kg, rl, input) {
452
464
  for (const r of usage.relationships) {
453
465
  const dotName = `.${r.name}`;
454
466
  const tgt = r.target_type ?? "?";
467
+ const lit = attrLitByName.get(r.name);
468
+ const litNote = lit ? ` ${DIM}(+${fmtNum(lit.count)} ${lit.datatype})${RESET}` : "";
455
469
  stdout.write(
456
- ` ${CYAN}${dotName.padEnd(nameW)}${RESET} ${DIM}\u2192${RESET} ${BOLD}${tgt.padEnd(tgtW)}${RESET} ${fmtNum(r.count).padStart(6)} ${DIM}(${pct(r.count)})${RESET}
470
+ ` ${CYAN}${dotName.padEnd(nameW)}${RESET} ${DIM}\u2192${RESET} ${BOLD}${tgt.padEnd(tgtW)}${RESET} ${fmtNum(r.count).padStart(6)} ${DIM}(${pct(r.count)})${RESET}${litNote}
457
471
  `
458
472
  );
459
473
  }
@@ -477,12 +491,321 @@ async function cmdType(client, kg, rl, input) {
477
491
  }
478
492
  stdout.write("\n");
479
493
  }
480
- function makePrompt(kg, triples) {
494
+ function lastUriSegment(uri) {
495
+ if (!uri) return uri;
496
+ const hash = uri.lastIndexOf("#");
497
+ if (hash >= 0 && hash < uri.length - 1) return uri.slice(hash + 1);
498
+ const slash = uri.lastIndexOf("/");
499
+ if (slash >= 0 && slash < uri.length - 1) return uri.slice(slash + 1);
500
+ return uri;
501
+ }
502
+ function relativeTime(iso) {
503
+ if (!iso) return "\u2014";
504
+ const t = Date.parse(iso);
505
+ if (!Number.isFinite(t)) return "\u2014";
506
+ const diffMs = Date.now() - t;
507
+ const s = Math.max(0, Math.floor(diffMs / 1e3));
508
+ if (s < 60) return `${s}s ago`;
509
+ const m = Math.floor(s / 60);
510
+ if (m < 60) return `${m}m ago`;
511
+ const h = Math.floor(m / 60);
512
+ if (h < 24) return `${h}h ago`;
513
+ const d = Math.floor(h / 24);
514
+ return `${d}d ago`;
515
+ }
516
+ function progressBar(processed, total, width = 20) {
517
+ if (!total || total <= 0) return "[" + " ".repeat(width) + "]";
518
+ const ratio = Math.max(0, Math.min(1, processed / total));
519
+ const filled = Math.round(ratio * width);
520
+ return "[" + "\u2588".repeat(filled) + "\u2591".repeat(width - filled) + "]";
521
+ }
522
+ function statusColor(status) {
523
+ switch (status) {
524
+ case "applied":
525
+ return GREEN;
526
+ case "failed":
527
+ return RED;
528
+ case "review":
529
+ return YELLOW;
530
+ case "cancelled":
531
+ return DIM;
532
+ default:
533
+ return CYAN;
534
+ }
535
+ }
536
+ async function cmdEnrichRun(client, kg, rl, args) {
537
+ if (args.length < 2) {
538
+ stdout.write(
539
+ ` ${YELLOW}Usage:${RESET} /enrich <Type> <attr1> [<attr2> ...]
540
+ `
541
+ );
542
+ return;
543
+ }
544
+ const typeInput = args[0];
545
+ const attrs = args.slice(1).map((a) => a.replace(/^\./, ""));
546
+ const typeName = await resolveTypeName(client, kg, rl, typeInput);
547
+ if (!typeName) return;
548
+ const tier = "lite";
549
+ const policy = "stage";
550
+ stdout.write(
551
+ `
552
+ ${BOLD}Plan:${RESET} enrich ${CYAN}${typeName}${RESET}.${attrs.map((a) => `${CYAN}${a}${RESET}`).join(`, .`)} in ${BOLD}${kg}${RESET} ${DIM}\xB7${RESET} tier: ${tier} ${DIM}\xB7${RESET} policy: ${policy}
553
+
554
+ `
555
+ );
556
+ const sp = startSpinner(`Queueing enrichment for ${typeName}...`);
557
+ let created;
558
+ try {
559
+ created = await client.enrichRun({
560
+ type_name: typeName,
561
+ attributes: attrs,
562
+ tier,
563
+ kg_name: kg,
564
+ conflict_policy: policy
565
+ });
566
+ } catch (err) {
567
+ sp.stop();
568
+ if (err instanceof CographError) printError(err.message);
569
+ else printError(err instanceof Error ? err.message : String(err));
570
+ return;
571
+ }
572
+ sp.stop();
573
+ const cost = (created.estimated_cost_usd ?? 0).toFixed(4);
574
+ stdout.write(
575
+ ` ${GREEN}\u2713${RESET} Job queued: ${CYAN_BOLD}${created.job_id}${RESET} ${DIM}\xB7${RESET} estimated cost ${BOLD}$${cost}${RESET} ${DIM}\xB7${RESET} ${fmtNum(created.total_entities ?? 0)} entities
576
+ `
577
+ );
578
+ const watch = (await ask(rl, ` Watch progress? [Y/n]: `)).trim().toLowerCase();
579
+ if (watch === "" || watch === "y" || watch === "yes") {
580
+ await watchJob(client, created.job_id);
581
+ } else {
582
+ stdout.write(
583
+ ` ${DIM}Tip: /enrich watch ${created.job_id} to follow it.${RESET}
584
+ `
585
+ );
586
+ }
587
+ }
588
+ async function watchJob(client, jobId) {
589
+ const startedAt = Date.now();
590
+ let lastJob = null;
591
+ const draw = (job) => {
592
+ const p2 = job.progress;
593
+ const bar = progressBar(p2.processed, p2.total);
594
+ const elapsed = Math.max(1, Math.floor((Date.now() - startedAt) / 1e3));
595
+ const rate = p2.processed / elapsed;
596
+ let etaStr = "\u2014";
597
+ if (rate > 0 && p2.total > p2.processed) {
598
+ const remaining = Math.ceil((p2.total - p2.processed) / rate);
599
+ etaStr = remaining < 60 ? `${remaining}s` : remaining < 3600 ? `${Math.floor(remaining / 60)}m` : `${Math.floor(remaining / 3600)}h`;
600
+ }
601
+ const sc = statusColor(job.status);
602
+ stdout.write(
603
+ `\r\x1B[2K ${sc}${job.status}${RESET} ${bar} ${fmtNum(p2.processed)}/${fmtNum(p2.total)} ${DIM}\xB7${RESET} filled ${GREEN}${fmtNum(p2.filled)}${RESET} ${DIM}\xB7${RESET} verified ${CYAN}${fmtNum(p2.verified)}${RESET} ${DIM}\xB7${RESET} conflicts ${YELLOW}${fmtNum(p2.conflicts)}${RESET} ${DIM}\xB7${RESET} ETA ${etaStr}`
604
+ );
605
+ };
606
+ while (true) {
607
+ let job;
608
+ try {
609
+ job = await client.enrichJob(jobId);
610
+ } catch (err) {
611
+ stdout.write("\r\x1B[2K");
612
+ if (err instanceof CographError) printError(err.message);
613
+ else printError(err instanceof Error ? err.message : String(err));
614
+ return;
615
+ }
616
+ lastJob = job;
617
+ draw(job);
618
+ if (job.status !== "running" && job.status !== "queued") break;
619
+ await new Promise((r) => setTimeout(r, 1500));
620
+ }
621
+ stdout.write("\n");
622
+ if (!lastJob) return;
623
+ const p = lastJob.progress;
624
+ if (lastJob.status === "review") {
625
+ stdout.write(
626
+ ` ${YELLOW}\u2726${RESET} ${fmtNum(p.conflicts)} conflict${p.conflicts === 1 ? "" : "s"} need review. ${DIM}Run${RESET} /enrich review ${lastJob.id}${DIM} to walk through them.${RESET}
627
+ `
628
+ );
629
+ } else if (lastJob.status === "applied") {
630
+ stdout.write(
631
+ ` ${GREEN}\u2713${RESET} Applied ${DIM}\xB7${RESET} filled ${fmtNum(p.filled)}, verified ${fmtNum(p.verified)}, skipped ${fmtNum(p.skipped)}
632
+ `
633
+ );
634
+ } else if (lastJob.status === "failed") {
635
+ printError(`Job failed: ${lastJob.error ?? "(no error message)"}`);
636
+ } else if (lastJob.status === "cancelled") {
637
+ stdout.write(` ${DIM}Job cancelled.${RESET}
638
+ `);
639
+ }
640
+ }
641
+ async function cmdEnrichJobs(client) {
642
+ const sp = startSpinner("Loading enrichment jobs...");
643
+ let jobs;
644
+ try {
645
+ jobs = await client.enrichJobs();
646
+ } catch (err) {
647
+ sp.stop();
648
+ if (err instanceof CographError) printError(err.message);
649
+ else printError(err instanceof Error ? err.message : String(err));
650
+ return;
651
+ }
652
+ sp.stop();
653
+ if (jobs.length === 0) {
654
+ stdout.write(` ${DIM}No enrichment jobs yet.${RESET}
655
+ `);
656
+ return;
657
+ }
658
+ const truncAttrs = (attrs) => {
659
+ const max = 30;
660
+ const joined = attrs.join(", ");
661
+ if (joined.length <= max) return joined;
662
+ return joined.slice(0, max - 1) + "\u2026";
663
+ };
664
+ const rows = jobs.map((j) => ({
665
+ id: j.id,
666
+ type: j.type_name,
667
+ attrs: truncAttrs(j.attributes ?? []),
668
+ status: j.status,
669
+ progress: `${fmtNum(j.progress?.processed ?? 0)}/${fmtNum(j.progress?.total ?? 0)}`,
670
+ created: relativeTime(j.created_at)
671
+ }));
672
+ const w = {
673
+ id: Math.max("ID".length, ...rows.map((r) => r.id.length)),
674
+ type: Math.max("Type".length, ...rows.map((r) => r.type.length)),
675
+ attrs: Math.max("Attrs".length, ...rows.map((r) => r.attrs.length)),
676
+ status: Math.max("Status".length, ...rows.map((r) => r.status.length)),
677
+ progress: Math.max("Progress".length, ...rows.map((r) => r.progress.length))
678
+ };
679
+ stdout.write("\n");
680
+ stdout.write(
681
+ ` ${BOLD}${"ID".padEnd(w.id)} ${"Type".padEnd(w.type)} ${"Attrs".padEnd(w.attrs)} ${"Status".padEnd(w.status)} ${"Progress".padEnd(w.progress)} Created${RESET}
682
+ `
683
+ );
684
+ for (const r of rows) {
685
+ const sc = statusColor(r.status);
686
+ stdout.write(
687
+ ` ${CYAN}${r.id.padEnd(w.id)}${RESET} ${r.type.padEnd(w.type)} ${DIM}${r.attrs.padEnd(w.attrs)}${RESET} ${sc}${r.status.padEnd(w.status)}${RESET} ${r.progress.padEnd(w.progress)} ${DIM}${r.created}${RESET}
688
+ `
689
+ );
690
+ }
691
+ stdout.write("\n");
692
+ }
693
+ async function cmdEnrichReview(client, rl, jobId) {
694
+ if (!jobId) {
695
+ stdout.write(` ${YELLOW}Usage:${RESET} /enrich review <job_id>
696
+ `);
697
+ return;
698
+ }
699
+ const sp = startSpinner(`Loading conflicts for ${jobId}...`);
700
+ let conflicts;
701
+ try {
702
+ conflicts = await client.enrichConflicts(jobId);
703
+ } catch (err) {
704
+ sp.stop();
705
+ if (err instanceof CographError) printError(err.message);
706
+ else printError(err instanceof Error ? err.message : String(err));
707
+ return;
708
+ }
709
+ sp.stop();
710
+ if (conflicts.length === 0) {
711
+ stdout.write(` ${DIM}No conflicts to review.${RESET}
712
+ `);
713
+ return;
714
+ }
715
+ const decisions = [];
716
+ let acceptAll = false;
717
+ let quitEarly = false;
718
+ for (let i = 0; i < conflicts.length; i++) {
719
+ const c = conflicts[i];
720
+ const entity = lastUriSegment(c.entity_uri);
721
+ const conf = (c.proposed?.confidence ?? 0).toFixed(2);
722
+ stdout.write("\n");
723
+ stdout.write(
724
+ ` ${DIM}[${i + 1}/${conflicts.length}]${RESET} ${BOLD}${entity}${RESET}.${CYAN}${c.attribute}${RESET}
725
+ `
726
+ );
727
+ stdout.write(
728
+ ` ${DIM}existing \u2192${RESET} ${c.existing_value}
729
+ ${DIM}proposed \u2192${RESET} ${BOLD}${c.proposed?.value ?? ""}${RESET} ${DIM}(conf ${conf}, src ${c.proposed?.source ?? "?"})${RESET}
730
+ `
731
+ );
732
+ if (c.proposed?.source_url) {
733
+ stdout.write(` ${DIM}url \u2192${RESET} ${c.proposed.source_url}
734
+ `);
735
+ }
736
+ let decision;
737
+ if (acceptAll) {
738
+ decision = "accept";
739
+ stdout.write(` ${GREEN}auto-accepted${RESET}
740
+ `);
741
+ } else {
742
+ const ans = (await ask(
743
+ rl,
744
+ ` [a]ccept / [r]eject / [s]kip / [A]ccept all remaining / [q]uit (saves progress) [s]: `
745
+ )).trim();
746
+ if (ans === "A") {
747
+ acceptAll = true;
748
+ decision = "accept";
749
+ } else if (ans === "a") {
750
+ decision = "accept";
751
+ } else if (ans === "r") {
752
+ decision = "reject";
753
+ } else if (ans === "q") {
754
+ quitEarly = true;
755
+ break;
756
+ } else {
757
+ decision = "skip";
758
+ }
759
+ }
760
+ decisions.push({ ...c, decision });
761
+ }
762
+ if (quitEarly) {
763
+ if (decisions.length === 0) {
764
+ stdout.write(` ${DIM}No decisions made \u2014 nothing to save.${RESET}
765
+ `);
766
+ return;
767
+ }
768
+ const save = (await ask(rl, ` Save ${decisions.length} decision(s) so far? [Y/n]: `)).trim().toLowerCase();
769
+ if (save !== "" && save !== "y" && save !== "yes") {
770
+ stdout.write(` ${DIM}Discarded.${RESET}
771
+ `);
772
+ return;
773
+ }
774
+ }
775
+ if (decisions.length === 0) {
776
+ stdout.write(` ${DIM}No decisions to apply.${RESET}
777
+ `);
778
+ return;
779
+ }
780
+ const sp2 = startSpinner(`Applying ${decisions.length} decision(s)...`);
781
+ try {
782
+ const res = await client.enrichApply(jobId, decisions);
783
+ sp2.stop();
784
+ stdout.write(
785
+ ` ${GREEN}\u2713${RESET} Applied ${BOLD}${fmtNum(res.applied)}${RESET} change${res.applied === 1 ? "" : "s"}.
786
+ `
787
+ );
788
+ } catch (err) {
789
+ sp2.stop();
790
+ if (err instanceof CographError) printError(err.message);
791
+ else printError(err instanceof Error ? err.message : String(err));
792
+ }
793
+ }
794
+ function urlHost(url) {
795
+ try {
796
+ return new URL(url).host;
797
+ } catch {
798
+ return url.replace(/^https?:\/\//, "").replace(/\/+$/, "");
799
+ }
800
+ }
801
+ function makePrompt(kg, triples, mode = "cloud", baseUrl) {
481
802
  const kgPart = `${DIM}(${kg})${RESET}`;
482
- if (triples > 0) {
483
- return ` ${CYAN_BOLD}cograph${RESET} ${kgPart} ${DIM}[${fmtNum(triples)}]${RESET} ${CYAN_BOLD}\u25B8${RESET} `;
803
+ const triplePart = triples > 0 ? `${DIM}[${fmtNum(triples)}]${RESET} ` : "";
804
+ if (mode === "self-hosted" && baseUrl) {
805
+ const host = urlHost(baseUrl);
806
+ return ` ${CYAN_BOLD}cograph${RESET}${DIM}@${host}${RESET} ${kgPart} ${triplePart}${CYAN_BOLD}\u25B8${RESET} `;
484
807
  }
485
- return ` ${CYAN_BOLD}cograph${RESET} ${kgPart} ${CYAN_BOLD}\u25B8${RESET} `;
808
+ return ` ${CYAN_BOLD}cograph${RESET} ${kgPart} ${triplePart}${CYAN_BOLD}\u25B8${RESET} `;
486
809
  }
487
810
  function splitArgs(s) {
488
811
  const out = [];
@@ -507,8 +830,21 @@ function splitArgs(s) {
507
830
  return out;
508
831
  }
509
832
  async function runShell(opts) {
510
- let client = new Client();
511
- if (!client.apiKey) {
833
+ const CLOUD_DEFAULT = "https://api.cograph.cloud";
834
+ const envUrl = process.env.COGRAPH_API_URL || process.env.OMNIX_API_URL;
835
+ const envIsSelfHosted = !!envUrl && envUrl !== CLOUD_DEFAULT;
836
+ const selfHostedHint = !!opts.local || !!opts.noLogin || envIsSelfHosted;
837
+ let client = opts.local ? new Client({ baseUrl: "http://localhost:8000", tenant: "default" }) : selfHostedHint ? new Client({ tenant: "default" }) : new Client();
838
+ const health = await client.healthCheck();
839
+ if (!health.ok) {
840
+ printError(
841
+ `Could not reach ${health.url}. Is the server running?`
842
+ );
843
+ return;
844
+ }
845
+ const selfHosted = selfHostedHint || !health.requiresAuth;
846
+ const mode = selfHosted ? "self-hosted" : "cloud";
847
+ if (!selfHosted && health.requiresAuth && !client.apiKey) {
512
848
  stdout.write(
513
849
  `
514
850
  ${DIM}Not signed in \u2014 opening your browser to log in...${RESET}
@@ -528,6 +864,13 @@ async function runShell(opts) {
528
864
  terminal: true
529
865
  });
530
866
  showBanner();
867
+ if (selfHosted) {
868
+ stdout.write(
869
+ `${DIM} Self-hosted mode \xB7 ${client.baseUrl} \xB7 tenant=${client.tenant}${RESET}
870
+
871
+ `
872
+ );
873
+ }
531
874
  let kg = opts.kg;
532
875
  if (!kg) {
533
876
  const picked = await selectKg(client, rl);
@@ -564,7 +907,7 @@ async function runShell(opts) {
564
907
  while (running) {
565
908
  let line;
566
909
  try {
567
- line = (await ask(rl, makePrompt(kg, triples))).trim();
910
+ line = (await ask(rl, makePrompt(kg, triples, mode, client.baseUrl))).trim();
568
911
  } catch {
569
912
  break;
570
913
  }
@@ -594,6 +937,29 @@ async function runShell(opts) {
594
937
  } else if (line.startsWith("/type ") || line === "/type") {
595
938
  const arg = line === "/type" ? "" : line.slice("/type ".length);
596
939
  await cmdType(client, kg, rl, arg);
940
+ } else if (line === "/enrich" || line.startsWith("/enrich ")) {
941
+ const args = splitArgs(line.slice("/enrich".length).trim());
942
+ if (args.length === 0) {
943
+ stdout.write(
944
+ ` ${YELLOW}Usage:${RESET} /enrich <Type> <attr> ... | /enrich watch <id> | /enrich jobs | /enrich review <id>
945
+ `
946
+ );
947
+ } else if (args[0] === "jobs") {
948
+ await cmdEnrichJobs(client);
949
+ } else if (args[0] === "watch") {
950
+ const jid = args[1];
951
+ if (!jid) {
952
+ stdout.write(` ${YELLOW}Usage:${RESET} /enrich watch <job_id>
953
+ `);
954
+ } else {
955
+ await watchJob(client, jid);
956
+ }
957
+ } else if (args[0] === "review") {
958
+ await cmdEnrichReview(client, rl, args[1] ?? "");
959
+ } else {
960
+ await cmdEnrichRun(client, kg, rl, args);
961
+ await refresh();
962
+ }
597
963
  } else if (line === "/status") {
598
964
  await cmdStatus(client, kg);
599
965
  await refresh();
@@ -718,7 +1084,7 @@ async function runShell(opts) {
718
1084
  }
719
1085
  } else if (line.startsWith("/")) {
720
1086
  stdout.write(
721
- ` ${YELLOW}Unknown command.${RESET} Try /ingest, /ask, /kg, /types, /type, /login, /status, /reset, /help, /quit
1087
+ ` ${YELLOW}Unknown command.${RESET} Try /ingest, /ask, /kg, /types, /type, /enrich, /login, /status, /reset, /help, /quit
722
1088
  `
723
1089
  );
724
1090
  } else {
@@ -734,4 +1100,4 @@ async function runShell(opts) {
734
1100
  export {
735
1101
  runShell
736
1102
  };
737
- //# sourceMappingURL=shell-2QXHLD4W.js.map
1103
+ //# sourceMappingURL=shell-VPPIRJSS.js.map