digital-brain 1.1.15 → 1.1.20

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
@@ -83,8 +83,8 @@ node ./bin/digital-brain.js init ./Digital Brain\ Vault
83
83
  - Generate reply-ready person context that keeps WhatsApp, iMessage, Slack, and LinkedIn evidence separate under the same person.
84
84
  - Create AI-readable memory files for future prompts.
85
85
  - Draft WhatsApp sends by default, send with explicit `--yes`, or configure auto-send mode during init.
86
- - Run an explicit WhatsApp auto-responder that uses Ollama or a Codex command plus vault memory while the command is running.
87
- - Choose the WhatsApp auto-reply provider during init: Ollama, Codex app bridge, or Codex CLI.
86
+ - Run an explicit WhatsApp auto-responder that uses Ollama, OpenAI, or Codex plus vault memory while the command is running.
87
+ - Choose the WhatsApp auto-reply provider during init: Ollama, OpenAI API, Codex app bridge, or Codex CLI.
88
88
  - Enforce an AI-disclosure guard after repeated AI-assisted sends.
89
89
 
90
90
  ## Core Commands
@@ -99,6 +99,7 @@ digital-brain import-linkedin --input ./linkedin-archive.zip
99
99
  digital-brain send-whatsapp --to "Name" --message "text"
100
100
  digital-brain auto-whatsapp --allow "Name" --model llama3.1
101
101
  digital-brain auto-whatsapp --contact "+15551234567" --model llama3.1
102
+ OPENAI_API_KEY="sk-..." digital-brain auto-whatsapp --allow-all --provider openai --model gpt-4.1-mini --yes
102
103
  digital-brain auto-whatsapp --allow-all --provider codex --yes
103
104
  digital-brain auto-whatsapp --allow-all --provider codex-app --yes
104
105
  ```
@@ -127,6 +128,7 @@ digital-brain auto-whatsapp --allow "Mom" --model llama3.1
127
128
  digital-brain auto-whatsapp --allow "Mom" --model llama3.1 --yes
128
129
  digital-brain auto-whatsapp --allow "Mom" --model llama3.1 --yes --no-process-unread
129
130
  digital-brain auto-whatsapp --contact "+15551234567" --model llama3.1 --yes
131
+ OPENAI_API_KEY="sk-..." digital-brain auto-whatsapp --allow-all --provider openai --model gpt-4.1-mini --yes
130
132
  digital-brain auto-whatsapp --allow-all --provider codex --yes
131
133
  digital-brain auto-whatsapp --allow-all --provider codex-app --yes
132
134
  ```
@@ -135,7 +137,13 @@ If you start without `--allow`, `--contact`, or `--allow-all` in an interactive
135
137
 
136
138
  Even with `--allow-all`, likely business, notification, OTP, delivery, bank, and support chats are skipped by default. Use explicit `--allow "Name"` or `--contact "+15551234567"` for trusted personal chats. Pass `--include-businesses` only if you intentionally want those chats included.
137
139
 
138
- The default provider is local Ollama. `--provider codex` uses the Codex CLI. `--provider codex-app` uses a file bridge for the Codex desktop app.
140
+ The default provider is local Ollama. `--provider openai` uses the OpenAI API with the same vault context prompt. `--provider codex` uses the Codex CLI. `--provider codex-app` uses a file bridge for the Codex desktop app.
141
+
142
+ ```bash
143
+ OPENAI_API_KEY="sk-..." digital-brain auto-whatsapp --allow-all --provider openai --model gpt-4.1-mini --yes
144
+ ```
145
+
146
+ If you select `OpenAI API` during `digital-brain init`, you can either paste an API key to store it in the local vault config or leave it blank and set `OPENAI_API_KEY` before running `auto-whatsapp`.
139
147
 
