owpenwork 0.1.5 → 0.1.8
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 +6 -3
- package/dist/cli.js +319 -118
- package/dist/config.js +3 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Quick run without install:
|
|
|
22
22
|
npx owpenwork
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Then follow the
|
|
25
|
+
Then follow the guided setup (choose what to configure, link WhatsApp, start).
|
|
26
26
|
|
|
27
27
|
1) One-command setup (installs deps, builds, creates `.env` if missing):
|
|
28
28
|
|
|
@@ -79,17 +79,20 @@ Use a separate WhatsApp number as the bot account so it stays independent from y
|
|
|
79
79
|
## Telegram (Untested)
|
|
80
80
|
|
|
81
81
|
Telegram support is wired but not E2E tested yet. To try it:
|
|
82
|
-
-
|
|
83
|
-
-
|
|
82
|
+
- Run `owpenwork login telegram --token <token>`.
|
|
83
|
+
- Or set `TELEGRAM_BOT_TOKEN` and `TELEGRAM_ENABLED=true`.
|
|
84
84
|
|
|
85
85
|
## Commands
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
owpenwork
|
|
89
89
|
owpenwork --non-interactive
|
|
90
|
+
owpenwork login whatsapp
|
|
91
|
+
owpenwork login telegram --token <token>
|
|
90
92
|
owpenwork pairing list
|
|
91
93
|
owpenwork pairing approve <code>
|
|
92
94
|
owpenwork status
|
|
95
|
+
owpenwork doctor --reset
|
|
93
96
|
```
|
|
94
97
|
|
|
95
98
|
## Defaults
|
package/dist/cli.js
CHANGED
|
@@ -1,50 +1,85 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { cancel, confirm, intro, isCancel, log, multiselect, outro, select, spinner, text, } from "@clack/prompts";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { startBridge } from "./bridge.js";
|
|
7
7
|
import { loadConfig, normalizeWhatsAppId, readConfigFile, writeConfigFile, } from "./config.js";
|
|
8
8
|
import { BridgeStore } from "./db.js";
|
|
9
9
|
import { createLogger } from "./logger.js";
|
|
10
10
|
import { loginWhatsApp, unpairWhatsApp } from "./whatsapp.js";
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
11
|
+
const VERSION = "0.1.8";
|
|
12
|
+
const STEP_OPTIONS = [
|
|
13
|
+
{
|
|
14
|
+
value: "config",
|
|
15
|
+
label: "Setup configuration",
|
|
16
|
+
hint: "DM policy + allowlist",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: "whatsapp",
|
|
20
|
+
label: "Link WhatsApp",
|
|
21
|
+
hint: "Scan QR",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: "telegram",
|
|
25
|
+
label: "Link Telegram",
|
|
26
|
+
hint: "Optional",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "start",
|
|
30
|
+
label: "Start bridge",
|
|
31
|
+
hint: "Listen for messages",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const STEP_SET = new Set(["config", "whatsapp", "telegram", "start"]);
|
|
35
|
+
function isInteractive() {
|
|
36
|
+
return Boolean(process.stdin.isTTY || process.env.OWPENWORK_FORCE_TUI === "1");
|
|
37
|
+
}
|
|
38
|
+
function unwrap(value) {
|
|
39
|
+
if (isCancel(value)) {
|
|
40
|
+
cancel("Setup cancelled");
|
|
33
41
|
process.exit(0);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
function parseSelections(raw) {
|
|
46
|
+
if (!raw)
|
|
47
|
+
return null;
|
|
48
|
+
const selections = raw
|
|
49
|
+
.split(",")
|
|
50
|
+
.map((item) => item.trim())
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.filter((item) => STEP_SET.has(item));
|
|
53
|
+
return selections.length ? selections : [];
|
|
54
|
+
}
|
|
55
|
+
function defaultSelections(configExists, whatsappLinked) {
|
|
56
|
+
const selections = [];
|
|
57
|
+
if (!configExists)
|
|
58
|
+
selections.push("config");
|
|
59
|
+
if (!whatsappLinked)
|
|
60
|
+
selections.push("whatsapp");
|
|
61
|
+
selections.push("start");
|
|
62
|
+
return selections;
|
|
63
|
+
}
|
|
64
|
+
function updateConfig(configPath, updater) {
|
|
65
|
+
const { config } = readConfigFile(configPath);
|
|
66
|
+
const base = config ?? { version: 1 };
|
|
67
|
+
const next = updater(base);
|
|
68
|
+
next.version = next.version ?? 1;
|
|
69
|
+
writeConfigFile(configPath, next);
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
72
|
+
async function runSetupWizard(config, options) {
|
|
73
|
+
const testMode = process.env.OWPENWORK_TEST_SETUP;
|
|
74
|
+
const next = updateConfig(config.configPath, (cfg) => ({ ...cfg }));
|
|
75
|
+
if (options.nonInteractive || testMode) {
|
|
43
76
|
next.channels = next.channels ?? {};
|
|
77
|
+
const mode = testMode === "dedicated" ? "dedicated" : "personal";
|
|
78
|
+
const allowFrom = mode === "personal" ? ["+15551234567"] : [];
|
|
44
79
|
next.channels.whatsapp = {
|
|
45
|
-
dmPolicy: "pairing",
|
|
46
|
-
allowFrom
|
|
47
|
-
selfChatMode:
|
|
80
|
+
dmPolicy: mode === "personal" ? "allowlist" : "pairing",
|
|
81
|
+
allowFrom,
|
|
82
|
+
selfChatMode: mode === "personal",
|
|
48
83
|
accounts: {
|
|
49
84
|
[config.whatsappAccountId]: {
|
|
50
85
|
authDir: config.whatsappAuthDir,
|
|
@@ -52,67 +87,126 @@ async function runSetupWizard(config, nonInteractive) {
|
|
|
52
87
|
},
|
|
53
88
|
};
|
|
54
89
|
writeConfigFile(config.configPath, next);
|
|
55
|
-
|
|
90
|
+
log.success(`Wrote ${config.configPath}`);
|
|
56
91
|
return;
|
|
57
92
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
if (options.useTui) {
|
|
94
|
+
const mode = unwrap(await select({
|
|
95
|
+
message: "WhatsApp number",
|
|
96
|
+
options: [
|
|
97
|
+
{
|
|
98
|
+
value: "personal",
|
|
99
|
+
label: "Personal number (no pairing)",
|
|
100
|
+
hint: "Allowlist your own number",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
value: "dedicated",
|
|
104
|
+
label: "Dedicated number",
|
|
105
|
+
hint: "Pairing or allowlist",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
initialValue: "personal",
|
|
109
|
+
}));
|
|
110
|
+
let dmPolicy = "pairing";
|
|
111
|
+
let allowFrom = [];
|
|
112
|
+
let selfChatMode = false;
|
|
113
|
+
if (mode === "personal") {
|
|
114
|
+
const number = unwrap(await text({
|
|
115
|
+
message: "Your WhatsApp number (E.164)",
|
|
116
|
+
placeholder: "+15551234567",
|
|
117
|
+
validate: (value) => {
|
|
118
|
+
const normalized = normalizeWhatsAppId(String(value ?? ""));
|
|
119
|
+
return /^\+\d+$/.test(normalized) ? undefined : "Use E.164 format (+123...)";
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
allowFrom = [normalizeWhatsAppId(number)];
|
|
123
|
+
dmPolicy = "allowlist";
|
|
124
|
+
selfChatMode = true;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
dmPolicy = unwrap(await select({
|
|
128
|
+
message: "Direct messages",
|
|
129
|
+
options: [
|
|
130
|
+
{ value: "pairing", label: "Pairing (recommended)" },
|
|
131
|
+
{ value: "allowlist", label: "Allowlist only" },
|
|
132
|
+
{ value: "open", label: "Open (public)" },
|
|
133
|
+
{ value: "disabled", label: "Disabled" },
|
|
134
|
+
],
|
|
135
|
+
initialValue: "pairing",
|
|
136
|
+
}));
|
|
137
|
+
const listInput = unwrap(await text({
|
|
138
|
+
message: "Allowlist numbers (comma-separated, optional)",
|
|
139
|
+
placeholder: "+15551234567, +15551234568",
|
|
140
|
+
}));
|
|
141
|
+
if (listInput.trim()) {
|
|
142
|
+
allowFrom = listInput
|
|
143
|
+
.split(",")
|
|
144
|
+
.map((item) => normalizeWhatsAppId(item))
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
}
|
|
147
|
+
if (dmPolicy === "open") {
|
|
148
|
+
allowFrom = allowFrom.length ? allowFrom : ["*"];
|
|
72
149
|
}
|
|
73
|
-
normalized = candidate;
|
|
74
150
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
151
|
+
next.channels = next.channels ?? {};
|
|
152
|
+
next.channels.whatsapp = {
|
|
153
|
+
dmPolicy,
|
|
154
|
+
allowFrom,
|
|
155
|
+
selfChatMode,
|
|
156
|
+
accounts: {
|
|
157
|
+
[config.whatsappAccountId]: {
|
|
158
|
+
authDir: config.whatsappAuthDir,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
writeConfigFile(config.configPath, next);
|
|
163
|
+
log.success(`Wrote ${config.configPath}`);
|
|
164
|
+
return;
|
|
78
165
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
dmPolicy = "disabled";
|
|
88
|
-
else
|
|
89
|
-
dmPolicy = "pairing";
|
|
90
|
-
const listInput = await rl.question("Allowlist numbers (comma-separated, optional): ");
|
|
91
|
-
if (listInput.trim()) {
|
|
92
|
-
allowFrom = listInput
|
|
93
|
-
.split(",")
|
|
94
|
-
.map((item) => normalizeWhatsAppId(item))
|
|
95
|
-
.filter(Boolean);
|
|
96
|
-
}
|
|
97
|
-
if (dmPolicy === "open") {
|
|
98
|
-
allowFrom = allowFrom.length ? allowFrom : ["*"];
|
|
166
|
+
}
|
|
167
|
+
async function runTelegramSetup(config, tokenOverride) {
|
|
168
|
+
const tokenFromEnv = tokenOverride ?? process.env.OWPENWORK_TEST_TELEGRAM_TOKEN;
|
|
169
|
+
let token = tokenFromEnv || config.telegramToken;
|
|
170
|
+
if (!token) {
|
|
171
|
+
if (!isInteractive()) {
|
|
172
|
+
log.warn("Telegram token missing. Provide TELEGRAM_BOT_TOKEN or use login command.");
|
|
173
|
+
return;
|
|
99
174
|
}
|
|
175
|
+
token = unwrap(await text({
|
|
176
|
+
message: "Telegram bot token",
|
|
177
|
+
placeholder: "123456:ABCDEF...",
|
|
178
|
+
validate: (value) => (String(value ?? "").trim() ? undefined : "Token required"),
|
|
179
|
+
}));
|
|
100
180
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
181
|
+
updateConfig(config.configPath, (cfg) => {
|
|
182
|
+
const next = { ...cfg };
|
|
183
|
+
next.channels = next.channels ?? {};
|
|
184
|
+
next.channels.telegram = {
|
|
185
|
+
token,
|
|
186
|
+
enabled: true,
|
|
187
|
+
};
|
|
188
|
+
return next;
|
|
189
|
+
});
|
|
190
|
+
log.success("Telegram token saved.");
|
|
191
|
+
}
|
|
192
|
+
async function runStart(pathOverride) {
|
|
193
|
+
if (pathOverride?.trim()) {
|
|
194
|
+
process.env.OPENCODE_DIRECTORY = pathOverride.trim();
|
|
195
|
+
}
|
|
196
|
+
const config = loadConfig();
|
|
197
|
+
const logger = createLogger(config.logLevel);
|
|
198
|
+
if (!process.env.OPENCODE_DIRECTORY) {
|
|
199
|
+
process.env.OPENCODE_DIRECTORY = config.opencodeDirectory;
|
|
200
|
+
}
|
|
201
|
+
const bridge = await startBridge(config, logger);
|
|
202
|
+
logger.info("Commands: owpenwork whatsapp login, owpenwork pairing list, owpenwork status");
|
|
203
|
+
const shutdown = async () => {
|
|
204
|
+
logger.info("shutting down");
|
|
205
|
+
await bridge.stop();
|
|
206
|
+
process.exit(0);
|
|
113
207
|
};
|
|
114
|
-
|
|
115
|
-
|
|
208
|
+
process.on("SIGINT", shutdown);
|
|
209
|
+
process.on("SIGTERM", shutdown);
|
|
116
210
|
}
|
|
117
211
|
async function runGuidedFlow(pathArg, opts) {
|
|
118
212
|
if (pathArg?.trim()) {
|
|
@@ -122,30 +216,95 @@ async function runGuidedFlow(pathArg, opts) {
|
|
|
122
216
|
if (!process.env.OPENCODE_DIRECTORY) {
|
|
123
217
|
process.env.OPENCODE_DIRECTORY = config.opencodeDirectory;
|
|
124
218
|
}
|
|
219
|
+
const authPath = path.join(config.whatsappAuthDir, "creds.json");
|
|
220
|
+
const whatsappLinked = fs.existsSync(authPath);
|
|
221
|
+
const configExists = fs.existsSync(config.configPath);
|
|
125
222
|
if (opts.check) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
console.log(`WhatsApp linked: ${linked ? "yes" : "no"}`);
|
|
131
|
-
process.exit(linked && cfgExists ? 0 : 1);
|
|
223
|
+
console.log(`Config: ${configExists ? "yes" : "no"}`);
|
|
224
|
+
console.log(`WhatsApp linked: ${whatsappLinked ? "yes" : "no"}`);
|
|
225
|
+
console.log(`Telegram configured: ${config.telegramToken ? "yes" : "no"}`);
|
|
226
|
+
process.exit(configExists && whatsappLinked ? 0 : 1);
|
|
132
227
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
228
|
+
if (opts.nonInteractive) {
|
|
229
|
+
await runSetupWizard(config, { nonInteractive: true, useTui: false });
|
|
230
|
+
console.log("Next: owpenwork whatsapp login");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!isInteractive()) {
|
|
234
|
+
console.log("Non-interactive shell. Run `owpenwork start` or `owpenwork --non-interactive`.");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
intro("Owpenwork Setup");
|
|
238
|
+
const selectedFromEnv = parseSelections(process.env.OWPENWORK_TEST_SELECTIONS);
|
|
239
|
+
const selections = selectedFromEnv ??
|
|
240
|
+
unwrap(await multiselect({
|
|
241
|
+
message: "Select what to set up",
|
|
242
|
+
options: STEP_OPTIONS,
|
|
243
|
+
initialValues: defaultSelections(configExists, whatsappLinked),
|
|
244
|
+
required: false,
|
|
245
|
+
}));
|
|
246
|
+
const selectedSteps = Array.isArray(selections) ? selections : [];
|
|
247
|
+
if (!selectedSteps.length) {
|
|
248
|
+
cancel("No steps selected");
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}
|
|
251
|
+
const total = selectedSteps.length;
|
|
252
|
+
let index = 1;
|
|
253
|
+
const runStep = async (label, task) => {
|
|
254
|
+
const spin = spinner();
|
|
255
|
+
spin.start(`Step ${index}/${total}: ${label}`);
|
|
256
|
+
await task();
|
|
257
|
+
spin.stop(`Step ${index}/${total}: ${label} done`);
|
|
258
|
+
index += 1;
|
|
259
|
+
};
|
|
260
|
+
for (const step of selectedSteps) {
|
|
261
|
+
if (step === "config") {
|
|
262
|
+
await runStep("Setup configuration", async () => {
|
|
263
|
+
await runSetupWizard(config, { nonInteractive: false, useTui: true });
|
|
264
|
+
config = loadConfig(process.env, { requireOpencode: false });
|
|
265
|
+
});
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (step === "whatsapp") {
|
|
269
|
+
await runStep("Link WhatsApp", async () => {
|
|
270
|
+
const alreadyLinked = fs.existsSync(authPath);
|
|
271
|
+
if (alreadyLinked) {
|
|
272
|
+
const relink = unwrap(await confirm({
|
|
273
|
+
message: "WhatsApp already linked. Relink?",
|
|
274
|
+
initialValue: false,
|
|
275
|
+
}));
|
|
276
|
+
if (!relink)
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
await loginWhatsApp(config, createLogger(config.logLevel));
|
|
280
|
+
});
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (step === "telegram") {
|
|
284
|
+
await runStep("Link Telegram", async () => {
|
|
285
|
+
await runTelegramSetup(config);
|
|
286
|
+
config = loadConfig(process.env, { requireOpencode: false });
|
|
287
|
+
});
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (step === "start") {
|
|
291
|
+
log.info("Starting bridge...");
|
|
292
|
+
outro("Owpenwork is running.");
|
|
293
|
+
await runStart(pathArg);
|
|
140
294
|
}
|
|
141
295
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!fs.existsSync(authPath)) {
|
|
145
|
-
await loginWhatsApp(config, createLogger(config.logLevel));
|
|
296
|
+
if (!selectedSteps.includes("start")) {
|
|
297
|
+
outro("Setup complete.");
|
|
146
298
|
}
|
|
147
|
-
await runStart(pathArg);
|
|
148
299
|
}
|
|
300
|
+
const program = new Command();
|
|
301
|
+
program
|
|
302
|
+
.name("owpenbot")
|
|
303
|
+
.version(VERSION)
|
|
304
|
+
.description("OpenCode WhatsApp + Telegram bridge")
|
|
305
|
+
.argument("[path]")
|
|
306
|
+
.option("--non-interactive", "Run setup defaults and exit", false)
|
|
307
|
+
.option("--check", "Validate config/auth and exit", false);
|
|
149
308
|
program
|
|
150
309
|
.command("start")
|
|
151
310
|
.description("Start the bridge")
|
|
@@ -164,7 +323,29 @@ program
|
|
|
164
323
|
.option("--non-interactive", "Write defaults without prompts", false)
|
|
165
324
|
.action(async (opts) => {
|
|
166
325
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
167
|
-
|
|
326
|
+
if (!isInteractive() && !opts.nonInteractive) {
|
|
327
|
+
console.log("Non-interactive shell. Use --non-interactive.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
intro("Owpenwork Setup");
|
|
331
|
+
await runSetupWizard(config, { nonInteractive: Boolean(opts.nonInteractive), useTui: true });
|
|
332
|
+
outro("Setup complete.");
|
|
333
|
+
});
|
|
334
|
+
const login = program.command("login").description("Link channels");
|
|
335
|
+
login
|
|
336
|
+
.command("whatsapp")
|
|
337
|
+
.description("Login to WhatsApp via QR code")
|
|
338
|
+
.action(async () => {
|
|
339
|
+
const config = loadConfig(process.env, { requireOpencode: false });
|
|
340
|
+
await loginWhatsApp(config, createLogger(config.logLevel));
|
|
341
|
+
});
|
|
342
|
+
login
|
|
343
|
+
.command("telegram")
|
|
344
|
+
.option("--token <token>", "Telegram bot token")
|
|
345
|
+
.description("Save Telegram bot token")
|
|
346
|
+
.action(async (opts) => {
|
|
347
|
+
const config = loadConfig(process.env, { requireOpencode: false });
|
|
348
|
+
await runTelegramSetup(config, opts.token);
|
|
168
349
|
});
|
|
169
350
|
program
|
|
170
351
|
.command("pairing-code")
|
|
@@ -190,32 +371,28 @@ whatsapp
|
|
|
190
371
|
.description("Login to WhatsApp via QR code")
|
|
191
372
|
.action(async () => {
|
|
192
373
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
193
|
-
|
|
194
|
-
await loginWhatsApp(config, logger);
|
|
374
|
+
await loginWhatsApp(config, createLogger(config.logLevel));
|
|
195
375
|
});
|
|
196
376
|
whatsapp
|
|
197
377
|
.command("logout")
|
|
198
378
|
.description("Logout of WhatsApp and clear auth state")
|
|
199
379
|
.action(() => {
|
|
200
380
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
201
|
-
|
|
202
|
-
unpairWhatsApp(config, logger);
|
|
381
|
+
unpairWhatsApp(config, createLogger(config.logLevel));
|
|
203
382
|
});
|
|
204
383
|
program
|
|
205
384
|
.command("qr")
|
|
206
385
|
.description("Print a WhatsApp QR code to pair")
|
|
207
386
|
.action(async () => {
|
|
208
387
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
209
|
-
|
|
210
|
-
await loginWhatsApp(config, logger);
|
|
388
|
+
await loginWhatsApp(config, createLogger(config.logLevel));
|
|
211
389
|
});
|
|
212
390
|
program
|
|
213
391
|
.command("unpair")
|
|
214
392
|
.description("Clear WhatsApp pairing data")
|
|
215
393
|
.action(() => {
|
|
216
394
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
217
|
-
|
|
218
|
-
unpairWhatsApp(config, logger);
|
|
395
|
+
unpairWhatsApp(config, createLogger(config.logLevel));
|
|
219
396
|
});
|
|
220
397
|
const pairing = program.command("pairing").description("Pairing requests");
|
|
221
398
|
pairing
|
|
@@ -273,22 +450,46 @@ program
|
|
|
273
450
|
const linked = fs.existsSync(authPath);
|
|
274
451
|
console.log(`Config: ${config.configPath}`);
|
|
275
452
|
console.log(`WhatsApp linked: ${linked ? "yes" : "no"}`);
|
|
453
|
+
console.log(`Telegram configured: ${config.telegramToken ? "yes" : "no"}`);
|
|
276
454
|
console.log(`Auth dir: ${config.whatsappAuthDir}`);
|
|
277
455
|
console.log(`OpenCode URL: ${config.opencodeUrl}`);
|
|
278
456
|
});
|
|
279
457
|
program
|
|
280
458
|
.command("doctor")
|
|
281
459
|
.description("Diagnose common issues")
|
|
282
|
-
.
|
|
460
|
+
.option("--reset", "Reset owpenbot state", false)
|
|
461
|
+
.action(async (opts) => {
|
|
283
462
|
const config = loadConfig(process.env, { requireOpencode: false });
|
|
284
463
|
const authPath = `${config.whatsappAuthDir}/creds.json`;
|
|
285
464
|
if (!fs.existsSync(authPath)) {
|
|
286
|
-
console.log("WhatsApp not linked. Run:
|
|
465
|
+
console.log("WhatsApp not linked. Run: owpenwork whatsapp login");
|
|
287
466
|
}
|
|
288
467
|
else {
|
|
289
468
|
console.log("WhatsApp linked.");
|
|
290
469
|
}
|
|
291
|
-
console.log(
|
|
470
|
+
console.log(`OpenCode URL: ${config.opencodeUrl}`);
|
|
471
|
+
console.log("If replies fail, ensure the server is running.");
|
|
472
|
+
const resetRequested = Boolean(opts.reset);
|
|
473
|
+
if (!resetRequested && !process.stdin.isTTY)
|
|
474
|
+
return;
|
|
475
|
+
let confirmed = resetRequested;
|
|
476
|
+
if (!resetRequested) {
|
|
477
|
+
const answer = await confirm({ message: "Reset owpenbot state?", initialValue: false });
|
|
478
|
+
if (isCancel(answer)) {
|
|
479
|
+
cancel("Reset cancelled");
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
confirmed = Boolean(answer);
|
|
483
|
+
}
|
|
484
|
+
if (!confirmed)
|
|
485
|
+
return;
|
|
486
|
+
const targets = [config.configPath, config.dbPath, config.whatsappAuthDir];
|
|
487
|
+
for (const target of targets) {
|
|
488
|
+
if (fs.existsSync(target)) {
|
|
489
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
console.log("Reset complete. Run: owpenwork");
|
|
292
493
|
});
|
|
293
494
|
program.parseAsync(process.argv).catch((error) => {
|
|
294
495
|
console.error(error);
|
package/dist/config.js
CHANGED
|
@@ -141,6 +141,7 @@ export function loadConfig(env = process.env, options = {}) {
|
|
|
141
141
|
const whatsappAllowFrom = new Set([...fileAllowFrom, ...envAllowFrom]);
|
|
142
142
|
const toolOutputLimit = parseInteger(env.TOOL_OUTPUT_LIMIT) ?? 1200;
|
|
143
143
|
const permissionMode = env.PERMISSION_MODE?.toLowerCase() === "deny" ? "deny" : "allow";
|
|
144
|
+
const telegramToken = env.TELEGRAM_BOT_TOKEN?.trim() || configFile.channels?.telegram?.token || undefined;
|
|
144
145
|
return {
|
|
145
146
|
configPath,
|
|
146
147
|
configFile,
|
|
@@ -148,8 +149,8 @@ export function loadConfig(env = process.env, options = {}) {
|
|
|
148
149
|
opencodeDirectory: resolvedDirectory,
|
|
149
150
|
opencodeUsername: env.OPENCODE_SERVER_USERNAME?.trim() || undefined,
|
|
150
151
|
opencodePassword: env.OPENCODE_SERVER_PASSWORD?.trim() || undefined,
|
|
151
|
-
telegramToken
|
|
152
|
-
telegramEnabled: parseBoolean(env.TELEGRAM_ENABLED,
|
|
152
|
+
telegramToken,
|
|
153
|
+
telegramEnabled: parseBoolean(env.TELEGRAM_ENABLED, configFile.channels?.telegram?.enabled ?? Boolean(telegramToken)),
|
|
153
154
|
whatsappAuthDir,
|
|
154
155
|
whatsappAccountId,
|
|
155
156
|
whatsappDmPolicy: dmPolicy,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owpenwork",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "WhatsApp bridge for a running OpenCode server",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"test:npx": "node scripts/test-npx.mjs"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@clack/prompts": "^0.11.0",
|
|
43
44
|
"@opencode-ai/sdk": "^1.1.19",
|
|
44
45
|
"@whiskeysockets/baileys": "7.0.0-rc.9",
|
|
45
46
|
"better-sqlite3": "^11.7.0",
|