digital-brain 0.1.3 → 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 CHANGED
@@ -24,7 +24,7 @@ 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
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
 
@@ -38,7 +38,7 @@ For a non-interactive setup:
38
38
  npx digital-brain init --yes
39
39
  ```
40
40
 
41
- For local always-on setup:
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 ./Digital Brain\ Vault
81
- digital-brain sync-whatsapp --vault ./Digital Brain\ Vault --days 30
82
- digital-brain extract --vault ./Digital Brain\ Vault --days 30
83
- digital-brain interpret --vault ./Digital Brain\ Vault --days 30
84
- digital-brain send-whatsapp --vault ./Digital Brain\ Vault --to "Name" --message "text"
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.
@@ -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"]);
@@ -49,18 +52,28 @@ async function init(argv, args) {
49
52
  vault ||= path.resolve(await ask(rl, "📁 Vault path", defaultVault, "Enter creates this folder if it does not exist."));
50
53
  selfName ||= await ask(rl, "👤 Your name", "Me");
51
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
60
  ["relationship-memory", "Relationship memory", "Map people, tone, and recurring patterns.", "🧠"],
54
61
  ["reply-help", "Reply help", "Prioritize drafting guidance and typing-style matching.", "💬"],
55
62
  ["work-context", "Work context", "Prioritize collaborators, projects, and operational notes.", "💼"],
56
63
  ], "relationship-memory");
57
- schedule = await select(rl, "Refresh cadence", [
58
- ["manual", "Manual", "Only runs when you run a command.", "🖐️"],
59
- ["daily", "Daily", "Good for a low-maintenance personal vault.", "🌅"],
60
- ["hourly", "Hourly", "Keeps memory warm without running constantly.", "⏱️"],
61
- ["every-30-min", "Every 30 minutes", "Useful for morning or work-window guidance.", "🔁"],
62
- ["always-on", "Always-on local loop", "Runs repeatedly while your computer is awake.", "⚡"],
63
- ], schedule);
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
78
  refreshIntervalMinutes = await askNumber(rl, "⏳ Always-on pull interval", refreshIntervalMinutes, {
66
79
  suffix: "minutes",
@@ -79,6 +92,7 @@ async function init(argv, args) {
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: fullAuto ? "full-auto" : "guided",
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(` digital-brain sync-whatsapp --vault "${vault}" --days ${dataWindowDays}`);
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
- const checks = [
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 resolved = withVault(argv);
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", resolveVault(process.cwd()), ...argv];
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 sync-whatsapp --vault "$VAULT" --days "$DAYS" --markdown-mode month
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
  `;
@@ -306,6 +421,10 @@ function shell(command, args, optional = false) {
306
421
  return (result.stdout || result.stderr).trim().split("\n")[0];
307
422
  }
308
423
 
424
+ function nodeMajor() {
425
+ return Number(process.version.replace(/^v/, "").split(".")[0]);
426
+ }
427
+
309
428
  function parseArgs(argv) {
310
429
  const out = {};
311
430
  for (let i = 0; i < argv.length; i += 1) {
@@ -341,10 +460,12 @@ function help() {
341
460
 
342
461
  Usage:
343
462
  digital-brain init [vault]
463
+ digital-brain run
344
464
  digital-brain doctor
345
- digital-brain sync-whatsapp --vault <path> --days 30
346
- digital-brain extract --vault <path> --days 30
347
- digital-brain interpret --vault <path> --days 30
348
- digital-brain send-whatsapp --vault <path> --to "Name" --message "Text" [--yes]
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]
349
470
  `);
350
471
  }
@@ -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
@@ -44,15 +53,15 @@ The answers are saved in:
44
53
  digital-brain.config.json
45
54
  ```
46
55
 
47
- ## Full Auto
56
+ ## Auto Mode
48
57
 
49
- Use:
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
- Full-auto 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.
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 `Tools/digital-brain-refresh.sh`. Verify completion and report only counts plus output paths. Do not print private message contents.
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "digital-brain",
3
- "version": "0.1.3",
3
+ "version": "0.1.7",
4
4
  "description": "Your private digital imprint for AI assistants.",
5
5
  "type": "module",
6
6
  "bin": {