140
148
  ```bash
141
149
  digital-brain auto-whatsapp --allow-all --provider codex --yes
@@ -50,6 +50,8 @@ async function init(argv, args) {
50
50
  let timezone = args.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
51
51
  let outboundMode = args["outbound-mode"] || "draft";
52
52
  let autoReplyProvider = args["auto-reply-provider"] || args.provider || "ollama";
53
+ let openaiApiKey = args["openai-api-key"] || "";
54
+ let openaiModel = args["openai-model"] || "gpt-4.1-mini";
53
55
  let privacyMode = args["privacy-mode"] || "standard";
54
56
  let sourceMarkdownMode = args["source-markdown-mode"] || "none";
55
57
  let selectedSources = parseList(args.sources || "whatsapp");
@@ -109,9 +111,17 @@ async function init(argv, args) {
109
111
  if (outboundMode !== "disabled") {
110
112
  autoReplyProvider = await select(rl, "WhatsApp auto-reply brain", [
111
113
  ["ollama", "Ollama local model", "Runs fully local if Ollama and the model are installed.", "🦙"],
114
+ ["openai", "OpenAI API", "Fast hosted replies using the same Digital Brain context prompt.", "⚡"],
112
115
  ["codex-app", "Codex app bridge", "Uses request/response files for a Codex desktop automation or thread.", "🧠"],
113
116
  ["codex", "Codex CLI", "Uses a local codex command; only choose this if the CLI works.", "⌨️"],
114
117
  ], autoReplyProvider);
118
+ if (autoReplyProvider === "openai") {
119
+ openaiModel = await ask(rl, "🤖 OpenAI model", openaiModel);
120
+ openaiApiKey = await askSecret(rl, "🔑 OpenAI API key", {
121
+ fallbackLabel: process.env.OPENAI_API_KEY ? "OPENAI_API_KEY env found" : "use OPENAI_API_KEY at runtime",
122
+ helpText: "Paste a key to store it locally in this vault config. Leave blank to use OPENAI_API_KEY at runtime.",
123
+ });
124
+ }
115
125
  }
116
126
  connectAi = await confirm(rl, "🔗 Add global AI pointers for Codex/Claude/Gemini?", true);
117
127
  responsibilityAccepted = await responsibilityGate(rl, { schedule, outboundMode });
@@ -141,6 +151,8 @@ async function init(argv, args) {
141
151
  timezone,
142
152
  outboundMode,
143
153
  autoReplyProvider,
154
+ autoReplyModel: autoReplyProvider === "openai" ? openaiModel : args.model || undefined,
155
+ openaiApiKey: autoReplyProvider === "openai" && openaiApiKey ? openaiApiKey : undefined,
144
156
  privacyMode,
145
157
  sourceMarkdownMode,
146
158
  selectedSources,
@@ -184,6 +196,9 @@ async function init(argv, args) {
184
196
  console.log("Codex app config was not detected. The bridge guide was still generated for later use.");
185
197
  }
186
198
  }
199
+ if (autoReplyProvider === "openai" && !openaiApiKey && !process.env.OPENAI_API_KEY) {
200
+ console.log("OpenAI provider selected. Set OPENAI_API_KEY before running auto-whatsapp, or add openaiApiKey to the vault config.");
201
+ }
187
202
  console.log("Next:");
188
203
  console.log(" digital-brain run");
189
204
  if (schedule === "always-on") console.log(` "${path.join(vault, "Tools", "digital-brain-watch.sh")}"`);
@@ -310,6 +325,14 @@ function printSetupCheck(vault, options = {}) {
310
325
  optional: true,
311
326
  },
312
327
  ];
328
+ if (config.autoReplyProvider === "openai") {
329
+ checks.push({
330
+ label: "OpenAI API key",
331
+ ok: Boolean(process.env.OPENAI_API_KEY || config.openaiApiKey),
332
+ value: process.env.OPENAI_API_KEY ? "found in OPENAI_API_KEY" : config.openaiApiKey ? "stored in vault config" : "not found",
333
+ hint: "Set OPENAI_API_KEY or re-run init and choose OpenAI API.",
334
+ });
335
+ }
313
336
  if (selectedSources.includes("whatsapp")) {
314
337
  checks.push({
315
338
  label: "WhatsApp Mac database",
@@ -507,6 +530,13 @@ async function ask(rl, label, fallback, helpText = "") {
507
530
  return answer.trim() || fallback;
508
531
  }
509
532
 
533
+ async function askSecret(rl, label, options = {}) {
534
+ if (options.helpText) console.log(` ${options.helpText}`);
535
+ const suffix = options.fallbackLabel ? ` [${options.fallbackLabel}]` : "";
536
+ const answer = await rl.question(`${label}${suffix}: `);
537
+ return answer.trim();
538
+ }
539
+
510
540
  async function askNumber(rl, label, fallback, options = {}) {
511
541
  const suffix = options.suffix ? ` ${options.suffix}` : "";
512
542
  const answer = await ask(rl, `${label}${suffix}`, String(fallback));
@@ -657,6 +687,6 @@ Usage:
657
687
  digital-brain extract --days 30
658
688
  digital-brain interpret --days 30
659
689
  digital-brain send-whatsapp --to "Name" --message "Text" [--yes]
660
- digital-brain auto-whatsapp --allow "Name" --contact "+15551234567" --provider ollama|codex --model llama3.1 [--yes] [--no-process-unread]
690
+ digital-brain auto-whatsapp --allow "Name" --contact "+15551234567" --provider ollama|openai|codex|codex-app --model llama3.1 [--yes] [--no-process-unread]
661
691
  `);
