@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.
- package/README.md +24 -15
- package/dist/index.js +62 -8
- 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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
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
|
-
|
|
251
|
-
await client.sendMessages(
|
|
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 +=
|
|
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 (${
|
|
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.
|
|
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.
|
|
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-
|
|
44
|
-
"@dtoolkit/adapter-
|
|
45
|
-
"@dtoolkit/
|
|
46
|
-
"@dtoolkit/
|
|
47
|
-
"@dtoolkit/
|
|
48
|
-
"@dtoolkit/
|
|
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",
|