@dtoolkit/dcontext 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +252 -50
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="../../logo.png" alt="dtoolkit" />
2
+ <img src="../../logo.png" alt="dtoolkit" width="420"/>
3
3
  </p>
4
4
 
5
5
  <h1 align="center">@dtoolkit/dcontext</h1>
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { Command } from "commander";
9
9
  import pc from "picocolors";
10
10
 
11
11
  // src/core/briefing.ts
12
- import { DBrainClient } from "@dtoolkit/sdk";
12
+ import { DBrainClient, DWorkClient } from "@dtoolkit/sdk";
13
13
  function cleanDocContent(content, sectionTitle) {
14
14
  const lines = content.split("\n");
15
15
  const cleaned = [];
@@ -36,7 +36,7 @@ function truncateFact(fact, max) {
36
36
  if (fact.length <= max) return fact;
37
37
  return fact.slice(0, max - 1) + "\u2026";
38
38
  }
39
- async function generateBriefing(projectEntity, config) {
39
+ async function generateBriefing(projectEntity, config, cwd) {
40
40
  const client = new DBrainClient(config.dbrain.url, config.dbrain.token);
41
41
  const safeQuery = `"${projectEntity}"`;
42
42
  const [results, documents, health] = await Promise.all([
@@ -94,6 +94,40 @@ async function generateBriefing(projectEntity, config) {
94
94
  } catch {
95
95
  }
96
96
  }
97
+ const dworkSlugs = cwd ? config.dworkProjects?.[cwd] : void 0;
98
+ if (config.dwork?.url && dworkSlugs && dworkSlugs.length > 0) {
99
+ try {
100
+ const dwork = new DWorkClient(config.dwork.url, config.dwork.token);
101
+ const allTasks = (await Promise.all(dworkSlugs.map((slug) => dwork.listTasks(slug).catch(() => [])))).flat();
102
+ const active = allTasks.filter(
103
+ (t) => t.status === "doing" || t.status === "blocked" || t.status === "todo"
104
+ );
105
+ if (active.length > 0) {
106
+ parts.push("");
107
+ parts.push(`### Tasks (dwork)`);
108
+ parts.push(
109
+ "> Active tasks are listed below. **Do NOT call `get_tasks` or `list_projects` unless the user explicitly asks.** Use `update_task` only when the user requests a status change."
110
+ );
111
+ const doing = active.filter((t) => t.status === "doing");
112
+ const blocked = active.filter((t) => t.status === "blocked");
113
+ const todo = active.filter((t) => t.status === "todo");
114
+ for (const group2 of [
115
+ { label: "doing", items: doing },
116
+ { label: "blocked", items: blocked },
117
+ { label: "todo", items: todo }
118
+ ]) {
119
+ for (const t of group2.items) {
120
+ const project = t.project_slug;
121
+ const meta = [t.priority, t.estimate, t.deadline].filter(Boolean).join(", ");
122
+ parts.push(
123
+ `- [${group2.label}] ${t.id} ${project}: ${truncateFact(t.title, 100)}${meta ? ` (${meta})` : ""}`
124
+ );
125
+ }
126
+ }
127
+ }
128
+ } catch {
129
+ }
130
+ }
97
131
  let briefing = parts.join("\n");
98
132
  if (briefing.length > config.briefing.maxChars) {
99
133
  briefing = briefing.slice(0, config.briefing.maxChars - 3) + "\u2026";
@@ -113,6 +147,7 @@ var DEFAULT_CONFIG = {
113
147
  token: ""
114
148
  },
115
149
  projects: {},
150
+ dworkProjects: {},
116
151
  briefing: {
117
152
  maxFacts: 15,
118
153
  includeIdentity: true,
@@ -395,7 +430,7 @@ async function handleSessionStart(target, cwd, config) {
395
430
  process.stdout.write("{}");
396
431
  return;
397
432
  }
398
- const briefing = await generateBriefing(projectEntity, config);
433
+ const briefing = await generateBriefing(projectEntity, config, cwd);
399
434
  if (!briefing) {
400
435
  process.stdout.write("{}");
401
436
  return;
@@ -421,7 +456,7 @@ async function handlePreCompact(sessionId, target, cwd, config) {
421
456
 
422
457
  // src/commands/init.ts
423
458
  import * as p from "@clack/prompts";
424
- import { DBrainClient as DBrainClient3 } from "@dtoolkit/sdk";
459
+ import { DBrainClient as DBrainClient3, DWorkClient as DWorkClient2 } from "@dtoolkit/sdk";
425
460
  import { Command as Command3 } from "commander";
426
461
  import pc2 from "picocolors";
427
462
  function createInitCommand() {
@@ -459,11 +494,18 @@ async function runInit() {
459
494
  }
460
495
  ];
461
496
  if (config.initialized && config.dbrain.url) {
462
- options.unshift({
463
- value: "remap",
464
- label: "Keep dbrain, remap this directory",
465
- description: `Change entity mapping for ${process.cwd()}`
466
- });
497
+ options.unshift(
498
+ {
499
+ value: "keep",
500
+ label: "Keep current config",
501
+ description: "Only update dwork connection"
502
+ },
503
+ {
504
+ value: "remap",
505
+ label: "Keep dbrain, remap this directory",
506
+ description: `Change entity mapping for ${process.cwd()}`
507
+ }
508
+ );
467
509
  }
468
510
  const mode = await p.select({
469
511
  message: config.initialized ? "What do you want to change?" : "How do you want to connect to dbrain?",
@@ -473,7 +515,21 @@ async function runInit() {
473
515
  p.cancel("Init cancelled.");
474
516
  process.exit(0);
475
517
  }
476
- if (mode === "remap") {
518
+ if (mode === "keep") {
519
+ await detectDwork(config);
520
+ config.initialized = true;
521
+ await saveConfig(config);
522
+ p.note(
523
+ [
524
+ `dbrain: ${pc2.green(config.dbrain.url)}`,
525
+ config.dwork ? `dwork: ${pc2.green(config.dwork.url)}` : "",
526
+ `Config: ${pc2.green(getDataDir() + "/config.json")}`
527
+ ].filter(Boolean).join("\n"),
528
+ "Configuration"
529
+ );
530
+ p.outro("Done");
531
+ return;
532
+ } else if (mode === "remap") {
477
533
  await finishInit(config);
478
534
  } else if (mode === "local") {
479
535
  await startLocalDbrain(config);
@@ -635,11 +691,13 @@ async function finishInit(config) {
635
691
  } catch (err) {
636
692
  p.log.warn(`Could not list entities: ${err.message}`);
637
693
  }
694
+ await detectDwork(config);
638
695
  config.initialized = true;
639
696
  await saveConfig(config);
640
697
  p.note(
641
698
  [
642
699
  `dbrain: ${pc2.green(config.dbrain.url)}`,
700
+ config.dwork ? `dwork: ${pc2.green(config.dwork.url)}` : "",
643
701
  entityName ? `Project: ${pc2.green(process.cwd())} \u2192 ${pc2.green(entityName)}` : "",
644
702
  `Config: ${pc2.green(getDataDir() + "/config.json")}`
645
703
  ].filter(Boolean).join("\n"),
@@ -647,6 +705,87 @@ async function finishInit(config) {
647
705
  );
648
706
  p.outro(`Next: ${pc2.cyan("dcontext install claude")}`);
649
707
  }
708
+ async function detectDwork(config) {
709
+ if (config.dwork?.url) {
710
+ try {
711
+ const client = new DWorkClient2(config.dwork.url, config.dwork.token);
712
+ const overview = await client.overview();
713
+ p.log.info(
714
+ `dwork: ${pc2.green(config.dwork.url)} \u2014 ${overview.totalProjects} projects, ${overview.totalTasks} tasks`
715
+ );
716
+ const keep = await p.confirm({
717
+ message: "Keep current dwork connection?",
718
+ initialValue: true
719
+ });
720
+ if (!p.isCancel(keep) && keep) return;
721
+ } catch {
722
+ p.log.warn(`dwork: ${pc2.red(config.dwork.url)} (unreachable)`);
723
+ }
724
+ }
725
+ const connect = await p.confirm({
726
+ message: "Connect to a dwork server? (project management)",
727
+ initialValue: !!config.dwork?.url
728
+ });
729
+ if (p.isCancel(connect) || !connect) {
730
+ config.dwork = void 0;
731
+ return;
732
+ }
733
+ const answers = await p.group(
734
+ {
735
+ url: () => p.text({
736
+ message: "dwork URL",
737
+ initialValue: config.dwork?.url || "http://localhost:7881",
738
+ validate: (v) => !v ? "URL is required" : void 0
739
+ }),
740
+ token: () => p.text({
741
+ message: "dwork token (empty if none)",
742
+ initialValue: config.dwork?.token || ""
743
+ })
744
+ },
745
+ {
746
+ onCancel: () => {
747
+ p.cancel("Init cancelled.");
748
+ process.exit(0);
749
+ }
750
+ }
751
+ );
752
+ const s = p.spinner();
753
+ s.start("Connecting to dwork");
754
+ let projects = [];
755
+ try {
756
+ const client = new DWorkClient2(answers.url, answers.token);
757
+ const listed = await client.listProjects();
758
+ projects = listed.map((pr) => ({ slug: pr.slug, name: pr.name }));
759
+ s.stop(`Connected \u2014 ${pc2.green(String(projects.length))} projects`);
760
+ config.dwork = { url: answers.url, token: answers.token };
761
+ } catch (err) {
762
+ s.stop(pc2.red("Connection failed"));
763
+ p.log.error(err.message);
764
+ p.log.warn("Skipping dwork. You can add it later with dcontext init.");
765
+ config.dwork = void 0;
766
+ return;
767
+ }
768
+ if (projects.length > 0) {
769
+ const cwd = process.cwd();
770
+ const current = config.dworkProjects[cwd] || [];
771
+ const selected = await p.multiselect({
772
+ message: `Map ${pc2.dim(cwd)} to dwork projects:`,
773
+ options: projects.map((pr) => ({
774
+ value: pr.slug,
775
+ label: `${pr.name} (${pr.slug})`,
776
+ selected: current.includes(pr.slug)
777
+ })),
778
+ required: false
779
+ });
780
+ if (!p.isCancel(selected)) {
781
+ if (selected.length > 0) {
782
+ config.dworkProjects[cwd] = selected;
783
+ } else {
784
+ config.dworkProjects[cwd] = [];
785
+ }
786
+ }
787
+ }
788
+ }
650
789
  async function requireInit() {
651
790
  const config = await loadConfig();
652
791
  if (!config.initialized) {
@@ -693,7 +832,7 @@ async function runInstall(targetName) {
693
832
  }
694
833
 
695
834
  // src/commands/status.ts
696
- import { DBrainClient as DBrainClient4 } from "@dtoolkit/sdk";
835
+ import { DBrainClient as DBrainClient4, DWorkClient as DWorkClient3 } from "@dtoolkit/sdk";
697
836
  import { Command as Command5 } from "commander";
698
837
  import pc4 from "picocolors";
699
838
  function createStatusCommand() {
@@ -706,99 +845,162 @@ function createStatusCommand() {
706
845
  }
707
846
  });
708
847
  }
848
+ function section(title) {
849
+ console.log(` ${pc4.bold(pc4.blue(title))}`);
850
+ }
851
+ function row(label, value) {
852
+ console.log(` ${pc4.dim(label.padEnd(12))} ${value}`);
853
+ }
854
+ function sep() {
855
+ console.log();
856
+ }
709
857
  async function runStatus() {
710
858
  const config = await loadConfig();
711
859
  const stats = await loadStats();
712
- console.log(pc4.bold("dcontext status"));
713
- console.log();
714
- console.log(pc4.bold("dbrain"));
715
860
  const { url, token } = config.dbrain;
861
+ console.log();
862
+ console.log(` ${pc4.bold("dcontext")} ${pc4.dim("status")}`);
863
+ console.log(` ${pc4.dim("\u2500".repeat(40))}`);
864
+ sep();
865
+ section("Services");
866
+ sep();
716
867
  if (url) {
717
868
  try {
718
869
  const client = new DBrainClient4(url, token);
719
870
  const health = await client.health();
720
- console.log(` ${pc4.green(url)} \u2014 ${health.entities} entities, ${health.facts} facts`);
721
871
  const brainType = health.brainType ?? "personal";
722
- console.log(` Type: ${brainType === "shared" ? pc4.green(brainType) : pc4.blue(brainType)}`);
872
+ row(
873
+ "dbrain",
874
+ `${pc4.green("\u25CF")} ${url} ${pc4.dim("\u2014")} ${health.entities} entities, ${health.facts} facts ${pc4.dim(`(${brainType})`)}`
875
+ );
723
876
  if (health.connectedBrains && health.connectedBrains > 0) {
724
- console.log(` Connected: ${pc4.green(String(health.connectedBrains))} brain(s)`);
725
877
  try {
726
878
  const conns = await client.listConnections();
727
879
  for (const c of conns) {
728
- const s = c.online ? pc4.green("online") : pc4.red("offline");
729
- console.log(` ${pc4.bold(c.name)} ${s}`);
880
+ const s = c.online ? pc4.green("\u25CF") : pc4.red("\u25CF");
881
+ row("", `${s} ${c.name}`);
730
882
  }
731
883
  } catch {
732
884
  }
733
885
  }
734
886
  } catch {
735
- console.log(` ${pc4.red(url)} (unreachable)`);
887
+ row("dbrain", `${pc4.red("\u25CF")} ${url} ${pc4.dim("(unreachable)")}`);
736
888
  }
737
889
  } else {
738
- console.log(` ${pc4.red("not configured")} \u2014 run ${pc4.cyan("dcontext init")}`);
890
+ row("dbrain", `${pc4.red("\u25CF")} ${pc4.dim("not configured")}`);
739
891
  }
740
- console.log();
741
- console.log(pc4.bold("Targets"));
892
+ if (config.dwork?.url) {
893
+ try {
894
+ const dworkClient = new DWorkClient3(config.dwork.url, config.dwork.token);
895
+ const overview = await dworkClient.overview();
896
+ row(
897
+ "dwork",
898
+ `${pc4.green("\u25CF")} ${config.dwork.url} ${pc4.dim("\u2014")} ${overview.totalProjects} projects, ${overview.totalTasks} tasks`
899
+ );
900
+ } catch {
901
+ row("dwork", `${pc4.red("\u25CF")} ${config.dwork.url} ${pc4.dim("(unreachable)")}`);
902
+ }
903
+ } else {
904
+ row("dwork", `${pc4.dim("\u2014")} ${pc4.dim("not configured")}`);
905
+ }
906
+ sep();
907
+ section("Targets");
908
+ sep();
742
909
  for (const name of ["claude", "gemini", "opencode"]) {
743
910
  const target = resolveTarget(name);
744
911
  if (!target) continue;
745
912
  const installed = await target.isInstalled();
746
913
  const cfg = config.targets[name];
747
914
  if (installed) {
748
- const since = cfg?.installedAt ? ` (since ${cfg.installedAt.split("T")[0]})` : "";
749
- console.log(` ${name}: ${pc4.green("installed")}${pc4.dim(since)}`);
915
+ const since = cfg?.installedAt ? pc4.dim(` since ${cfg.installedAt.split("T")[0]}`) : "";
916
+ row(name, `${pc4.green("\u25CF")} installed${since}`);
750
917
  } else {
751
- console.log(` ${name}: ${pc4.dim("not installed")}`);
918
+ row(name, `${pc4.dim("\u25CB")} ${pc4.dim("not installed")}`);
752
919
  }
753
920
  }
754
- console.log();
921
+ sep();
755
922
  const cwd = process.cwd();
756
923
  const entity = config.projects[cwd];
757
- console.log(pc4.bold("Context") + pc4.dim(` (${cwd})`));
924
+ const dworkSlugs = config.dworkProjects?.[cwd];
925
+ section("Context");
926
+ row("cwd", pc4.dim(cwd));
927
+ sep();
758
928
  if (entity) {
759
- console.log(` Entity: ${pc4.blue(entity)}`);
929
+ row("entity", pc4.blue(entity));
760
930
  if (url) {
761
931
  try {
762
932
  const client = new DBrainClient4(url, token);
763
933
  const results = await client.search(`"${entity}"`, { limit: 5 });
764
934
  if (results.length > 0) {
765
- console.log(` Facts: ${pc4.green(String(results.length))}+ matching`);
935
+ row("facts", `${pc4.green(String(results.length))}+ matching`);
766
936
  for (const r of results.slice(0, 3)) {
767
- console.log(` ${pc4.dim("\xB7")} ${r.fact.fact.slice(0, 80)}`);
937
+ console.log(` ${pc4.dim(" \xB7 " + r.fact.fact.slice(0, 72))}`);
768
938
  }
769
939
  if (results.length > 3) {
770
- console.log(` ${pc4.dim(`... and ${results.length - 3} more`)}`);
940
+ console.log(` ${pc4.dim(` \u2026 and ${results.length - 3} more`)}`);
771
941
  }
772
942
  } else {
773
- console.log(` Facts: ${pc4.dim("none found")}`);
943
+ row("facts", pc4.dim("none"));
774
944
  }
775
945
  } catch {
776
- console.log(` Facts: ${pc4.dim("could not query")}`);
946
+ row("facts", pc4.dim("could not query"));
777
947
  }
778
948
  }
779
949
  } else {
780
- console.log(
781
- ` ${pc4.dim("no entity mapped")} \u2014 run ${pc4.cyan("dcontext init")} in this directory`
782
- );
950
+ row("entity", `${pc4.dim("not mapped")} \u2014 run ${pc4.cyan("dcontext init")}`);
783
951
  }
784
- console.log();
952
+ if (dworkSlugs && dworkSlugs.length > 0) {
953
+ row("dwork", dworkSlugs.map((s) => pc4.blue(s)).join(pc4.dim(", ")));
954
+ if (config.dwork?.url) {
955
+ try {
956
+ const dworkClient = new DWorkClient3(config.dwork.url, config.dwork.token);
957
+ let totalTasks = 0;
958
+ let totalDocs = 0;
959
+ for (const slug of dworkSlugs) {
960
+ const tasks = await dworkClient.listTasks(slug).catch(() => []);
961
+ const docs = await dworkClient.listDocs(slug).catch(() => []);
962
+ totalTasks += tasks.length;
963
+ totalDocs += docs.length;
964
+ }
965
+ row("tasks", String(totalTasks));
966
+ row("docs", String(totalDocs));
967
+ } catch {
968
+ }
969
+ }
970
+ }
971
+ sep();
785
972
  const projects = Object.entries(config.projects).filter(([path]) => path !== cwd);
786
973
  if (projects.length > 0) {
787
- console.log(pc4.bold("Other projects"));
974
+ section("Other projects");
975
+ sep();
788
976
  for (const [path, ent] of projects) {
789
- console.log(` ${pc4.dim(path)} \u2192 ${pc4.blue(ent)}`);
977
+ const short = path.replace(/^\/Users\/[^/]+/, "~");
978
+ row(ent, pc4.dim(short));
790
979
  }
791
- console.log();
980
+ sep();
792
981
  }
793
- console.log(pc4.bold("Stats"));
794
- console.log(
795
- ` Briefings: ${stats.briefings.total}${stats.briefings.lastAt ? pc4.dim(` (last: ${stats.briefings.lastAt.split("T")[0]})`) : ""}`
796
- );
797
- console.log(
798
- ` Extractions: ${stats.extractions.total}${stats.extractions.messagesSaved ? ` (${stats.extractions.messagesSaved} messages)` : ""}${stats.extractions.lastAt ? pc4.dim(` (last: ${stats.extractions.lastAt.split("T")[0]})`) : ""}`
799
- );
982
+ section("Stats");
983
+ sep();
984
+ const lastBriefing = stats.briefings.lastAt ? pc4.dim(` last ${stats.briefings.lastAt.split("T")[0]}`) : "";
985
+ row("briefings", `${stats.briefings.total}${lastBriefing}`);
986
+ const msgCount = stats.extractions.messagesSaved ? ` ${pc4.dim(`(${stats.extractions.messagesSaved} msgs)`)}` : "";
987
+ const lastExtraction = stats.extractions.lastAt ? pc4.dim(` last ${stats.extractions.lastAt.split("T")[0]}`) : "";
988
+ row("extractions", `${stats.extractions.total}${msgCount}${lastExtraction}`);
989
+ if (config.dwork?.url) {
990
+ try {
991
+ const dworkClient = new DWorkClient3(config.dwork.url, config.dwork.token);
992
+ const overview = await dworkClient.overview();
993
+ const activeTasks = overview.totalTasks;
994
+ row(
995
+ "tasks",
996
+ `${activeTasks} ${pc4.dim(`across ${overview.totalProjects} projects, ${overview.totalDocs} docs`)}`
997
+ );
998
+ } catch {
999
+ }
1000
+ }
1001
+ sep();
1002
+ console.log(` ${pc4.dim(`Config: ${getDataDir()}/config.json`)}`);
800
1003
  console.log();
801
- console.log(pc4.dim(`Config: ${getDataDir()}/config.json`));
802
1004
  }
803
1005
 
804
1006
  // src/commands/uninstall.ts
@@ -849,7 +1051,7 @@ var description = `${pc6.green(banner)}
849
1051
 
850
1052
  ${pc6.green("dbrain hooks for AI coding CLIs")}
851
1053
  ${pc6.dim("Part of the dtoolkit suite")}`;
852
- program.name("dcontext").description(description).version("0.1.4");
1054
+ program.name("dcontext").description(description).version("0.2.0");
853
1055
  var guarded = (cmd) => {
854
1056
  cmd.hook("preAction", async () => {
855
1057
  await requireInit();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtoolkit/dcontext",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "dbrain hooks for AI coding CLIs",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,12 +40,12 @@
40
40
  "@clack/prompts": "^1.3.0",
41
41
  "commander": "^13.0.0",
42
42
  "picocolors": "^1.1.0",
43
- "@dtoolkit/adapter-gemini": "1.3.2",
44
- "@dtoolkit/adapter-claude": "1.3.2",
45
43
  "@dtoolkit/core": "0.5.0",
46
- "@dtoolkit/adapter-opencode": "1.3.2",
47
44
  "@dtoolkit/adapter-codex": "1.3.2",
48
- "@dtoolkit/sdk": "0.4.1"
45
+ "@dtoolkit/sdk": "0.5.1",
46
+ "@dtoolkit/adapter-claude": "1.3.2",
47
+ "@dtoolkit/adapter-opencode": "1.3.2",
48
+ "@dtoolkit/adapter-gemini": "1.3.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.0.0",