662
692
  }
@@ -98,13 +98,14 @@ The generated script loops forever and sleeps for `refreshIntervalMinutes`. The
98
98
 
99
99
  ## WhatsApp Auto-Reply
100
100
 
101
- `digital-brain auto-whatsapp` is separate from refresh automation. It uses WhatsApp Web for live incoming messages and either Ollama or a Codex command for reply generation. On startup it scans unread WhatsApp Web chats, then continues listening for new messages.
101
+ `digital-brain auto-whatsapp` is separate from refresh automation. It uses WhatsApp Web for live incoming messages and Ollama, OpenAI, or Codex for reply generation. On startup it scans unread WhatsApp Web chats, then continues listening for new messages.
102
102
 
103
103
  Draft-only:
104
104
 
105
105
  ```bash
106
106
  digital-brain auto-whatsapp --allow "Mom" --model llama3.1
107
107
  digital-brain auto-whatsapp --contact "+15551234567" --model llama3.1
108
+ OPENAI_API_KEY="sk-..." digital-brain auto-whatsapp --allow-all --provider openai --model gpt-4.1-mini
108
109
  digital-brain auto-whatsapp --allow-all --provider codex
109
110
  digital-brain auto-whatsapp --allow-all --provider codex-app
110
111
  ```
@@ -114,6 +115,7 @@ Auto-send while the command is running:
114
115
  ```bash
115
116
  digital-brain auto-whatsapp --allow "Mom" --model llama3.1 --yes
116
117
  digital-brain auto-whatsapp --contact "+15551234567" --model llama3.1 --yes
118
+ OPENAI_API_KEY="sk-..." digital-brain auto-whatsapp --allow-all --provider openai --model gpt-4.1-mini --yes
117
119
  digital-brain auto-whatsapp --allow-all --provider codex --yes
118
120
  digital-brain auto-whatsapp --allow-all --provider codex-app --yes
119
121
  ```
@@ -132,11 +134,14 @@ Provider options:
132
134
 
133
135
  ```bash
134
136
  digital-brain auto-whatsapp --allow "Mom" --provider ollama --model llama3.1 --yes
137
+ OPENAI_API_KEY="sk-..." digital-brain auto-whatsapp --allow "Mom" --provider openai --model gpt-4.1-mini --yes
135
138
  digital-brain auto-whatsapp --allow "Mom" --provider codex --yes
136
139
  digital-brain auto-whatsapp --allow "Mom" --provider codex-app --yes
137
140
  digital-brain auto-whatsapp --allow "Mom" --provider codex --codex-command "codex exec --skip-git-repo-check" --yes
138
141
  ```
139
142
 
143
+ `--provider openai` sends the same Digital Brain prompt and relevant vault context to the OpenAI API. Set `OPENAI_API_KEY`, pass `--openai-api-key`, or choose OpenAI during `digital-brain init` and paste the key into the local vault config.
144
+
140
145
  `--provider codex` runs a local Codex command. If `--codex-command` contains `{promptFile}`, Digital Brain writes the prompt to a temp file and substitutes the path; otherwise it pipes the prompt to stdin.
141
146
 
142
147
  `--provider codex-app` does not use the Codex CLI. It writes request JSON files to `08 Sources/WhatsApp/Outbound/Codex App Bridge/requests` and waits for response JSON files in `responses`. A Codex desktop automation or live Codex thread must process those request files and write `{"reply":"..."}` to the provided `responsePath`.
@@ -153,6 +158,7 @@ Guardrails:
153
158
 
154
159
  - with `--provider ollama`, requires Ollama running locally
155
160
  - with `--provider ollama`, requires the selected model, for example `ollama pull llama3.1`
161
+ - with `--provider openai`, requires `OPENAI_API_KEY`, `--openai-api-key`, or `openaiApiKey` in the vault config
156
162
  - with `--provider codex`, requires a working local Codex command
157
163
  - with `--provider codex-app`, requires a Codex desktop bridge automation/thread that writes response files
158
164
  - requires `--allow "Name"` or `--contact "+15551234567"` unless `--allow-all` is explicitly passed
@@ -161,7 +167,7 @@ Guardrails:
161
167
  - processes unread chats on startup unless `--no-process-unread` is passed
162
168
  - skips groups unless `--include-groups` is passed
163
169
  - uses a per-chat cooldown, default 20 minutes
164
- - caps replies per chat per run, default 5
170
+ - no reply cap by default, so conversations can continue naturally; pass `--max-replies-per-chat <n>` to add one
165
171
  - logs metadata by default, not full sent text
