owpenwork 0.1.7 → 0.1.9

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