@dtoolkit/dcontext 0.1.0 → 0.1.2

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 +24 -15
  2. package/dist/index.js +62 -8
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -60,21 +60,27 @@ dcontext hooks into two moments of a session's lifecycle:
60
60
 
61
61
  The AI receives the context transparently via hooks — it never calls dcontext directly.
62
62
 
63
- ```
64
- ┌─────────────────┐ SessionStart ┌─────────────┐
65
- │ Claude Code │◄───────────────────── │ dcontext │
66
- │ Gemini CLI │ additionalContext │ (hooks) │
67
- │ OpenCode │ │ │
68
- │ │ PreCompact │ │
69
- │ │─────────────────────► │ │
70
- └─────────────────┘ transcript └──────┬───────┘
71
-
72
- search / save
73
-
74
- ┌──────▼───────┐
75
- dbrain │
76
- │ (memory) │
77
- └──────────────┘
63
+ ```mermaid
64
+ graph LR
65
+ subgraph Clients["AI Coding CLIs"]
66
+ claude["Claude Code"]
67
+ gemini["Gemini CLI"]
68
+ opencode["OpenCode"]
69
+ end
70
+
71
+ subgraph Hooks["dcontext (hooks)"]
72
+ hook["SessionStart<br/><small>additionalContext</small><br/><br/>PreCompact<br/><small>transcript</small>"]
73
+ end
74
+
75
+ hook -- "additionalContext" --> Clients
76
+ Clients -- "transcript" --> hook
77
+ hook -- "search / save" --> brain
78
+
79
+ brain["dbrain<br/><strong>memory</strong>"]
80
+
81
+ style Clients fill:#f8fafc,color:#1e293b,stroke:#e2e8f0
82
+ style Hooks fill:#0f3460,color:#fff,stroke:#16213e
83
+ style brain fill:#2563eb,color:#fff,stroke:#1e40af
78
84
  ```
79
85
 
80
86
  ### What gets injected
@@ -85,9 +91,12 @@ The session briefing includes (in order):
85
91
  2. **Soul** — behavioral guidelines
86
92
  3. **User** — who you are (name, timezone)
87
93
  4. **Project facts** — recent decisions, milestones, preferences, context (up to 15 facts, truncated to 200 chars each)
94
+ 5. **Connected brains** — if the personal brain has connections to shared brains, their names and online/offline status
88
95
 
89
96
  Total briefing capped at 8000 characters.
90
97
 
98
+ > When connected to a brain with shared brain connections, dcontext automatically includes federation status in the briefing. The `share` MCP tool is available for pushing facts to team brains.
99
+
91
100
  ### Instruction file integration
92
101
 
93
102
  When you run `dcontext install <target>`, it modifies the dbrain section in the client's instruction file to tell the AI **not to call `recall` at session start** — since the context is already injected. Run `dbrain connect <target>` to restore the original dbrain instructions after uninstalling.
package/dist/index.js CHANGED
@@ -39,9 +39,10 @@ function truncateFact(fact, max) {
39
39
  async function generateBriefing(projectEntity, config) {
40
40
  const client = new DBrainClient(config.dbrain.url, config.dbrain.token);
41
41
  const safeQuery = `"${projectEntity}"`;
42
- const [results, documents] = await Promise.all([
42
+ const [results, documents, health] = await Promise.all([
43
43
  client.search(safeQuery, { limit: config.briefing.maxFacts }).catch(() => []),
44
- config.briefing.includeIdentity ? client.listDocuments() : Promise.resolve([])
44
+ config.briefing.includeIdentity ? client.listDocuments() : Promise.resolve([]),
45
+ client.health().catch(() => null)
45
46
  ]);
46
47
  if (results.length === 0 && documents.length === 0) return null;
47
48
  const parts = [];
@@ -77,6 +78,22 @@ async function generateBriefing(projectEntity, config) {
77
78
  parts.push(`- [${category}] ${truncateFact(r.fact.fact, config.briefing.maxCharsPerFact)}`);
78
79
  }
79
80
  }
81
+ if (health && health.connectedBrains && health.connectedBrains > 0) {
82
+ try {
83
+ const connections = await client.listConnections();
84
+ parts.push("");
85
+ parts.push(`### Connected Brains (${connections.length})`);
86
+ for (const conn of connections) {
87
+ const status = conn.online ? "online" : "offline";
88
+ parts.push(`- **${conn.name}** (${status})${conn.brainName ? ` \u2014 ${conn.brainName}` : ""}`);
89
+ }
90
+ parts.push("");
91
+ parts.push(
92
+ "> `recall` automatically searches these brains. Use `share` to push a fact to a connected brain."
93
+ );
94
+ } catch {
95
+ }
96
+ }
80
97
  let briefing = parts.join("\n");