166
172
  - enforces the AI disclosure rule after repeated AI-assisted sends, but does not repeat it after that chat has already received a disclosure
167
173
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "digital-brain",
3
- "version": "1.1.15",
3
+ "version": "1.1.20",
4
4
  "description": "Your private digital imprint for AI assistants.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,8 +23,9 @@ const whitelistPath = path.join(outboundDir, "auto-reply-whitelist.json");
23
23
  const codexAppBridgeDir = path.join(outboundDir, "Codex App Bridge");
24
24
  const config = readConfig(vault);
25
25
  const provider = args.provider || config.autoReplyProvider || "ollama";
26
- const model = args.model || config.autoReplyModel || "llama3.1";
26
+ const model = args.model || config.autoReplyModel || (provider === "openai" ? "gpt-4.1-mini" : "llama3.1");
27
27
  const codexCommand = args["codex-command"] || process.env.DIGITAL_BRAIN_CODEX_COMMAND || config.codexCommand || "codex exec --skip-git-repo-check";
28
+ const openaiApiKey = args["openai-api-key"] || process.env.OPENAI_API_KEY || config.openaiApiKey || "";
28
29
  const allow = parseList(args.allow || "");
29
30
  const deny = parseList(args.deny || "");
30
31
  const contactNumbers = parseList([args.contact, args.phone, args["contact-number"]].filter(Boolean).join(","))
@@ -38,7 +39,7 @@ const includeBusinesses = Boolean(args["include-businesses"]);
38
39
  const sendEnabled = Boolean(args.yes) || config.outboundMode === "auto-send";
39
40
  const processUnreadOnStart = !Boolean(args["no-process-unread"]);
40
41
  const cooldownMinutes = numberArg("cooldown-minutes", 20);
41
- const maxRepliesPerChat = numberArg("max-replies-per-chat", 5);
42
+ const maxRepliesPerChat = numberArg("max-replies-per-chat", 0);
42
43
  const maxContextChars = numberArg("max-context-chars", 12000);
43
44
  const outboundLogMode = args["log-mode"] || config.outboundLogMode || "metadata";
44
45
  const state = loadState();
