paseo-acp-agy 1.1.2 → 1.1.4
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/PROTOCOL.md +25 -3
- package/README.md +32 -0
- package/dist/acp-server.js +2 -1
- package/dist/antigravity-process.d.ts +1 -0
- package/dist/antigravity-process.js +46 -8
- package/dist/attachments.js +6 -2
- package/dist/permissions.d.ts +2 -0
- package/dist/permissions.js +14 -2
- package/dist/protocol.js +42 -26
- package/dist/session-store.js +9 -2
- package/dist/slash-commands.js +27 -9
- package/dist/version.js +25 -5
- package/package.json +1 -1
package/PROTOCOL.md
CHANGED
|
@@ -10,14 +10,15 @@ Each Paseo ACP session owns one persistent `agy` process:
|
|
|
10
10
|
agy --input-format stream-json --output-format stream-json --print=""
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Session flags applied when the process starts or is restarted:
|
|
14
14
|
|
|
15
15
|
- `--model <model-id>`
|
|
16
16
|
- `--effort <low|medium|high>` when supported by the selected model
|
|
17
17
|
- `--mode <mode-id>`
|
|
18
18
|
- `--conversation <conversation-id>` when resuming
|
|
19
|
-
- `--
|
|
20
|
-
- `--dangerously-skip-permissions`
|
|
19
|
+
- `--add-dir <cwd>` registering the active workspace directory
|
|
20
|
+
- `--dangerously-skip-permissions` (enabled by default under ACP; see Permission Model)
|
|
21
|
+
- `--sandbox` when configured via `AGY_ACP_SANDBOX=1`
|
|
21
22
|
|
|
22
23
|
Configuration changes schedule a controlled restart before the next prompt. On POSIX systems `agy` is launched as a process-group leader; shutdown, restart and turn cancellation signal the process group so tool subprocesses are not left orphaned.
|
|
23
24
|
|
|
@@ -69,6 +70,27 @@ For backwards compatibility, the server also accepts the exact pre-hardening ali
|
|
|
69
70
|
|
|
70
71
|
`agy-acp` currently advertises `loadSession: false`. ACP `session/load` requires the agent to replay prior conversation history through `session/update` notifications. The adapter intentionally does not claim that capability until replay is implemented. Paseo persistence uses `session/resume`, which restores model context without replaying already-rendered history.
|
|
71
72
|
|
|
73
|
+
## Permission Model & CLI Limitations
|
|
74
|
+
|
|
75
|
+
In the standard ACP specification, clients like Paseo or Zed act as supervisors: an agent requests authorization for actions via `session/request_permission`, and the client presents prompts to the user or evaluates auto-accept policies.
|
|
76
|
+
|
|
77
|
+
### The Antigravity headless stream-json constraint
|
|
78
|
+
|
|
79
|
+
The official Antigravity CLI (`agy`) was created as a terminal-interactive CLI rather than a native ACP server:
|
|
80
|
+
1. **Piped Stdio vs. Interactive TTY**: `agy` runs headlessly over JSON-RPC stdio pipes. In this mode, `agy`'s internal TTY consent prompts (`Allow <tool>? [y/n]`) cannot query the user.
|
|
81
|
+
2. **Immediate Denial on Non-Interactive Stdin**: Without `--dangerously-skip-permissions`, whenever `agy` invokes a tool (`read_file`, `write_file`, shell execution) in a workspace path that has not been manually pre-trusted in `~/.gemini/antigravity-cli/settings.json`, it immediately fails with:
|
|
82
|
+
`permission check failed for <tool>: user denied permission for <tool>(<path>)`.
|
|
83
|
+
3. **Absence of a Protocol-Level Permission Callback**: The `agy stream-json` protocol currently lacks an inbound pause-and-resume event (such as `permission_response`). By the time `agy` outputs a `step_update` event with `step_type: "tool"` and `state: "ACTIVE"`, execution has already been initiated by the binary.
|
|
84
|
+
|
|
85
|
+
### How `agy-acp` resolves this
|
|
86
|
+
|
|
87
|
+
- **Delegated Trust**: `agy-acp` applies `--dangerously-skip-permissions` by default for ACP sessions so the underlying CLI does not fail on headless stdin prompts.
|
|
88
|
+
- **Workspace Demarcation**: `agy-acp` passes `--add-dir <cwd>` on startup to explicitly register the current working directory in Antigravity's workspace context.
|
|
89
|
+
- **Live Tool Streaming**: Tool calls are mapped and streamed immediately via `session/update` (`tool_call` and `tool_call_update`) so Paseo provides real-time auditability of actions and outputs.
|
|
90
|
+
- **Opt-out & Sandbox**: Users can customize this behavior:
|
|
91
|
+
- Setting `AGY_ACP_DANGEROUSLY_SKIP_PERMISSIONS=false` disables `--dangerously-skip-permissions` for environments where `~/.gemini/antigravity-cli/settings.json` is strictly maintained.
|
|
92
|
+
- Setting `AGY_ACP_SANDBOX=true` adds `--sandbox` to enable terminal execution restrictions.
|
|
93
|
+
|
|
72
94
|
## Turn concurrency and cancellation
|
|
73
95
|
|
|
74
96
|
A session-level prompt operation is reserved synchronously before any slash-command or process startup/restart `await`. Therefore:
|
package/README.md
CHANGED
|
@@ -98,11 +98,43 @@ To include `paseo-acp-agy` in Paseo's built-in provider store (`ACP_PROVIDER_CAT
|
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
101
|
+
## Security, Permissions & Antigravity CLI Limitations
|
|
102
|
+
|
|
103
|
+
In the Agent Client Protocol (ACP) specification, the host client (e.g., [Paseo](https://paseo.sh) or Zed) acts as the security supervisor. Native ACP agents query the host via `session/request_permission` before executing tools (`read_file`, `write_file`, terminal commands).
|
|
104
|
+
|
|
105
|
+
### Why `--dangerously-skip-permissions` is enabled by default
|
|
106
|
+
|
|
107
|
+
The official Google Antigravity CLI (`agy`) communicates with this adapter via `--input-format stream-json --output-format stream-json`:
|
|
108
|
+
1. **No Interactive TTY**: `agy` runs headlessly over piped JSON-RPC stdio. In this mode, `agy`'s internal terminal prompt (`Allow <tool>? [y/n]`) cannot reach an interactive user.
|
|
109
|
+
2. **Headless Permission Denials**: Without `--dangerously-skip-permissions`, whenever `agy` accesses a workspace folder or runs a tool that has not been explicitly pre-approved in `~/.gemini/antigravity-cli/settings.json`, its internal check fails and aborts immediately with:
|
|
110
|
+
```text
|
|
111
|
+
permission check failed for read_file: user denied permission for read_file(...)
|
|
112
|
+
```
|
|
113
|
+
3. **No External Permission Callback**: Unlike native ACP agents or Claude's Agent SDK, `agy`'s `stream-json` interface does not support an external pause-and-confirm handshake. Once a tool step starts in `agy`, it has already been scheduled internally.
|
|
114
|
+
|
|
115
|
+
To prevent unrecoverable `user denied permission` errors on fresh workspaces (especially on new Windows or Linux installations), `paseo-acp-agy`:
|
|
116
|
+
- Passes `--dangerously-skip-permissions` by default so tool authorization is delegated to the host application.
|
|
117
|
+
- Passes `--add-dir <cwd>` on process startup to demarcate the current project workspace directory.
|
|
118
|
+
- Streams tool invocations (`step_update` → `tool_call`) in real time to the Paseo UI so the user retains full visibility over all actions.
|
|
119
|
+
|
|
120
|
+
### Controlling Permissions & Sandbox Mode
|
|
121
|
+
|
|
122
|
+
You can adjust this behavior using environment variables in your Paseo configuration or shell:
|
|
123
|
+
|
|
124
|
+
| Variable | Description | Default |
|
|
125
|
+
| :--- | :--- | :--- |
|
|
126
|
+
| `AGY_ACP_DANGEROUSLY_SKIP_PERMISSIONS` | Set to `false` or `0` to disable automatic skip and strictly require pre-approved permissions in `~/.gemini/antigravity-cli/settings.json` | `true` |
|
|
127
|
+
| `AGY_ACP_SANDBOX` | Set to `true` or `1` to launch `agy` with terminal restrictions enabled (`--sandbox`) | `false` |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
101
131
|
## Environment Variables
|
|
102
132
|
|
|
103
133
|
| Variable | Description | Default |
|
|
104
134
|
| :--- | :--- | :--- |
|
|
105
135
|
| `AGY_BIN_PATH` | Path to the Google Antigravity binary | Auto-detected from `PATH` or `~/.local/bin/agy` |
|
|
136
|
+
| `AGY_ACP_DANGEROUSLY_SKIP_PERMISSIONS` | Auto-approve tool permissions in headless `agy` | `true` |
|
|
137
|
+
| `AGY_ACP_SANDBOX` | Enable terminal sandbox mode (`--sandbox`) | `false` |
|
|
106
138
|
| `AGY_ACP_LOG_FILE` | Enable debug file logging | Disabled |
|
|
107
139
|
|
|
108
140
|
---
|
package/dist/acp-server.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ACP_METHODS, AVAILABLE_MODES, fetchAvailableModels, buildConfigOptionsF
|
|
|
4
4
|
import { SessionManager } from "./session.js";
|
|
5
5
|
import { executeSlashCommand, AVAILABLE_SLASH_COMMANDS } from "./slash-commands.js";
|
|
6
6
|
import { getShortVersion } from "./version.js";
|
|
7
|
+
import { resolveDefaultAgyBinary } from "./antigravity-process.js";
|
|
7
8
|
function splitModelAndEffort(modelInput) {
|
|
8
9
|
if (!modelInput)
|
|
9
10
|
return {};
|
|
@@ -25,7 +26,7 @@ export class ACPServer {
|
|
|
25
26
|
constructor(options = {}) {
|
|
26
27
|
this.input = options.input || process.stdin;
|
|
27
28
|
this.output = options.output || process.stdout;
|
|
28
|
-
this.binaryPath = options.binaryPath ||
|
|
29
|
+
this.binaryPath = options.binaryPath || resolveDefaultAgyBinary();
|
|
29
30
|
this.sessionManager =
|
|
30
31
|
options.sessionManager || new SessionManager({ defaultBinaryPath: this.binaryPath });
|
|
31
32
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { AgyInitEvent, AgyStepUpdateEvent, AgyResultEvent } from "./protocol.js";
|
|
3
3
|
import { PermissionSettings } from "./permissions.js";
|
|
4
|
+
export declare function resolveDefaultAgyBinary(): string;
|
|
4
5
|
export interface AntigravityProcessOptions {
|
|
5
6
|
binaryPath?: string;
|
|
6
7
|
cwd?: string;
|
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
1
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { logger } from "./logger.js";
|
|
6
7
|
import { getEffectiveEffortForModel, } from "./protocol.js";
|
|
7
|
-
import { buildAgyArgs } from "./permissions.js";
|
|
8
|
-
function resolveDefaultAgyBinary() {
|
|
8
|
+
import { buildAgyArgs, resolvePermissionSettings } from "./permissions.js";
|
|
9
|
+
export function resolveDefaultAgyBinary() {
|
|
9
10
|
if (process.env.AGY_BIN_PATH)
|
|
10
11
|
return process.env.AGY_BIN_PATH;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
if (home) {
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.join(home, ".local", "bin", "agy.exe"),
|
|
17
|
+
path.join(home, "AppData", "Local", "Programs", "antigravity", "agy.exe"),
|
|
18
|
+
path.join(home, ".local", "bin", "agy.cmd"),
|
|
19
|
+
path.join(home, ".local", "bin", "agy.bat"),
|
|
20
|
+
];
|
|
21
|
+
for (const cand of candidates) {
|
|
22
|
+
if (fs.existsSync(cand))
|
|
23
|
+
return cand;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const out = execFileSync("where.exe", ["agy"], { encoding: "utf-8", timeout: 1000 }).trim();
|
|
27
|
+
const first = out.split(/\r?\n/)[0]?.trim();
|
|
28
|
+
if (first && fs.existsSync(first))
|
|
29
|
+
return first;
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const localPath = path.join(home, ".local", "bin", "agy");
|
|
35
|
+
if (fs.existsSync(localPath))
|
|
36
|
+
return localPath;
|
|
37
|
+
}
|
|
15
38
|
}
|
|
16
39
|
return "agy";
|
|
17
40
|
}
|
|
@@ -38,7 +61,7 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
38
61
|
this.effort = options.effort;
|
|
39
62
|
this.mode = options.mode;
|
|
40
63
|
this.conversationId = options.conversationId;
|
|
41
|
-
this.permissions = options.permissions ||
|
|
64
|
+
this.permissions = options.permissions || resolvePermissionSettings();
|
|
42
65
|
this.env = { ...process.env, ...options.env };
|
|
43
66
|
// EventEmitter treats "error" specially. Keep a defensive listener for
|
|
44
67
|
// callers/tests that emit it directly; child failures use process_error.
|
|
@@ -131,6 +154,15 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
131
154
|
});
|
|
132
155
|
}
|
|
133
156
|
}
|
|
157
|
+
else if (pid && process.platform === "win32") {
|
|
158
|
+
try {
|
|
159
|
+
execFileSync("taskkill", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore" });
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Fall back to child.kill
|
|
164
|
+
}
|
|
165
|
+
}
|
|
134
166
|
try {
|
|
135
167
|
return child.kill(signal);
|
|
136
168
|
}
|
|
@@ -212,10 +244,15 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
212
244
|
extraArgs.push("--effort", effectiveEffort);
|
|
213
245
|
if (this.conversationId)
|
|
214
246
|
extraArgs.push("--conversation", this.conversationId);
|
|
247
|
+
const addDirs = [...(this.permissions.addDirs || [])];
|
|
248
|
+
if (this.cwd && !addDirs.includes(this.cwd)) {
|
|
249
|
+
addDirs.push(this.cwd);
|
|
250
|
+
}
|
|
215
251
|
const args = buildAgyArgs({
|
|
216
252
|
sandbox: this.permissions.sandbox,
|
|
217
253
|
dangerouslySkipPermissions: this.permissions.dangerouslySkipPermissions,
|
|
218
254
|
mode: this.mode,
|
|
255
|
+
addDirs,
|
|
219
256
|
}, extraArgs);
|
|
220
257
|
logger.info("Spawning agy process", {
|
|
221
258
|
binary: this.binaryPath,
|
|
@@ -228,6 +265,7 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
228
265
|
env: this.env,
|
|
229
266
|
stdio: ["pipe", "pipe", "pipe"],
|
|
230
267
|
detached: process.platform !== "win32",
|
|
268
|
+
shell: process.platform === "win32",
|
|
231
269
|
});
|
|
232
270
|
this.child = child;
|
|
233
271
|
if (!child.stdin || !child.stdout || !child.stderr) {
|
package/dist/attachments.js
CHANGED
|
@@ -14,7 +14,9 @@ function getAttachmentsDir() {
|
|
|
14
14
|
const attachmentsDir = path.join(getStateRoot(), "attachments");
|
|
15
15
|
try {
|
|
16
16
|
fs.mkdirSync(attachmentsDir, { recursive: true, mode: 0o700 });
|
|
17
|
-
|
|
17
|
+
if (process.platform !== "win32") {
|
|
18
|
+
fs.chmodSync(attachmentsDir, 0o700);
|
|
19
|
+
}
|
|
18
20
|
}
|
|
19
21
|
catch { }
|
|
20
22
|
return attachmentsDir;
|
|
@@ -88,7 +90,9 @@ export function saveBase64Image(data, mimeType = "image/png") {
|
|
|
88
90
|
if (!fs.existsSync(filePath)) {
|
|
89
91
|
try {
|
|
90
92
|
fs.writeFileSync(filePath, buffer, { mode: 0o600, flag: "wx" });
|
|
91
|
-
|
|
93
|
+
if (process.platform !== "win32") {
|
|
94
|
+
fs.chmodSync(filePath, 0o600);
|
|
95
|
+
}
|
|
92
96
|
logger.info("Saved attached image for session", { filePath, sizeBytes: buffer.length });
|
|
93
97
|
}
|
|
94
98
|
catch (err) {
|
package/dist/permissions.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ export interface PermissionSettings {
|
|
|
2
2
|
sandbox: boolean;
|
|
3
3
|
dangerouslySkipPermissions: boolean;
|
|
4
4
|
mode?: string;
|
|
5
|
+
addDirs?: string[];
|
|
5
6
|
}
|
|
6
7
|
export declare function resolvePermissionSettings(options?: {
|
|
7
8
|
sandbox?: boolean;
|
|
8
9
|
dangerouslySkipPermissions?: boolean;
|
|
9
10
|
mode?: string;
|
|
11
|
+
addDirs?: string[];
|
|
10
12
|
}): PermissionSettings;
|
|
11
13
|
export declare function buildAgyArgs(settings: PermissionSettings, extraArgs?: string[]): string[];
|
package/dist/permissions.js
CHANGED
|
@@ -3,12 +3,17 @@ export function resolvePermissionSettings(options) {
|
|
|
3
3
|
const envSandbox = process.env.AGY_ACP_SANDBOX;
|
|
4
4
|
const envSkipPerms = process.env.AGY_ACP_DANGEROUSLY_SKIP_PERMISSIONS;
|
|
5
5
|
const sandbox = options?.sandbox ?? (envSandbox === "true" || envSandbox === "1");
|
|
6
|
-
//
|
|
7
|
-
|
|
6
|
+
// In ACP mode, permissions and tool approvals are governed by the host application (e.g. Paseo).
|
|
7
|
+
// Because agy runs non-interactively over stream-json pipes without an interactive TTY, agy's
|
|
8
|
+
// internal terminal prompts cannot query the user and will fail with "user denied permission".
|
|
9
|
+
// Therefore, dangerouslySkipPermissions defaults to true unless explicitly disabled.
|
|
10
|
+
const dangerouslySkipPermissions = options?.dangerouslySkipPermissions ??
|
|
11
|
+
(envSkipPerms !== undefined ? envSkipPerms === "true" || envSkipPerms === "1" : true);
|
|
8
12
|
return {
|
|
9
13
|
sandbox,
|
|
10
14
|
dangerouslySkipPermissions,
|
|
11
15
|
mode: options?.mode,
|
|
16
|
+
addDirs: options?.addDirs,
|
|
12
17
|
};
|
|
13
18
|
}
|
|
14
19
|
export function buildAgyArgs(settings, extraArgs) {
|
|
@@ -22,6 +27,13 @@ export function buildAgyArgs(settings, extraArgs) {
|
|
|
22
27
|
if (settings.mode && settings.mode !== "default") {
|
|
23
28
|
args.push("--mode", settings.mode);
|
|
24
29
|
}
|
|
30
|
+
if (settings.addDirs && settings.addDirs.length > 0) {
|
|
31
|
+
for (const dir of settings.addDirs) {
|
|
32
|
+
if (dir) {
|
|
33
|
+
args.push("--add-dir", dir);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
25
37
|
if (extraArgs && extraArgs.length > 0) {
|
|
26
38
|
args.push(...extraArgs);
|
|
27
39
|
}
|
package/dist/protocol.js
CHANGED
|
@@ -208,34 +208,47 @@ export function parseAgyQuotaOutput(raw) {
|
|
|
208
208
|
const trimmed = line.trim();
|
|
209
209
|
if (!trimmed || trimmed.toLowerCase().startsWith("quota:"))
|
|
210
210
|
continue;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
windows.push({
|
|
230
|
-
id,
|
|
231
|
-
label,
|
|
232
|
-
usedPct,
|
|
233
|
-
remainingPct,
|
|
234
|
-
resetsAt,
|
|
235
|
-
tone: deriveUsageTone(usedPct),
|
|
236
|
-
});
|
|
211
|
+
let scope = "";
|
|
212
|
+
let limitType = "";
|
|
213
|
+
let remainingMatch = null;
|
|
214
|
+
let resetsAt = null;
|
|
215
|
+
const m = trimmed.match(/^(.*?)\s{2,}(.*?Remaining)\s+(\d+%)(?:\s+(.*))?$/i);
|
|
216
|
+
if (m) {
|
|
217
|
+
scope = m[1].trim();
|
|
218
|
+
limitType = m[2].trim();
|
|
219
|
+
remainingMatch = m[3].match(/(\d+)%/);
|
|
220
|
+
resetsAt = m[4]?.trim() || null;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const parts = trimmed.split(/\t+|\s{2,}/).map((p) => p.trim());
|
|
224
|
+
if (parts.length >= 3) {
|
|
225
|
+
scope = parts[0];
|
|
226
|
+
limitType = parts[1];
|
|
227
|
+
remainingMatch = parts[2].match(/(\d+)%/);
|
|
228
|
+
resetsAt = parts[3] || null;
|
|
237
229
|
}
|
|
238
230
|
}
|
|
231
|
+
if (remainingMatch) {
|
|
232
|
+
const remainingPct = parseInt(remainingMatch[1], 10);
|
|
233
|
+
const usedPct = Math.max(0, Math.min(100, 100 - remainingPct));
|
|
234
|
+
const isFiveHour = /five\s*hour/i.test(limitType);
|
|
235
|
+
const isWeekly = /weekly/i.test(limitType);
|
|
236
|
+
const isGemini = /gemini/i.test(scope);
|
|
237
|
+
let id = isFiveHour ? "session" : isWeekly ? "weekly" : "quota";
|
|
238
|
+
let label = isFiveHour ? "Session (5h)" : isWeekly ? "Weekly" : limitType;
|
|
239
|
+
if (!isGemini) {
|
|
240
|
+
id = `claude_${id}`;
|
|
241
|
+
label = `Claude ${label}`;
|
|
242
|
+
}
|
|
243
|
+
windows.push({
|
|
244
|
+
id,
|
|
245
|
+
label,
|
|
246
|
+
usedPct,
|
|
247
|
+
remainingPct,
|
|
248
|
+
resetsAt,
|
|
249
|
+
tone: deriveUsageTone(usedPct),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
239
252
|
}
|
|
240
253
|
return windows;
|
|
241
254
|
}
|
|
@@ -279,11 +292,13 @@ export async function fetchAntigravityUsage(binaryPath = "agy", force = false) {
|
|
|
279
292
|
timeout: 8_000,
|
|
280
293
|
maxBuffer: 1024 * 1024,
|
|
281
294
|
env: process.env,
|
|
295
|
+
shell: process.platform === "win32",
|
|
282
296
|
}),
|
|
283
297
|
execFileAsync(binaryPath, ["--print", "/credits"], {
|
|
284
298
|
timeout: 8_000,
|
|
285
299
|
maxBuffer: 1024 * 1024,
|
|
286
300
|
env: process.env,
|
|
301
|
+
shell: process.platform === "win32",
|
|
287
302
|
}),
|
|
288
303
|
]);
|
|
289
304
|
const balances = [];
|
|
@@ -408,6 +423,7 @@ export async function fetchAvailableModels(binaryPath = "agy", force = false) {
|
|
|
408
423
|
timeout: 5_000,
|
|
409
424
|
env: process.env,
|
|
410
425
|
maxBuffer: 4 * 1024 * 1024,
|
|
426
|
+
shell: process.platform === "win32",
|
|
411
427
|
});
|
|
412
428
|
const parsed = parseAgyModelsOutput(stdout);
|
|
413
429
|
cachedModels = parsed;
|
package/dist/session-store.js
CHANGED
|
@@ -19,7 +19,9 @@ export class SessionStore {
|
|
|
19
19
|
this.dir = dir || path.join(getStateRoot(), "sessions");
|
|
20
20
|
try {
|
|
21
21
|
fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 });
|
|
22
|
-
|
|
22
|
+
if (process.platform !== "win32") {
|
|
23
|
+
fs.chmodSync(this.dir, 0o700);
|
|
24
|
+
}
|
|
23
25
|
}
|
|
24
26
|
catch { }
|
|
25
27
|
}
|
|
@@ -56,8 +58,13 @@ export class SessionStore {
|
|
|
56
58
|
mode: 0o600,
|
|
57
59
|
flag: "wx",
|
|
58
60
|
});
|
|
61
|
+
if (process.platform === "win32" && fs.existsSync(filePath)) {
|
|
62
|
+
fs.unlinkSync(filePath);
|
|
63
|
+
}
|
|
59
64
|
fs.renameSync(tempPath, filePath);
|
|
60
|
-
|
|
65
|
+
if (process.platform !== "win32") {
|
|
66
|
+
fs.chmodSync(filePath, 0o600);
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
69
|
catch (err) {
|
|
63
70
|
try {
|
package/dist/slash-commands.js
CHANGED
|
@@ -31,7 +31,7 @@ function transcriptPath(conversationId) {
|
|
|
31
31
|
}
|
|
32
32
|
function summarizeConversation(cid, logFile, mtime) {
|
|
33
33
|
try {
|
|
34
|
-
const lines = fs.readFileSync(logFile, "utf8").split(
|
|
34
|
+
const lines = fs.readFileSync(logFile, "utf8").split(/\r?\n/).filter(Boolean);
|
|
35
35
|
let firstPrompt = "";
|
|
36
36
|
let userTurns = 0;
|
|
37
37
|
for (const line of lines) {
|
|
@@ -111,7 +111,7 @@ export function getConversationSteps(cid) {
|
|
|
111
111
|
if (!logFile || !fs.existsSync(logFile))
|
|
112
112
|
return null;
|
|
113
113
|
try {
|
|
114
|
-
const lines = fs.readFileSync(logFile, "utf8").split(
|
|
114
|
+
const lines = fs.readFileSync(logFile, "utf8").split(/\r?\n/).filter(Boolean);
|
|
115
115
|
const steps = [];
|
|
116
116
|
let userTurns = 0;
|
|
117
117
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -272,11 +272,30 @@ function formatCountdown(isoStr) {
|
|
|
272
272
|
}
|
|
273
273
|
export function formatUsageOutput(rawText) {
|
|
274
274
|
const groups = new Map();
|
|
275
|
-
for (const line of rawText.trim().split(
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
275
|
+
for (const line of rawText.trim().split(/\r?\n/)) {
|
|
276
|
+
const trimmedLine = line.trim();
|
|
277
|
+
if (!trimmedLine || trimmedLine.toLowerCase().startsWith("quota:"))
|
|
278
278
|
continue;
|
|
279
|
-
let fam =
|
|
279
|
+
let fam = "";
|
|
280
|
+
let rawWindow = "";
|
|
281
|
+
let pctStr = "";
|
|
282
|
+
let reset = "";
|
|
283
|
+
const m = trimmedLine.match(/^(.*?)\s{2,}(.*?Remaining)\s+(\d+%)(?:\s+(.*))?$/i);
|
|
284
|
+
if (m) {
|
|
285
|
+
fam = m[1].trim();
|
|
286
|
+
rawWindow = m[2].trim();
|
|
287
|
+
pctStr = m[3].trim();
|
|
288
|
+
reset = m[4]?.trim() || "";
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
const parts = trimmedLine.includes("\t") ? trimmedLine.split("\t") : trimmedLine.split(/\s{2,}/);
|
|
292
|
+
if (parts.length < 3)
|
|
293
|
+
continue;
|
|
294
|
+
fam = parts[0].trim();
|
|
295
|
+
rawWindow = parts[1].trim();
|
|
296
|
+
pctStr = parts[2].trim();
|
|
297
|
+
reset = parts[3]?.trim() || "";
|
|
298
|
+
}
|
|
280
299
|
let icon = "🤖";
|
|
281
300
|
if (fam.toLowerCase().includes("gemini")) {
|
|
282
301
|
fam = "Google Gemini";
|
|
@@ -286,7 +305,7 @@ export function formatUsageOutput(rawText) {
|
|
|
286
305
|
fam = "Claude & GPT";
|
|
287
306
|
icon = "🔶";
|
|
288
307
|
}
|
|
289
|
-
|
|
308
|
+
rawWindow = rawWindow.replace(/\s+Remaining$/i, "").trim();
|
|
290
309
|
const lowerWindow = rawWindow.toLowerCase();
|
|
291
310
|
const isFiveHour = lowerWindow.includes("five hour") || /\b5\s*hour/.test(lowerWindow);
|
|
292
311
|
const win = isFiveHour
|
|
@@ -294,10 +313,8 @@ export function formatUsageOutput(rawText) {
|
|
|
294
313
|
: lowerWindow.includes("weekly")
|
|
295
314
|
? "Cota Semanal"
|
|
296
315
|
: rawWindow;
|
|
297
|
-
const pctStr = parts[2].trim();
|
|
298
316
|
const parsedPct = Number.parseInt(pctStr.replace("%", ""), 10);
|
|
299
317
|
const pct = Number.isFinite(parsedPct) ? parsedPct : 0;
|
|
300
|
-
const reset = parts[3]?.trim() || "";
|
|
301
318
|
if (!groups.has(fam))
|
|
302
319
|
groups.set(fam, { icon, items: [] });
|
|
303
320
|
groups.get(fam).items.push({ win, isFiveHour, pct, pctStr, reset });
|
|
@@ -324,6 +341,7 @@ async function runAgySlash(binaryPath, cwd, slashCommand) {
|
|
|
324
341
|
env: process.env,
|
|
325
342
|
timeout: AGY_COMMAND_TIMEOUT_MS,
|
|
326
343
|
maxBuffer: AGY_COMMAND_MAX_BUFFER,
|
|
344
|
+
shell: process.platform === "win32",
|
|
327
345
|
});
|
|
328
346
|
return stdout.trim() || stderr.trim();
|
|
329
347
|
}
|
package/dist/version.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -31,16 +32,35 @@ function resolveAgyInfo() {
|
|
|
31
32
|
let binaryPath = process.env.AGY_BIN_PATH;
|
|
32
33
|
if (!binaryPath) {
|
|
33
34
|
try {
|
|
34
|
-
|
|
35
|
+
const lookupCmd = process.platform === "win32"
|
|
36
|
+
? "where.exe agy"
|
|
37
|
+
: "which agy 2>/dev/null || command -v agy 2>/dev/null";
|
|
38
|
+
const output = execSync(lookupCmd, { encoding: "utf8" }).trim();
|
|
39
|
+
if (output) {
|
|
40
|
+
binaryPath = output.split(/\r?\n/)[0].trim();
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
43
|
catch {
|
|
37
44
|
// ignore
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
|
-
if (!binaryPath
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
47
|
+
if (!binaryPath) {
|
|
48
|
+
const homeDir = os.homedir();
|
|
49
|
+
if (homeDir) {
|
|
50
|
+
const candidates = process.platform === "win32"
|
|
51
|
+
? [
|
|
52
|
+
path.join(homeDir, ".local", "bin", "agy.exe"),
|
|
53
|
+
path.join(homeDir, "AppData", "Local", "Programs", "antigravity", "agy.exe"),
|
|
54
|
+
path.join(homeDir, ".local", "bin", "agy.cmd"),
|
|
55
|
+
path.join(homeDir, ".local", "bin", "agy.bat"),
|
|
56
|
+
]
|
|
57
|
+
: [path.join(homeDir, ".local", "bin", "agy")];
|
|
58
|
+
for (const cand of candidates) {
|
|
59
|
+
if (fs.existsSync(cand)) {
|
|
60
|
+
binaryPath = cand;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
44
64
|
}
|
|
45
65
|
}
|
|
46
66
|
if (!binaryPath)
|