digital-brain 1.1.11 → 1.1.18
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 +23 -2
- package/bin/digital-brain.js +95 -1
- package/docs/AUTOMATIONS.md +17 -1
- package/docs/PRIVACY.md +1 -1
- package/package.json +1 -1
- package/whatsapp-web/auto-reply.mjs +114 -5
package/README.md
CHANGED
|
@@ -83,7 +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
|
|
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.
|
|
87
88
|
- Enforce an AI-disclosure guard after repeated AI-assisted sends.
|
|
88
89
|
|
|
89
90
|
## Core Commands
|
|
@@ -98,7 +99,9 @@ digital-brain import-linkedin --input ./linkedin-archive.zip
|
|
|
98
99
|
digital-brain send-whatsapp --to "Name" --message "text"
|
|
99
100
|
digital-brain auto-whatsapp --allow "Name" --model llama3.1
|
|
100
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
|
|
101
103
|
digital-brain auto-whatsapp --allow-all --provider codex --yes
|
|
104
|
+
digital-brain auto-whatsapp --allow-all --provider codex-app --yes
|
|
102
105
|
```
|
|
103
106
|
|
|
104
107
|
`init` remembers your vault globally, so `run` works from anywhere. `run` syncs the live local sources you selected, extracts relationships, and writes interpreted memory in one command.
|
|
@@ -125,14 +128,22 @@ digital-brain auto-whatsapp --allow "Mom" --model llama3.1
|
|
|
125
128
|
digital-brain auto-whatsapp --allow "Mom" --model llama3.1 --yes
|
|
126
129
|
digital-brain auto-whatsapp --allow "Mom" --model llama3.1 --yes --no-process-unread
|
|
127
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
|
|
128
132
|
digital-brain auto-whatsapp --allow-all --provider codex --yes
|
|
133
|
+
digital-brain auto-whatsapp --allow-all --provider codex-app --yes
|
|
129
134
|
```
|
|
130
135
|
|
|
131
136
|
If you start without `--allow`, `--contact`, or `--allow-all` in an interactive terminal, Digital Brain asks whether to cover all contacts or select contacts from your WhatsApp chat list. With `--allow-all`, it still asks once before the first AI reply to each new chat and stores the decision in `08 Sources/WhatsApp/Outbound/auto-reply-whitelist.json`. Use `--auto-approve-new-chats` only if you intentionally want unattended first sends.
|
|
132
137
|
|
|
133
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.
|
|
134
139
|
|
|
135
|
-
The default provider is local Ollama.
|
|
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`.
|
|
136
147
|
|
|
137
148
|
```bash
|
|
138
149
|
digital-brain auto-whatsapp --allow-all --provider codex --yes
|
|
@@ -140,6 +151,16 @@ digital-brain auto-whatsapp --allow-all --provider codex --yes
|
|
|
140
151
|
|
|
141
152
|
If your Codex CLI needs a custom command, pass `--codex-command "..."` or set `DIGITAL_BRAIN_CODEX_COMMAND`. If the command contains `{promptFile}`, Digital Brain writes the reply prompt to a temp file and substitutes that path; otherwise it pipes the prompt to stdin.
|
|
142
153
|
|
|
154
|
+
For the Codex desktop app bridge:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
digital-brain auto-whatsapp --allow-all --provider codex-app --yes
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Digital Brain writes requests to `08 Sources/WhatsApp/Outbound/Codex App Bridge/requests` and waits for matching JSON responses in `responses`.
|
|
161
|
+
|
|
162
|
+
If you select `Codex app bridge` during `digital-brain init`, the vault also gets `Tools/Codex App Bridge Automation.md` with the exact prompt to use in the Codex app.
|
|
163
|
+
|
|
143
164
|
If Digital Brain has already sent two AI-assisted messages to the same chat in the last 24 hours, the next send must disclose that AI is helping. Once that chat has received an AI disclosure, Digital Brain will not keep repeating it.
|
|
144
165
|
|
|
145
166
|
## Automation
|
package/bin/digital-brain.js
CHANGED
|
@@ -49,6 +49,9 @@ async function init(argv, args) {
|
|
|
49
49
|
let activeWindow = args["active-window"] || "08:00-12:00";
|
|
50
50
|
let timezone = args.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || "local";
|
|
51
51
|
let outboundMode = args["outbound-mode"] || "draft";
|
|
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";
|
|
52
55
|
let privacyMode = args["privacy-mode"] || "standard";
|
|
53
56
|
let sourceMarkdownMode = args["source-markdown-mode"] || "none";
|
|
54
57
|
let selectedSources = parseList(args.sources || "whatsapp");
|
|
@@ -105,6 +108,21 @@ async function init(argv, args) {
|
|
|
105
108
|
["send-with-confirmation", "Send with confirmation", "Can send only after explicit command confirmation.", "✅"],
|
|
106
109
|
["auto-send", "Auto-send while running", "Lets auto-whatsapp send from allowlisted chats while it is running.", "🚦"],
|
|
107
110
|
], outboundMode);
|
|
111
|
+
if (outboundMode !== "disabled") {
|
|
112
|
+
autoReplyProvider = await select(rl, "WhatsApp auto-reply brain", [
|
|
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.", "⚡"],
|
|
115
|
+
["codex-app", "Codex app bridge", "Uses request/response files for a Codex desktop automation or thread.", "🧠"],
|
|
116
|
+
["codex", "Codex CLI", "Uses a local codex command; only choose this if the CLI works.", "⌨️"],
|
|
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
|
+
}
|
|
125
|
+
}
|
|
108
126
|
connectAi = await confirm(rl, "🔗 Add global AI pointers for Codex/Claude/Gemini?", true);
|
|
109
127
|
responsibilityAccepted = await responsibilityGate(rl, { schedule, outboundMode });
|
|
110
128
|
if (!responsibilityAccepted && needsResponsibilityGate({ schedule, outboundMode })) {
|
|
@@ -132,6 +150,9 @@ async function init(argv, args) {
|
|
|
132
150
|
activeWindow,
|
|
133
151
|
timezone,
|
|
134
152
|
outboundMode,
|
|
153
|
+
autoReplyProvider,
|
|
154
|
+
autoReplyModel: autoReplyProvider === "openai" ? openaiModel : args.model || undefined,
|
|
155
|
+
openaiApiKey: autoReplyProvider === "openai" && openaiApiKey ? openaiApiKey : undefined,
|
|
135
156
|
privacyMode,
|
|
136
157
|
sourceMarkdownMode,
|
|
137
158
|
selectedSources,
|
|
@@ -154,6 +175,7 @@ async function init(argv, args) {
|
|
|
154
175
|
writeDefaultVault(vault);
|
|
155
176
|
writeRefreshScript(vault, config);
|
|
156
177
|
writeWatchScript(vault, config);
|
|
178
|
+
writeCodexAppBridgeGuide(vault, config);
|
|
157
179
|
|
|
158
180
|
if (connectAi) {
|
|
159
181
|
addGlobalPointer(path.join(os.homedir(), ".codex", "AGENTS.md"), vault, "Codex");
|
|
@@ -166,6 +188,17 @@ async function init(argv, args) {
|
|
|
166
188
|
console.log(`Default vault saved: ${vault}`);
|
|
167
189
|
console.log(`Refresh script: ${path.join(vault, "Tools", "digital-brain-refresh.sh")}`);
|
|
168
190
|
console.log(`Always-on script: ${path.join(vault, "Tools", "digital-brain-watch.sh")}`);
|
|
191
|
+
if (autoReplyProvider === "codex-app") {
|
|
192
|
+
console.log(`Codex app bridge guide: ${path.join(vault, "Tools", "Codex App Bridge Automation.md")}`);
|
|
193
|
+
if (codexAppLooksAvailable()) {
|
|
194
|
+
console.log("Codex app detected. Add the generated bridge prompt as a Codex automation/thread to answer WhatsApp reply requests.");
|
|
195
|
+
} else {
|
|
196
|
+
console.log("Codex app config was not detected. The bridge guide was still generated for later use.");
|
|
197
|
+
}
|
|
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
|
+
}
|
|
169
202
|
console.log("Next:");
|
|
170
203
|
console.log(" digital-brain run");
|
|
171
204
|
if (schedule === "always-on") console.log(` "${path.join(vault, "Tools", "digital-brain-watch.sh")}"`);
|
|
@@ -292,6 +325,14 @@ function printSetupCheck(vault, options = {}) {
|
|
|
292
325
|
optional: true,
|
|
293
326
|
},
|
|
294
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
|
+
}
|
|
295
336
|
if (selectedSources.includes("whatsapp")) {
|
|
296
337
|
checks.push({
|
|
297
338
|
label: "WhatsApp Mac database",
|
|
@@ -408,6 +449,48 @@ done
|
|
|
408
449
|
fs.chmodSync(scriptPath, 0o755);
|
|
409
450
|
}
|
|
410
451
|
|
|
452
|
+
function writeCodexAppBridgeGuide(vault, config) {
|
|
453
|
+
const toolsDir = path.join(vault, "Tools");
|
|
454
|
+
ensureDir(toolsDir);
|
|
455
|
+
const bridgeDir = path.join(vault, "08 Sources", "WhatsApp", "Outbound", "Codex App Bridge");
|
|
456
|
+
const requestsDir = path.join(bridgeDir, "requests");
|
|
457
|
+
const responsesDir = path.join(bridgeDir, "responses");
|
|
458
|
+
ensureDir(requestsDir);
|
|
459
|
+
ensureDir(responsesDir);
|
|
460
|
+
const prompt = `# Codex App Bridge Automation
|
|
461
|
+
|
|
462
|
+
Use this prompt in a Codex desktop automation or a live Codex thread when Digital Brain is configured with:
|
|
463
|
+
|
|
464
|
+
\`\`\`bash
|
|
465
|
+
digital-brain auto-whatsapp --provider codex-app --yes
|
|
466
|
+
\`\`\`
|
|
467
|
+
|
|
468
|
+
Request folder:
|
|
469
|
+
|
|
470
|
+
\`${requestsDir}\`
|
|
471
|
+
|
|
472
|
+
Response folder:
|
|
473
|
+
|
|
474
|
+
\`${responsesDir}\`
|
|
475
|
+
|
|
476
|
+
Automation prompt:
|
|
477
|
+
|
|
478
|
+
\`\`\`text
|
|
479
|
+
Check for pending Digital Brain WhatsApp reply requests in ${requestsDir}. For each .json request that does not already have its response file present, read the request JSON, use its prompt field to produce exactly one WhatsApp reply as the user, and write JSON to the request's responsePath in the exact shape {"reply":"..."}. Do not send any WhatsApp message yourself. Do not write markdown or explanations in the response file. If a request cannot be answered, write {"error":"short reason"} to responsePath.
|
|
480
|
+
\`\`\`
|
|
481
|
+
|
|
482
|
+
Notes:
|
|
483
|
+
|
|
484
|
+
- Digital Brain sends the WhatsApp message after the response file appears.
|
|
485
|
+
- Keep the automation active while \`digital-brain auto-whatsapp --provider codex-app\` is running.
|
|
486
|
+
- The default wait timeout is 5 minutes. Override with \`--provider-timeout-ms\`.
|
|
487
|
+
`;
|
|
488
|
+
writeFileAtomic(path.join(toolsDir, "Codex App Bridge Automation.md"), prompt);
|
|
489
|
+
if (config.autoReplyProvider === "codex-app") {
|
|
490
|
+
writeFileAtomic(path.join(bridgeDir, "README.md"), prompt);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
411
494
|
function addGlobalPointer(file, vault, label) {
|
|
412
495
|
ensureDir(path.dirname(file));
|
|
413
496
|
const block = `
|
|
@@ -447,6 +530,13 @@ async function ask(rl, label, fallback, helpText = "") {
|
|
|
447
530
|
return answer.trim() || fallback;
|
|
448
531
|
}
|
|
449
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
|
+
|
|
450
540
|
async function askNumber(rl, label, fallback, options = {}) {
|
|
451
541
|
const suffix = options.suffix ? ` ${options.suffix}` : "";
|
|
452
542
|
const answer = await ask(rl, `${label}${suffix}`, String(fallback));
|
|
@@ -522,6 +612,10 @@ function needsResponsibilityGate({ schedule, outboundMode }) {
|
|
|
522
612
|
return schedule === "always-on" || ["send-with-confirmation", "auto-send"].includes(outboundMode);
|
|
523
613
|
}
|
|
524
614
|
|
|
615
|
+
function codexAppLooksAvailable() {
|
|
616
|
+
return fs.existsSync(path.join(os.homedir(), ".codex"));
|
|
617
|
+
}
|
|
618
|
+
|
|
525
619
|
function letterFor(index) {
|
|
526
620
|
return String.fromCharCode(65 + index);
|
|
527
621
|
}
|
|
@@ -593,6 +687,6 @@ Usage:
|
|
|
593
687
|
digital-brain extract --days 30
|
|
594
688
|
digital-brain interpret --days 30
|
|
595
689
|
digital-brain send-whatsapp --to "Name" --message "Text" [--yes]
|
|
596
|
-
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]
|
|
597
691
|
`);
|
|
598
692
|
}
|
package/docs/AUTOMATIONS.md
CHANGED
|
@@ -31,6 +31,7 @@ digital-brain run
|
|
|
31
31
|
- refresh interval in minutes for always-on mode, clamped to a minimum of 1
|
|
32
32
|
- active time window
|
|
33
33
|
- WhatsApp outbound mode
|
|
34
|
+
- WhatsApp auto-reply provider: Ollama, Codex app bridge, or Codex CLI
|
|
34
35
|
- whether to add AI adapter pointers
|
|
35
36
|
|
|
36
37
|
Most questions are multiple choice. Pick with `A/B/C`, `1/2/3`, the exact value, or press Enter to use the displayed default.
|
|
@@ -45,6 +46,7 @@ Important defaults:
|
|
|
45
46
|
- skipped always-on interval uses 5 minutes, with a hard minimum of 1 minute
|
|
46
47
|
- skipped active window uses `08:00-12:00`
|
|
47
48
|
- skipped outbound mode uses draft-only
|
|
49
|
+
- skipped auto-reply provider uses Ollama
|
|
48
50
|
- auto-send mode can be selected during init, but only after the responsibility check
|
|
49
51
|
- skipped AI pointers are added during the guided quiz
|
|
50
52
|
|
|
@@ -96,14 +98,16 @@ The generated script loops forever and sleeps for `refreshIntervalMinutes`. The
|
|
|
96
98
|
|
|
97
99
|
## WhatsApp Auto-Reply
|
|
98
100
|
|
|
99
|
-
`digital-brain auto-whatsapp` is separate from refresh automation. It uses WhatsApp Web for live incoming messages and
|
|
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.
|
|
100
102
|
|
|
101
103
|
Draft-only:
|
|
102
104
|
|
|
103
105
|
```bash
|
|
104
106
|
digital-brain auto-whatsapp --allow "Mom" --model llama3.1
|
|
105
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
|
|
106
109
|
digital-brain auto-whatsapp --allow-all --provider codex
|
|
110
|
+
digital-brain auto-whatsapp --allow-all --provider codex-app
|
|
107
111
|
```
|
|
108
112
|
|
|
109
113
|
Auto-send while the command is running:
|
|
@@ -111,7 +115,9 @@ Auto-send while the command is running:
|
|
|
111
115
|
```bash
|
|
112
116
|
digital-brain auto-whatsapp --allow "Mom" --model llama3.1 --yes
|
|
113
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
|
|
114
119
|
digital-brain auto-whatsapp --allow-all --provider codex --yes
|
|
120
|
+
digital-brain auto-whatsapp --allow-all --provider codex-app --yes
|
|
115
121
|
```
|
|
116
122
|
|
|
117
123
|
Broad auto-send for personal chats:
|
|
@@ -128,12 +134,20 @@ Provider options:
|
|
|
128
134
|
|
|
129
135
|
```bash
|
|
130
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
|
|
131
138
|
digital-brain auto-whatsapp --allow "Mom" --provider codex --yes
|
|
139
|
+
digital-brain auto-whatsapp --allow "Mom" --provider codex-app --yes
|
|
132
140
|
digital-brain auto-whatsapp --allow "Mom" --provider codex --codex-command "codex exec --skip-git-repo-check" --yes
|
|
133
141
|
```
|
|
134
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
|
+
|
|
135
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.
|
|
136
146
|
|
|
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`.
|
|
148
|
+
|
|
149
|
+
When `codex-app` is selected during `init`, Digital Brain creates `Tools/Codex App Bridge Automation.md` with the exact Codex automation prompt and bridge folder paths.
|
|
150
|
+
|
|
137
151
|
If you selected `Auto-send while running` during init, `auto-whatsapp` can send without `--yes` while it is running:
|
|
138
152
|
|
|
139
153
|
```bash
|
|
@@ -144,7 +158,9 @@ Guardrails:
|
|
|
144
158
|
|
|
145
159
|
- with `--provider ollama`, requires Ollama running locally
|
|
146
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
|
|
147
162
|
- with `--provider codex`, requires a working local Codex command
|
|
163
|
+
- with `--provider codex-app`, requires a Codex desktop bridge automation/thread that writes response files
|
|
148
164
|
- requires `--allow "Name"` or `--contact "+15551234567"` unless `--allow-all` is explicitly passed
|
|
149
165
|
- single-threads reply generation so multiple incoming chats do not trigger overlapping sends
|
|
150
166
|
- skips likely business, notification, OTP, and service chats unless `--include-businesses` is passed or the chat is explicitly allowlisted by name or contact number
|
package/docs/PRIVACY.md
CHANGED
|
@@ -7,7 +7,7 @@ Digital Brain is designed for local use.
|
|
|
7
7
|
- No cloud API is called by default.
|
|
8
8
|
- Ollama interpretation is local when enabled.
|
|
9
9
|
- WhatsApp sending uses a local WhatsApp Web session.
|
|
10
|
-
- WhatsApp auto-reply uses local Ollama by default
|
|
10
|
+
- WhatsApp auto-reply uses local Ollama by default, a configured local Codex command, or a Codex desktop file bridge. It runs only while the command is active and requires an allowlist unless explicitly overridden. If init is configured for auto-send mode, it can send without `--yes`.
|
|
11
11
|
- Raw source data stays under `08 Sources/`; normal AI context should use `06 AI Memory/` and human notes under `04 People/`.
|
|
12
12
|
- Same-person matching across sources is provisional and file-based; keep source evidence visible when using merged person context.
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -20,10 +20,12 @@ const outboundDir = path.join(whatsAppDir, "Outbound");
|
|
|
20
20
|
const sessionDir = path.join(whatsAppDir, ".session");
|
|
21
21
|
const statePath = path.join(outboundDir, "auto-reply-state.json");
|
|
22
22
|
const whitelistPath = path.join(outboundDir, "auto-reply-whitelist.json");
|
|
23
|
+
const codexAppBridgeDir = path.join(outboundDir, "Codex App Bridge");
|
|
23
24
|
const config = readConfig(vault);
|
|
24
25
|
const provider = args.provider || config.autoReplyProvider || "ollama";
|
|
25
|
-
const model = args.model || config.autoReplyModel || "llama3.1";
|
|
26
|
+
const model = args.model || config.autoReplyModel || (provider === "openai" ? "gpt-4.1-mini" : "llama3.1");
|
|
26
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 || "";
|
|
27
29
|
const allow = parseList(args.allow || "");
|
|
28
30
|
const deny = parseList(args.deny || "");
|
|
29
31
|
const contactNumbers = parseList([args.contact, args.phone, args["contact-number"]].filter(Boolean).join(","))
|
|
@@ -62,8 +64,10 @@ if (!hasInitialScope && !interactiveTerminal) {
|
|
|
62
64
|
|
|
63
65
|
if (provider === "ollama") {
|
|
64
66
|
await assertOllamaModel(model);
|
|
65
|
-
} else if (
|
|
66
|
-
|
|
67
|
+
} else if (provider === "openai") {
|
|
68
|
+
assertOpenAiConfig();
|
|
69
|
+
} else if (!["codex", "codex-app"].includes(provider)) {
|
|
70
|
+
throw new Error(`Unsupported auto-reply provider "${provider}". Use "ollama", "openai", "codex", or "codex-app".`);
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
const client = new Client({
|
|
@@ -77,11 +81,12 @@ client.on("qr", (qr) => {
|
|
|
77
81
|
});
|
|
78
82
|
|
|
79
83
|
client.on("ready", async () => {
|
|
80
|
-
console.log(`Digital Brain WhatsApp auto-reply running with provider: ${provider}${
|
|
84
|
+
console.log(`Digital Brain WhatsApp auto-reply running with provider: ${provider}${["ollama", "openai"].includes(provider) ? ` (${model})` : ""}`);
|
|
81
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.");
|
|
82
86
|
if (!hasInitialScope) await configureInteractiveScope();
|
|
83
87
|
console.log(runtimeAllowAll ? "Allowlist: all chats, with first-send approval per new chat." : allowlistSummary());
|
|
84
88
|
if (provider === "codex") console.log(`Codex command: ${codexCommand}`);
|
|
89
|
+
if (provider === "codex-app") console.log(`Codex App bridge: ${codexAppBridgeDir}`);
|
|
85
90
|
if (!includeBusinesses) console.log("Likely business, notification, OTP, and service chats are skipped by default.");
|
|
86
91
|
try {
|
|
87
92
|
if (processUnreadOnStart) {
|
|
@@ -344,9 +349,45 @@ function readMemoryContext(chatName) {
|
|
|
344
349
|
|
|
345
350
|
async function generateReply(prompt) {
|
|
346
351
|
if (provider === "codex") return generateCodexReply(prompt);
|
|
352
|
+
if (provider === "codex-app") return generateCodexAppReply(prompt);
|
|
353
|
+
if (provider === "openai") return generateOpenAiReply(prompt);
|
|
347
354
|
return generateOllamaReply(prompt);
|
|
348
355
|
}
|
|
349
356
|
|
|
357
|
+
async function generateOpenAiReply(prompt) {
|
|
358
|
+
const timeoutMs = numberArg("provider-timeout-ms", 45000);
|
|
359
|
+
const controller = new AbortController();
|
|
360
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
361
|
+
let response;
|
|
362
|
+
try {
|
|
363
|
+
response = await fetch("https://api.openai.com/v1/responses", {
|
|
364
|
+
method: "POST",
|
|
365
|
+
signal: controller.signal,
|
|
366
|
+
headers: {
|
|
367
|
+
"authorization": `Bearer ${openaiApiKey}`,
|
|
368
|
+
"content-type": "application/json",
|
|
369
|
+
},
|
|
370
|
+
body: JSON.stringify({
|
|
371
|
+
model,
|
|
372
|
+
input: prompt,
|
|
373
|
+
max_output_tokens: 160,
|
|
374
|
+
temperature: 0.35,
|
|
375
|
+
}),
|
|
376
|
+
});
|
|
377
|
+
} catch (error) {
|
|
378
|
+
if (error.name === "AbortError") throw new Error(`OpenAI reply timed out after ${timeoutMs}ms`);
|
|
379
|
+
throw error;
|
|
380
|
+
} finally {
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
}
|
|
383
|
+
const text = await response.text();
|
|
384
|
+
if (!response.ok) throw new Error(`OpenAI reply failed: ${response.status} ${summarize(text)}`);
|
|
385
|
+
const body = parseJsonLine(text);
|
|
386
|
+
const reply = extractOpenAiText(body);
|
|
387
|
+
if (!reply) throw new Error("OpenAI returned an empty reply.");
|
|
388
|
+
return cleanReply(reply);
|
|
389
|
+
}
|
|
390
|
+
|
|
350
391
|
async function generateOllamaReply(prompt) {
|
|
351
392
|
const response = await fetch("http://127.0.0.1:11434/api/generate", {
|
|
352
393
|
method: "POST",
|
|
@@ -367,6 +408,52 @@ async function generateCodexReply(prompt) {
|
|
|
367
408
|
return runReplyCommand(codexCommand, prompt, "codex");
|
|
368
409
|
}
|
|
369
410
|
|
|
411
|
+
async function generateCodexAppReply(prompt) {
|
|
412
|
+
const timeoutMs = numberArg("provider-timeout-ms", 300000);
|
|
413
|
+
const request = createCodexAppRequest(prompt);
|
|
414
|
+
console.log(`Waiting for Codex App bridge response: ${request.responsePath}`);
|
|
415
|
+
return await waitForCodexAppResponse(request.responsePath, timeoutMs);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function createCodexAppRequest(prompt) {
|
|
419
|
+
const requestId = `${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
420
|
+
const requestsDir = path.join(codexAppBridgeDir, "requests");
|
|
421
|
+
const responsesDir = path.join(codexAppBridgeDir, "responses");
|
|
422
|
+
fs.mkdirSync(requestsDir, { recursive: true });
|
|
423
|
+
fs.mkdirSync(responsesDir, { recursive: true });
|
|
424
|
+
const requestPath = path.join(requestsDir, `${requestId}.json`);
|
|
425
|
+
const responsePath = path.join(responsesDir, `${requestId}.json`);
|
|
426
|
+
writeJsonAtomic(requestPath, {
|
|
427
|
+
schemaVersion: 1,
|
|
428
|
+
requestId,
|
|
429
|
+
createdAt: new Date().toISOString(),
|
|
430
|
+
responsePath,
|
|
431
|
+
prompt,
|
|
432
|
+
instructions: [
|
|
433
|
+
"Write exactly one WhatsApp reply as the user.",
|
|
434
|
+
"Return JSON only: {\"reply\":\"...\"}.",
|
|
435
|
+
"No markdown, no explanations, no surrounding text.",
|
|
436
|
+
],
|
|
437
|
+
});
|
|
438
|
+
return { requestId, requestPath, responsePath };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function waitForCodexAppResponse(responsePath, timeoutMs) {
|
|
442
|
+
const startedAt = Date.now();
|
|
443
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
444
|
+
if (fs.existsSync(responsePath)) {
|
|
445
|
+
const response = parseJsonLine(fs.readFileSync(responsePath, "utf8"));
|
|
446
|
+
if (!response) throw new Error(`Codex App bridge wrote invalid JSON: ${responsePath}`);
|
|
447
|
+
if (response.error) throw new Error(`Codex App bridge error: ${response.error}`);
|
|
448
|
+
const reply = cleanReply(response.reply || "");
|
|
449
|
+
if (!reply) throw new Error(`Codex App bridge wrote an empty reply: ${responsePath}`);
|
|
450
|
+
return reply;
|
|
451
|
+
}
|
|
452
|
+
await sleep(1000);
|
|
453
|
+
}
|
|
454
|
+
throw new Error(`Codex App bridge timed out after ${timeoutMs}ms. No response at ${responsePath}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
370
457
|
async function runReplyCommand(command, prompt, label) {
|
|
371
458
|
const timeoutMs = numberArg("provider-timeout-ms", 120000);
|
|
372
459
|
const usesPromptFile = command.includes("{promptFile}");
|
|
@@ -416,6 +503,24 @@ async function assertOllamaModel(modelName) {
|
|
|
416
503
|
}
|
|
417
504
|
}
|
|
418
505
|
|
|
506
|
+
function assertOpenAiConfig() {
|
|
507
|
+
if (!openaiApiKey) {
|
|
508
|
+
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.");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function extractOpenAiText(body) {
|
|
513
|
+
if (!body) return "";
|
|
514
|
+
if (typeof body.output_text === "string") return body.output_text;
|
|
515
|
+
const chunks = [];
|
|
516
|
+
for (const item of body.output || []) {
|
|
517
|
+
for (const content of item.content || []) {
|
|
518
|
+
if (typeof content.text === "string") chunks.push(content.text);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return chunks.join(" ");
|
|
522
|
+
}
|
|
523
|
+
|
|
419
524
|
function cleanReply(value) {
|
|
420
525
|
return String(value)
|
|
421
526
|
.replace(/^["'\s]+|["'\s]+$/g, "")
|
|
@@ -685,6 +790,10 @@ function cleanupPromptFile(file) {
|
|
|
685
790
|
}
|
|
686
791
|
}
|
|
687
792
|
|
|
793
|
+
function sleep(ms) {
|
|
794
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
795
|
+
}
|
|
796
|
+
|
|
688
797
|
function shellQuote(value) {
|
|
689
798
|
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
690
799
|
}
|
|
@@ -752,6 +861,6 @@ function parseArgs(argv) {
|
|
|
752
861
|
}
|
|
753
862
|
|
|
754
863
|
function usage() {
|
|
755
|
-
console.error('Usage: digital-brain auto-whatsapp --allow "Name" --contact "+15551234567" --model llama3.1 [--yes] [--allow-all] [--include-groups] [--include-businesses]');
|
|
864
|
+
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]');
|
|
756
865
|
process.exit(1);
|
|
757
866
|
}
|