@@ -63,8 +64,10 @@ if (!hasInitialScope && !interactiveTerminal) {
63
64
 
64
65
  if (provider === "ollama") {
65
66
  await assertOllamaModel(model);
67
+ } else if (provider === "openai") {
68
+ assertOpenAiConfig();
66
69
  } else if (!["codex", "codex-app"].includes(provider)) {
67
- throw new Error(`Unsupported auto-reply provider "${provider}". Use "ollama", "codex", or "codex-app".`);
70
+ throw new Error(`Unsupported auto-reply provider "${provider}". Use "ollama", "openai", "codex", or "codex-app".`);
68
71
  }
69
72
 
70
73
  const client = new Client({
@@ -78,13 +81,14 @@ client.on("qr", (qr) => {
78
81
  });
79
82
 
80
83
  client.on("ready", async () => {
81
- console.log(`Digital Brain WhatsApp auto-reply running with provider: ${provider}${provider === "ollama" ? ` (${model})` : ""}`);
84
+ console.log(`Digital Brain WhatsApp auto-reply running with provider: ${provider}${["ollama", "openai"].includes(provider) ? ` (${model})` : ""}`);
82
85
  console.log(sendEnabled ? "Auto-send is enabled." : "Draft mode. Replies will be logged but not sent. Add --yes or set outboundMode=auto-send to send.");
83
86
  if (!hasInitialScope) await configureInteractiveScope();
84
87
  console.log(runtimeAllowAll ? "Allowlist: all chats, with first-send approval per new chat." : allowlistSummary());
85
88
  if (provider === "codex") console.log(`Codex command: ${codexCommand}`);
86
89
  if (provider === "codex-app") console.log(`Codex App bridge: ${codexAppBridgeDir}`);
87
90
  if (!includeBusinesses) console.log("Likely business, notification, OTP, and service chats are skipped by default.");
91
+ console.log(maxRepliesPerChat > 0 ? `Reply cap: ${maxRepliesPerChat} per chat per run.` : "Reply cap: unlimited.");
88
92
  try {
89
93
  if (processUnreadOnStart) {
90
94
  await processUnreadChats();
@@ -126,7 +130,7 @@ async function processUnreadChats() {
126
130
  console.log(`Skipping unread chat during cooldown: ${name}`);
127
131
  continue;
128
132
  }
129
- if (replyCount(name) >= maxRepliesPerChat) {
133
+ if (isAtReplyCap(name)) {
130
134
  console.log(`Skipping unread chat at reply cap: ${name}`);
131
135
  continue;
132
136
  }
@@ -242,7 +246,7 @@ async function handleMessage(message, knownChat = null) {
242
246
  console.log(`Skipping chat during cooldown: ${name}`);
243
247
  return;
244
248
  }
245
- if (replyCount(name) >= maxRepliesPerChat) {
249
+ if (isAtReplyCap(name)) {
246
250
  console.log(`Skipping chat at reply cap: ${name}`);
247
251
  return;
248
252
  }
@@ -347,9 +351,44 @@ function readMemoryContext(chatName) {
347
351
  async function generateReply(prompt) {
348
352
  if (provider === "codex") return generateCodexReply(prompt);
349
353
  if (provider === "codex-app") return generateCodexAppReply(prompt);
354
+ if (provider === "openai") return generateOpenAiReply(prompt);
350
355
  return generateOllamaReply(prompt);
351
356
  }
352
357
 
358
+ async function generateOpenAiReply(prompt) {
359
+ const timeoutMs = numberArg("provider-timeout-ms", 45000);
360
+ const controller = new AbortController();
361
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
362
+ let response;
363
+ try {
364
+ response = await fetch("https://api.openai.com/v1/responses", {
365
+ method: "POST",
366
+ signal: controller.signal,
367
+ headers: {
368
+ "authorization": `Bearer ${openaiApiKey}`,
369
+ "content-type": "application/json",
370
+ },
371
+ body: JSON.stringify({
372
+ model,
373
+ input: prompt,
374
+ max_output_tokens: 160,
375
+ temperature: 0.35,
376
+ }),
377
+ });
378
+ } catch (error) {
379
+ if (error.name === "AbortError") throw new Error(`OpenAI reply timed out after ${timeoutMs}ms`);
380
+ throw error;
381
+ } finally {
382
+ clearTimeout(timer);
383
+ }
384
+ const text = await response.text();
385
+ if (!response.ok) throw new Error(`OpenAI reply failed: ${response.status} ${summarize(text)}`);
386
+ const body = parseJsonLine(text);
387
+ const reply = extractOpenAiText(body);
388
+ if (!reply) throw new Error("OpenAI returned an empty reply.");
389
+ return cleanReply(reply);
390
+ }
391
+
353
392
  async function generateOllamaReply(prompt) {
354
393
  const response = await fetch("http://127.0.0.1:11434/api/generate", {
355
394
  method: "POST",
@@ -465,6 +504,24 @@ async function assertOllamaModel(modelName) {
465
504
  }
466
505
  }
467
506
 
507
+ function assertOpenAiConfig() {
508
+ if (!openaiApiKey) {
509
+ throw new Error("OpenAI provider requires an API key. Set OPENAI_API_KEY, pass --openai-api-key, or re-run init and enter the key.");
510
+ }
511
+ }
512
+
513
+ function extractOpenAiText(body) {
514
+ if (!body) return "";
515
+ if (typeof body.output_text === "string") return body.output_text;
516
+ const chunks = [];
517
+ for (const item of body.output || []) {
518
+ for (const content of item.content || []) {
519
+ if (typeof content.text === "string") chunks.push(content.text);
520
+ }
521
+ }
522
+ return chunks.join(" ");
523
+ }
524
+
468
525
  function cleanReply(value) {
469
526
  return String(value)
470
527
  .replace(/^["'\s]+|["'\s]+$/g, "")
@@ -630,6 +687,10 @@ function replyCount(name) {
630
687
  return state.sentCountByChat[name] || 0;
631
688
  }
632
689
 
690
+ function isAtReplyCap(name) {
691
+ return maxRepliesPerChat > 0 && replyCount(name) >= maxRepliesPerChat;
692
+ }
693
+
633
694
  function markProcessed(message, name, options = { sent: true }) {
634
695
  state.processedMessageIds.push(message.id?._serialized);
635
696
  state.processedMessageIds = state.processedMessageIds.filter(Boolean).slice(-1000);
@@ -805,6 +866,6 @@ function parseArgs(argv) {
805
866
  }
806
867
 
807
868
  function usage() {
808
- console.error('Usage: digital-brain auto-whatsapp --allow "Name" --contact "+15551234567" --provider ollama|codex|codex-app --model llama3.1 [--yes] [--allow-all] [--include-groups] [--include-businesses]');
869
+ console.error('Usage: digital-brain auto-whatsapp --allow "Name" --contact "+15551234567" --provider ollama|openai|codex|codex-app --model llama3.1 [--yes] [--allow-all] [--include-groups] [--include-businesses]');
809
870
  process.exit(1);
810
871
  }