paseo-acp-agy 1.1.7 → 1.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/dist/acp-server.js +31 -2
- package/dist/antigravity-process.d.ts +4 -1
- package/dist/antigravity-process.js +112 -32
- package/dist/index.js +175 -11
- package/dist/paseo-patcher.d.ts +32 -2
- package/dist/paseo-patcher.js +442 -35
- package/dist/protocol.d.ts +6 -1
- package/dist/protocol.js +17 -7
- package/dist/slash-commands.js +5 -4
- package/package.json +3 -2
package/dist/acp-server.js
CHANGED
|
@@ -204,8 +204,37 @@ export class ACPServer {
|
|
|
204
204
|
}
|
|
205
205
|
case ACP_METHODS.SESSION_LOAD:
|
|
206
206
|
case ACP_METHODS.SESSION_LOAD_ALIAS: {
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
const sessionId = String(params.sessionId || "");
|
|
208
|
+
const cwd = typeof params.cwd === "string" ? params.cwd : undefined;
|
|
209
|
+
if (!sessionId) {
|
|
210
|
+
if (!isNotification)
|
|
211
|
+
this.sendError(id, -32602, "sessionId is required");
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
let session = this.sessionManager.getSession(sessionId);
|
|
215
|
+
let created = false;
|
|
216
|
+
try {
|
|
217
|
+
if (!session) {
|
|
218
|
+
session = this.sessionManager.createSession({
|
|
219
|
+
id: sessionId,
|
|
220
|
+
cwd,
|
|
221
|
+
binaryPath: this.binaryPath,
|
|
222
|
+
});
|
|
223
|
+
created = true;
|
|
224
|
+
}
|
|
225
|
+
if (!created && session.process.currentConversationId) {
|
|
226
|
+
await session.ensureReadyForResume();
|
|
227
|
+
}
|
|
228
|
+
if (!isNotification) {
|
|
229
|
+
this.sendSuccess(id, await this.sessionState(session));
|
|
230
|
+
this.publishCommands(session.id);
|
|
231
|
+
this.publishUsageUpdate(session);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
if (!isNotification) {
|
|
236
|
+
this.sendError(id, -32603, `Failed to load session ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
237
|
+
}
|
|
209
238
|
}
|
|
210
239
|
break;
|
|
211
240
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
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
|
|
4
|
+
export declare const BINARY_RESOLVE_TTL_MS: number;
|
|
5
|
+
export declare function clearBinaryResolutionCache(): void;
|
|
6
|
+
export declare function isWindowsBatchScript(filePath: string): boolean;
|
|
7
|
+
export declare function resolveDefaultAgyBinary(force?: boolean): string;
|
|
5
8
|
export interface AntigravityProcessOptions {
|
|
6
9
|
binaryPath?: string;
|
|
7
10
|
cwd?: string;
|
|
@@ -6,41 +6,120 @@ import path from "node:path";
|
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
7
|
import { getEffectiveEffortForModel, } from "./protocol.js";
|
|
8
8
|
import { buildAgyArgs, resolvePermissionSettings } from "./permissions.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
let cachedBinaryPath = null;
|
|
10
|
+
let lastBinaryResolveTime = 0;
|
|
11
|
+
export const BINARY_RESOLVE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours TTL
|
|
12
|
+
export function clearBinaryResolutionCache() {
|
|
13
|
+
cachedBinaryPath = null;
|
|
14
|
+
lastBinaryResolveTime = 0;
|
|
15
|
+
}
|
|
16
|
+
export function isWindowsBatchScript(filePath) {
|
|
17
|
+
return /\.(cmd|bat)$/i.test(filePath);
|
|
18
|
+
}
|
|
19
|
+
export function resolveDefaultAgyBinary(force = false) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
if (!force &&
|
|
22
|
+
cachedBinaryPath &&
|
|
23
|
+
now - lastBinaryResolveTime < BINARY_RESOLVE_TTL_MS &&
|
|
24
|
+
(cachedBinaryPath === "agy" || fs.existsSync(cachedBinaryPath))) {
|
|
25
|
+
return cachedBinaryPath;
|
|
26
|
+
}
|
|
27
|
+
let resolved = "agy";
|
|
28
|
+
if (process.env.AGY_BIN_PATH) {
|
|
29
|
+
resolved = process.env.AGY_BIN_PATH;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const home = os.homedir();
|
|
33
|
+
if (home) {
|
|
34
|
+
if (process.platform === "win32") {
|
|
35
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
36
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
37
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
38
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
39
|
+
const exeCandidates = [
|
|
40
|
+
path.join(localAppData, "Programs", "Antigravity", "bin", "agy.exe"),
|
|
41
|
+
path.join(localAppData, "Programs", "antigravity", "agy.exe"),
|
|
42
|
+
path.join(localAppData, "Programs", "Antigravity", "agy.exe"),
|
|
43
|
+
path.join(programFiles, "Antigravity", "bin", "agy.exe"),
|
|
44
|
+
path.join(programFilesX86, "Antigravity", "bin", "agy.exe"),
|
|
45
|
+
path.join(localAppData, "Microsoft", "WindowsApps", "agy.exe"),
|
|
46
|
+
path.join(home, ".local", "bin", "agy.exe"),
|
|
47
|
+
path.join(appData, "npm", "agy.exe"),
|
|
48
|
+
path.join(localAppData, "npm", "agy.exe"),
|
|
49
|
+
];
|
|
50
|
+
for (const cand of exeCandidates) {
|
|
51
|
+
if (fs.existsSync(cand)) {
|
|
52
|
+
resolved = cand;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (resolved === "agy") {
|
|
57
|
+
for (const target of ["agy", "agy.exe"]) {
|
|
58
|
+
try {
|
|
59
|
+
const out = execFileSync("where.exe", [target], {
|
|
60
|
+
encoding: "utf-8",
|
|
61
|
+
timeout: 2000,
|
|
62
|
+
windowsHide: true,
|
|
63
|
+
}).trim();
|
|
64
|
+
const lines = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
if (/\.exe$/i.test(line) && fs.existsSync(line)) {
|
|
67
|
+
resolved = line;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (resolved !== "agy")
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (resolved === "agy") {
|
|
78
|
+
const batchCandidates = [
|
|
79
|
+
path.join(appData, "npm", "agy.cmd"),
|
|
80
|
+
path.join(localAppData, "npm", "agy.cmd"),
|
|
81
|
+
path.join(home, ".local", "bin", "agy.cmd"),
|
|
82
|
+
path.join(appData, "npm", "agy.bat"),
|
|
83
|
+
];
|
|
84
|
+
for (const cand of batchCandidates) {
|
|
85
|
+
if (fs.existsSync(cand)) {
|
|
86
|
+
resolved = cand;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (resolved === "agy") {
|
|
91
|
+
for (const target of ["agy", "agy.cmd", "agy.bat"]) {
|
|
92
|
+
try {
|
|
93
|
+
const out = execFileSync("where.exe", [target], {
|
|
94
|
+
encoding: "utf-8",
|
|
95
|
+
timeout: 2000,
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
}).trim();
|
|
98
|
+
const lines = out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
if (isWindowsBatchScript(line) && fs.existsSync(line)) {
|
|
101
|
+
resolved = line;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (resolved !== "agy")
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
24
112
|
}
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
windowsHide: true,
|
|
30
|
-
}).trim();
|
|
31
|
-
const first = out.split(/\r?\n/)[0]?.trim();
|
|
32
|
-
if (first && fs.existsSync(first))
|
|
33
|
-
return first;
|
|
113
|
+
else {
|
|
114
|
+
const localPath = path.join(home, ".local", "bin", "agy");
|
|
115
|
+
if (fs.existsSync(localPath))
|
|
116
|
+
resolved = localPath;
|
|
34
117
|
}
|
|
35
|
-
catch { }
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const localPath = path.join(home, ".local", "bin", "agy");
|
|
39
|
-
if (fs.existsSync(localPath))
|
|
40
|
-
return localPath;
|
|
41
118
|
}
|
|
42
119
|
}
|
|
43
|
-
|
|
120
|
+
cachedBinaryPath = resolved;
|
|
121
|
+
lastBinaryResolveTime = now;
|
|
122
|
+
return resolved;
|
|
44
123
|
}
|
|
45
124
|
export class AntigravityProcess extends EventEmitter {
|
|
46
125
|
child = null;
|
|
@@ -269,12 +348,13 @@ export class AntigravityProcess extends EventEmitter {
|
|
|
269
348
|
effectiveEffort,
|
|
270
349
|
});
|
|
271
350
|
const isWin = process.platform === "win32";
|
|
351
|
+
const isBatch = isWin && isWindowsBatchScript(this.binaryPath);
|
|
272
352
|
const child = spawn(this.binaryPath, args, {
|
|
273
353
|
cwd: this.cwd,
|
|
274
354
|
env: this.env,
|
|
275
355
|
stdio: ["pipe", "pipe", "pipe"],
|
|
276
356
|
detached: !isWin,
|
|
277
|
-
shell:
|
|
357
|
+
shell: isBatch,
|
|
278
358
|
windowsHide: true,
|
|
279
359
|
});
|
|
280
360
|
this.child = child;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
2
4
|
import { ACPServer } from "./acp-server.js";
|
|
3
5
|
import { logger } from "./logger.js";
|
|
4
6
|
import { formatDiagnosticVersion, resolveBuildMetadata } from "./version.js";
|
|
5
|
-
import { ensurePaseoIntegration } from "./paseo-patcher.js";
|
|
7
|
+
import { ensurePaseoIntegration, findPaseoServerInstallations, findPaseoAsarPaths, isPaseoServerPatched, isPaseoAsarPatched, isPaseoRunning, } from "./paseo-patcher.js";
|
|
8
|
+
import { resolveDefaultAgyBinary, isWindowsBatchScript } from "./antigravity-process.js";
|
|
9
|
+
import { parseAgyQuotaOutput } from "./protocol.js";
|
|
6
10
|
const args = process.argv.slice(2);
|
|
7
11
|
if (args.includes("--version") || args.includes("-v")) {
|
|
8
12
|
if (args.includes("--json")) {
|
|
@@ -21,13 +25,16 @@ Usage:
|
|
|
21
25
|
agy-acp [options]
|
|
22
26
|
npx -y paseo-acp-agy [options]
|
|
23
27
|
npx -y paseo-acp-agy setup
|
|
28
|
+
npx -y paseo-acp-agy doctor
|
|
24
29
|
|
|
25
30
|
Commands:
|
|
26
31
|
setup, patch Configure and integrate Antigravity telemetry with Paseo
|
|
32
|
+
doctor Diagnose Antigravity binary, quota provider, and Paseo status
|
|
27
33
|
|
|
28
34
|
Options:
|
|
29
35
|
--acp Start ACP server over stdio (default)
|
|
30
36
|
--setup Integrate Antigravity with local Paseo server installation
|
|
37
|
+
--doctor Run environment, binary, and telemetry diagnostics
|
|
31
38
|
-v, --version Show version
|
|
32
39
|
--json Show version in JSON format (with --version)
|
|
33
40
|
-h, --help Show help
|
|
@@ -39,30 +46,190 @@ Environment Variables:
|
|
|
39
46
|
AGY_ACP_DANGEROUSLY_SKIP_PERMISSIONS Set to 'true' to auto-approve tool permissions
|
|
40
47
|
AGY_BIN_PATH Path to agy binary (default: agy in PATH or ~/.local/bin/agy)
|
|
41
48
|
PASEO_SERVER_PATH Path to local @getpaseo/server directory
|
|
49
|
+
PASEO_ASAR_PATH Path to local Paseo app.asar package
|
|
42
50
|
`);
|
|
43
51
|
process.exit(0);
|
|
44
52
|
}
|
|
53
|
+
if (args.includes("doctor") || args.includes("--doctor")) {
|
|
54
|
+
process.stdout.write("=== Paseo & Antigravity Doctor ===\n");
|
|
55
|
+
process.stdout.write(`Operating System: ${process.platform} (${process.arch})\n`);
|
|
56
|
+
process.stdout.write(`Node.js Version: ${process.version}\n`);
|
|
57
|
+
process.stdout.write(`Paseo ACP Adapter: ${formatDiagnosticVersion()}\n\n`);
|
|
58
|
+
process.stdout.write("1. Antigravity Binary Resolution:\n");
|
|
59
|
+
const binPath = resolveDefaultAgyBinary(true);
|
|
60
|
+
const exists = fs.existsSync(binPath);
|
|
61
|
+
const isWin = process.platform === "win32";
|
|
62
|
+
let execType = "Executable";
|
|
63
|
+
if (isWin) {
|
|
64
|
+
if (/\.exe$/i.test(binPath))
|
|
65
|
+
execType = "Native Windows Executable (.exe)";
|
|
66
|
+
else if (/\.cmd$/i.test(binPath))
|
|
67
|
+
execType = "Windows Command Script (.cmd)";
|
|
68
|
+
else if (/\.bat$/i.test(binPath))
|
|
69
|
+
execType = "Windows Batch Script (.bat)";
|
|
70
|
+
else
|
|
71
|
+
execType = "CLI command in PATH";
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
execType = "POSIX binary";
|
|
75
|
+
}
|
|
76
|
+
if (exists) {
|
|
77
|
+
process.stdout.write(` [OK] Binary found: ${binPath} (${execType})\n`);
|
|
78
|
+
}
|
|
79
|
+
else if (binPath === "agy") {
|
|
80
|
+
process.stdout.write(` [?] Binary fallback: 'agy' via system PATH (${execType})\n`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
process.stdout.write(` [FAIL] Binary not found at: ${binPath}\n`);
|
|
84
|
+
}
|
|
85
|
+
process.stdout.write("\n2. CLI Telemetry & Runtime Probes:\n");
|
|
86
|
+
const isBatch = isWin && isWindowsBatchScript(binPath);
|
|
87
|
+
let modelsSuccess = false;
|
|
88
|
+
let modelsCount = 0;
|
|
89
|
+
const t0 = Date.now();
|
|
90
|
+
try {
|
|
91
|
+
const modelsOut = execFileSync(binPath, ["models"], {
|
|
92
|
+
encoding: "utf-8",
|
|
93
|
+
timeout: 15000,
|
|
94
|
+
windowsHide: true,
|
|
95
|
+
shell: isBatch,
|
|
96
|
+
});
|
|
97
|
+
const latency = Date.now() - t0;
|
|
98
|
+
const modelLines = modelsOut
|
|
99
|
+
.trim()
|
|
100
|
+
.split(/\r?\n/)
|
|
101
|
+
.map((l) => l.trim())
|
|
102
|
+
.filter((l) => Boolean(l) && !l.toLowerCase().startsWith("models:"));
|
|
103
|
+
modelsCount = modelLines.length;
|
|
104
|
+
modelsSuccess = true;
|
|
105
|
+
process.stdout.write(` [OK] 'agy models' probe succeeded in ${latency}ms (${modelsCount} models detected)\n`);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const latency = Date.now() - t0;
|
|
109
|
+
process.stdout.write(` [WARN] 'agy models' probe failed in ${latency}ms: ${err?.message || String(err)}\n`);
|
|
110
|
+
}
|
|
111
|
+
let quotaSuccess = false;
|
|
112
|
+
let quotaCount = 0;
|
|
113
|
+
const t1 = Date.now();
|
|
114
|
+
try {
|
|
115
|
+
const usageOut = execFileSync(binPath, ["--print-timeout", "24h", "--print", "/usage"], {
|
|
116
|
+
encoding: "utf-8",
|
|
117
|
+
timeout: 15000,
|
|
118
|
+
windowsHide: true,
|
|
119
|
+
shell: isBatch,
|
|
120
|
+
});
|
|
121
|
+
const latency = Date.now() - t1;
|
|
122
|
+
const normalized = usageOut.replace(/\r\n/g, "\n");
|
|
123
|
+
const windows = parseAgyQuotaOutput(normalized);
|
|
124
|
+
quotaCount = windows.length;
|
|
125
|
+
quotaSuccess = true;
|
|
126
|
+
process.stdout.write(` [OK] 'agy --print /usage' probe succeeded in ${latency}ms (${quotaCount} quota limit item(s) parsed)\n`);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const latency = Date.now() - t1;
|
|
130
|
+
process.stdout.write(` [WARN] 'agy --print /usage' probe failed in ${latency}ms: ${err?.message || String(err)}\n`);
|
|
131
|
+
}
|
|
132
|
+
process.stdout.write("\n3. Paseo Installation Targets:\n");
|
|
133
|
+
const serverPaths = findPaseoServerInstallations();
|
|
134
|
+
const asarPaths = findPaseoAsarPaths();
|
|
135
|
+
const totalFound = serverPaths.length + asarPaths.length;
|
|
136
|
+
if (totalFound === 0) {
|
|
137
|
+
process.stdout.write(" [!] No Paseo server or desktop installations discovered in standard paths.\n");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
for (const sPath of serverPaths) {
|
|
141
|
+
const patched = isPaseoServerPatched(sPath);
|
|
142
|
+
process.stdout.write(` - [Server] ${sPath} -> ${patched ? "[PATCHED]" : "[UNPATCHED]"}\n`);
|
|
143
|
+
}
|
|
144
|
+
for (const aPath of asarPaths) {
|
|
145
|
+
const patched = await isPaseoAsarPatched(aPath);
|
|
146
|
+
process.stdout.write(` - [ASAR] ${aPath} -> ${patched ? "[PATCHED]" : "[UNPATCHED]"}\n`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
process.stdout.write("\n4. Paseo Process Status:\n");
|
|
150
|
+
const paseoRunning = isPaseoRunning();
|
|
151
|
+
if (paseoRunning) {
|
|
152
|
+
process.stdout.write(" [!] Paseo process is currently running.\n");
|
|
153
|
+
process.stdout.write(" Note: If you run setup/patch, please close Paseo completely (check system tray and Task Manager) to avoid EBUSY file locking.\n");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
process.stdout.write(" [OK] Paseo is not currently running (safe to patch/update).\n");
|
|
157
|
+
}
|
|
158
|
+
process.stdout.write("\n=== Doctor Recommendations ===\n");
|
|
159
|
+
let unpatchedCount = 0;
|
|
160
|
+
for (const sPath of serverPaths) {
|
|
161
|
+
if (!isPaseoServerPatched(sPath))
|
|
162
|
+
unpatchedCount++;
|
|
163
|
+
}
|
|
164
|
+
for (const aPath of asarPaths) {
|
|
165
|
+
if (!(await isPaseoAsarPatched(aPath)))
|
|
166
|
+
unpatchedCount++;
|
|
167
|
+
}
|
|
168
|
+
let hasIssue = false;
|
|
169
|
+
if (!exists && binPath !== "agy") {
|
|
170
|
+
hasIssue = true;
|
|
171
|
+
process.stdout.write(" - Antigravity CLI ('agy') executable was not found.\n");
|
|
172
|
+
process.stdout.write(" Action: Install Antigravity CLI or set AGY_BIN_PATH environment variable.\n");
|
|
173
|
+
}
|
|
174
|
+
if (totalFound === 0) {
|
|
175
|
+
hasIssue = true;
|
|
176
|
+
process.stdout.write(" - No Paseo installations found.\n");
|
|
177
|
+
process.stdout.write(" Action: Set PASEO_SERVER_PATH or PASEO_ASAR_PATH to your Paseo install directory if located elsewhere.\n");
|
|
178
|
+
}
|
|
179
|
+
else if (unpatchedCount > 0) {
|
|
180
|
+
hasIssue = true;
|
|
181
|
+
if (paseoRunning) {
|
|
182
|
+
process.stdout.write(` - Found ${unpatchedCount} unpatched Paseo target(s), but Paseo is currently running.\n`);
|
|
183
|
+
process.stdout.write(" Action: Close Paseo.exe completely, then run:\n");
|
|
184
|
+
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
process.stdout.write(` - Found ${unpatchedCount} unpatched Paseo target(s).\n`);
|
|
188
|
+
process.stdout.write(" Action: Run setup to patch Paseo for Antigravity telemetry:\n");
|
|
189
|
+
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!modelsSuccess || !quotaSuccess) {
|
|
193
|
+
if (exists || binPath === "agy") {
|
|
194
|
+
hasIssue = true;
|
|
195
|
+
process.stdout.write(" - Antigravity CLI telemetry probes experienced errors.\n");
|
|
196
|
+
process.stdout.write(" Action: Ensure you are logged in to Antigravity CLI (try running 'agy auth' or 'agy /status').\n");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!hasIssue) {
|
|
200
|
+
process.stdout.write(" [ALL OK] Everything is properly configured! Antigravity quota and telemetry are ready to use in Paseo.\n");
|
|
201
|
+
}
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
45
204
|
if (args.includes("setup") ||
|
|
46
205
|
args.includes("patch") ||
|
|
47
206
|
args.includes("--setup") ||
|
|
48
207
|
args.includes("--patch")) {
|
|
208
|
+
if (isPaseoRunning()) {
|
|
209
|
+
process.stdout.write("Notice: Paseo appears to be running. If setup fails with EBUSY, please close Paseo completely (from system tray / task manager) and re-run setup.\n\n");
|
|
210
|
+
}
|
|
49
211
|
process.stdout.write("Checking Paseo installation and configuring Antigravity telemetry...\n");
|
|
50
212
|
try {
|
|
51
|
-
const res = ensurePaseoIntegration({ verbose: true });
|
|
213
|
+
const res = await ensurePaseoIntegration({ verbose: true });
|
|
52
214
|
if (!res.found) {
|
|
53
|
-
process.stdout.write("Notice: No active @getpaseo/server installation found in standard paths.\n" +
|
|
54
|
-
"If Paseo is installed in a custom directory, set PASEO_SERVER_PATH and run setup again.\n");
|
|
215
|
+
process.stdout.write("Notice: No active @getpaseo/server installation or app.asar found in standard paths.\n" +
|
|
216
|
+
"If Paseo is installed in a custom directory, set PASEO_SERVER_PATH or PASEO_ASAR_PATH and run setup again.\n");
|
|
55
217
|
}
|
|
56
218
|
else {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
219
|
+
const totalFound = res.serverPaths.length + (res.asarPaths?.length || 0);
|
|
220
|
+
process.stdout.write(`Found ${totalFound} Paseo installation target(s) (${res.serverPaths.length} server dir(s), ${res.asarPaths?.length || 0} asar package(s)).\n`);
|
|
221
|
+
const allPatched = [...res.patchedPaths, ...(res.patchedAsarPaths || [])];
|
|
222
|
+
if (allPatched.length > 0) {
|
|
223
|
+
process.stdout.write(`Successfully integrated with: \n${allPatched.map((p) => ` - ${p}`).join("\n")}\n\n` +
|
|
60
224
|
`Antigravity quota provider and context-window telemetry are now enabled!\n` +
|
|
61
225
|
`Please restart Paseo (or run 'paseo daemon restart') to apply changes.\n`);
|
|
62
226
|
}
|
|
63
227
|
else {
|
|
64
228
|
process.stdout.write("Paseo is already up-to-date and configured for Antigravity telemetry.\n");
|
|
65
229
|
}
|
|
230
|
+
if (res.errors.length > 0) {
|
|
231
|
+
process.stderr.write(`Notice: Some paths could not be modified (may require admin/close Paseo):\n${res.errors.map(e => ` - ${e}`).join("\n")}\n`);
|
|
232
|
+
}
|
|
66
233
|
}
|
|
67
234
|
}
|
|
68
235
|
catch (err) {
|
|
@@ -71,10 +238,7 @@ if (args.includes("setup") ||
|
|
|
71
238
|
process.exit(0);
|
|
72
239
|
}
|
|
73
240
|
// Auto-run integration in background when starting ACP server
|
|
74
|
-
|
|
75
|
-
ensurePaseoIntegration();
|
|
76
|
-
}
|
|
77
|
-
catch { }
|
|
241
|
+
void ensurePaseoIntegration().catch(() => { });
|
|
78
242
|
const server = new ACPServer();
|
|
79
243
|
const cleanup = async () => {
|
|
80
244
|
try {
|
package/dist/paseo-patcher.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export interface PatchResult {
|
|
|
2
2
|
found: boolean;
|
|
3
3
|
serverPaths: string[];
|
|
4
4
|
patchedPaths: string[];
|
|
5
|
+
asarPaths?: string[];
|
|
6
|
+
patchedAsarPaths?: string[];
|
|
5
7
|
errors: string[];
|
|
6
8
|
}
|
|
7
9
|
/**
|
|
@@ -26,9 +28,37 @@ export declare function patchPaseoServer(serverDir: string): {
|
|
|
26
28
|
error?: string;
|
|
27
29
|
};
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
31
|
+
* Searches the host machine for Paseo Desktop app.asar archives across
|
|
32
|
+
* Windows, macOS, and Linux.
|
|
33
|
+
*/
|
|
34
|
+
export declare function findPaseoAsarPaths(): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Extracts, patches, and repacks a Paseo app.asar archive to integrate Antigravity
|
|
37
|
+
* quota fetchers and token telemetry.
|
|
38
|
+
*/
|
|
39
|
+
export declare function patchPaseoAsar(asarPath: string): Promise<{
|
|
40
|
+
success: boolean;
|
|
41
|
+
changes: string[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Discovers and patches all accessible Paseo installations (both directory and app.asar).
|
|
30
46
|
*/
|
|
31
47
|
export declare function ensurePaseoIntegration(options?: {
|
|
32
48
|
verbose?: boolean;
|
|
33
49
|
targetPaths?: string[];
|
|
34
|
-
|
|
50
|
+
targetAsarPaths?: string[];
|
|
51
|
+
}): Promise<PatchResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether a @getpaseo/server installation directory is already patched
|
|
54
|
+
* for Antigravity quota and telemetry support.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isPaseoServerPatched(serverDir: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Checks whether a Paseo app.asar archive is already patched with Antigravity telemetry.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isPaseoAsarPatched(asarPath: string): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Detects if Paseo Desktop is currently running on the host.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isPaseoRunning(): boolean;
|
package/dist/paseo-patcher.js
CHANGED
|
@@ -92,6 +92,8 @@ export function findPaseoServerInstallations() {
|
|
|
92
92
|
path.join(path.dirname(paseoDir), "node_modules", "@getpaseo", "server"),
|
|
93
93
|
path.join(paseoDir, "resources", "app.asar.unpacked", "node_modules", "@getpaseo", "server"),
|
|
94
94
|
path.join(paseoDir, "resources", "app", "node_modules", "@getpaseo", "server"),
|
|
95
|
+
path.join(path.dirname(paseoDir), "resources", "app.asar.unpacked", "node_modules", "@getpaseo", "server"),
|
|
96
|
+
path.join(path.dirname(paseoDir), "resources", "app", "node_modules", "@getpaseo", "server"),
|
|
95
97
|
];
|
|
96
98
|
for (const c of checks) {
|
|
97
99
|
if (fs.existsSync(c))
|
|
@@ -179,31 +181,100 @@ import { toneFromUsedPct, windowFromUsedPct, unavailableUsage } from "../usage.j
|
|
|
179
181
|
|
|
180
182
|
const execFileAsync = promisify(execFile);
|
|
181
183
|
|
|
184
|
+
let cachedAgyBin = null;
|
|
185
|
+
let cachedAgyBinTime = 0;
|
|
186
|
+
const BIN_CACHE_TTL_MS = 86400000; // 24 hours
|
|
187
|
+
|
|
182
188
|
function resolveAgyBinary() {
|
|
183
189
|
if (process.env.AGY_BIN_PATH) return process.env.AGY_BIN_PATH;
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
if (cachedAgyBin && (now - cachedAgyBinTime < BIN_CACHE_TTL_MS)) {
|
|
192
|
+
return cachedAgyBin;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let resolved = "agy";
|
|
184
196
|
const home = os.homedir();
|
|
185
197
|
if (home) {
|
|
186
198
|
if (process.platform === "win32") {
|
|
187
|
-
const
|
|
199
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
200
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(home, "AppData", "Local");
|
|
201
|
+
const programFiles = process.env.ProgramFiles || "C:\\\\Program Files";
|
|
202
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\\\Program Files (x86)";
|
|
203
|
+
|
|
204
|
+
// Prioritize .exe candidates over .cmd / .bat
|
|
205
|
+
const exeCandidates = [
|
|
206
|
+
path.join(localAppData, "Programs", "Antigravity", "bin", "agy.exe"),
|
|
207
|
+
path.join(localAppData, "Programs", "antigravity", "agy.exe"),
|
|
208
|
+
path.join(localAppData, "Programs", "Antigravity", "agy.exe"),
|
|
209
|
+
path.join(programFiles, "Antigravity", "bin", "agy.exe"),
|
|
210
|
+
path.join(programFilesX86, "Antigravity", "bin", "agy.exe"),
|
|
211
|
+
path.join(localAppData, "Microsoft", "WindowsApps", "agy.exe"),
|
|
188
212
|
path.join(home, ".local", "bin", "agy.exe"),
|
|
189
|
-
path.join(
|
|
190
|
-
path.join(
|
|
191
|
-
path.join(home, ".local", "bin", "agy.bat"),
|
|
213
|
+
path.join(appData, "npm", "agy.exe"),
|
|
214
|
+
path.join(localAppData, "npm", "agy.exe"),
|
|
192
215
|
];
|
|
193
|
-
for (const cand of
|
|
194
|
-
if (fs.existsSync(cand))
|
|
216
|
+
for (const cand of exeCandidates) {
|
|
217
|
+
if (fs.existsSync(cand)) {
|
|
218
|
+
resolved = cand;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check where.exe agy and select first .exe
|
|
224
|
+
if (resolved === "agy") {
|
|
225
|
+
for (const target of ["agy", "agy.exe"]) {
|
|
226
|
+
try {
|
|
227
|
+
const out = execFileSync("where.exe", [target], { encoding: "utf-8", timeout: 2000, windowsHide: true }).trim();
|
|
228
|
+
const lines = out.split(/\\r?\\n/).map(l => l.trim()).filter(Boolean);
|
|
229
|
+
const firstExe = lines.find(l => /\\.exe$/i.test(l) && fs.existsSync(l));
|
|
230
|
+
if (firstExe) {
|
|
231
|
+
resolved = firstExe;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
} catch {}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Fallback to batch scripts (.cmd / .bat) if no .exe found
|
|
239
|
+
if (resolved === "agy") {
|
|
240
|
+
const batchCandidates = [
|
|
241
|
+
path.join(appData, "npm", "agy.cmd"),
|
|
242
|
+
path.join(localAppData, "npm", "agy.cmd"),
|
|
243
|
+
path.join(home, ".local", "bin", "agy.cmd"),
|
|
244
|
+
path.join(appData, "npm", "agy.bat"),
|
|
245
|
+
path.join(localAppData, "npm", "agy.bat"),
|
|
246
|
+
path.join(home, ".local", "bin", "agy.bat"),
|
|
247
|
+
];
|
|
248
|
+
for (const cand of batchCandidates) {
|
|
249
|
+
if (fs.existsSync(cand)) {
|
|
250
|
+
resolved = cand;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (resolved === "agy") {
|
|
257
|
+
for (const target of ["agy", "agy.cmd", "agy.bat"]) {
|
|
258
|
+
try {
|
|
259
|
+
const out = execFileSync("where.exe", [target], { encoding: "utf-8", timeout: 2000, windowsHide: true }).trim();
|
|
260
|
+
const lines = out.split(/\\r?\\n/).map(l => l.trim()).filter(Boolean);
|
|
261
|
+
const firstAny = lines.find(l => /\\.(cmd|bat)$/i.test(l) && fs.existsSync(l));
|
|
262
|
+
if (firstAny) {
|
|
263
|
+
resolved = firstAny;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
} catch {}
|
|
267
|
+
}
|
|
195
268
|
}
|
|
196
|
-
try {
|
|
197
|
-
const out = execFileSync("where.exe", ["agy"], { encoding: "utf-8", timeout: 2000, windowsHide: true }).trim();
|
|
198
|
-
const first = out.split(/\\r?\\n/)[0]?.trim();
|
|
199
|
-
if (first && fs.existsSync(first)) return first;
|
|
200
|
-
} catch {}
|
|
201
269
|
} else {
|
|
202
270
|
const localPath = path.join(home, ".local", "bin", "agy");
|
|
203
|
-
if (fs.existsSync(localPath))
|
|
271
|
+
if (fs.existsSync(localPath)) resolved = localPath;
|
|
204
272
|
}
|
|
205
273
|
}
|
|
206
|
-
|
|
274
|
+
|
|
275
|
+
cachedAgyBin = resolved;
|
|
276
|
+
cachedAgyBinTime = now;
|
|
277
|
+
return resolved;
|
|
207
278
|
}
|
|
208
279
|
|
|
209
280
|
export class AntigravityQuotaProvider {
|
|
@@ -217,20 +288,22 @@ export class AntigravityQuotaProvider {
|
|
|
217
288
|
async fetchUsage() {
|
|
218
289
|
try {
|
|
219
290
|
const isWin = process.platform === "win32";
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
291
|
+
const bin = resolveAgyBinary();
|
|
292
|
+
this.binaryPath = bin;
|
|
293
|
+
const isBatch = isWin && /\\.(cmd|bat)$/i.test(bin);
|
|
224
294
|
const [usageRes, creditsRes] = await Promise.allSettled([
|
|
225
|
-
execFileAsync(bin, ["--print", "/usage"], { timeout:
|
|
226
|
-
execFileAsync(bin, ["--print", "/credits"], { timeout:
|
|
295
|
+
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/usage"], { timeout: 15000, env: process.env, shell: isBatch, windowsHide: true }),
|
|
296
|
+
execFileAsync(bin, ["--print-timeout", "24h", "--print", "/credits"], { timeout: 15000, env: process.env, shell: isBatch, windowsHide: true }),
|
|
227
297
|
]);
|
|
228
298
|
|
|
229
|
-
const
|
|
230
|
-
const
|
|
299
|
+
const rawUsageOut = usageRes.status === "fulfilled" ? usageRes.value.stdout || usageRes.value.stderr : "";
|
|
300
|
+
const rawCreditsOut = creditsRes.status === "fulfilled" ? creditsRes.value.stdout || creditsRes.value.stderr : "";
|
|
301
|
+
|
|
302
|
+
const usageOut = (rawUsageOut || "").replace(/\\r\\n/g, "\\n");
|
|
303
|
+
const creditsOut = (rawCreditsOut || "").replace(/\\r\\n/g, "\\n");
|
|
231
304
|
|
|
232
305
|
const rawWindows = [];
|
|
233
|
-
for (const line of usageOut.split(
|
|
306
|
+
for (const line of usageOut.split("\\n")) {
|
|
234
307
|
const trimmed = line.trim();
|
|
235
308
|
if (!trimmed || trimmed.toLowerCase().startsWith("quota:")) continue;
|
|
236
309
|
|
|
@@ -348,7 +421,7 @@ export function patchPaseoServer(serverDir) {
|
|
|
348
421
|
if (found)
|
|
349
422
|
return found;
|
|
350
423
|
}
|
|
351
|
-
else if (e.isFile() && e.name === "manifest.js" && dir.includes("quota-fetcher")) {
|
|
424
|
+
else if (e.isFile() && e.name === "manifest.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
|
|
352
425
|
return full;
|
|
353
426
|
}
|
|
354
427
|
}
|
|
@@ -375,9 +448,9 @@ export function patchPaseoServer(serverDir) {
|
|
|
375
448
|
}
|
|
376
449
|
if (!manifestCode.includes('providerId: "antigravity"')) {
|
|
377
450
|
const entryToAdd = ` {\n providerId: "antigravity",\n create: (options) => new AntigravityQuotaProvider({\n logger: options.logger,\n fetch: options.fetch,\n }),\n },\n`;
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
380
|
-
manifestCode = manifestCode.replace(
|
|
451
|
+
const fetcherArrayRegex = /export\s+const\s+PROVIDER_USAGE_FETCHERS\s*=\s*\[/;
|
|
452
|
+
if (fetcherArrayRegex.test(manifestCode)) {
|
|
453
|
+
manifestCode = manifestCode.replace(fetcherArrayRegex, (match) => `${match}\n${entryToAdd}`);
|
|
381
454
|
manifestModified = true;
|
|
382
455
|
}
|
|
383
456
|
}
|
|
@@ -434,11 +507,44 @@ export function patchPaseoServer(serverDir) {
|
|
|
434
507
|
acpCode = acpCode.replace(oldMapRegex, newMap);
|
|
435
508
|
acpModified = true;
|
|
436
509
|
}
|
|
510
|
+
else {
|
|
511
|
+
const mapMatch = acpCode.match(/export\s+function\s+mapACPUsage\s*\([^)]*\)\s*\{/);
|
|
512
|
+
if (mapMatch && mapMatch.index !== undefined) {
|
|
513
|
+
const mapStart = mapMatch.index;
|
|
514
|
+
const openBrace = acpCode.indexOf("{", mapStart);
|
|
515
|
+
let depth = 1;
|
|
516
|
+
let i = openBrace + 1;
|
|
517
|
+
while (i < acpCode.length && depth > 0) {
|
|
518
|
+
if (acpCode[i] === "{")
|
|
519
|
+
depth++;
|
|
520
|
+
else if (acpCode[i] === "}")
|
|
521
|
+
depth--;
|
|
522
|
+
i++;
|
|
523
|
+
}
|
|
524
|
+
if (depth === 0) {
|
|
525
|
+
acpCode = acpCode.slice(0, mapStart) + newMap + acpCode.slice(i);
|
|
526
|
+
acpModified = true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
437
530
|
}
|
|
438
531
|
// Patch handleUsageUpdate
|
|
439
|
-
if (acpCode.includes("handleUsageUpdate
|
|
440
|
-
const
|
|
441
|
-
|
|
532
|
+
if (acpCode.includes("handleUsageUpdate") && (!acpCode.includes("this.deliverTranslatedEvents") || acpCode.includes("this.notifySubscribers"))) {
|
|
533
|
+
const startIdx = acpCode.search(/\bhandleUsageUpdate\s*\(/);
|
|
534
|
+
if (startIdx !== -1) {
|
|
535
|
+
const openBrace = acpCode.indexOf("{", startIdx);
|
|
536
|
+
if (openBrace !== -1) {
|
|
537
|
+
let depth = 1;
|
|
538
|
+
let i = openBrace + 1;
|
|
539
|
+
while (i < acpCode.length && depth > 0) {
|
|
540
|
+
if (acpCode[i] === "{")
|
|
541
|
+
depth++;
|
|
542
|
+
else if (acpCode[i] === "}")
|
|
543
|
+
depth--;
|
|
544
|
+
i++;
|
|
545
|
+
}
|
|
546
|
+
if (depth === 0) {
|
|
547
|
+
const newHandler = `handleUsageUpdate(update) {
|
|
442
548
|
if (!update) return;
|
|
443
549
|
const usage = mapACPUsage(update);
|
|
444
550
|
if (usage) {
|
|
@@ -456,9 +562,10 @@ export function patchPaseoServer(serverDir) {
|
|
|
456
562
|
}
|
|
457
563
|
}
|
|
458
564
|
}`;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
565
|
+
acpCode = acpCode.slice(0, startIdx) + newHandler + acpCode.slice(i);
|
|
566
|
+
acpModified = true;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
462
569
|
}
|
|
463
570
|
}
|
|
464
571
|
if (acpModified) {
|
|
@@ -475,11 +582,197 @@ export function patchPaseoServer(serverDir) {
|
|
|
475
582
|
}
|
|
476
583
|
}
|
|
477
584
|
/**
|
|
478
|
-
*
|
|
585
|
+
* Searches the host machine for Paseo Desktop app.asar archives across
|
|
586
|
+
* Windows, macOS, and Linux.
|
|
587
|
+
*/
|
|
588
|
+
export function findPaseoAsarPaths() {
|
|
589
|
+
const candidates = new Set();
|
|
590
|
+
const home = os.homedir();
|
|
591
|
+
if (process.env.PASEO_ASAR_PATH && fs.existsSync(process.env.PASEO_ASAR_PATH)) {
|
|
592
|
+
candidates.add(path.resolve(process.env.PASEO_ASAR_PATH));
|
|
593
|
+
}
|
|
594
|
+
if (process.platform === "win32") {
|
|
595
|
+
const localAppData = process.env.LOCALAPPDATA || (home ? path.join(home, "AppData", "Local") : "");
|
|
596
|
+
const appData = process.env.APPDATA || (home ? path.join(home, "AppData", "Roaming") : "");
|
|
597
|
+
const programFiles = process.env.ProgramFiles || "C:\\Program Files";
|
|
598
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
599
|
+
const winAsarLocations = [
|
|
600
|
+
path.join(localAppData, "Programs", "Paseo", "resources", "app.asar"),
|
|
601
|
+
path.join(localAppData, "Programs", "paseo", "resources", "app.asar"),
|
|
602
|
+
path.join(localAppData, "Paseo", "resources", "app.asar"),
|
|
603
|
+
path.join(programFiles, "Paseo", "resources", "app.asar"),
|
|
604
|
+
path.join(programFiles, "paseo", "resources", "app.asar"),
|
|
605
|
+
path.join(programFilesX86, "Paseo", "resources", "app.asar"),
|
|
606
|
+
path.join(programFilesX86, "paseo", "resources", "app.asar"),
|
|
607
|
+
path.join(appData, "Paseo", "resources", "app.asar"),
|
|
608
|
+
];
|
|
609
|
+
for (const loc of winAsarLocations) {
|
|
610
|
+
if (loc && fs.existsSync(loc))
|
|
611
|
+
candidates.add(path.resolve(loc));
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
const whereOut = execFileSync("where.exe", ["paseo"], {
|
|
615
|
+
encoding: "utf-8",
|
|
616
|
+
timeout: 2000,
|
|
617
|
+
windowsHide: true,
|
|
618
|
+
}).trim();
|
|
619
|
+
for (const line of whereOut.split(/\r?\n/).map((l) => l.trim()).filter(Boolean)) {
|
|
620
|
+
const asarCandidate1 = path.join(path.dirname(line), "resources", "app.asar");
|
|
621
|
+
const asarCandidate2 = path.resolve(path.dirname(line), "..", "resources", "app.asar");
|
|
622
|
+
const asarCandidate3 = path.resolve(line, "..", "..", "resources", "app.asar");
|
|
623
|
+
for (const cand of [asarCandidate1, asarCandidate2, asarCandidate3]) {
|
|
624
|
+
if (fs.existsSync(cand))
|
|
625
|
+
candidates.add(path.resolve(cand));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
catch { }
|
|
630
|
+
}
|
|
631
|
+
else if (process.platform === "darwin") {
|
|
632
|
+
const macLocations = [
|
|
633
|
+
"/Applications/Paseo.app/Contents/Resources/app.asar",
|
|
634
|
+
path.join(home, "Applications", "Paseo.app", "Contents", "Resources", "app.asar"),
|
|
635
|
+
];
|
|
636
|
+
for (const loc of macLocations) {
|
|
637
|
+
if (fs.existsSync(loc))
|
|
638
|
+
candidates.add(path.resolve(loc));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
const linuxLocations = [
|
|
643
|
+
"/opt/Paseo/resources/app.asar",
|
|
644
|
+
"/usr/lib/paseo/resources/app.asar",
|
|
645
|
+
path.join(home, ".local", "share", "paseo", "resources", "app.asar"),
|
|
646
|
+
];
|
|
647
|
+
for (const loc of linuxLocations) {
|
|
648
|
+
if (fs.existsSync(loc))
|
|
649
|
+
candidates.add(path.resolve(loc));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return Array.from(candidates);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Extracts, patches, and repacks a Paseo app.asar archive to integrate Antigravity
|
|
656
|
+
* quota fetchers and token telemetry.
|
|
657
|
+
*/
|
|
658
|
+
export async function patchPaseoAsar(asarPath) {
|
|
659
|
+
const changes = [];
|
|
660
|
+
let tempDir = null;
|
|
661
|
+
let tempAsar = null;
|
|
662
|
+
try {
|
|
663
|
+
if (!fs.existsSync(asarPath)) {
|
|
664
|
+
return { success: false, changes: [], error: `Asar archive not found: ${asarPath}` };
|
|
665
|
+
}
|
|
666
|
+
// Dynamic import of @electron/asar
|
|
667
|
+
const asarModule = await import("@electron/asar");
|
|
668
|
+
const asar = asarModule.default || asarModule;
|
|
669
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paseo-asar-extract-"));
|
|
670
|
+
asar.extractAll(asarPath, tempDir);
|
|
671
|
+
// Look for server directory in extracted files
|
|
672
|
+
const serverCandidates = [
|
|
673
|
+
path.join(tempDir, "node_modules", "@getpaseo", "server"),
|
|
674
|
+
path.join(tempDir, "dist", "node_modules", "@getpaseo", "server"),
|
|
675
|
+
];
|
|
676
|
+
let serverDir = serverCandidates.find((c) => fs.existsSync(c));
|
|
677
|
+
if (!serverDir) {
|
|
678
|
+
// Search recursively within 3 levels
|
|
679
|
+
const searchDirs = [tempDir];
|
|
680
|
+
while (searchDirs.length > 0 && !serverDir) {
|
|
681
|
+
const current = searchDirs.shift();
|
|
682
|
+
try {
|
|
683
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
684
|
+
for (const ent of entries) {
|
|
685
|
+
if (ent.isDirectory()) {
|
|
686
|
+
const full = path.join(current, ent.name);
|
|
687
|
+
const normalizedFull = full.replace(/\\/g, "/");
|
|
688
|
+
if (ent.name === "server" && normalizedFull.includes("@getpaseo/server")) {
|
|
689
|
+
serverDir = full;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
if (full.split(path.sep).length - tempDir.split(path.sep).length < 4) {
|
|
693
|
+
searchDirs.push(full);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
catch { }
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (!serverDir) {
|
|
702
|
+
return { success: false, changes: [], error: `Could not locate @getpaseo/server inside ${asarPath}` };
|
|
703
|
+
}
|
|
704
|
+
const patchResult = patchPaseoServer(serverDir);
|
|
705
|
+
if (!patchResult.success) {
|
|
706
|
+
return { success: false, changes: [], error: patchResult.error };
|
|
707
|
+
}
|
|
708
|
+
if (patchResult.changes.length === 0) {
|
|
709
|
+
// Already patched!
|
|
710
|
+
return { success: true, changes: [] };
|
|
711
|
+
}
|
|
712
|
+
changes.push(...patchResult.changes);
|
|
713
|
+
// Create backup if not already present
|
|
714
|
+
const backupPath = `${asarPath}.bak`;
|
|
715
|
+
if (!fs.existsSync(backupPath)) {
|
|
716
|
+
try {
|
|
717
|
+
fs.copyFileSync(asarPath, backupPath);
|
|
718
|
+
changes.push(`Backed up original asar to ${backupPath}`);
|
|
719
|
+
}
|
|
720
|
+
catch (backupErr) {
|
|
721
|
+
logger.warn(`Could not create asar backup at ${backupPath}`, { error: String(backupErr) });
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
tempAsar = path.join(os.tmpdir(), `app-${Date.now()}.asar`);
|
|
725
|
+
await asar.createPackage(tempDir, tempAsar);
|
|
726
|
+
// Replace original archive with locked file handling for Windows
|
|
727
|
+
try {
|
|
728
|
+
fs.copyFileSync(tempAsar, asarPath);
|
|
729
|
+
changes.push(`Repacked updated asar archive at ${asarPath}`);
|
|
730
|
+
}
|
|
731
|
+
catch (copyErr) {
|
|
732
|
+
if (copyErr && (copyErr.code === "EBUSY" || copyErr.code === "EPERM" || copyErr.code === "EACCES")) {
|
|
733
|
+
const oldPath = `${asarPath}.old-${Date.now()}`;
|
|
734
|
+
try {
|
|
735
|
+
fs.renameSync(asarPath, oldPath);
|
|
736
|
+
fs.copyFileSync(tempAsar, asarPath);
|
|
737
|
+
changes.push(`Repacked updated asar archive at ${asarPath} (safe replaced locked file, moved previous to ${oldPath})`);
|
|
738
|
+
}
|
|
739
|
+
catch (renameErr) {
|
|
740
|
+
throw new Error(`Cannot update ${asarPath}: file is locked by a running Paseo process (${copyErr.code}). Please close Paseo completely (Paseo.exe in system tray / Task Manager) and retry.`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
throw copyErr;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return { success: true, changes };
|
|
748
|
+
}
|
|
749
|
+
catch (err) {
|
|
750
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
751
|
+
return { success: false, changes, error: msg };
|
|
752
|
+
}
|
|
753
|
+
finally {
|
|
754
|
+
if (tempDir) {
|
|
755
|
+
try {
|
|
756
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
757
|
+
}
|
|
758
|
+
catch { }
|
|
759
|
+
}
|
|
760
|
+
if (tempAsar) {
|
|
761
|
+
try {
|
|
762
|
+
fs.unlinkSync(tempAsar);
|
|
763
|
+
}
|
|
764
|
+
catch { }
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Discovers and patches all accessible Paseo installations (both directory and app.asar).
|
|
479
770
|
*/
|
|
480
|
-
export function ensurePaseoIntegration(options) {
|
|
771
|
+
export async function ensurePaseoIntegration(options) {
|
|
481
772
|
const serverPaths = options?.targetPaths || findPaseoServerInstallations();
|
|
773
|
+
const asarPaths = options?.targetAsarPaths || findPaseoAsarPaths();
|
|
482
774
|
const patchedPaths = [];
|
|
775
|
+
const patchedAsarPaths = [];
|
|
483
776
|
const errors = [];
|
|
484
777
|
for (const sPath of serverPaths) {
|
|
485
778
|
const res = patchPaseoServer(sPath);
|
|
@@ -495,10 +788,124 @@ export function ensurePaseoIntegration(options) {
|
|
|
495
788
|
errors.push(`${sPath}: ${res.error}`);
|
|
496
789
|
}
|
|
497
790
|
}
|
|
791
|
+
for (const aPath of asarPaths) {
|
|
792
|
+
const res = await patchPaseoAsar(aPath);
|
|
793
|
+
if (res.success) {
|
|
794
|
+
if (res.changes.length > 0) {
|
|
795
|
+
patchedAsarPaths.push(aPath);
|
|
796
|
+
if (options?.verbose) {
|
|
797
|
+
logger.info(`Integrated with Paseo desktop asar at ${aPath}`, { changes: res.changes });
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else if (res.error) {
|
|
802
|
+
errors.push(`${aPath}: ${res.error}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
498
805
|
return {
|
|
499
|
-
found: serverPaths.length > 0,
|
|
806
|
+
found: serverPaths.length > 0 || asarPaths.length > 0,
|
|
500
807
|
serverPaths,
|
|
501
808
|
patchedPaths,
|
|
809
|
+
asarPaths,
|
|
810
|
+
patchedAsarPaths,
|
|
502
811
|
errors,
|
|
503
812
|
};
|
|
504
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Checks whether a @getpaseo/server installation directory is already patched
|
|
816
|
+
* for Antigravity quota and telemetry support.
|
|
817
|
+
*/
|
|
818
|
+
export function isPaseoServerPatched(serverDir) {
|
|
819
|
+
try {
|
|
820
|
+
if (!fs.existsSync(serverDir))
|
|
821
|
+
return false;
|
|
822
|
+
const antigravityJsCandidates = [
|
|
823
|
+
path.join(serverDir, "dist", "server", "services", "quota-fetcher", "providers", "antigravity.js"),
|
|
824
|
+
path.join(serverDir, "dist", "services", "quota-fetcher", "providers", "antigravity.js"),
|
|
825
|
+
];
|
|
826
|
+
let hasProvider = antigravityJsCandidates.some((p) => fs.existsSync(p));
|
|
827
|
+
if (!hasProvider && fs.existsSync(path.join(serverDir, "dist"))) {
|
|
828
|
+
const checkRecursive = (dir, depth = 0) => {
|
|
829
|
+
if (depth > 5)
|
|
830
|
+
return false;
|
|
831
|
+
try {
|
|
832
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
833
|
+
for (const e of entries) {
|
|
834
|
+
if (e.isDirectory() && e.name !== "node_modules") {
|
|
835
|
+
if (checkRecursive(path.join(dir, e.name), depth + 1))
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
else if (e.isFile() && e.name === "antigravity.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch { }
|
|
844
|
+
return false;
|
|
845
|
+
};
|
|
846
|
+
hasProvider = checkRecursive(path.join(serverDir, "dist"));
|
|
847
|
+
}
|
|
848
|
+
if (hasProvider)
|
|
849
|
+
return true;
|
|
850
|
+
const manifestCandidates = [
|
|
851
|
+
path.join(serverDir, "dist", "server", "services", "quota-fetcher", "manifest.js"),
|
|
852
|
+
path.join(serverDir, "dist", "services", "quota-fetcher", "manifest.js"),
|
|
853
|
+
];
|
|
854
|
+
for (const cand of manifestCandidates) {
|
|
855
|
+
if (fs.existsSync(cand)) {
|
|
856
|
+
const content = fs.readFileSync(cand, "utf-8");
|
|
857
|
+
if (content.includes('providerId: "antigravity"'))
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
catch {
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Checks whether a Paseo app.asar archive is already patched with Antigravity telemetry.
|
|
869
|
+
*/
|
|
870
|
+
export async function isPaseoAsarPatched(asarPath) {
|
|
871
|
+
try {
|
|
872
|
+
if (!fs.existsSync(asarPath))
|
|
873
|
+
return false;
|
|
874
|
+
const asarModule = await import("@electron/asar");
|
|
875
|
+
const asar = asarModule.default || asarModule;
|
|
876
|
+
const files = asar.listPackage(asarPath);
|
|
877
|
+
return files.some((f) => f.includes("antigravity.js"));
|
|
878
|
+
}
|
|
879
|
+
catch {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Detects if Paseo Desktop is currently running on the host.
|
|
885
|
+
*/
|
|
886
|
+
export function isPaseoRunning() {
|
|
887
|
+
try {
|
|
888
|
+
if (process.platform === "win32") {
|
|
889
|
+
const out = execFileSync("tasklist.exe", ["/FI", "IMAGENAME eq Paseo*", "/NH"], {
|
|
890
|
+
encoding: "utf-8",
|
|
891
|
+
timeout: 3000,
|
|
892
|
+
windowsHide: true,
|
|
893
|
+
});
|
|
894
|
+
return /paseo/i.test(out) && !out.includes("INFO:") && !out.includes("No tasks");
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
const out = execFileSync("pgrep", ["-i", "-x", "paseo"], {
|
|
898
|
+
encoding: "utf-8",
|
|
899
|
+
timeout: 2000,
|
|
900
|
+
});
|
|
901
|
+
const pids = out
|
|
902
|
+
.split(/\r?\n/)
|
|
903
|
+
.map((s) => parseInt(s.trim(), 10))
|
|
904
|
+
.filter((p) => !isNaN(p) && p !== process.pid);
|
|
905
|
+
return pids.length > 0;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
catch {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -173,7 +173,12 @@ export interface ProviderUsage {
|
|
|
173
173
|
}>;
|
|
174
174
|
error: string | null;
|
|
175
175
|
}
|
|
176
|
-
export declare function formatExecBinaryPath(binaryPath: string): string;
|
|
176
|
+
export declare function formatExecBinaryPath(binaryPath: string, shell?: boolean): string;
|
|
177
|
+
export interface ResolvedCommandExecution {
|
|
178
|
+
cmd: string;
|
|
179
|
+
shell: boolean;
|
|
180
|
+
}
|
|
181
|
+
export declare function resolveCommandExecution(binaryPath: string): ResolvedCommandExecution;
|
|
177
182
|
export declare function fetchAntigravityUsage(binaryPath?: string, force?: boolean): Promise<ProviderUsage>;
|
|
178
183
|
export declare const ALL_THINKING_LEVELS: Record<string, {
|
|
179
184
|
name: string;
|
package/dist/protocol.js
CHANGED
|
@@ -3,6 +3,7 @@ import { promisify } from "node:util";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { logger } from "./logger.js";
|
|
5
5
|
import { saveBase64Image } from "./attachments.js";
|
|
6
|
+
import { isWindowsBatchScript } from "./antigravity-process.js";
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
// ACP wire methods use snake_case. The aliases retain the public method names
|
|
8
9
|
// accepted by the adapter before the hardening work so existing clients do not
|
|
@@ -278,12 +279,19 @@ function cacheProviderUsage(binaryPath, result, now) {
|
|
|
278
279
|
lastProviderUsageFetch = now;
|
|
279
280
|
return result;
|
|
280
281
|
}
|
|
281
|
-
export function formatExecBinaryPath(binaryPath) {
|
|
282
|
-
|
|
282
|
+
export function formatExecBinaryPath(binaryPath, shell) {
|
|
283
|
+
const isWin = process.platform === "win32";
|
|
284
|
+
const needsShell = shell ?? (isWin && isWindowsBatchScript(binaryPath));
|
|
285
|
+
if (needsShell && isWin && binaryPath.includes(" ") && !binaryPath.startsWith('"')) {
|
|
283
286
|
return `"${binaryPath}"`;
|
|
284
287
|
}
|
|
285
288
|
return binaryPath;
|
|
286
289
|
}
|
|
290
|
+
export function resolveCommandExecution(binaryPath) {
|
|
291
|
+
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
292
|
+
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
293
|
+
return { cmd, shell: isBatch };
|
|
294
|
+
}
|
|
287
295
|
export async function fetchAntigravityUsage(binaryPath = "agy", force = false) {
|
|
288
296
|
const now = Date.now();
|
|
289
297
|
if (!force &&
|
|
@@ -292,21 +300,22 @@ export async function fetchAntigravityUsage(binaryPath = "agy", force = false) {
|
|
|
292
300
|
now - lastProviderUsageFetch < PROVIDER_USAGE_CACHE_TTL_MS) {
|
|
293
301
|
return cachedProviderUsage;
|
|
294
302
|
}
|
|
295
|
-
const
|
|
303
|
+
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
304
|
+
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
296
305
|
try {
|
|
297
306
|
const [usageResult, creditsResult] = await Promise.allSettled([
|
|
298
307
|
execFileAsync(cmd, ["--print", "/usage"], {
|
|
299
308
|
timeout: 8_000,
|
|
300
309
|
maxBuffer: 1024 * 1024,
|
|
301
310
|
env: process.env,
|
|
302
|
-
shell:
|
|
311
|
+
shell: isBatch,
|
|
303
312
|
windowsHide: true,
|
|
304
313
|
}),
|
|
305
314
|
execFileAsync(cmd, ["--print", "/credits"], {
|
|
306
315
|
timeout: 8_000,
|
|
307
316
|
maxBuffer: 1024 * 1024,
|
|
308
317
|
env: process.env,
|
|
309
|
-
shell:
|
|
318
|
+
shell: isBatch,
|
|
310
319
|
windowsHide: true,
|
|
311
320
|
}),
|
|
312
321
|
]);
|
|
@@ -429,13 +438,14 @@ export async function fetchAvailableModels(binaryPath = "agy", force = false) {
|
|
|
429
438
|
return inFlightModelFetch;
|
|
430
439
|
}
|
|
431
440
|
inFlightModelFetch = (async () => {
|
|
432
|
-
const
|
|
441
|
+
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
442
|
+
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
433
443
|
try {
|
|
434
444
|
const { stdout } = await execFileAsync(cmd, ["models"], {
|
|
435
445
|
timeout: 10_000,
|
|
436
446
|
env: process.env,
|
|
437
447
|
maxBuffer: 4 * 1024 * 1024,
|
|
438
|
-
shell:
|
|
448
|
+
shell: isBatch,
|
|
439
449
|
windowsHide: true,
|
|
440
450
|
});
|
|
441
451
|
const parsed = parseAgyModelsOutput(stdout);
|
package/dist/slash-commands.js
CHANGED
|
@@ -5,7 +5,7 @@ import { execFile } from "node:child_process";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
7
|
import { sessionStore } from "./session-store.js";
|
|
8
|
-
import {
|
|
8
|
+
import { isWindowsBatchScript } from "./antigravity-process.js";
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
const AGY_COMMAND_TIMEOUT_MS = 30_000;
|
|
11
11
|
const AGY_COMMAND_MAX_BUFFER = 4 * 1024 * 1024;
|
|
@@ -337,13 +337,14 @@ export function formatUsageOutput(rawText) {
|
|
|
337
337
|
return out;
|
|
338
338
|
}
|
|
339
339
|
async function runAgySlash(binaryPath, cwd, slashCommand) {
|
|
340
|
-
const
|
|
341
|
-
const
|
|
340
|
+
const isBatch = process.platform === "win32" && isWindowsBatchScript(binaryPath);
|
|
341
|
+
const cmd = isBatch && binaryPath.includes(" ") && !binaryPath.startsWith('"') ? `"${binaryPath}"` : binaryPath;
|
|
342
|
+
const { stdout, stderr } = await execFileAsync(cmd, ["--print-timeout", "24h", "--print", slashCommand], {
|
|
342
343
|
cwd,
|
|
343
344
|
env: process.env,
|
|
344
345
|
timeout: AGY_COMMAND_TIMEOUT_MS,
|
|
345
346
|
maxBuffer: AGY_COMMAND_MAX_BUFFER,
|
|
346
|
-
shell:
|
|
347
|
+
shell: isBatch,
|
|
347
348
|
windowsHide: true,
|
|
348
349
|
});
|
|
349
350
|
return stdout.trim() || stderr.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paseo-acp-agy",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "ACP (Agent Client Protocol) provider for Google Antigravity in Paseo and Zed",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://github.com/tucomel/paseo-acp-agy#readme",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@agentclientprotocol/sdk": "^0.17.1"
|
|
48
|
+
"@agentclientprotocol/sdk": "^0.17.1",
|
|
49
|
+
"@electron/asar": "^4.3.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^22.0.0",
|