@tritard/waterbrother 0.16.10 → 0.16.12
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 +1 -0
- package/package.json +2 -2
- package/src/cli.js +63 -4
- package/src/gateway-state.js +37 -0
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ It is Vercel-ready via `vercel.json` (clean URLs, no build step required).
|
|
|
32
32
|
- tries a live provider model list after credentials, shows when that live list is in use, then falls back to the built-in catalog
|
|
33
33
|
- prompts for default model and agent profile
|
|
34
34
|
- can optionally configure Telegram during onboarding and offer to open the Telegram guide in your browser
|
|
35
|
+
- if Telegram is configured for startup, the TUI autostarts the background Telegram gateway on launch
|
|
35
36
|
- Multi-provider chat integration through provider adapters and model registry
|
|
36
37
|
- Vision command for local images: `waterbrother vision <image-path> <prompt>`
|
|
37
38
|
- Authenticated GitHub repo reading for GitHub URLs, including private repos when `gh` is logged in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tritard/waterbrother",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.12",
|
|
4
4
|
"description": "Waterbrother: bring-your-own-model coding CLI with local tools, sessions, operator modes, and approval controls",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"type": "git",
|
|
28
28
|
"url": "git+https://github.com/PhillipHolland/waterbrother.git"
|
|
29
29
|
},
|
|
30
|
-
"homepage": "https://
|
|
30
|
+
"homepage": "https://waterbrother.app",
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/PhillipHolland/waterbrother/issues"
|
|
33
33
|
},
|
package/src/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
@@ -10,7 +10,7 @@ import { getConfigPath, loadConfigLayers, resolveRuntimeConfig, saveConfig } fro
|
|
|
10
10
|
import { createChatCompletion, createJsonCompletion, listModels } from "./model-client.js";
|
|
11
11
|
import { buildChannelOnboardingPayload, getChannelStatuses, getGatewayStatus } from "./channels.js";
|
|
12
12
|
import { runGatewayService } from "./gateway.js";
|
|
13
|
-
import { DEFAULT_PENDING_PAIRING_TTL_MINUTES, loadGatewayState, prunePendingPairings, saveGatewayState } from "./gateway-state.js";
|
|
13
|
+
import { DEFAULT_PENDING_PAIRING_TTL_MINUTES, loadGatewayProcessInfo, loadGatewayState, prunePendingPairings, saveGatewayProcessInfo, saveGatewayState } from "./gateway-state.js";
|
|
14
14
|
import {
|
|
15
15
|
getDefaultDesignModelForProvider,
|
|
16
16
|
getDefaultModelForProvider,
|
|
@@ -60,7 +60,8 @@ import { parseCharterFromGoal, runExperimentLoop, formatExperimentSummary, gitRe
|
|
|
60
60
|
|
|
61
61
|
const execFileAsync = promisify(execFile);
|
|
62
62
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
63
|
-
const
|
|
63
|
+
const BIN_PATH = path.join(PACKAGE_ROOT, "bin", "waterbrother.js");
|
|
64
|
+
const DOCS_BASE_URL = String(process.env.WATERBROTHER_DOCS_BASE_URL || "https://waterbrother.app").trim().replace(/\/+$/, "");
|
|
64
65
|
|
|
65
66
|
|
|
66
67
|
const MODEL_CATALOG = listLocalModels();
|
|
@@ -2708,6 +2709,62 @@ function getDocsUrl(pathname = "") {
|
|
|
2708
2709
|
return `${DOCS_BASE_URL}${path}`;
|
|
2709
2710
|
}
|
|
2710
2711
|
|
|
2712
|
+
function isProcessAlive(pid) {
|
|
2713
|
+
const value = Number(pid);
|
|
2714
|
+
if (!Number.isFinite(value) || value <= 0) return false;
|
|
2715
|
+
try {
|
|
2716
|
+
process.kill(Math.floor(value), 0);
|
|
2717
|
+
return true;
|
|
2718
|
+
} catch {
|
|
2719
|
+
return false;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
async function maybeAutostartGateway(runtime, { cwd, io = console } = {}) {
|
|
2724
|
+
if (process.env.WATERBROTHER_GATEWAY_CHILD === "1") {
|
|
2725
|
+
return { attempted: false, started: false, reason: "gateway-child" };
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
const gateway = runtime?.gateway || {};
|
|
2729
|
+
const startupChannels = Array.isArray(gateway.startupChannels)
|
|
2730
|
+
? gateway.startupChannels.map((value) => String(value || "").trim().toLowerCase()).filter(Boolean)
|
|
2731
|
+
: [];
|
|
2732
|
+
|
|
2733
|
+
if (!gateway.enabled || !startupChannels.includes("telegram")) {
|
|
2734
|
+
return { attempted: false, started: false, reason: "not-configured" };
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
const telegram = runtime?.channels?.telegram || {};
|
|
2738
|
+
if (!telegram.enabled || !String(telegram.botToken || "").trim()) {
|
|
2739
|
+
return { attempted: false, started: false, reason: "telegram-not-ready" };
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
const processInfo = await loadGatewayProcessInfo("telegram");
|
|
2743
|
+
if (isProcessAlive(processInfo.pid)) {
|
|
2744
|
+
return { attempted: true, started: false, reason: "already-running", pid: processInfo.pid };
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
const child = spawn(process.execPath, [BIN_PATH, "gateway", "run", "telegram", "--skip-onboarding"], {
|
|
2748
|
+
cwd,
|
|
2749
|
+
detached: true,
|
|
2750
|
+
stdio: "ignore",
|
|
2751
|
+
env: {
|
|
2752
|
+
...process.env,
|
|
2753
|
+
WATERBROTHER_GATEWAY_CHILD: "1"
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
child.unref();
|
|
2757
|
+
|
|
2758
|
+
await saveGatewayProcessInfo("telegram", {
|
|
2759
|
+
pid: child.pid,
|
|
2760
|
+
startedAt: new Date().toISOString(),
|
|
2761
|
+
command: "gateway run telegram --skip-onboarding"
|
|
2762
|
+
});
|
|
2763
|
+
|
|
2764
|
+
io.log?.(dim(`telegram gateway autostarted in background (pid ${child.pid})`));
|
|
2765
|
+
return { attempted: true, started: true, pid: child.pid };
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2711
2768
|
async function chooseFromInteractiveMenu({ title, options, defaultIndex = 0 }) {
|
|
2712
2769
|
if (!process.stdin.isTTY || !process.stdout.isTTY || options.length === 0) {
|
|
2713
2770
|
return options[Math.max(0, Math.min(defaultIndex, options.length - 1))] || null;
|
|
@@ -3075,7 +3132,8 @@ async function runOnboardingWizard(config, { cwd }) {
|
|
|
3075
3132
|
};
|
|
3076
3133
|
|
|
3077
3134
|
console.log(green("Telegram configured for DM-first pairing."));
|
|
3078
|
-
console.log(dim("
|
|
3135
|
+
console.log(dim("When you launch the Waterbrother TUI, the Telegram gateway will now autostart in the background."));
|
|
3136
|
+
console.log(dim("You can still run `waterbrother gateway run telegram` manually if you want a dedicated gateway process."));
|
|
3079
3137
|
} else {
|
|
3080
3138
|
console.log(dim("Skipping Telegram setup for now. You can always run `waterbrother onboarding telegram` later."));
|
|
3081
3139
|
}
|
|
@@ -6476,6 +6534,7 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
|
|
|
6476
6534
|
}
|
|
6477
6535
|
}
|
|
6478
6536
|
|
|
6537
|
+
await maybeAutostartGateway(context.runtime, { cwd: context.cwd });
|
|
6479
6538
|
printInteractiveHeader({ runtime: context.runtime, agent, cwd: context.cwd });
|
|
6480
6539
|
console.log("Interactive mode. Type /exit to quit, /help for commands.");
|
|
6481
6540
|
if (process.stdout.isTTY) {
|
package/src/gateway-state.js
CHANGED
|
@@ -9,6 +9,10 @@ function gatewayStatePath(serviceId) {
|
|
|
9
9
|
return path.join(GATEWAY_DIR, `${String(serviceId || "").trim().toLowerCase()}.json`);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function gatewayProcessPath(serviceId) {
|
|
13
|
+
return path.join(GATEWAY_DIR, `${String(serviceId || "").trim().toLowerCase()}.process.json`);
|
|
14
|
+
}
|
|
15
|
+
|
|
12
16
|
function normalizeGatewayState(parsed = {}) {
|
|
13
17
|
return {
|
|
14
18
|
offset: Number.isFinite(Number(parsed?.offset)) ? Math.max(0, Math.floor(Number(parsed.offset))) : 0,
|
|
@@ -68,3 +72,36 @@ export async function saveGatewayState(serviceId, state) {
|
|
|
68
72
|
await fs.rename(tmpPath, filePath);
|
|
69
73
|
return next;
|
|
70
74
|
}
|
|
75
|
+
|
|
76
|
+
export async function loadGatewayProcessInfo(serviceId) {
|
|
77
|
+
await ensureGatewayStateDir();
|
|
78
|
+
const filePath = gatewayProcessPath(serviceId);
|
|
79
|
+
try {
|
|
80
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
81
|
+
const parsed = JSON.parse(raw);
|
|
82
|
+
return {
|
|
83
|
+
pid: Number.isFinite(Number(parsed?.pid)) ? Math.floor(Number(parsed.pid)) : 0,
|
|
84
|
+
startedAt: String(parsed?.startedAt || "").trim(),
|
|
85
|
+
command: String(parsed?.command || "").trim()
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error?.code === "ENOENT") {
|
|
89
|
+
return { pid: 0, startedAt: "", command: "" };
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function saveGatewayProcessInfo(serviceId, info = {}) {
|
|
96
|
+
await ensureGatewayStateDir();
|
|
97
|
+
const filePath = gatewayProcessPath(serviceId);
|
|
98
|
+
const next = {
|
|
99
|
+
pid: Number.isFinite(Number(info?.pid)) ? Math.floor(Number(info.pid)) : 0,
|
|
100
|
+
startedAt: String(info?.startedAt || "").trim(),
|
|
101
|
+
command: String(info?.command || "").trim()
|
|
102
|
+
};
|
|
103
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
104
|
+
await fs.writeFile(tmpPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
105
|
+
await fs.rename(tmpPath, filePath);
|
|
106
|
+
return next;
|
|
107
|
+
}
|