@infomity/remogent 0.2.2 → 0.2.3
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 +16 -10
- package/dist/adapters/gemini.js +76 -18
- package/dist/adapters/util.js +1 -1
- package/dist/index.js +16 -6
- package/dist/protocol.js +1 -1
- package/dist/sessions.js +1 -1
- package/dist/store.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>Zero-Recurring-Cost Android Remote AI Agent & Terminal Client</strong><br>
|
|
5
|
-
<em>Drive Gemini CLI, Claude Code CLI and Codex CLI directly from your phone over secure, pinned-TLS transport.</em>
|
|
5
|
+
<em>Drive Gemini CLI, Antigravity CLI, Claude Code CLI and Codex CLI directly from your phone over secure, pinned-TLS transport.</em>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
@@ -15,21 +15,26 @@
|
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
|
18
|
-
<img src="docs/screenshots/01-new-chat.png" alt="Remogent new chat screen with agent,
|
|
19
|
-
<img src="docs/screenshots/
|
|
20
|
-
<img src="docs/screenshots/
|
|
18
|
+
<img src="docs/screenshots/01-new-chat.png" alt="Remogent new chat screen with agent, model and permission-mode chips" width="190">
|
|
19
|
+
<img src="docs/screenshots/05-agents.png" alt="Remogent agent picker with Claude Code, Gemini CLI, Antigravity CLI and Codex CLI" width="190">
|
|
20
|
+
<img src="docs/screenshots/09-folder.png" alt="Remogent working folder sheet with recent folders" width="190">
|
|
21
|
+
<img src="docs/screenshots/07-permissions.png" alt="Remogent permission mode sheet: Ask before acting, Auto-edit, Plan first, Full auto" width="190">
|
|
22
|
+
<br>
|
|
21
23
|
<img src="docs/screenshots/04-approval.png" alt="Remogent tool approval sheet with diff preview and Deny, Allow once and For session buttons" width="190">
|
|
24
|
+
<img src="docs/screenshots/03-chat.png" alt="Remogent chat showing a shell tool card and the agent's reply" width="190">
|
|
25
|
+
<img src="docs/screenshots/02-sessions.png" alt="Remogent drawer listing chat sessions with their agent badges" width="190">
|
|
26
|
+
<img src="docs/screenshots/08-settings.png" alt="Remogent settings with theme, default working folder and pinned TLS fingerprint" width="190">
|
|
22
27
|
</p>
|
|
23
28
|
|
|
24
29
|
---
|
|
25
30
|
|
|
26
31
|
## Overview
|
|
27
32
|
|
|
28
|
-
**Remogent** enables software engineers to monitor, prompt, and approve actions from **Gemini CLI**, **Claude Code CLI** and **Codex CLI** from their Android phone without paying monthly cloud relay fees.
|
|
33
|
+
**Remogent** enables software engineers to monitor, prompt, and approve actions from **Gemini CLI**, **Antigravity CLI**, **Claude Code CLI** and **Codex CLI** from their Android phone without paying monthly cloud relay fees.
|
|
29
34
|
|
|
30
35
|
### Core Pillars
|
|
31
36
|
- **$0.00 Infrastructure Cost**: Direct peer-to-peer transport over local Wi-Fi, Tailscale, WireGuard, or Cloudflare tunnels. No intermediary cloud servers, no subscriptions.
|
|
32
|
-
- **
|
|
37
|
+
- **Four AI Engines**: First-class support for **Gemini CLI** (stdio JSON-RPC 2.0 ACP protocol), **Antigravity CLI** (`agy acp`, the same ACP dialect), **Claude Code CLI** (bidirectional stream-json protocol) and **Codex CLI** (`codex app-server` JSON-RPC).
|
|
33
38
|
- **Per-Chat Model Choice**: The model list is read from each engine's own CLI on your PC (so it follows your logins and settings, including a custom Claude provider). Pick a model when you start a chat and switch it between turns from the chat details; without a choice the engine uses its own default.
|
|
34
39
|
- **Interactive Tool Approvals**: Destructive operations (`Bash`, file edits, network calls) trigger an audible/haptic approval bottom sheet on your phone with granular control: `Deny`, `Allow Once`, or `Allow for Session`.
|
|
35
40
|
- **Biometric Guard**: Gate tool authorizations and sensitive executions behind on-device biometric fingerprint / face authentication.
|
|
@@ -57,7 +62,7 @@
|
|
|
57
62
|
|
|
58
63
|
### 1. Host Companion Setup (PC)
|
|
59
64
|
|
|
60
|
-
**Prerequisites:** Node.js >= 20.x, and at least one of `gemini`, `claude` or `codex` installed (found automatically, no PATH editing needed).
|
|
65
|
+
**Prerequisites:** Node.js >= 20.x, and at least one of `gemini`, `agy`, `claude` or `codex` installed (found automatically, no PATH editing needed).
|
|
61
66
|
|
|
62
67
|
```bash
|
|
63
68
|
# Easiest: one command, no clone needed (npm package @infomity/remogent).
|
|
@@ -80,7 +85,7 @@ cd Remogent/companion
|
|
|
80
85
|
npm run setup
|
|
81
86
|
```
|
|
82
87
|
|
|
83
|
-
`doctor` finds the Claude Code, Gemini and Codex CLIs by itself (PATH, then the native installer, npm -g, Volta, pnpm, nvm, Homebrew, scoop), so you never edit PATH, and tells you exactly what to install if something is missing. Run it any time with `remogent doctor` (or `npm run doctor` from source). `install` (the last step of `setup`) registers an autostart service that runs as your user (it needs your `claude` / `gemini` / `codex` logins), starts at sign-in and restarts if it crashes: Task Scheduler on Windows, launchd on macOS, `systemd --user` on Linux. After pairing, the host is simply there; no terminal to keep open. Related commands (from source use `node dist/index.js` instead of `remogent`):
|
|
88
|
+
`doctor` finds the Claude Code, Gemini, Antigravity and Codex CLIs by itself (PATH, then the native installer, npm -g, Volta, pnpm, nvm, Homebrew, scoop), so you never edit PATH, and tells you exactly what to install if something is missing. Run it any time with `remogent doctor` (or `npm run doctor` from source). `install` (the last step of `setup`) registers an autostart service that runs as your user (it needs your `claude` / `gemini` / `codex` logins), starts at sign-in and restarts if it crashes: Task Scheduler on Windows, launchd on macOS, `systemd --user` on Linux. After pairing, the host is simply there; no terminal to keep open. Related commands (from source use `node dist/index.js` instead of `remogent`):
|
|
84
89
|
|
|
85
90
|
```bash
|
|
86
91
|
remogent pair # show the pairing QR again (new phone, or you closed the setup window)
|
|
@@ -105,7 +110,7 @@ To rotate the authentication secret at any time:
|
|
|
105
110
|
node dist/index.js install --rotate # re-registers the service with a new secret; re-pair the phone afterwards
|
|
106
111
|
```
|
|
107
112
|
|
|
108
|
-
Chats survive daemon restarts: transcripts are saved to `~/.remogent/sessions.json` (owner-only file, newest ~2 MiB per chat, 14 days / 100 chats). Claude Code and Codex chats resume with their full context on the next prompt; Gemini chats come back as read-only history. Pending approvals and "Allow for session" grants are never restored. To keep nothing on disk, start with `--no-persist`.
|
|
113
|
+
Chats survive daemon restarts: transcripts are saved to `~/.remogent/sessions.json` (owner-only file, newest ~2 MiB per chat, 14 days / 100 chats). Claude Code and Codex chats resume with their full context on the next prompt; Gemini and Antigravity chats come back as read-only history. Pending approvals and "Allow for session" grants are never restored. To keep nothing on disk, start with `--no-persist`.
|
|
109
114
|
|
|
110
115
|
---
|
|
111
116
|
|
|
@@ -162,13 +167,14 @@ Keep Tailscale running on the phone (Android may close VPN apps aggressively und
|
|
|
162
167
|
1. In the active host card, tap **NEW AI SESSION**.
|
|
163
168
|
2. Select your desired engine:
|
|
164
169
|
- **Gemini CLI** (stdio ACP JSON-RPC)
|
|
170
|
+
- **Antigravity CLI** (stdio `agy acp`, same ACP dialect as Gemini; found in `~/.gemini/bin`). Note: inside folders listed in `trustedWorkspaces` of `~/.gemini/antigravity-cli/settings.json`, agy applies edits without asking even in Ask mode (shell commands still ask); the host warns in the chat and in `doctor`.
|
|
165
171
|
- **Claude Code CLI** (stdio stream-json)
|
|
166
172
|
- **Codex CLI** (stdio `codex app-server`; sign in once with `codex login`)
|
|
167
173
|
3. Optionally tap the model chip to pick one of the models the engine's CLI lists (skip it to use the engine's default).
|
|
168
174
|
4. Set the working directory (e.g. `.` or an absolute project path).
|
|
169
175
|
5. Choose the permission mode:
|
|
170
176
|
- `Ask` (Default): Every mutating shell command or file edit prompts for your confirmation.
|
|
171
|
-
- `Auto (YOLO)`: Automatically approves commands (Gemini `yolo` / Claude `bypassPermissions` / Codex no sandbox, never ask).
|
|
177
|
+
- `Auto (YOLO)`: Automatically approves commands (Gemini `yolo` / Antigravity `auto` / Claude `bypassPermissions` / Codex no sandbox, never ask).
|
|
172
178
|
- `Plan Only`: Read-only execution mode.
|
|
173
179
|
6. Tap **START SESSION**.
|
|
174
180
|
7. Prompt the agent in real time, inspect reasoning in collapsible thought bubbles, and monitor tool outputs.
|
package/dist/adapters/gemini.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve, sep } from "node:path";
|
|
2
4
|
import { RpcError, RpcPeer } from "./rpc.js";
|
|
3
5
|
import { findOnPath, killTree, spawnCli } from "./util.js";
|
|
4
6
|
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -93,11 +95,50 @@ function toEvent(update) {
|
|
|
93
95
|
return undefined; // plan, available_commands_update, current_mode_update, user_message_chunk (history on load)
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
|
-
const
|
|
98
|
+
const GEMINI_MODE_IDS = { ask: "default", plan: "plan", auto_edit: "autoEdit", auto: "yolo" };
|
|
99
|
+
// Antigravity CLI (`agy acp`, verified on agy 1.5.6): same ACP dialect, different launch args and mode ids.
|
|
100
|
+
const AGY_MODE_IDS = { ask: "default", plan: "plan", auto_edit: "accept-edits", auto: "auto" };
|
|
101
|
+
const GEMINI_FLAVOR = { engine: "gemini", label: "gemini", args: ["--acp"], modeIds: GEMINI_MODE_IDS, signIn: "run `gemini` once on the host to sign in" };
|
|
102
|
+
const AGY_FLAVOR = { engine: "agy", label: "agy", args: ["acp"], modeIds: AGY_MODE_IDS, signIn: "run `agy` once on the host to sign in" };
|
|
97
103
|
/** Path of the `gemini` executable if it is installed on this host. */
|
|
98
104
|
export function findGemini() {
|
|
99
105
|
return findOnPath("gemini");
|
|
100
106
|
}
|
|
107
|
+
/** Path of the Antigravity CLI (`agy`, installed to ~/.gemini/bin) if it is installed on this host. */
|
|
108
|
+
export function findAgy() {
|
|
109
|
+
return findOnPath("agy");
|
|
110
|
+
}
|
|
111
|
+
/** Where Antigravity keeps its settings (shared `~/.gemini` config dir). */
|
|
112
|
+
export const AGY_SETTINGS_FILE = () => join(homedir(), ".gemini", "antigravity-cli", "settings.json");
|
|
113
|
+
/**
|
|
114
|
+
* The trusted workspace (`trustedWorkspaces` in agy's settings) that contains `cwd`, if any. Verified on agy 1.5.6: inside a
|
|
115
|
+
* trusted folder agy applies file edits WITHOUT a `session/request_permission` even in its default mode (shell commands still
|
|
116
|
+
* ask), and `--settings` cannot override it for an `acp` session. Best effort: unreadable settings mean "not trusted".
|
|
117
|
+
*/
|
|
118
|
+
export function agyTrustedWorkspace(cwd) {
|
|
119
|
+
let list;
|
|
120
|
+
try {
|
|
121
|
+
list = JSON.parse(readFileSync(AGY_SETTINGS_FILE(), "utf8"))["trustedWorkspaces"];
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(list))
|
|
127
|
+
return undefined;
|
|
128
|
+
const norm = (p) => {
|
|
129
|
+
const r = resolve(p);
|
|
130
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
131
|
+
};
|
|
132
|
+
const here = norm(cwd);
|
|
133
|
+
for (const t of list) {
|
|
134
|
+
if (typeof t !== "string" || !t)
|
|
135
|
+
continue;
|
|
136
|
+
const n = norm(t);
|
|
137
|
+
if (here === n || here.startsWith(n.endsWith(sep) ? n : n + sep))
|
|
138
|
+
return t;
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
101
142
|
function lastLine(s) {
|
|
102
143
|
const lines = s.trim().split(/\r?\n/);
|
|
103
144
|
return lines[lines.length - 1] ?? "";
|
|
@@ -120,15 +161,15 @@ function parseGeminiModels(res) {
|
|
|
120
161
|
});
|
|
121
162
|
}
|
|
122
163
|
/** Asks a short-lived `gemini --acp` process for its model list (initialize + session/new; no model call is made). */
|
|
123
|
-
async function
|
|
124
|
-
const child = spawnCli(binary,
|
|
164
|
+
async function queryAcpModels(binary, f) {
|
|
165
|
+
const child = spawnCli(binary, f.args, homedir());
|
|
125
166
|
const rpc = new RpcPeer(child);
|
|
126
167
|
let stderrTail = "";
|
|
127
168
|
child.stderr.on("data", (d) => {
|
|
128
169
|
stderrTail = (stderrTail + d.toString()).slice(-2000);
|
|
129
170
|
});
|
|
130
|
-
child.on("error", (e) => rpc.failAll(new Error(
|
|
131
|
-
child.on("exit", (code) => rpc.failAll(new Error(stderrTail ?
|
|
171
|
+
child.on("error", (e) => rpc.failAll(new Error(`${f.label} failed to start: ${e.message}`)));
|
|
172
|
+
child.on("exit", (code) => rpc.failAll(new Error(stderrTail ? `${f.label} exited (code ${code}): ${lastLine(stderrTail)}` : `${f.label} exited (code ${code})`)));
|
|
132
173
|
let timer;
|
|
133
174
|
try {
|
|
134
175
|
const work = (async () => {
|
|
@@ -136,7 +177,7 @@ async function queryGeminiModels(binary) {
|
|
|
136
177
|
return parseGeminiModels(await rpc.request("session/new", { cwd: homedir(), mcpServers: [] }));
|
|
137
178
|
})();
|
|
138
179
|
const timeout = new Promise((_, reject) => {
|
|
139
|
-
timer = setTimeout(() => reject(new Error(
|
|
180
|
+
timer = setTimeout(() => reject(new Error(`${f.label} did not answer in time`)), 20_000);
|
|
140
181
|
});
|
|
141
182
|
return await Promise.race([work, timeout]);
|
|
142
183
|
}
|
|
@@ -146,12 +187,12 @@ async function queryGeminiModels(binary) {
|
|
|
146
187
|
killTree(child);
|
|
147
188
|
}
|
|
148
189
|
}
|
|
149
|
-
|
|
190
|
+
function createAcpAdapter(binary, f) {
|
|
150
191
|
return {
|
|
151
|
-
engine:
|
|
152
|
-
listModels: () =>
|
|
192
|
+
engine: f.engine,
|
|
193
|
+
listModels: () => queryAcpModels(binary, f),
|
|
153
194
|
async start({ cwd, permissionMode, model }, sink) {
|
|
154
|
-
const child = spawnCli(binary,
|
|
195
|
+
const child = spawnCli(binary, f.args, cwd);
|
|
155
196
|
const rpc = new RpcPeer(child);
|
|
156
197
|
let closed = false; // set once the session is intentionally closed or has died
|
|
157
198
|
let inTurn = false; // a prompt request is in flight (its rejection reports process death)
|
|
@@ -170,8 +211,8 @@ export function createGeminiAdapter(binary) {
|
|
|
170
211
|
sink.emit({ kind: "error", message: err.message });
|
|
171
212
|
sink.emit({ kind: "status", status: "error" });
|
|
172
213
|
};
|
|
173
|
-
child.on("exit", (code) => died(
|
|
174
|
-
child.on("error", (e) => died(
|
|
214
|
+
child.on("exit", (code) => died(`${f.label} exited (code ${code})`));
|
|
215
|
+
child.on("error", (e) => died(`${f.label} failed to start: ${e.message}`));
|
|
175
216
|
rpc.onNotification = (method, params) => {
|
|
176
217
|
if (method !== "session/update" || !isObj(params) || !isObj(params["update"]))
|
|
177
218
|
return;
|
|
@@ -221,32 +262,43 @@ export function createGeminiAdapter(binary) {
|
|
|
221
262
|
const res = await rpc.request("session/new", { cwd, mcpServers: [] });
|
|
222
263
|
sessionId = isObj(res) ? str(res["sessionId"]) : undefined;
|
|
223
264
|
if (!sessionId)
|
|
224
|
-
throw new Error(
|
|
265
|
+
throw new Error(`${f.label} did not return a sessionId`);
|
|
225
266
|
// set_model accepts any id without complaint (host validated it against the listed models); skip it when the
|
|
226
267
|
// CLI already runs that model.
|
|
227
268
|
const startModel = isObj(res) && isObj(res["models"]) ? str(res["models"]["currentModelId"]) : undefined;
|
|
228
269
|
if (model && model !== startModel)
|
|
229
270
|
await rpc.request("session/set_model", { sessionId, modelId: model });
|
|
230
271
|
if (permissionMode !== "ask") {
|
|
231
|
-
await rpc.request("session/set_mode", { sessionId, modeId:
|
|
272
|
+
await rpc.request("session/set_mode", { sessionId, modeId: f.modeIds[permissionMode] });
|
|
232
273
|
}
|
|
233
274
|
}
|
|
234
275
|
catch (e) {
|
|
235
276
|
closed = true;
|
|
236
277
|
killTree(child);
|
|
237
278
|
const msg = e instanceof Error ? e.message : String(e);
|
|
238
|
-
throw new Error(/auth/i.test(msg) ? `${msg} (
|
|
279
|
+
throw new Error(/auth/i.test(msg) ? `${msg} (${f.signIn})` : msg);
|
|
280
|
+
}
|
|
281
|
+
// agy auto-applies edits inside its trusted workspaces even in default mode (shell commands still ask); say so, because
|
|
282
|
+
// "Ask" would otherwise look like it gates edits.
|
|
283
|
+
if (f.engine === "agy" && permissionMode === "ask") {
|
|
284
|
+
const trusted = agyTrustedWorkspace(cwd);
|
|
285
|
+
if (trusted) {
|
|
286
|
+
sink.emit({
|
|
287
|
+
kind: "error",
|
|
288
|
+
message: `Heads-up: Antigravity trusts ${trusted}, so in Ask mode it edits files in this folder without asking (shell commands still ask). To get edit approvals, remove it from "trustedWorkspaces" in ~/.gemini/antigravity-cli/settings.json or start the chat in a folder outside it.`,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
239
291
|
}
|
|
240
292
|
const sid = sessionId;
|
|
241
293
|
return {
|
|
242
294
|
async prompt(text) {
|
|
243
295
|
if (closed)
|
|
244
|
-
throw new Error(
|
|
296
|
+
throw new Error(`${f.label} process is not running`);
|
|
245
297
|
inTurn = true;
|
|
246
298
|
try {
|
|
247
299
|
const res = await rpc.request("session/prompt", { sessionId: sid, prompt: [{ type: "text", text }] });
|
|
248
300
|
const stop = isObj(res) ? str(res["stopReason"]) : undefined;
|
|
249
|
-
const usage = isObj(res) ? res["_meta"] : undefined;
|
|
301
|
+
const usage = isObj(res) ? (res["usage"] ?? res["_meta"]) : undefined; // agy reports token usage in `usage`, Gemini CLI in `_meta`
|
|
250
302
|
if (stop === "refusal")
|
|
251
303
|
sink.emit({ kind: "error", message: "the model refused this request" });
|
|
252
304
|
sink.emit({
|
|
@@ -271,7 +323,7 @@ export function createGeminiAdapter(binary) {
|
|
|
271
323
|
},
|
|
272
324
|
async setModel(next) {
|
|
273
325
|
if (closed)
|
|
274
|
-
throw new Error(
|
|
326
|
+
throw new Error(`${f.label} process is not running`);
|
|
275
327
|
if (inTurn)
|
|
276
328
|
throw new Error("a turn is already running");
|
|
277
329
|
await rpc.request("session/set_model", { sessionId: sid, modelId: next });
|
|
@@ -284,4 +336,10 @@ export function createGeminiAdapter(binary) {
|
|
|
284
336
|
},
|
|
285
337
|
};
|
|
286
338
|
}
|
|
339
|
+
export function createGeminiAdapter(binary) {
|
|
340
|
+
return createAcpAdapter(binary, GEMINI_FLAVOR);
|
|
341
|
+
}
|
|
342
|
+
export function createAgyAdapter(binary) {
|
|
343
|
+
return createAcpAdapter(binary, AGY_FLAVOR);
|
|
344
|
+
}
|
|
287
345
|
//# sourceMappingURL=gemini.js.map
|
package/dist/adapters/util.js
CHANGED
|
@@ -25,7 +25,7 @@ export function extraSearchDirs() {
|
|
|
25
25
|
const win = process.platform === "win32";
|
|
26
26
|
const home = homedir();
|
|
27
27
|
const env = process.env;
|
|
28
|
-
const dirs = [dirname(process.execPath), join(home, ".local", "bin"), join(home, ".claude", "local")];
|
|
28
|
+
const dirs = [dirname(process.execPath), join(home, ".local", "bin"), join(home, ".claude", "local"), join(home, ".gemini", "bin")];
|
|
29
29
|
if (win) {
|
|
30
30
|
dirs.push(env["APPDATA"] && join(env["APPDATA"], "npm"), env["LOCALAPPDATA"] && join(env["LOCALAPPDATA"], "pnpm"), env["LOCALAPPDATA"] && join(env["LOCALAPPDATA"], "Volta", "bin"), env["NVM_SYMLINK"], env["ProgramFiles"] && join(env["ProgramFiles"], "nodejs"), join(home, "scoop", "shims"), join(home, ".bun", "bin"));
|
|
31
31
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,9 +10,9 @@ import { DEFAULT_PORT, loadOrCreateIdentity, savedPort } from "./identity.js";
|
|
|
10
10
|
import { isPortFree, pickFreePort } from "./port.js";
|
|
11
11
|
import { createClaudeAdapter, findClaude } from "./adapters/claude.js";
|
|
12
12
|
import { createCodexAdapter, findCodex } from "./adapters/codex.js";
|
|
13
|
-
import { createGeminiAdapter, findGemini } from "./adapters/gemini.js";
|
|
13
|
+
import { AGY_SETTINGS_FILE, agyTrustedWorkspace, createAgyAdapter, createGeminiAdapter, findAgy, findGemini } from "./adapters/gemini.js";
|
|
14
14
|
import { killTree, spawnCli } from "./adapters/util.js";
|
|
15
|
-
import { SessionManager } from "./sessions.js";
|
|
15
|
+
import { DEFAULT_PROJECTS_DIR, SessionManager } from "./sessions.js";
|
|
16
16
|
import { SESSIONS_FILE, SessionStore } from "./store.js";
|
|
17
17
|
import { logToFile } from "./logfile.js";
|
|
18
18
|
import { LOG_FILE, installService, probeHealth, serviceStatus, stopService, uninstallService, waitHealthy } from "./service.js";
|
|
@@ -108,13 +108,16 @@ async function start(flags) {
|
|
|
108
108
|
const geminiBin = findGemini();
|
|
109
109
|
if (geminiBin)
|
|
110
110
|
sessions.registerAdapter(createGeminiAdapter(geminiBin));
|
|
111
|
+
const agyBin = findAgy();
|
|
112
|
+
if (agyBin)
|
|
113
|
+
sessions.registerAdapter(createAgyAdapter(agyBin));
|
|
111
114
|
const claudeBin = findClaude();
|
|
112
115
|
if (claudeBin)
|
|
113
116
|
sessions.registerAdapter(createClaudeAdapter(claudeBin));
|
|
114
117
|
const codexBin = findCodex();
|
|
115
118
|
if (codexBin)
|
|
116
119
|
sessions.registerAdapter(createCodexAdapter(codexBin));
|
|
117
|
-
console.log(`Engines: ${sessions.engines().join(", ") || "none found (install gemini, claude or codex CLI)"}`);
|
|
120
|
+
console.log(`Engines: ${sessions.engines().join(", ") || "none found (install gemini, agy, claude or codex CLI)"}`);
|
|
118
121
|
if (persist) {
|
|
119
122
|
const restored = await sessions.restore();
|
|
120
123
|
console.log(`Sessions: ${restored} restored from ${SESSIONS_FILE} (--no-persist disables saving)`);
|
|
@@ -275,9 +278,11 @@ async function doctor(flags) {
|
|
|
275
278
|
const claude = findClaude();
|
|
276
279
|
const gemini = findGemini();
|
|
277
280
|
const codex = findCodex();
|
|
281
|
+
const agy = findAgy();
|
|
278
282
|
for (const [name, file, install] of [
|
|
279
283
|
["Claude Code", claude, "install it from https://claude.com/product/claude-code, then run `claude` once to sign in"],
|
|
280
284
|
["Gemini CLI", gemini, "install it with `npm i -g @google/gemini-cli`, then run `gemini` once to sign in"],
|
|
285
|
+
["Antigravity CLI (agy)", agy, "install Antigravity CLI (it puts `agy` in ~/.gemini/bin), then run `agy` once to sign in"],
|
|
281
286
|
["Codex CLI", codex, "install it with `npm i -g @openai/codex`, then run `codex login` once to sign in"],
|
|
282
287
|
]) {
|
|
283
288
|
if (!file) {
|
|
@@ -290,8 +295,13 @@ async function doctor(flags) {
|
|
|
290
295
|
else
|
|
291
296
|
warn(`${name} found at ${file} but \`--version\` did not answer; run it in a terminal once to sign in or repair it`);
|
|
292
297
|
}
|
|
293
|
-
if (!claude && !gemini && !codex)
|
|
294
|
-
bad("no engine found: the app has nothing to drive. Install Claude Code, Gemini CLI or Codex CLI (see above), then run `doctor` again");
|
|
298
|
+
if (!claude && !gemini && !codex && !agy)
|
|
299
|
+
bad("no engine found: the app has nothing to drive. Install Claude Code, Gemini CLI, Antigravity CLI or Codex CLI (see above), then run `doctor` again");
|
|
300
|
+
if (agy) {
|
|
301
|
+
const trusted = agyTrustedWorkspace(DEFAULT_PROJECTS_DIR);
|
|
302
|
+
if (trusted)
|
|
303
|
+
warn(`Antigravity trusts ${trusted}, which contains Remogent's default chat folder (${DEFAULT_PROJECTS_DIR}): in Ask mode agy edits files there without asking (shell commands still ask). For edit approvals remove it from "trustedWorkspaces" in ${AGY_SETTINGS_FILE()}`);
|
|
304
|
+
}
|
|
295
305
|
const addrs = [];
|
|
296
306
|
for (const [iface, list] of Object.entries(networkInterfaces())) {
|
|
297
307
|
for (const a of list ?? []) {
|
|
@@ -408,7 +418,7 @@ else {
|
|
|
408
418
|
" firewall Windows: allow the phone through Defender Firewall on private networks (add --elevate to get the permission prompt)\n" +
|
|
409
419
|
" status Show autostart state and whether the host answers\n" +
|
|
410
420
|
" pair Show the pairing QR again (for a new phone), close itself once the phone connects; the installed host keeps running\n" +
|
|
411
|
-
" doctor Check Node, the Claude/Gemini/Codex CLIs (found automatically), network address and autostart, and say how to fix gaps\n" +
|
|
421
|
+
" doctor Check Node, the Claude/Gemini/Antigravity/Codex CLIs (found automatically), network address and autostart, and say how to fix gaps\n" +
|
|
412
422
|
" start Run the host in this terminal (no autostart)\n" +
|
|
413
423
|
"Options: [--port N] [--host IP] [--rotate] [--no-qr] [--show-url] [--no-persist] [--log FILE] [--no-wait] [--timeout MIN]");
|
|
414
424
|
process.exit(cmd === "help" || cmd === "--help" ? 0 : 1);
|
package/dist/protocol.js
CHANGED
|
@@ -9,7 +9,7 @@ export class ProtocolError extends Error {
|
|
|
9
9
|
this.reqId = reqId;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
const ENGINES = ["gemini", "claude", "codex"];
|
|
12
|
+
const ENGINES = ["gemini", "claude", "codex", "agy"];
|
|
13
13
|
const MODES = ["ask", "plan", "auto_edit", "auto"];
|
|
14
14
|
const DECISIONS = ["deny", "allow_once", "allow_session"];
|
|
15
15
|
const MAX_MODEL_ID = 200;
|
package/dist/sessions.js
CHANGED
|
@@ -453,7 +453,7 @@ export class SessionManager {
|
|
|
453
453
|
if (s.lastStallWarnAt && now - s.lastStallWarnAt < this.stallRepeatMs)
|
|
454
454
|
continue;
|
|
455
455
|
s.lastStallWarnAt = now;
|
|
456
|
-
const who = s.engine === "gemini" ? "Gemini" : s.engine === "codex" ? "Codex" : "Claude";
|
|
456
|
+
const who = s.engine === "gemini" ? "Gemini" : s.engine === "codex" ? "Codex" : s.engine === "agy" ? "Antigravity" : "Claude";
|
|
457
457
|
const how = silent >= 120_000 ? `${Math.round(silent / 60_000)} min` : `${Math.round(silent / 1000)} s`;
|
|
458
458
|
const what = s.toolRunning ? "a tool call is still running" : `${who} may be stuck waiting on the model API`;
|
|
459
459
|
this.emit(s, { kind: "error", message: `No output from ${who} for ${how}: ${what}. Tap Stop and resend if it does not recover.` }, true);
|
package/dist/store.js
CHANGED
|
@@ -15,7 +15,7 @@ function valid(p) {
|
|
|
15
15
|
if (!isObj(p))
|
|
16
16
|
return false;
|
|
17
17
|
return (typeof p["id"] === "string" &&
|
|
18
|
-
(p["engine"] === "claude" || p["engine"] === "gemini" || p["engine"] === "codex") &&
|
|
18
|
+
(p["engine"] === "claude" || p["engine"] === "gemini" || p["engine"] === "codex" || p["engine"] === "agy") &&
|
|
19
19
|
typeof p["cwd"] === "string" &&
|
|
20
20
|
typeof p["permissionMode"] === "string" &&
|
|
21
21
|
typeof p["createdAt"] === "number" &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infomity/remogent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Remogent host: drive Claude Code, Gemini CLI and Codex CLI on your PC from the Remogent Android app. No cloud relay, no account.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|