81
98
  if (briefing.length > config.briefing.maxChars) {
82
99
  briefing = briefing.slice(0, config.briefing.maxChars - 3) + "\u2026";
@@ -173,6 +190,26 @@ async function updateStats(fn) {
173
190
  await ensureDataDir();
174
191
  await atomicWriteFile(join(DATA_DIR, "stats.json"), JSON.stringify(stats, null, 2) + "\n");
175
192
  }
193
+ async function loadSessionOffset(sessionId) {
194
+ try {
195
+ const raw = await readFile(join(DATA_DIR, "sessions.json"), "utf-8");
196
+ const sessions = JSON.parse(raw);
197
+ return sessions[sessionId] ?? 0;
198
+ } catch {
199
+ return 0;
200
+ }
201
+ }
202
+ async function saveSessionOffset(sessionId, offset) {
203
+ await ensureDataDir();
204
+ let sessions = {};
205
+ try {
206
+ const raw = await readFile(join(DATA_DIR, "sessions.json"), "utf-8");
207
+ sessions = JSON.parse(raw);
208
+ } catch {
209
+ }
210
+ sessions[sessionId] = offset;
211
+ await atomicWriteFile(join(DATA_DIR, "sessions.json"), JSON.stringify(sessions, null, 2) + "\n");
212
+ }
176
213
  async function logError(message) {
177
214
  try {
178
215
  await ensureDataDir();
@@ -244,24 +281,28 @@ async function extractAndSave(sessionId, target, cwd, config) {
244
281
  }
245
282
  const filtered = entries.filter((e) => e.content.length >= 50);
246
283
  if (filtered.length === 0) return null;
247
- const messages = filtered.map((e) => ({ role: e.role, content: e.content }));
284
+ const savedOffset = await loadSessionOffset(sessionId);
285
+ const newMessages = filtered.slice(savedOffset).map((e) => ({ role: e.role, content: e.content }));
286
+ if (newMessages.length === 0) return null;
248
287
  try {
249
288
  const client = new DBrainClient2(config.dbrain.url, config.dbrain.token);
250
- const conv = await client.startConversation(`dcontext-${target.name}`);
251
- await client.sendMessages(conv.id, messages);
289
+ await client.startConversation(`dcontext-${target.name}`, sessionId);
290
+ await client.sendMessages(sessionId, newMessages);
252
291
  } catch (err) {
253
292
  await logError(`Failed to save to dbrain: ${err.message}`);
293
+ return null;
254
294
  }
295
+ await saveSessionOffset(sessionId, filtered.length);
255
296
  await updateStats((stats) => {
256
297
  stats.extractions.total++;
257
298
  stats.extractions.lastAt = (/* @__PURE__ */ new Date()).toISOString();
258
- stats.extractions.messagesSaved += messages.length;
299
+ stats.extractions.messagesSaved += newMessages.length;
259
300
  if (!stats.byTarget[target.name]) {
260
301
  stats.byTarget[target.name] = { briefings: 0, extractions: 0 };
261
302
  }
262
303
  stats.byTarget[target.name].extractions++;
263
304
  });
264
- return `Session log saved to dbrain (${messages.length} messages from ${target.name})`;
305
+ return `Session log saved to dbrain (${newMessages.length} new messages from ${target.name})`;
265
306
  }
266
307
 
267
308
  // src/commands/targets.ts
@@ -680,6 +721,19 @@ async function runStatus() {
680
721
  const client = new DBrainClient4(url, token);
681
722
  const health = await client.health();
682
723
  console.log(` ${pc4.green(url)} \u2014 ${health.entities} entities, ${health.facts} facts`);
724
+ const brainType = health.brainType ?? "personal";
725
+ console.log(` Type: ${brainType === "shared" ? pc4.green(brainType) : pc4.blue(brainType)}`);
726
+ if (health.connectedBrains && health.connectedBrains > 0) {
727
+ console.log(` Connected: ${pc4.green(String(health.connectedBrains))} brain(s)`);
728
+ try {
729
+ const conns = await client.listConnections();
730
+ for (const c of conns) {
731
+ const s = c.online ? pc4.green("online") : pc4.red("offline");
732
+ console.log(` ${pc4.bold(c.name)} ${s}`);
733
+ }
734
+ } catch {
735
+ }
736
+ }
683
737
  } catch {
684
738
  console.log(` ${pc4.red(url)} (unreachable)`);
685
739
  }
@@ -798,7 +852,7 @@ var description = `${pc6.green(banner)}
798
852
 
799
853
  ${pc6.green("dbrain hooks for AI coding CLIs")}
800
854
  ${pc6.dim("Part of the dtoolkit suite")}`;
801
- program.name("dcontext").description(description).version("0.1.0");
855
+ program.name("dcontext").description(description).version("0.1.2");
802
856
  var guarded = (cmd) => {
803
857
  cmd.hook("preAction", async () => {
804
858
  await requireInit();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dtoolkit/dcontext",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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.0",
44
- "@dtoolkit/adapter-claude": "1.3.0",
45
- "@dtoolkit/core": "0.4.0",
46
- "@dtoolkit/sdk": "0.3.1",
47
- "@dtoolkit/adapter-opencode": "1.3.0",
48
- "@dtoolkit/adapter-codex": "1.3.0"
43
+ "@dtoolkit/adapter-claude": "1.3.1",
44
+ "@dtoolkit/adapter-codex": "1.3.1",
45
+ "@dtoolkit/adapter-opencode": "1.3.1",
46
+ "@dtoolkit/adapter-gemini": "1.3.1",
47
+ "@dtoolkit/core": "0.4.1",
48
+ "@dtoolkit/sdk": "0.4.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.0.0",