@kenkaiiii/ggcoder 4.10.2 → 4.11.1

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.
@@ -0,0 +1,250 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { log } from "./logger.js";
5
+ export const RADIO_STATIONS = [
6
+ {
7
+ id: "somafm-groove-salad",
8
+ name: "SomaFM · Groove Salad",
9
+ description: "Chilled downtempo, ambient grooves",
10
+ url: "http://ice1.somafm.com/groovesalad-128-mp3",
11
+ },
12
+ {
13
+ id: "somafm-drone-zone",
14
+ name: "SomaFM · Drone Zone",
15
+ description: "Atmospheric textures with minimal beats",
16
+ url: "http://ice1.somafm.com/dronezone-128-mp3",
17
+ },
18
+ {
19
+ id: "radio-paradise",
20
+ name: "Radio Paradise",
21
+ description: "Eclectic mix — rock, electronica, jazz",
22
+ url: "http://stream.radioparadise.com/mp3-128",
23
+ },
24
+ {
25
+ id: "george-fm",
26
+ name: "George FM",
27
+ description: "NZ dance + electronic",
28
+ url: "https://mediaworks.streamguys1.com/george_net_icy",
29
+ },
30
+ ];
31
+ /**
32
+ * Well-known directories where streaming players get installed but which a
33
+ * GUI app's minimal PATH usually omits. macOS apps launched from Finder/Dock
34
+ * inherit only `/usr/bin:/bin:/usr/sbin:/sbin` — not Homebrew (`/opt/homebrew/bin`
35
+ * on Apple Silicon, `/usr/local/bin` on Intel) or MacPorts (`/opt/local/bin`),
36
+ * so `spawn("mpv")` ENOENTs even when mpv is installed. We search these in
37
+ * addition to PATH. Linux dirs cover the common package-manager prefixes.
38
+ */
39
+ function extraPlayerDirs() {
40
+ switch (process.platform) {
41
+ case "darwin":
42
+ return ["/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin"];
43
+ case "linux":
44
+ return ["/usr/bin", "/usr/local/bin", "/bin", "/snap/bin"];
45
+ default:
46
+ return [];
47
+ }
48
+ }
49
+ /**
50
+ * Resolve a player command to a runnable path. Probes PATH plus the well-known
51
+ * install dirs and returns the first absolute hit, so GUI apps find players in
52
+ * Homebrew/MacPorts dirs their minimal PATH omits. Returns null when the binary
53
+ * isn't found anywhere we look.
54
+ *
55
+ * Windows is left to the OS: executables carry extensions (.exe/.cmd) resolved
56
+ * via PATHEXT, and GUI apps there inherit a usable PATH — so we return `cmd`
57
+ * unchanged and let spawn do its normal lookup (probing bare names here would
58
+ * miss `mpv.exe` and regress Windows).
59
+ */
60
+ function resolvePlayerPath(cmd) {
61
+ // An explicit path is used as-is.
62
+ if (cmd.includes(path.sep))
63
+ return existsSync(cmd) ? cmd : null;
64
+ // Defer to the OS on Windows (PATHEXT handles the extension).
65
+ if (process.platform === "win32")
66
+ return cmd;
67
+ // 1) PATH (covers terminal launches + any inherited shell environment).
68
+ const pathDirs = (process.env.PATH ?? "").split(path.delimiter).filter(Boolean);
69
+ // 2) Well-known dirs a GUI PATH typically omits.
70
+ for (const dir of [...pathDirs, ...extraPlayerDirs()]) {
71
+ const candidate = path.join(dir, cmd);
72
+ if (existsSync(candidate))
73
+ return candidate;
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Streaming-capable players in priority order. Each gets its quietest flag
79
+ * combination — radio runs in the background, we don't want stdout/stderr
80
+ * spam. Stdio is also redirected to "ignore" at spawn time.
81
+ */
82
+ const PLAYERS = [
83
+ { cmd: "mpv", args: (u) => ["--really-quiet", "--no-video", "--no-terminal", u] },
84
+ { cmd: "ffplay", args: (u) => ["-nodisp", "-autoexit", "-loglevel", "quiet", u] },
85
+ { cmd: "mpg123", args: (u) => ["-q", u] },
86
+ { cmd: "cvlc", args: (u) => ["--play-and-exit", "--quiet", u] },
87
+ ];
88
+ let currentChild = null;
89
+ let currentStationId = null;
90
+ export function getCurrentStation() {
91
+ return currentStationId;
92
+ }
93
+ /**
94
+ * Stop whatever's currently playing. Idempotent — safe to call when nothing
95
+ * is playing. Sends SIGTERM (graceful), child cleans up the audio device.
96
+ */
97
+ export function stopRadio() {
98
+ if (!currentChild)
99
+ return;
100
+ try {
101
+ // Detached children sit in their own process group on POSIX; kill the
102
+ // whole group so any helper threads/forks die too. On Windows there's no
103
+ // process group concept — kill() targets the child only.
104
+ if (process.platform !== "win32" && currentChild.pid) {
105
+ try {
106
+ process.kill(-currentChild.pid, "SIGTERM");
107
+ }
108
+ catch {
109
+ currentChild.kill("SIGTERM");
110
+ }
111
+ }
112
+ else {
113
+ currentChild.kill("SIGTERM");
114
+ }
115
+ }
116
+ catch {
117
+ // Already exited — nothing to do.
118
+ }
119
+ currentChild = null;
120
+ currentStationId = null;
121
+ log("INFO", "radio", "stopped");
122
+ }
123
+ /**
124
+ * On WSL2, native Linux audio binaries can't reach the Windows audio device
125
+ * through WSLg's bridge in any useful way for streaming — detect WSL and route
126
+ * through the Windows host instead.
127
+ */
128
+ function isWsl() {
129
+ if (process.platform !== "linux")
130
+ return false;
131
+ return !!process.env.WSL_DISTRO_NAME || existsSync("/proc/sys/fs/binfmt_misc/WSLInterop");
132
+ }
133
+ /**
134
+ * Stream a station through powershell.exe + WPF MediaPlayer on the Windows host
135
+ * instead of a Linux binary. Returns the ChildProcess or null on spawn failure.
136
+ * The URL is allowlist-checked, scheme-enforced, and passed via env (never
137
+ * interpolated into the -Command string).
138
+ */
139
+ function tryPlayOnWindowsHost(station) {
140
+ const allowedUrls = new Set(RADIO_STATIONS.map((s) => s.url));
141
+ if (!allowedUrls.has(station.url))
142
+ return null;
143
+ if (!/^https?:\/\//i.test(station.url))
144
+ return null;
145
+ const psScript = [
146
+ "Add-Type -AssemblyName presentationCore;",
147
+ "Add-Type -AssemblyName WindowsBase;",
148
+ "$p = New-Object System.Windows.Media.MediaPlayer;",
149
+ "$p.Open([uri]$env:GG_RADIO_URL);",
150
+ "$p.Play();",
151
+ "[System.Windows.Threading.Dispatcher]::Run();",
152
+ ].join(" ");
153
+ try {
154
+ return spawn("powershell.exe", ["-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript], {
155
+ detached: true,
156
+ stdio: "ignore",
157
+ env: {
158
+ ...process.env,
159
+ GG_RADIO_URL: station.url,
160
+ WSLENV: (process.env.WSLENV ? process.env.WSLENV + ":" : "") + "GG_RADIO_URL",
161
+ },
162
+ });
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
168
+ /**
169
+ * Spawn a streaming player for the given station. If one is already playing,
170
+ * it's killed first. Returns ok=false with a hint if no compatible player is
171
+ * installed — caller should surface the error to the user.
172
+ */
173
+ export function playRadio(stationId) {
174
+ const station = RADIO_STATIONS.find((s) => s.id === stationId);
175
+ if (!station)
176
+ return { ok: false, error: `Unknown station: ${stationId}` };
177
+ // Always stop the previous stream before starting a new one.
178
+ stopRadio();
179
+ if (isWsl()) {
180
+ const child = tryPlayOnWindowsHost(station);
181
+ if (child) {
182
+ let errored = false;
183
+ child.once("error", () => {
184
+ errored = true;
185
+ });
186
+ if (child.pid && !errored) {
187
+ currentChild = child;
188
+ currentStationId = stationId;
189
+ log("INFO", "radio", "playing", {
190
+ station: station.id,
191
+ player: "powershell.exe (wsl→host)",
192
+ url: station.url,
193
+ });
194
+ child.unref();
195
+ return { ok: true };
196
+ }
197
+ }
198
+ }
199
+ for (const player of PLAYERS) {
200
+ // Resolve to an absolute path first so we find players in Homebrew/MacPorts
201
+ // dirs that a GUI app's minimal PATH omits. Skip when not installed.
202
+ const bin = resolvePlayerPath(player.cmd);
203
+ if (!bin)
204
+ continue;
205
+ try {
206
+ const child = spawn(bin, player.args(station.url), {
207
+ detached: process.platform !== "win32",
208
+ stdio: "ignore",
209
+ });
210
+ let errored = false;
211
+ child.once("error", () => {
212
+ errored = true;
213
+ });
214
+ if (child.pid && !errored) {
215
+ currentChild = child;
216
+ currentStationId = stationId;
217
+ log("INFO", "radio", "playing", {
218
+ station: station.id,
219
+ player: player.cmd,
220
+ url: station.url,
221
+ });
222
+ child.unref();
223
+ return { ok: true };
224
+ }
225
+ }
226
+ catch {
227
+ // ENOENT or permission — try the next player.
228
+ }
229
+ }
230
+ log("WARN", "radio", "no compatible player found", { platform: process.platform });
231
+ return { ok: false, error: buildInstallHint() };
232
+ }
233
+ /**
234
+ * Platform-specific one-line install hint so the user can copy-paste rather
235
+ * than reading generic suggestions.
236
+ */
237
+ function buildInstallHint() {
238
+ const base = "Radio needs a streaming player. Install one of: mpv (recommended), ffplay, mpg123, or vlc.";
239
+ switch (process.platform) {
240
+ case "darwin":
241
+ return `${base} On macOS: \`brew install mpv\` (or \`brew install ffmpeg\` for ffplay).`;
242
+ case "linux":
243
+ return `${base} On Linux (Debian/Ubuntu): \`sudo apt install mpv\`. Fedora: \`sudo dnf install mpv\`. Arch: \`sudo pacman -S mpv\`.`;
244
+ case "win32":
245
+ return `${base} On Windows: \`winget install mpv.mpv\` (or download from https://mpv.io).`;
246
+ default:
247
+ return `${base} See https://mpv.io for platform installation instructions.`;
248
+ }
249
+ }
250
+ //# sourceMappingURL=radio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radio.js","sourceRoot":"","sources":["../../src/core/radio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Compute the enriched PATH (login shell ∪ current ∪ existing well-known dirs).
3
+ * Cached after first call. Order is preserved and duplicates removed.
4
+ */
5
+ export declare function resolveEnrichedPath(currentPath?: string): Promise<string>;
6
+ /**
7
+ * Enrich `process.env.PATH` in place so every later spawn/execFile (the bash
8
+ * tool, background tasks, LSP servers, git helpers) inherits a working PATH.
9
+ * Idempotent and safe to call once at startup. No-op on Windows.
10
+ */
11
+ export declare function enrichProcessPath(): Promise<void>;
12
+ //# sourceMappingURL=shell-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-path.d.ts","sourceRoot":"","sources":["../../src/core/shell-path.ts"],"names":[],"mappings":"AA4HA;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,GAAE,MAA+B,GAC3C,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUvD"}
@@ -0,0 +1,166 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { log } from "./logger.js";
6
+ /**
7
+ * Resolve a usable PATH for shelling out to developer tools.
8
+ *
9
+ * GUI apps launched from Finder/Dock (the packaged desktop app) inherit a
10
+ * minimal PATH — on macOS just `/usr/bin:/bin:/usr/sbin:/sbin` — which omits
11
+ * Homebrew (`/opt/homebrew/bin`, `/usr/local/bin`), Cargo (`~/.cargo/bin`),
12
+ * version managers (nvm/asdf/pyenv), and most toolchains. A coding agent that
13
+ * can't find `node`, `npm`, `git`, `python3`, `rg`, `cargo`, … is useless, so
14
+ * we enrich the process PATH once at sidecar startup.
15
+ *
16
+ * Strategy (union, de-duped, order-preserving):
17
+ * 1. The user's real login-shell PATH (captures nvm/asdf/pyenv/custom dirs).
18
+ * 2. The current process PATH (whatever we were launched with).
19
+ * 3. Well-known install dirs that exist on disk (fallback when 1 fails).
20
+ *
21
+ * No-op on Windows, where GUI apps inherit a usable PATH and there's no
22
+ * equivalent login-shell concept.
23
+ */
24
+ /** Well-known bin dirs a GUI PATH commonly omits, by platform. */
25
+ function wellKnownBinDirs() {
26
+ const home = os.homedir();
27
+ if (process.platform === "darwin") {
28
+ return [
29
+ "/opt/homebrew/bin",
30
+ "/opt/homebrew/sbin",
31
+ "/usr/local/bin",
32
+ "/usr/local/sbin",
33
+ "/opt/local/bin", // MacPorts
34
+ "/opt/local/sbin",
35
+ path.join(home, ".local", "bin"),
36
+ path.join(home, ".cargo", "bin"),
37
+ path.join(home, "go", "bin"),
38
+ path.join(home, ".deno", "bin"),
39
+ path.join(home, ".bun", "bin"),
40
+ ];
41
+ }
42
+ if (process.platform === "linux") {
43
+ return [
44
+ "/usr/local/bin",
45
+ "/usr/local/sbin",
46
+ "/usr/bin",
47
+ "/bin",
48
+ "/usr/sbin",
49
+ "/sbin",
50
+ "/snap/bin",
51
+ path.join(home, ".local", "bin"),
52
+ path.join(home, ".cargo", "bin"),
53
+ path.join(home, "go", "bin"),
54
+ path.join(home, ".deno", "bin"),
55
+ path.join(home, ".bun", "bin"),
56
+ ];
57
+ }
58
+ return [];
59
+ }
60
+ const DELIMITER = "_GG_SHELL_ENV_DELIMITER_";
61
+ /**
62
+ * Ask the user's login shell for its PATH. Best-effort: resolves to null on any
63
+ * error/timeout so the caller falls back to the well-known dirs. Mirrors the
64
+ * approach used by `shell-env`/`fix-path` (delimited echo, detached to dodge a
65
+ * zsh hang, oh-my-zsh auto-update disabled).
66
+ */
67
+ function loginShellPath() {
68
+ if (process.platform === "win32")
69
+ return Promise.resolve(null);
70
+ const shell = process.env.SHELL || "/bin/zsh";
71
+ return new Promise((resolve) => {
72
+ let settled = false;
73
+ const finish = (value) => {
74
+ if (settled)
75
+ return;
76
+ settled = true;
77
+ resolve(value);
78
+ };
79
+ try {
80
+ const child = spawn(shell, ["-ilc", `echo -n "${DELIMITER}"; printf "%s" "$PATH"; echo -n "${DELIMITER}"; exit`], {
81
+ // zsh can hang on stdin without detaching; matches shell-env's fix.
82
+ detached: true,
83
+ stdio: ["ignore", "pipe", "ignore"],
84
+ env: { ...process.env, DISABLE_AUTO_UPDATE: "true" },
85
+ });
86
+ let out = "";
87
+ child.stdout?.on("data", (d) => {
88
+ out += d.toString("utf-8");
89
+ });
90
+ child.once("error", () => finish(null));
91
+ child.once("close", () => {
92
+ const start = out.indexOf(DELIMITER);
93
+ const end = out.lastIndexOf(DELIMITER);
94
+ if (start === -1 || end === -1 || end <= start) {
95
+ finish(null);
96
+ return;
97
+ }
98
+ const value = out.slice(start + DELIMITER.length, end).trim();
99
+ finish(value || null);
100
+ });
101
+ // Hard cap: some shells (slow rc files) can stall; don't block startup.
102
+ const timer = setTimeout(() => {
103
+ try {
104
+ if (child.pid)
105
+ process.kill(-child.pid, "SIGKILL");
106
+ }
107
+ catch {
108
+ child.kill("SIGKILL");
109
+ }
110
+ finish(null);
111
+ }, 5000);
112
+ timer.unref();
113
+ }
114
+ catch {
115
+ finish(null);
116
+ }
117
+ });
118
+ }
119
+ let cached;
120
+ /**
121
+ * Compute the enriched PATH (login shell ∪ current ∪ existing well-known dirs).
122
+ * Cached after first call. Order is preserved and duplicates removed.
123
+ */
124
+ export async function resolveEnrichedPath(currentPath = process.env.PATH ?? "") {
125
+ if (cached !== undefined)
126
+ return cached;
127
+ const parts = [];
128
+ const seen = new Set();
129
+ const add = (dir) => {
130
+ const d = dir.trim();
131
+ if (d && !seen.has(d)) {
132
+ seen.add(d);
133
+ parts.push(d);
134
+ }
135
+ };
136
+ const login = await loginShellPath();
137
+ if (login)
138
+ for (const d of login.split(path.delimiter))
139
+ add(d);
140
+ for (const d of currentPath.split(path.delimiter))
141
+ add(d);
142
+ // Only append fallback dirs that actually exist, so PATH stays clean.
143
+ for (const d of wellKnownBinDirs())
144
+ if (existsSync(d))
145
+ add(d);
146
+ cached = parts.join(path.delimiter);
147
+ return cached;
148
+ }
149
+ /**
150
+ * Enrich `process.env.PATH` in place so every later spawn/execFile (the bash
151
+ * tool, background tasks, LSP servers, git helpers) inherits a working PATH.
152
+ * Idempotent and safe to call once at startup. No-op on Windows.
153
+ */
154
+ export async function enrichProcessPath() {
155
+ if (process.platform === "win32")
156
+ return;
157
+ const before = process.env.PATH ?? "";
158
+ const enriched = await resolveEnrichedPath(before);
159
+ if (enriched && enriched !== before) {
160
+ process.env.PATH = enriched;
161
+ log("INFO", "shell-path", "enriched PATH for tools", {
162
+ added: enriched.length - before.length,
163
+ });
164
+ }
165
+ }
166
+ //# sourceMappingURL=shell-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-path.js","sourceRoot":"","sources":["../../src/core/shell-path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface TelegramConfig {
2
+ botToken: string;
3
+ userId: number;
4
+ }
5
+ /** Read the saved Telegram config, or null when unset/incomplete. */
6
+ export declare function loadTelegramConfig(): Promise<TelegramConfig | null>;
7
+ /** Persist the Telegram config with owner-only permissions (0600). */
8
+ export declare function saveTelegramConfig(config: TelegramConfig): Promise<void>;
9
+ /** Rough bot-token shape check (`123456789:ABCdef...`). */
10
+ export declare function isValidBotTokenFormat(token: string): boolean;
11
+ export interface BotVerification {
12
+ ok: boolean;
13
+ username?: string;
14
+ firstName?: string;
15
+ }
16
+ /**
17
+ * Verify a bot token against Telegram's getMe. Returns `{ ok: false }` when the
18
+ * token is malformed or Telegram rejects it (never throws on a bad token).
19
+ */
20
+ export declare function verifyBotToken(botToken: string): Promise<BotVerification>;
21
+ //# sourceMappingURL=telegram-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-config.d.ts","sourceRoot":"","sources":["../../src/core/telegram-config.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CASzE;AAED,sEAAsE;AACtE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAM9E;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAa/E"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Shared Telegram bot configuration: the `~/.gg/telegram.json` store (bot token
3
+ * + authorized user id) plus bot-token verification. Used by the CLI
4
+ * (`ggcoder telegram` / `ggcoder serve`) and the desktop app sidecar so both
5
+ * read/write the same file with the same validation.
6
+ */
7
+ import fs from "node:fs/promises";
8
+ import { ensureAppDirs, getAppPaths } from "../config.js";
9
+ /** Read the saved Telegram config, or null when unset/incomplete. */
10
+ export async function loadTelegramConfig() {
11
+ try {
12
+ const raw = await fs.readFile(getAppPaths().telegramFile, "utf-8");
13
+ const data = JSON.parse(raw);
14
+ if (data.botToken && data.userId)
15
+ return data;
16
+ return null;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ /** Persist the Telegram config with owner-only permissions (0600). */
23
+ export async function saveTelegramConfig(config) {
24
+ const paths = await ensureAppDirs();
25
+ await fs.writeFile(paths.telegramFile, JSON.stringify(config, null, 2), {
26
+ encoding: "utf-8",
27
+ mode: 0o600,
28
+ });
29
+ }
30
+ /** Rough bot-token shape check (`123456789:ABCdef...`). */
31
+ export function isValidBotTokenFormat(token) {
32
+ return /^\d+:[A-Za-z0-9_-]+$/.test(token);
33
+ }
34
+ /**
35
+ * Verify a bot token against Telegram's getMe. Returns `{ ok: false }` when the
36
+ * token is malformed or Telegram rejects it (never throws on a bad token).
37
+ */
38
+ export async function verifyBotToken(botToken) {
39
+ if (!isValidBotTokenFormat(botToken))
40
+ return { ok: false };
41
+ try {
42
+ const res = await fetch(`https://api.telegram.org/bot${botToken}/getMe`, { method: "POST" });
43
+ const data = (await res.json());
44
+ if (!data.ok || !data.result)
45
+ return { ok: false };
46
+ return { ok: true, username: data.result.username, firstName: data.result.first_name };
47
+ }
48
+ catch {
49
+ return { ok: false };
50
+ }
51
+ }
52
+ //# sourceMappingURL=telegram-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-config.js","sourceRoot":"","sources":["../../src/core/telegram-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAO1D,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;QAC/C,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAsB;IAC7D,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACtE,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5C,CAAC;AAQD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB;IACnD,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,+BAA+B,QAAQ,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7F,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAG7B,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -9,6 +9,17 @@ export interface ServeModeOptions {
9
9
  botToken: string;
10
10
  userId: number;
11
11
  };
12
+ /**
13
+ * Embedded mode (desktop app): skip the terminal banner/console output and
14
+ * don't register process signal handlers — the host process owns those. The
15
+ * bot polls in the background and the caller stops it via the returned
16
+ * controller. Defaults to false (the CLI's blocking, banner-printing flow).
17
+ */
18
+ embedded?: boolean;
19
+ }
20
+ /** Handle for an embedded serve session: stop polling + dispose all sessions. */
21
+ export interface ServeController {
22
+ stop: () => Promise<void>;
12
23
  }
13
24
  /**
14
25
  * Serve mode: run ggcoder controlled via Telegram.
@@ -17,5 +28,18 @@ export interface ServeModeOptions {
17
28
  * - Groups → linked projects via /link <path>
18
29
  * - Each chat gets its own AgentSession, tools, context
19
30
  */
31
+ /**
32
+ * Build a serve session: wire the Telegram bot to per-chat AgentSessions and
33
+ * start long-polling in the background. Returns a controller to stop it.
34
+ *
35
+ * Shared by the CLI (`ggcoder serve`, via {@link runServeMode}) and the desktop
36
+ * app sidecar. In `embedded` mode it stays silent and leaves process-signal
37
+ * handling to the host.
38
+ */
39
+ export declare function startServeMode(options: ServeModeOptions): Promise<ServeController>;
40
+ /**
41
+ * CLI serve flow (`ggcoder serve`): prints the banner, starts the bot, wires
42
+ * SIGINT/SIGTERM to a graceful shutdown, then blocks until the process exits.
43
+ */
20
44
  export declare function runServeMode(options: ServeModeOptions): Promise<void>;
21
45
  //# sourceMappingURL=serve-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serve-mode.d.ts","sourceRoot":"","sources":["../../src/modes/serve-mode.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAehE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AA2ED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAurB3E"}
1
+ {"version":3,"file":"serve-mode.d.ts","sourceRoot":"","sources":["../../src/modes/serve-mode.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAgBhE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AA2ED;;;;;;GAMG;AACH;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA+rBxF;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3E"}