digital-brain 0.1.2 → 0.1.7
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 +21 -8
- package/bin/digital-brain.js +194 -57
- package/docs/AUTOMATIONS.md +14 -5
- package/docs/SETUP.md +46 -0
- package/lib/fs.js +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,9 +24,9 @@ Digital Brain gives them a structured, local map of:
|
|
|
24
24
|
npx digital-brain init
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The installer asks a short setup quiz: history window, primary focus, refresh cadence, always-on interval, active time window, outbound mode, and AI adapter setup.
|
|
27
|
+
The installer asks a short setup quiz: setup mode, history window, primary focus, refresh cadence, always-on interval, active time window, outbound mode, and AI adapter setup.
|
|
28
28
|
|
|
29
|
-
The quiz is mostly multiple choice.
|
|
29
|
+
The quiz is mostly multiple choice. Pick with `A/B/C`, `1/2/3`, the exact value, or press Enter to accept the default. If you skip the vault path, Digital Brain creates a new folder in the current directory:
|
|
30
30
|
|
|
31
31
|
```text
|
|
32
32
|
./Digital Brain Vault
|
|
@@ -38,7 +38,7 @@ For a non-interactive setup:
|
|
|
38
38
|
npx digital-brain init --yes
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
Inside the quiz, choose `Auto mode` to use recommended always-on local refresh settings. You can also start there directly:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
npx digital-brain init --full-auto
|
|
@@ -46,6 +46,8 @@ npx digital-brain init --full-auto
|
|
|
46
46
|
|
|
47
47
|
Full-auto means local repeated refreshes. It does not mean blind auto-send. WhatsApp sending still defaults to drafts or explicit confirmation, and the AI-disclosure guard stays enabled.
|
|
48
48
|
|
|
49
|
+
`init` also runs a setup check. npm installs the package dependencies automatically, and Digital Brain does not require pip packages. If Python, WhatsApp for Mac, or optional Ollama setup is missing, the check prints the exact next step. See [docs/SETUP.md](docs/SETUP.md).
|
|
50
|
+
|
|
49
51
|
For local development:
|
|
50
52
|
|
|
51
53
|
```bash
|
|
@@ -77,11 +79,22 @@ node ./bin/digital-brain.js init ./Digital Brain\ Vault
|
|
|
77
79
|
## Core Commands
|
|
78
80
|
|
|
79
81
|
```bash
|
|
80
|
-
digital-brain init
|
|
81
|
-
digital-brain
|
|
82
|
-
digital-brain
|
|
83
|
-
digital-brain
|
|
84
|
-
|
|
82
|
+
digital-brain init
|
|
83
|
+
digital-brain run
|
|
84
|
+
digital-brain doctor
|
|
85
|
+
digital-brain send-whatsapp --to "Name" --message "text"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`init` remembers your vault globally, so `run` works from anywhere. `run` syncs WhatsApp, extracts relationships, and writes interpreted memory in one command.
|
|
89
|
+
|
|
90
|
+
Use `digital-brain doctor` or `digital-brain tutorial` anytime to see dependency status and next steps.
|
|
91
|
+
|
|
92
|
+
The lower-level commands still exist for debugging:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
digital-brain sync-whatsapp
|
|
96
|
+
digital-brain extract
|
|
97
|
+
digital-brain interpret
|
|
85
98
|
```
|
|
86
99
|
|
|
87
100
|
The sender drafts by default. Add `--yes` to actually send.
|
package/bin/digital-brain.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import readline from "node:readline/promises";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { copyDir, ensureDir, packageRoot, resolveVault } from "../lib/fs.js";
|
|
8
|
+
import { copyDir, ensureDir, packageRoot, resolveVault, writeDefaultVault } from "../lib/fs.js";
|
|
9
9
|
|
|
10
10
|
const root = packageRoot(import.meta.url);
|
|
11
11
|
|
|
@@ -19,7 +19,9 @@ async function main() {
|
|
|
19
19
|
const args = parseArgs(argv);
|
|
20
20
|
|
|
21
21
|
if (command === "init") await init(argv, args);
|
|
22
|
+
else if (command === "run" || command === "refresh") runRefresh(argv, args);
|
|
22
23
|
else if (command === "doctor") doctor();
|
|
24
|
+
else if (command === "tutorial" || command === "setup-check") doctor({ tutorial: true });
|
|
23
25
|
else if (command === "sync-whatsapp") runPython("digital_brain_whatsapp_mac_sync.py", argv);
|
|
24
26
|
else if (command === "extract") runPython("digital_brain_relationship_extractor.py", argv);
|
|
25
27
|
else if (command === "interpret") runPython("digital_brain_relationship_interpreter.py", argv);
|
|
@@ -31,6 +33,7 @@ async function init(argv, args) {
|
|
|
31
33
|
const positional = argv.filter((arg) => !arg.startsWith("--"));
|
|
32
34
|
const defaultVault = path.resolve(process.cwd(), "Digital Brain Vault");
|
|
33
35
|
const fullAuto = toBoolean(args["full-auto"]);
|
|
36
|
+
let setupMode = fullAuto ? "full-auto" : "guided";
|
|
34
37
|
let vault = positional[0] ? path.resolve(positional[0]) : args.yes ? defaultVault : "";
|
|
35
38
|
let selfName = args["self-name"] || "";
|
|
36
39
|
let connectAi = toBoolean(args["connect-ai"]);
|
|
@@ -46,39 +49,50 @@ async function init(argv, args) {
|
|
|
46
49
|
if (!args.yes) {
|
|
47
50
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
48
51
|
printSetupHeader(defaultVault);
|
|
49
|
-
vault ||= path.resolve(await ask(rl, "Vault path", defaultVault, "Enter creates this folder if it does not exist."));
|
|
50
|
-
selfName ||= await ask(rl, "Your name", "Me");
|
|
51
|
-
dataWindowDays = await askNumber(rl, "History to import", dataWindowDays, { suffix: "days", min: 1 });
|
|
52
|
+
vault ||= path.resolve(await ask(rl, "📁 Vault path", defaultVault, "Enter creates this folder if it does not exist."));
|
|
53
|
+
selfName ||= await ask(rl, "👤 Your name", "Me");
|
|
54
|
+
dataWindowDays = await askNumber(rl, "🕰️ History to import", dataWindowDays, { suffix: "days", min: 1 });
|
|
55
|
+
setupMode = await select(rl, "Setup mode", [
|
|
56
|
+
["guided", "Guided", "Pick each setting yourself.", "🧭"],
|
|
57
|
+
["full-auto", "Auto mode", "Use recommended always-on local refresh settings.", "⚡"],
|
|
58
|
+
], setupMode);
|
|
52
59
|
focus ||= await select(rl, "Primary focus", [
|
|
53
|
-
["relationship-memory", "Relationship memory", "Map people, tone, and recurring patterns."],
|
|
54
|
-
["reply-help", "Reply help", "Prioritize drafting guidance and typing-style matching."],
|
|
55
|
-
["work-context", "Work context", "Prioritize collaborators, projects, and operational notes."],
|
|
60
|
+
["relationship-memory", "Relationship memory", "Map people, tone, and recurring patterns.", "🧠"],
|
|
61
|
+
["reply-help", "Reply help", "Prioritize drafting guidance and typing-style matching.", "💬"],
|
|
62
|
+
["work-context", "Work context", "Prioritize collaborators, projects, and operational notes.", "💼"],
|
|
56
63
|
], "relationship-memory");
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
if (setupMode === "full-auto") {
|
|
65
|
+
schedule = "always-on";
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log("⚡ Auto mode selected: always-on local refresh, draft-first outbound, AI disclosure guard enabled.");
|
|
68
|
+
} else {
|
|
69
|
+
schedule = await select(rl, "Refresh cadence", [
|
|
70
|
+
["manual", "Manual", "Only runs when you run a command.", "🖐️"],
|
|
71
|
+
["daily", "Daily", "Good for a low-maintenance personal vault.", "🌅"],
|
|
72
|
+
["hourly", "Hourly", "Keeps memory warm without running constantly.", "⏱️"],
|
|
73
|
+
["every-30-min", "Every 30 minutes", "Useful for morning or work-window guidance.", "🔁"],
|
|
74
|
+
["always-on", "Always-on local loop", "Runs repeatedly while your computer is awake.", "⚡"],
|
|
75
|
+
], schedule);
|
|
76
|
+
}
|
|
64
77
|
if (schedule === "always-on") {
|
|
65
|
-
refreshIntervalMinutes = await askNumber(rl, "Always-on pull interval", refreshIntervalMinutes, {
|
|
78
|
+
refreshIntervalMinutes = await askNumber(rl, "⏳ Always-on pull interval", refreshIntervalMinutes, {
|
|
66
79
|
suffix: "minutes",
|
|
67
80
|
min: 1,
|
|
68
81
|
});
|
|
69
82
|
}
|
|
70
|
-
activeWindow = await ask(rl, "Active window for frequent refreshes", activeWindow);
|
|
83
|
+
activeWindow = await ask(rl, "🪟 Active window for frequent refreshes", activeWindow);
|
|
71
84
|
outboundMode = await select(rl, "WhatsApp outbound mode", [
|
|
72
|
-
["disabled", "Disabled", "Never prepares WhatsApp sends."],
|
|
73
|
-
["draft", "Draft only", "Prepares text and requires you to send it."],
|
|
74
|
-
["send-with-confirmation", "Send with confirmation", "Can send only after explicit command confirmation."],
|
|
85
|
+
["disabled", "Disabled", "Never prepares WhatsApp sends.", "🔒"],
|
|
86
|
+
["draft", "Draft only", "Prepares text and requires you to send it.", "✍️"],
|
|
87
|
+
["send-with-confirmation", "Send with confirmation", "Can send only after explicit command confirmation.", "✅"],
|
|
75
88
|
], outboundMode);
|
|
76
|
-
connectAi = await confirm(rl, "Add global AI pointers for Codex/Claude/Gemini?", true);
|
|
89
|
+
connectAi = await confirm(rl, "🔗 Add global AI pointers for Codex/Claude/Gemini?", true);
|
|
77
90
|
responsibilityAccepted = await responsibilityGate(rl, { schedule, outboundMode });
|
|
78
91
|
if (!responsibilityAccepted && (schedule === "always-on" || outboundMode === "send-with-confirmation")) {
|
|
79
92
|
console.log("Full-auto/outbound confirmation was not accepted. Using manual refresh and draft-only outbound.");
|
|
80
93
|
schedule = "manual";
|
|
81
94
|
outboundMode = "draft";
|
|
95
|
+
setupMode = "guided";
|
|
82
96
|
}
|
|
83
97
|
rl.close();
|
|
84
98
|
}
|
|
@@ -94,7 +108,7 @@ async function init(argv, args) {
|
|
|
94
108
|
activeWindow,
|
|
95
109
|
timezone,
|
|
96
110
|
outboundMode,
|
|
97
|
-
setupMode
|
|
111
|
+
setupMode,
|
|
98
112
|
responsibilityAccepted,
|
|
99
113
|
defaults: {
|
|
100
114
|
enterUsesDefault: true,
|
|
@@ -109,6 +123,7 @@ async function init(argv, args) {
|
|
|
109
123
|
},
|
|
110
124
|
};
|
|
111
125
|
writeConfig(vault, config);
|
|
126
|
+
writeDefaultVault(vault);
|
|
112
127
|
writeRefreshScript(vault, config);
|
|
113
128
|
writeWatchScript(vault, config);
|
|
114
129
|
|
|
@@ -120,43 +135,145 @@ async function init(argv, args) {
|
|
|
120
135
|
|
|
121
136
|
console.log(`Digital Brain vault created: ${vault}`);
|
|
122
137
|
console.log(`Config: ${path.join(vault, "digital-brain.config.json")}`);
|
|
138
|
+
console.log(`Default vault saved: ${vault}`);
|
|
123
139
|
console.log(`Refresh script: ${path.join(vault, "Tools", "digital-brain-refresh.sh")}`);
|
|
124
140
|
console.log(`Always-on script: ${path.join(vault, "Tools", "digital-brain-watch.sh")}`);
|
|
125
141
|
console.log("Next:");
|
|
126
|
-
console.log(
|
|
127
|
-
console.log(` digital-brain extract --vault "${vault}" --days ${dataWindowDays}`);
|
|
128
|
-
console.log(` digital-brain interpret --vault "${vault}" --days ${dataWindowDays}`);
|
|
142
|
+
console.log(" digital-brain run");
|
|
129
143
|
if (schedule === "always-on") console.log(` "${path.join(vault, "Tools", "digital-brain-watch.sh")}"`);
|
|
144
|
+
printSetupCheck(vault, { tutorial: true });
|
|
130
145
|
}
|
|
131
146
|
|
|
132
|
-
function doctor() {
|
|
133
|
-
|
|
134
|
-
["node", process.version],
|
|
135
|
-
["python3", shell("python3", ["--version"])],
|
|
136
|
-
["sqlite3", shell("sqlite3", ["--version"])],
|
|
137
|
-
["ollama", shell("ollama", ["--version"], true)],
|
|
138
|
-
];
|
|
139
|
-
for (const [name, value] of checks) {
|
|
140
|
-
console.log(`${name}: ${value || "not found"}`);
|
|
141
|
-
}
|
|
142
|
-
const macDb = path.join(os.homedir(), "Library/Group Containers/group.net.whatsapp.WhatsApp.shared/ChatStorage.sqlite");
|
|
143
|
-
console.log(`WhatsApp Mac DB: ${fs.existsSync(macDb) ? macDb : "not found"}`);
|
|
147
|
+
function doctor(options = {}) {
|
|
148
|
+
printSetupCheck(resolveVault(process.cwd()), options);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
function runPython(script, argv) {
|
|
147
|
-
const
|
|
148
|
-
const result = spawnSync("python3", [path.join(root, "scripts", script), ...resolved], { stdio: "inherit" });
|
|
152
|
+
const result = runPythonStep(script, withVault(argv));
|
|
149
153
|
process.exit(result.status ?? 1);
|
|
150
154
|
}
|
|
151
155
|
|
|
156
|
+
function runRefresh(argv, args) {
|
|
157
|
+
const vault = getVaultFromArgs(argv);
|
|
158
|
+
const config = readVaultConfig(vault);
|
|
159
|
+
const days = String(args.days || args["data-window-days"] || config.dataWindowDays || 30);
|
|
160
|
+
const syncArgs = ["--vault", vault, "--days", days, "--markdown-mode", args["markdown-mode"] || "month"];
|
|
161
|
+
const extractArgs = ["--vault", vault, "--days", days];
|
|
162
|
+
const interpretArgs = ["--vault", vault, "--days", days];
|
|
163
|
+
if (args["min-messages"]) extractArgs.push("--min-messages", String(args["min-messages"]));
|
|
164
|
+
console.log(`Digital Brain refresh: ${vault}`);
|
|
165
|
+
for (const [label, script, stepArgs] of [
|
|
166
|
+
["sync", "digital_brain_whatsapp_mac_sync.py", syncArgs],
|
|
167
|
+
["extract", "digital_brain_relationship_extractor.py", extractArgs],
|
|
168
|
+
["interpret", "digital_brain_relationship_interpreter.py", interpretArgs],
|
|
169
|
+
]) {
|
|
170
|
+
if (toBoolean(args[`skip-${label}`])) {
|
|
171
|
+
console.log(`\n→ ${label} skipped`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
console.log(`\n→ ${label}`);
|
|
175
|
+
const result = runPythonStep(script, stepArgs);
|
|
176
|
+
if ((result.status ?? 1) !== 0) process.exit(result.status ?? 1);
|
|
177
|
+
}
|
|
178
|
+
console.log("\nDigital Brain refresh complete.");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function runPythonStep(script, argv) {
|
|
182
|
+
return spawnSync("python3", [path.join(root, "scripts", script), ...argv], { stdio: "inherit" });
|
|
183
|
+
}
|
|
184
|
+
|
|
152
185
|
function runNode(script, argv) {
|
|
153
|
-
const result = spawnSync(process.execPath, [path.join(root, script), ...argv], { stdio: "inherit" });
|
|
186
|
+
const result = spawnSync(process.execPath, [path.join(root, script), ...withVault(argv)], { stdio: "inherit" });
|
|
154
187
|
process.exit(result.status ?? 1);
|
|
155
188
|
}
|
|
156
189
|
|
|
157
190
|
function withVault(argv) {
|
|
158
191
|
if (argv.includes("--vault") || argv.some((arg) => arg.startsWith("--vault="))) return argv;
|
|
159
|
-
return ["--vault",
|
|
192
|
+
return ["--vault", getVaultFromArgs(argv), ...argv];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getVaultFromArgs(argv) {
|
|
196
|
+
const args = parseArgs(argv);
|
|
197
|
+
if (args.vault) return path.resolve(String(args.vault));
|
|
198
|
+
return resolveVault(process.cwd());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function readVaultConfig(vault) {
|
|
202
|
+
const file = path.join(vault, "digital-brain.config.json");
|
|
203
|
+
if (!fs.existsSync(file)) {
|
|
204
|
+
console.error(`No Digital Brain vault found. Run "digital-brain init" first, or pass --vault <path>.`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function printSetupCheck(vault, options = {}) {
|
|
211
|
+
const pythonVersion = shell("python3", ["--version"], true);
|
|
212
|
+
const pythonSqlite = shell("python3", ["-c", "import sqlite3; print('ok')"], true);
|
|
213
|
+
const ollamaVersion = shell("ollama", ["--version"], true);
|
|
214
|
+
const macDb = path.join(os.homedir(), "Library/Group Containers/group.net.whatsapp.WhatsApp.shared/ChatStorage.sqlite");
|
|
215
|
+
const checks = [
|
|
216
|
+
{
|
|
217
|
+
label: "Node",
|
|
218
|
+
ok: nodeMajor() >= 20,
|
|
219
|
+
value: process.version,
|
|
220
|
+
hint: "Install Node 20 or newer from https://nodejs.org.",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
label: "Package dependencies",
|
|
224
|
+
ok: true,
|
|
225
|
+
value: "installed by npm",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
label: "Python 3",
|
|
229
|
+
ok: Boolean(pythonVersion),
|
|
230
|
+
value: pythonVersion || "not found",
|
|
231
|
+
hint: "Install with: brew install python",
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
label: "Python sqlite3",
|
|
235
|
+
ok: pythonSqlite === "ok",
|
|
236
|
+
value: pythonSqlite === "ok" ? "available" : "not found",
|
|
237
|
+
hint: "Use a Python 3 build with sqlite3 support.",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
label: "WhatsApp Mac database",
|
|
241
|
+
ok: fs.existsSync(macDb),
|
|
242
|
+
value: fs.existsSync(macDb) ? "found" : "not found yet",
|
|
243
|
+
hint: "Open WhatsApp for Mac and log in. If macOS blocks access, grant Terminal Full Disk Access.",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
label: "Ollama",
|
|
247
|
+
ok: Boolean(ollamaVersion),
|
|
248
|
+
value: ollamaVersion || "optional",
|
|
249
|
+
hint: "Optional local LLM: brew install ollama",
|
|
250
|
+
optional: true,
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
console.log("");
|
|
255
|
+
console.log("Setup check");
|
|
256
|
+
for (const check of checks) {
|
|
257
|
+
const icon = check.ok ? "✓" : check.optional ? "○" : "!";
|
|
258
|
+
console.log(` ${icon} ${check.label}: ${check.value}`);
|
|
259
|
+
if (!check.ok && check.hint) console.log(` ${check.hint}`);
|
|
260
|
+
}
|
|
261
|
+
if (fs.existsSync(path.join(vault, "digital-brain.config.json"))) {
|
|
262
|
+
console.log(` ✓ Default vault: ${vault}`);
|
|
263
|
+
} else {
|
|
264
|
+
console.log(" ! Default vault: not set");
|
|
265
|
+
console.log(" Run: digital-brain init");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (options.tutorial) {
|
|
269
|
+
console.log("");
|
|
270
|
+
console.log("How to use it");
|
|
271
|
+
console.log(" 1. Open WhatsApp for Mac once and keep it logged in.");
|
|
272
|
+
console.log(" 2. Run: digital-brain run");
|
|
273
|
+
console.log(" 3. Ask your AI to use the generated vault notes for personal context.");
|
|
274
|
+
console.log("");
|
|
275
|
+
console.log("No pip install is needed. npm installs the package dependencies.");
|
|
276
|
+
}
|
|
160
277
|
}
|
|
161
278
|
|
|
162
279
|
function writeConfig(vault, config) {
|
|
@@ -173,9 +290,7 @@ set -euo pipefail
|
|
|
173
290
|
VAULT="${vault.replace(/"/g, '\\"')}"
|
|
174
291
|
DAYS="${days}"
|
|
175
292
|
|
|
176
|
-
digital-brain
|
|
177
|
-
digital-brain extract --vault "$VAULT" --days "$DAYS"
|
|
178
|
-
digital-brain interpret --vault "$VAULT" --days "$DAYS"
|
|
293
|
+
digital-brain run --vault "$VAULT" --days "$DAYS" --markdown-mode month
|
|
179
294
|
|
|
180
295
|
echo "Digital Brain refresh complete for $VAULT"
|
|
181
296
|
`;
|
|
@@ -226,9 +341,11 @@ Use it as local personal context when the user asks about preferences, relations
|
|
|
226
341
|
|
|
227
342
|
function printSetupHeader(defaultVault) {
|
|
228
343
|
console.log("");
|
|
229
|
-
console.log("
|
|
230
|
-
console.log("
|
|
231
|
-
console.log(
|
|
344
|
+
console.log("╭────────────────────────────────────────╮");
|
|
345
|
+
console.log("│ 🧠 Digital Brain setup │");
|
|
346
|
+
console.log("╰────────────────────────────────────────╯");
|
|
347
|
+
console.log("Pick with A/B/C, 1/2/3, exact value, or press Enter for the default.");
|
|
348
|
+
console.log(`Skipping the vault path creates: ${defaultVault}`);
|
|
232
349
|
console.log("");
|
|
233
350
|
}
|
|
234
351
|
|
|
@@ -250,18 +367,22 @@ async function askNumber(rl, label, fallback, options = {}) {
|
|
|
250
367
|
async function select(rl, label, options, fallback) {
|
|
251
368
|
const defaultIndex = Math.max(0, options.findIndex(([value]) => value === fallback));
|
|
252
369
|
console.log("");
|
|
253
|
-
console.log(
|
|
254
|
-
options.forEach(([, title, description], index) => {
|
|
255
|
-
const marker = index === defaultIndex ? "default" : "";
|
|
256
|
-
|
|
370
|
+
console.log(`◇ ${label}`);
|
|
371
|
+
options.forEach(([, title, description, icon = "•"], index) => {
|
|
372
|
+
const marker = index === defaultIndex ? " ← default" : "";
|
|
373
|
+
const letter = letterFor(index);
|
|
374
|
+
console.log(` ${letter}) ${icon} ${title}${marker}`);
|
|
257
375
|
console.log(` ${description}`);
|
|
258
376
|
});
|
|
259
|
-
const answer = await rl.question(`
|
|
377
|
+
const answer = await rl.question(`Choose ${letterFor(defaultIndex)}/${defaultIndex + 1} [${letterFor(defaultIndex)}]: `);
|
|
260
378
|
const trimmed = answer.trim();
|
|
261
379
|
if (!trimmed) return options[defaultIndex][0];
|
|
380
|
+
const letterIndex = indexFromLetter(trimmed);
|
|
381
|
+
if (letterIndex >= 0 && letterIndex < options.length) return options[letterIndex][0];
|
|
262
382
|
const selected = Number(trimmed);
|
|
263
383
|
if (Number.isInteger(selected) && selected >= 1 && selected <= options.length) return options[selected - 1][0];
|
|
264
|
-
const
|
|
384
|
+
const lower = trimmed.toLowerCase();
|
|
385
|
+
const exact = options.find(([value, title]) => value.toLowerCase() === lower || title.toLowerCase() === lower);
|
|
265
386
|
return exact ? exact[0] : options[defaultIndex][0];
|
|
266
387
|
}
|
|
267
388
|
|
|
@@ -276,13 +397,23 @@ async function responsibilityGate(rl, { schedule, outboundMode }) {
|
|
|
276
397
|
const needsGate = schedule === "always-on" || outboundMode === "send-with-confirmation";
|
|
277
398
|
if (!needsGate) return true;
|
|
278
399
|
console.log("");
|
|
279
|
-
console.log("Responsibility check:");
|
|
400
|
+
console.log("⚠️ Responsibility check:");
|
|
280
401
|
console.log(" Digital Brain may use local databases, WhatsApp Web, and black-box third-party app behavior.");
|
|
281
402
|
console.log(" You are responsible for consent, privacy, message content, and any sends triggered from this machine.");
|
|
282
403
|
console.log(" Enter does not approve this mode.");
|
|
283
404
|
return confirm(rl, "I understand and want this mode enabled", false);
|
|
284
405
|
}
|
|
285
406
|
|
|
407
|
+
function letterFor(index) {
|
|
408
|
+
return String.fromCharCode(65 + index);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function indexFromLetter(value) {
|
|
412
|
+
const normalized = value.trim().toUpperCase();
|
|
413
|
+
if (!/^[A-Z]$/.test(normalized)) return -1;
|
|
414
|
+
return normalized.charCodeAt(0) - 65;
|
|
415
|
+
}
|
|
416
|
+
|
|
286
417
|
function shell(command, args, optional = false) {
|
|
287
418
|
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
288
419
|
if (result.error && optional) return "";
|
|
@@ -290,6 +421,10 @@ function shell(command, args, optional = false) {
|
|
|
290
421
|
return (result.stdout || result.stderr).trim().split("\n")[0];
|
|
291
422
|
}
|
|
292
423
|
|
|
424
|
+
function nodeMajor() {
|
|
425
|
+
return Number(process.version.replace(/^v/, "").split(".")[0]);
|
|
426
|
+
}
|
|
427
|
+
|
|
293
428
|
function parseArgs(argv) {
|
|
294
429
|
const out = {};
|
|
295
430
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -325,10 +460,12 @@ function help() {
|
|
|
325
460
|
|
|
326
461
|
Usage:
|
|
327
462
|
digital-brain init [vault]
|
|
463
|
+
digital-brain run
|
|
328
464
|
digital-brain doctor
|
|
329
|
-
digital-brain
|
|
330
|
-
digital-brain
|
|
331
|
-
digital-brain
|
|
332
|
-
digital-brain
|
|
465
|
+
digital-brain tutorial
|
|
466
|
+
digital-brain sync-whatsapp --days 30
|
|
467
|
+
digital-brain extract --days 30
|
|
468
|
+
digital-brain interpret --days 30
|
|
469
|
+
digital-brain send-whatsapp --to "Name" --message "Text" [--yes]
|
|
333
470
|
`);
|
|
334
471
|
}
|
package/docs/AUTOMATIONS.md
CHANGED
|
@@ -10,6 +10,14 @@ Tools/digital-brain-refresh.sh
|
|
|
10
10
|
|
|
11
11
|
That script runs sync, extract, and interpret using the install-time config.
|
|
12
12
|
|
|
13
|
+
After setup, the normal manual command is:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
digital-brain run
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`init` saves your default vault in `~/.digital-brain/config.json`, so `run` does not need a vault path.
|
|
20
|
+
|
|
13
21
|
## Configurable At Install
|
|
14
22
|
|
|
15
23
|
`digital-brain init` asks:
|
|
@@ -17,6 +25,7 @@ That script runs sync, extract, and interpret using the install-time config.
|
|
|
17
25
|
- vault path
|
|
18
26
|
- your name
|
|
19
27
|
- history window in days
|
|
28
|
+
- setup mode: guided or auto mode
|
|
20
29
|
- primary focus: relationship memory, reply help, work context
|
|
21
30
|
- refresh cadence: manual, daily, hourly, every 30 minutes, or always-on
|
|
22
31
|
- refresh interval in minutes for always-on mode, clamped to a minimum of 1
|
|
@@ -24,7 +33,7 @@ That script runs sync, extract, and interpret using the install-time config.
|
|
|
24
33
|
- WhatsApp outbound mode
|
|
25
34
|
- whether to add AI adapter pointers
|
|
26
35
|
|
|
27
|
-
Most questions are multiple choice.
|
|
36
|
+
Most questions are multiple choice. Pick with `A/B/C`, `1/2/3`, the exact value, or press Enter to use the displayed default.
|
|
28
37
|
|
|
29
38
|
Important defaults:
|
|
30
39
|
|
|
@@ -44,15 +53,15 @@ The answers are saved in:
|
|
|
44
53
|
digital-brain.config.json
|
|
45
54
|
```
|
|
46
55
|
|
|
47
|
-
##
|
|
56
|
+
## Auto Mode
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
Choose `Auto mode` during `digital-brain init`, or use:
|
|
50
59
|
|
|
51
60
|
```bash
|
|
52
61
|
digital-brain init --full-auto
|
|
53
62
|
```
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
Auto mode configures local always-on refreshes with a 5 minute default interval. It still uses the local watch script, so it only runs while the machine and runner are awake.
|
|
56
65
|
|
|
57
66
|
During the guided quiz, always-on and send-with-confirmation require an explicit responsibility check. Pressing Enter does not approve that check. If it is skipped, Digital Brain falls back to manual refresh and draft-only outbound.
|
|
58
67
|
|
|
@@ -63,7 +72,7 @@ Use a Codex cron automation pointed at the generated vault.
|
|
|
63
72
|
Prompt:
|
|
64
73
|
|
|
65
74
|
```text
|
|
66
|
-
Run `
|
|
75
|
+
Run `digital-brain run`. Verify completion and report only counts plus output paths. Do not print private message contents.
|
|
67
76
|
```
|
|
68
77
|
|
|
69
78
|
Example schedule ideas:
|
package/docs/SETUP.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
Digital Brain is designed to install with one npm command:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx digital-brain init
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
npm installs the Node dependencies. No pip install is needed; the Python scripts only use the standard library.
|
|
10
|
+
|
|
11
|
+
## What Init Checks
|
|
12
|
+
|
|
13
|
+
After the quiz, `init` runs a setup check for:
|
|
14
|
+
|
|
15
|
+
- Node 20+
|
|
16
|
+
- npm package dependencies
|
|
17
|
+
- Python 3
|
|
18
|
+
- Python sqlite3 support
|
|
19
|
+
- WhatsApp for Mac local database access
|
|
20
|
+
- optional Ollama
|
|
21
|
+
|
|
22
|
+
Run the check again anytime:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
digital-brain doctor
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For a short usage guide:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
digital-brain tutorial
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## What Cannot Be Silently Installed
|
|
35
|
+
|
|
36
|
+
Digital Brain does not silently install or configure system apps.
|
|
37
|
+
|
|
38
|
+
- WhatsApp for Mac must be installed and logged in by the user.
|
|
39
|
+
- macOS may require Full Disk Access for the terminal app.
|
|
40
|
+
- Ollama is optional and only needed for local LLM workflows.
|
|
41
|
+
|
|
42
|
+
Once setup passes, normal use is:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
digital-brain run
|
|
46
|
+
```
|
package/lib/fs.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
|
|
@@ -28,5 +29,31 @@ export function resolveVault(cwd) {
|
|
|
28
29
|
if (parent === current) break;
|
|
29
30
|
current = parent;
|
|
30
31
|
}
|
|
32
|
+
const configured = readDefaultVault();
|
|
33
|
+
if (configured) return configured;
|
|
31
34
|
return path.resolve(cwd);
|
|
32
35
|
}
|
|
36
|
+
|
|
37
|
+
export function globalConfigPath() {
|
|
38
|
+
return path.join(os.homedir(), ".digital-brain", "config.json");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function readDefaultVault() {
|
|
42
|
+
const file = globalConfigPath();
|
|
43
|
+
if (!fs.existsSync(file)) return "";
|
|
44
|
+
try {
|
|
45
|
+
const config = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
46
|
+
if (config.defaultVault && fs.existsSync(path.join(config.defaultVault, "digital-brain.config.json"))) {
|
|
47
|
+
return config.defaultVault;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
return "";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function writeDefaultVault(vault) {
|
|
56
|
+
const file = globalConfigPath();
|
|
57
|
+
ensureDir(path.dirname(file));
|
|
58
|
+
fs.writeFileSync(file, `${JSON.stringify({ defaultVault: vault }, null, 2)}\n`);
|
|
59
|
+
}
|