paseo-acp-agy 1.2.16 → 1.3.0
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 +14 -9
- package/dist/acp-server.d.ts +9 -0
- package/dist/acp-server.js +108 -24
- package/dist/antigravity-process.js +14 -3
- package/dist/asar.d.ts +5 -0
- package/dist/asar.js +34 -0
- package/dist/index.js +36 -9
- package/dist/paseo-patcher.d.ts +11 -1
- package/dist/paseo-patcher.js +166 -16
- package/dist/session.d.ts +1 -0
- package/dist/session.js +13 -1
- package/dist/transcript.d.ts +5 -0
- package/dist/transcript.js +30 -0
- package/dist/version.js +8 -1
- package/package.json +2 -1
- package/paseo-plugin/client/data.ts +111 -0
- package/paseo-plugin/client/format.ts +61 -0
- package/paseo-plugin/client/pill.tsx +208 -0
- package/paseo-plugin/client/ui-helpers.ts +8 -0
- package/paseo-plugin/client/ui.tsx +379 -0
- package/paseo-plugin/icon.svg +5 -0
- package/paseo-plugin/index.client.tsx +44 -0
- package/paseo-plugin/index.server.ts +47 -0
- package/paseo-plugin/paseo-plugin.json +7 -0
- package/paseo-plugin/server/agy-usage.ts +621 -0
- package/paseo-plugin/shared/telemetry.ts +77 -0
- package/paseo-plugin/tsconfig.json +20 -0
package/README.md
CHANGED
|
@@ -92,24 +92,29 @@ To include `paseo-acp-agy` in Paseo's built-in provider store (`ACP_PROVIDER_CAT
|
|
|
92
92
|
|
|
93
93
|
## Plan Usage & Context Window Meter Setup
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
### Recommended (Paseo >= 0.11.0): Official Update-Proof Paseo Plugin
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
npx -y paseo-acp-agy setup
|
|
99
|
-
```
|
|
97
|
+
Install the bundled **`antigravity-telemetry`** Paseo plugin once. It registers in `~/.paseo/config.json` and survives all future `Paseo.app`, Web, iPhone, and CLI updates without modifying signed `.app` bundles:
|
|
100
98
|
|
|
101
|
-
Or if installed globally:
|
|
102
99
|
```bash
|
|
103
|
-
paseo-acp-agy
|
|
100
|
+
paseo plugin install "$(paseo-acp-agy --plugin-path)"
|
|
104
101
|
```
|
|
105
102
|
|
|
106
|
-
|
|
103
|
+
What this provides across Desktop, Web, and Mobile (iPhone):
|
|
104
|
+
- Native **Google Antigravity** quota gauges in Paseo's Sidebar and `Settings -> Usage` (`5h` window, weekly pool, credits)
|
|
105
|
+
- Live per-agent **Composer Pill** (`$cost · ctx X% · 5h Y%`) with interactive Popover / Mobile Bottom Sheet
|
|
106
|
+
- Dedicated **Antigravity Telemetry** workspace panel, Command Center action, and `/agy-telemetry` slash command
|
|
107
|
+
- Combined with native ACP `usage_update` notifications so Paseo's built-in `ContextWindowMeter` ("bolinha") stays deterministic on every session open, resume, model switch, and turn completion
|
|
108
|
+
|
|
109
|
+
### Standalone Server Patch (Optional, Linux / Paseo < 0.11.0)
|
|
110
|
+
|
|
111
|
+
For standalone `@getpaseo/cli` servers where you prefer direct server-level patching:
|
|
112
|
+
|
|
107
113
|
```bash
|
|
114
|
+
paseo-acp-agy setup
|
|
108
115
|
paseo daemon restart
|
|
109
116
|
```
|
|
110
117
|
|
|
111
|
-
> **Note**: `paseo-acp-agy` also checks and auto-configures your local Paseo server automatically whenever it launches via `--acp`. Running `setup` manually ensures your existing daemon instance picks up the telemetry hooks immediately upon restart.
|
|
112
|
-
|
|
113
118
|
---
|
|
114
119
|
|
|
115
120
|
## Requirements
|
package/dist/acp-server.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class ACPServer extends EventEmitter {
|
|
|
11
11
|
private rl;
|
|
12
12
|
private isRunning;
|
|
13
13
|
private pendingRequests;
|
|
14
|
+
private usagePublishTimers;
|
|
14
15
|
private nextRequestId;
|
|
15
16
|
constructor(options?: {
|
|
16
17
|
input?: Readable;
|
|
@@ -27,6 +28,14 @@ export declare class ACPServer extends EventEmitter {
|
|
|
27
28
|
sendRequest<T = any>(method: string, params: unknown): Promise<T>;
|
|
28
29
|
private publishCommands;
|
|
29
30
|
private publishUsageUpdate;
|
|
31
|
+
private clearUsagePublishTimers;
|
|
32
|
+
/**
|
|
33
|
+
* Schedules coalesced, unref'd usage_update notifications so ACP hosts (like Paseo)
|
|
34
|
+
* deterministically receive context window & session telemetry:
|
|
35
|
+
* - after awaiting newSession/loadSession + applyConfiguredOverrides() + session.subscribe()
|
|
36
|
+
* - after turn_completed overwrites or initializes currentTurnUsage in Paseo 0.10..0.13+
|
|
37
|
+
*/
|
|
38
|
+
private scheduleUsagePublishBurst;
|
|
30
39
|
private sessionState;
|
|
31
40
|
private requireSession;
|
|
32
41
|
private validateModel;
|
package/dist/acp-server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { SessionManager } from "./session.js";
|
|
|
6
6
|
import { executeSlashCommand, AVAILABLE_SLASH_COMMANDS } from "./slash-commands.js";
|
|
7
7
|
import { getShortVersion } from "./version.js";
|
|
8
8
|
import { resolveDefaultAgyBinary } from "./antigravity-process.js";
|
|
9
|
-
import { loadConversationTranscript } from "./transcript.js";
|
|
9
|
+
import { loadConversationTranscript, estimateReplayContextTokens } from "./transcript.js";
|
|
10
10
|
import { normalizeToolCall, normalizeToolResult, extractPlanEntries } from "./tool-formatter.js";
|
|
11
11
|
import { McpBridge } from "./mcp-bridge.js";
|
|
12
12
|
function splitModelAndEffort(modelInput) {
|
|
@@ -30,6 +30,7 @@ export class ACPServer extends EventEmitter {
|
|
|
30
30
|
rl = null;
|
|
31
31
|
isRunning = false;
|
|
32
32
|
pendingRequests = new Map();
|
|
33
|
+
usagePublishTimers = new Map();
|
|
33
34
|
nextRequestId = 1;
|
|
34
35
|
constructor(options = {}) {
|
|
35
36
|
super();
|
|
@@ -138,6 +139,53 @@ export class ACPServer extends EventEmitter {
|
|
|
138
139
|
},
|
|
139
140
|
});
|
|
140
141
|
}
|
|
142
|
+
clearUsagePublishTimers(sessionId) {
|
|
143
|
+
if (sessionId !== undefined) {
|
|
144
|
+
const timers = this.usagePublishTimers.get(sessionId);
|
|
145
|
+
if (timers) {
|
|
146
|
+
for (const t of timers)
|
|
147
|
+
clearTimeout(t);
|
|
148
|
+
this.usagePublishTimers.delete(sessionId);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const timers of this.usagePublishTimers.values()) {
|
|
153
|
+
for (const t of timers)
|
|
154
|
+
clearTimeout(t);
|
|
155
|
+
}
|
|
156
|
+
this.usagePublishTimers.clear();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Schedules coalesced, unref'd usage_update notifications so ACP hosts (like Paseo)
|
|
160
|
+
* deterministically receive context window & session telemetry:
|
|
161
|
+
* - after awaiting newSession/loadSession + applyConfiguredOverrides() + session.subscribe()
|
|
162
|
+
* - after turn_completed overwrites or initializes currentTurnUsage in Paseo 0.10..0.13+
|
|
163
|
+
*/
|
|
164
|
+
scheduleUsagePublishBurst(sessionId, delaysMs) {
|
|
165
|
+
this.clearUsagePublishTimers(sessionId);
|
|
166
|
+
if (!this.isRunning || delaysMs.length === 0)
|
|
167
|
+
return;
|
|
168
|
+
const timers = new Set();
|
|
169
|
+
this.usagePublishTimers.set(sessionId, timers);
|
|
170
|
+
for (const delayMs of delaysMs) {
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
timers.delete(timer);
|
|
173
|
+
if (timers.size === 0) {
|
|
174
|
+
this.usagePublishTimers.delete(sessionId);
|
|
175
|
+
}
|
|
176
|
+
if (!this.isRunning)
|
|
177
|
+
return;
|
|
178
|
+
const current = this.sessionManager.getSession(sessionId);
|
|
179
|
+
if (current) {
|
|
180
|
+
this.publishUsageUpdate(current);
|
|
181
|
+
}
|
|
182
|
+
}, delayMs);
|
|
183
|
+
if (typeof timer.unref === "function") {
|
|
184
|
+
timer.unref();
|
|
185
|
+
}
|
|
186
|
+
timers.add(timer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
141
189
|
async sessionState(session, forceModels = false) {
|
|
142
190
|
const availableModels = await fetchAvailableModels(this.binaryPath, forceModels);
|
|
143
191
|
return {
|
|
@@ -265,17 +313,9 @@ export class ACPServer extends EventEmitter {
|
|
|
265
313
|
this.sendSuccess(id, await this.sessionState(session, true));
|
|
266
314
|
this.publishCommands(session.id);
|
|
267
315
|
this.publishUsageUpdate(session);
|
|
268
|
-
// Re-publish usage
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
this.publishUsageUpdate(session);
|
|
272
|
-
}
|
|
273
|
-
}, 150);
|
|
274
|
-
setTimeout(() => {
|
|
275
|
-
if (this.sessionManager.getSession(session.id)) {
|
|
276
|
-
this.publishUsageUpdate(session);
|
|
277
|
-
}
|
|
278
|
-
}, 500);
|
|
316
|
+
// Re-publish usage across startup window so ACPAgent receives it after
|
|
317
|
+
// awaiting newSession + applyConfiguredOverrides() + session.subscribe()
|
|
318
|
+
this.scheduleUsagePublishBurst(session.id, [120, 450, 1200, 2800]);
|
|
279
319
|
}
|
|
280
320
|
break;
|
|
281
321
|
}
|
|
@@ -311,6 +351,9 @@ export class ACPServer extends EventEmitter {
|
|
|
311
351
|
session.setTitle(firstUser.text.slice(0, 80).replace(/\r?\n/g, " ").trim());
|
|
312
352
|
}
|
|
313
353
|
}
|
|
354
|
+
if (replayItems.length > 0) {
|
|
355
|
+
session.seedEstimatedContextUsage(estimateReplayContextTokens(replayItems));
|
|
356
|
+
}
|
|
314
357
|
for (const item of replayItems) {
|
|
315
358
|
switch (item.type) {
|
|
316
359
|
case "user_message":
|
|
@@ -378,12 +421,7 @@ export class ACPServer extends EventEmitter {
|
|
|
378
421
|
this.sendSuccess(id, await this.sessionState(session));
|
|
379
422
|
this.publishCommands(session.id);
|
|
380
423
|
this.publishUsageUpdate(session);
|
|
381
|
-
|
|
382
|
-
setTimeout(() => {
|
|
383
|
-
if (this.sessionManager.getSession(activeSession.id)) {
|
|
384
|
-
this.publishUsageUpdate(activeSession);
|
|
385
|
-
}
|
|
386
|
-
}, 150);
|
|
424
|
+
this.scheduleUsagePublishBurst(session.id, [120, 450, 1200, 2800]);
|
|
387
425
|
}
|
|
388
426
|
}
|
|
389
427
|
catch (err) {
|
|
@@ -414,10 +452,23 @@ export class ACPServer extends EventEmitter {
|
|
|
414
452
|
created = true;
|
|
415
453
|
}
|
|
416
454
|
await session.ensureReadyForResume();
|
|
455
|
+
const resumeConversationId = session.process.currentConversationId;
|
|
456
|
+
if (resumeConversationId &&
|
|
457
|
+
session.usage.contextWindowUsedTokens === 0 &&
|
|
458
|
+
session.usage.totalTokens === 0) {
|
|
459
|
+
try {
|
|
460
|
+
const replayItems = await loadConversationTranscript(resumeConversationId);
|
|
461
|
+
if (replayItems.length > 0) {
|
|
462
|
+
session.seedEstimatedContextUsage(estimateReplayContextTokens(replayItems));
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch { }
|
|
466
|
+
}
|
|
417
467
|
if (!isNotification) {
|
|
418
468
|
this.sendSuccess(id, await this.sessionState(session));
|
|
419
469
|
this.publishCommands(session.id);
|
|
420
470
|
this.publishUsageUpdate(session);
|
|
471
|
+
this.scheduleUsagePublishBurst(session.id, [120, 450, 1200, 2800]);
|
|
421
472
|
}
|
|
422
473
|
}
|
|
423
474
|
catch (err) {
|
|
@@ -458,6 +509,17 @@ export class ACPServer extends EventEmitter {
|
|
|
458
509
|
mode: sourceSession?.mode,
|
|
459
510
|
permission: sourceSession?.permission,
|
|
460
511
|
conversationId: sourceSession?.process.currentConversationId,
|
|
512
|
+
usage: sourceSession
|
|
513
|
+
? {
|
|
514
|
+
inputTokens: 0,
|
|
515
|
+
outputTokens: 0,
|
|
516
|
+
cachedInputTokens: 0,
|
|
517
|
+
totalTokens: 0,
|
|
518
|
+
totalCostUsd: 0,
|
|
519
|
+
contextWindowUsedTokens: sourceSession.usage.contextWindowUsedTokens,
|
|
520
|
+
contextWindowMaxTokens: sourceSession.usage.contextWindowMaxTokens,
|
|
521
|
+
}
|
|
522
|
+
: undefined,
|
|
461
523
|
binaryPath: this.binaryPath,
|
|
462
524
|
});
|
|
463
525
|
if (sourceSession?.title) {
|
|
@@ -467,6 +529,7 @@ export class ACPServer extends EventEmitter {
|
|
|
467
529
|
this.sendSuccess(id, await this.sessionState(newSession));
|
|
468
530
|
this.publishCommands(newSession.id);
|
|
469
531
|
this.publishUsageUpdate(newSession);
|
|
532
|
+
this.scheduleUsagePublishBurst(newSession.id, [120, 450, 1200]);
|
|
470
533
|
}
|
|
471
534
|
break;
|
|
472
535
|
}
|
|
@@ -517,8 +580,11 @@ export class ACPServer extends EventEmitter {
|
|
|
517
580
|
},
|
|
518
581
|
});
|
|
519
582
|
}
|
|
520
|
-
|
|
583
|
+
this.publishUsageUpdate(session);
|
|
584
|
+
if (!isNotification) {
|
|
521
585
|
this.sendSuccess(id, { stopReason: "end_turn" });
|
|
586
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
587
|
+
}
|
|
522
588
|
break;
|
|
523
589
|
}
|
|
524
590
|
let hasSentNonNarration = false;
|
|
@@ -754,6 +820,7 @@ export class ACPServer extends EventEmitter {
|
|
|
754
820
|
if (session.isCancelled) {
|
|
755
821
|
if (!isNotification) {
|
|
756
822
|
this.sendSuccess(id, { stopReason: "cancelled", usage: usagePayload });
|
|
823
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
757
824
|
}
|
|
758
825
|
}
|
|
759
826
|
else if (resultEvent.result?.status === "ERROR") {
|
|
@@ -768,10 +835,12 @@ export class ACPServer extends EventEmitter {
|
|
|
768
835
|
});
|
|
769
836
|
if (!isNotification) {
|
|
770
837
|
this.sendSuccess(id, { stopReason: "refusal", usage: usagePayload });
|
|
838
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
771
839
|
}
|
|
772
840
|
}
|
|
773
841
|
else if (!isNotification) {
|
|
774
842
|
this.sendSuccess(id, { stopReason: "end_turn", usage: usagePayload });
|
|
843
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
775
844
|
}
|
|
776
845
|
}
|
|
777
846
|
catch (promptErr) {
|
|
@@ -779,8 +848,10 @@ export class ACPServer extends EventEmitter {
|
|
|
779
848
|
if (session.isCancelled ||
|
|
780
849
|
errorMsg.includes("cancelled") ||
|
|
781
850
|
errorMsg.includes("Turn was cancelled")) {
|
|
782
|
-
if (!isNotification)
|
|
851
|
+
if (!isNotification) {
|
|
783
852
|
this.sendSuccess(id, { stopReason: "cancelled" });
|
|
853
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
854
|
+
}
|
|
784
855
|
}
|
|
785
856
|
else {
|
|
786
857
|
logger.error("Error executing prompt turn", { error: errorMsg, sessionId });
|
|
@@ -791,8 +862,10 @@ export class ACPServer extends EventEmitter {
|
|
|
791
862
|
content: { type: "text", text: `\n\n**Error:** ${errorMsg}\n` },
|
|
792
863
|
},
|
|
793
864
|
});
|
|
794
|
-
if (!isNotification)
|
|
865
|
+
if (!isNotification) {
|
|
795
866
|
this.sendSuccess(id, { stopReason: "refusal" });
|
|
867
|
+
this.scheduleUsagePublishBurst(session.id, [25, 150]);
|
|
868
|
+
}
|
|
796
869
|
}
|
|
797
870
|
}
|
|
798
871
|
}
|
|
@@ -816,6 +889,7 @@ export class ACPServer extends EventEmitter {
|
|
|
816
889
|
case ACP_METHODS.SESSION_CLOSE:
|
|
817
890
|
case ACP_METHODS.SESSION_CLOSE_ALIAS: {
|
|
818
891
|
const sessionId = String(params.sessionId || "");
|
|
892
|
+
this.clearUsagePublishTimers(sessionId);
|
|
819
893
|
await this.sessionManager.closeSession(sessionId);
|
|
820
894
|
if (!isNotification)
|
|
821
895
|
this.sendSuccess(id, {});
|
|
@@ -838,8 +912,11 @@ export class ACPServer extends EventEmitter {
|
|
|
838
912
|
break;
|
|
839
913
|
}
|
|
840
914
|
session.setMode(modeId);
|
|
841
|
-
if (!isNotification)
|
|
915
|
+
if (!isNotification) {
|
|
842
916
|
this.sendSuccess(id, {});
|
|
917
|
+
this.publishUsageUpdate(session);
|
|
918
|
+
this.scheduleUsagePublishBurst(session.id, [80, 350, 1000]);
|
|
919
|
+
}
|
|
843
920
|
break;
|
|
844
921
|
}
|
|
845
922
|
case ACP_METHODS.SESSION_SET_MODEL:
|
|
@@ -881,8 +958,11 @@ export class ACPServer extends EventEmitter {
|
|
|
881
958
|
currentPermission: session.permission,
|
|
882
959
|
availableModels: models,
|
|
883
960
|
});
|
|
884
|
-
if (!isNotification)
|
|
961
|
+
if (!isNotification) {
|
|
885
962
|
this.sendSuccess(id, { configOptions });
|
|
963
|
+
this.publishUsageUpdate(session);
|
|
964
|
+
this.scheduleUsagePublishBurst(session.id, [80, 350, 1000]);
|
|
965
|
+
}
|
|
886
966
|
break;
|
|
887
967
|
}
|
|
888
968
|
case ACP_METHODS.SESSION_SET_CONFIG_OPTION:
|
|
@@ -949,8 +1029,11 @@ export class ACPServer extends EventEmitter {
|
|
|
949
1029
|
currentPermission: session.permission,
|
|
950
1030
|
availableModels: models,
|
|
951
1031
|
});
|
|
952
|
-
if (!isNotification)
|
|
1032
|
+
if (!isNotification) {
|
|
953
1033
|
this.sendSuccess(id, { configOptions });
|
|
1034
|
+
this.publishUsageUpdate(session);
|
|
1035
|
+
this.scheduleUsagePublishBurst(session.id, [80, 350, 1000]);
|
|
1036
|
+
}
|
|
954
1037
|
break;
|
|
955
1038
|
}
|
|
956
1039
|
case "provider/usage":
|
|
@@ -977,6 +1060,7 @@ export class ACPServer extends EventEmitter {
|
|
|
977
1060
|
if (!this.isRunning)
|
|
978
1061
|
return;
|
|
979
1062
|
this.isRunning = false;
|
|
1063
|
+
this.clearUsagePublishTimers();
|
|
980
1064
|
if (this.rl) {
|
|
981
1065
|
this.rl.close();
|
|
982
1066
|
this.rl = null;
|
|
@@ -135,9 +135,20 @@ export function resolveDefaultAgyBinary(force = false) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
else {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const posixCandidates = [
|
|
139
|
+
path.join(home, ".local", "bin", "agy"),
|
|
140
|
+
"/opt/homebrew/bin/agy",
|
|
141
|
+
"/usr/local/bin/agy",
|
|
142
|
+
"/usr/bin/agy",
|
|
143
|
+
path.join(home, ".antigravity", "bin", "agy"),
|
|
144
|
+
path.join(home, ".gemini", "antigravity-cli", "bin", "agy"),
|
|
145
|
+
];
|
|
146
|
+
for (const cand of posixCandidates) {
|
|
147
|
+
if (fs.existsSync(cand)) {
|
|
148
|
+
resolved = cand;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
141
152
|
}
|
|
142
153
|
}
|
|
143
154
|
}
|
package/dist/asar.d.ts
CHANGED
|
@@ -21,6 +21,11 @@ export declare function readArchiveHeader(archivePath: string): {
|
|
|
21
21
|
* Lists all file paths inside an ASAR archive.
|
|
22
22
|
*/
|
|
23
23
|
export declare function listPackage(archivePath: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Reads a single UTF-8 text file directly from an ASAR archive without extracting
|
|
26
|
+
* the entire archive to disk. Returns null if the archive or entry cannot be read.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readFileFromPackage(archivePath: string, relPath: string): string | null;
|
|
24
29
|
/**
|
|
25
30
|
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
26
31
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
package/dist/asar.js
CHANGED
|
@@ -54,6 +54,40 @@ export function listPackage(archivePath) {
|
|
|
54
54
|
walk(header, "");
|
|
55
55
|
return result;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads a single UTF-8 text file directly from an ASAR archive without extracting
|
|
59
|
+
* the entire archive to disk. Returns null if the archive or entry cannot be read.
|
|
60
|
+
*/
|
|
61
|
+
export function readFileFromPackage(archivePath, relPath) {
|
|
62
|
+
try {
|
|
63
|
+
const { header, dataBaseOffset } = readArchiveHeader(archivePath);
|
|
64
|
+
const parts = relPath.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
65
|
+
let node = header;
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
node = node?.files?.[part];
|
|
68
|
+
if (!node)
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (node.unpacked || node.size === undefined || node.offset === undefined) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (node.size === 0)
|
|
75
|
+
return "";
|
|
76
|
+
const fileOffset = dataBaseOffset + parseInt(node.offset, 10);
|
|
77
|
+
const fd = fs.openSync(archivePath, "r");
|
|
78
|
+
try {
|
|
79
|
+
const buf = Buffer.alloc(node.size);
|
|
80
|
+
const bytesRead = fs.readSync(fd, buf, 0, node.size, fileOffset);
|
|
81
|
+
return buf.subarray(0, bytesRead).toString("utf8");
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
fs.closeSync(fd);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
57
91
|
/**
|
|
58
92
|
* Pure TypeScript, zero-dependency ASAR archive extractor.
|
|
59
93
|
* Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import { execFileSync } from "node:child_process";
|
|
4
6
|
import { ACPServer } from "./acp-server.js";
|
|
5
7
|
import { logger } from "./logger.js";
|
|
@@ -8,6 +10,11 @@ import { ensurePaseoIntegration, restorePaseoIntegration, findPaseoServerInstall
|
|
|
8
10
|
import { resolveDefaultAgyBinary, isWindowsBatchScript } from "./antigravity-process.js";
|
|
9
11
|
import { parseAgyQuotaOutput } from "./protocol.js";
|
|
10
12
|
const args = process.argv.slice(2);
|
|
13
|
+
const bundledPluginDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "paseo-plugin");
|
|
14
|
+
if (args.includes("plugin-path") || args.includes("--plugin-path")) {
|
|
15
|
+
process.stdout.write(`${bundledPluginDir}\n`);
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
11
18
|
if (args.includes("--version") || args.includes("-v")) {
|
|
12
19
|
if (args.includes("--json")) {
|
|
13
20
|
process.stdout.write(JSON.stringify(resolveBuildMetadata(), null, 2) + "\n");
|
|
@@ -31,12 +38,14 @@ Commands:
|
|
|
31
38
|
setup, patch Configure and integrate Antigravity telemetry with Paseo
|
|
32
39
|
doctor Diagnose Antigravity binary, quota provider, and Paseo status
|
|
33
40
|
restore Restore original Paseo files from backup (.bak)
|
|
41
|
+
plugin-path Print path to the update-proof Paseo telemetry plugin directory
|
|
34
42
|
|
|
35
43
|
Options:
|
|
36
44
|
--acp Start ACP server over stdio (default)
|
|
37
45
|
--setup Integrate Antigravity with local Paseo server installation
|
|
38
46
|
--doctor Run environment, binary, and telemetry diagnostics
|
|
39
47
|
--restore Restore original Paseo files from backup
|
|
48
|
+
--plugin-path Print bundled Paseo plugin directory for 'paseo plugin install'
|
|
40
49
|
--force-macos-app Force patching inside macOS .app bundle (re-signs with ad-hoc)
|
|
41
50
|
--skip-naration Drop leading narration-only assistant chunks (e.g. 'I will...')
|
|
42
51
|
--skip-narration Alias for --skip-naration
|
|
@@ -139,6 +148,22 @@ if (args.includes("doctor") || args.includes("--doctor")) {
|
|
|
139
148
|
process.stdout.write("\n3. Paseo Installation Targets:\n");
|
|
140
149
|
const serverPaths = findPaseoServerInstallations();
|
|
141
150
|
const asarPaths = findPaseoAsarPaths();
|
|
151
|
+
let telemetryPluginEnabled = false;
|
|
152
|
+
try {
|
|
153
|
+
const paseoHome = process.env.PASEO_HOME || path.join(process.env.HOME || process.env.USERPROFILE || "", ".paseo");
|
|
154
|
+
const cfgPath = path.join(paseoHome, "config.json");
|
|
155
|
+
if (fs.existsSync(cfgPath)) {
|
|
156
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf-8"));
|
|
157
|
+
if (cfg?.pluginsEnabled === true && cfg?.plugins) {
|
|
158
|
+
const p = cfg.plugins["antigravity-telemetry"];
|
|
159
|
+
if (p && p.enabled !== false) {
|
|
160
|
+
telemetryPluginEnabled = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch { }
|
|
166
|
+
process.stdout.write(` - [Plugin] antigravity-telemetry -> ${telemetryPluginEnabled ? "[ENABLED]" : "[NOT INSTALLED]"}\n`);
|
|
142
167
|
const totalFound = serverPaths.length + asarPaths.length;
|
|
143
168
|
if (totalFound === 0) {
|
|
144
169
|
process.stdout.write(" [!] No Paseo server or desktop installations discovered in standard paths.\n");
|
|
@@ -185,21 +210,22 @@ if (args.includes("doctor") || args.includes("--doctor")) {
|
|
|
185
210
|
process.stdout.write(" - Antigravity CLI ('agy') executable was not found.\n");
|
|
186
211
|
process.stdout.write(" Action: Install Antigravity CLI or set AGY_BIN_PATH environment variable.\n");
|
|
187
212
|
}
|
|
188
|
-
if (totalFound === 0) {
|
|
213
|
+
if (totalFound === 0 && !telemetryPluginEnabled) {
|
|
189
214
|
hasIssue = true;
|
|
190
215
|
process.stdout.write(" - No Paseo installations found.\n");
|
|
191
216
|
process.stdout.write(" Action: Set PASEO_SERVER_PATH or PASEO_ASAR_PATH to your Paseo install directory if located elsewhere.\n");
|
|
192
217
|
}
|
|
193
|
-
else if (unpatchedCount > 0) {
|
|
218
|
+
else if (unpatchedCount > 0 && !telemetryPluginEnabled) {
|
|
194
219
|
hasIssue = true;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
220
|
+
process.stdout.write(` - Found ${unpatchedCount} unpatched Paseo target(s) and 'antigravity-telemetry' plugin is not yet installed.\n`);
|
|
221
|
+
process.stdout.write(" Recommended update-proof action (survives Paseo updates without touching .app signatures):\n");
|
|
222
|
+
process.stdout.write(` paseo plugin install "${bundledPluginDir}"\n`);
|
|
223
|
+
if (isWin) {
|
|
224
|
+
process.stdout.write(" Or to patch standalone Paseo after closing it (Stop-Process -Name \"Paseo\" -Force):\n");
|
|
198
225
|
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
199
226
|
}
|
|
200
227
|
else {
|
|
201
|
-
process.stdout.write(
|
|
202
|
-
process.stdout.write(" Action: Run setup to patch Paseo for Antigravity telemetry:\n");
|
|
228
|
+
process.stdout.write(" Or for standalone @getpaseo/cli servers:\n");
|
|
203
229
|
process.stdout.write(" npx -y paseo-acp-agy setup\n");
|
|
204
230
|
}
|
|
205
231
|
}
|
|
@@ -272,8 +298,9 @@ if (args.includes("setup") ||
|
|
|
272
298
|
process.stdout.write(`\n🛡️ Preserved macOS Code Signature:\n` +
|
|
273
299
|
` Skipped ${res.skippedPaths.length} target(s) inside macOS .app bundle to protect official Apple Developer ID signature\n` +
|
|
274
300
|
` and prevent Gatekeeper "damaged app" warnings.\n` +
|
|
275
|
-
`
|
|
276
|
-
`
|
|
301
|
+
` Recommended update-proof telemetry setup (survives every Paseo update):\n` +
|
|
302
|
+
` paseo plugin install "${bundledPluginDir}"\n` +
|
|
303
|
+
` (Or if you specifically wish to patch the .app bundle: paseo-acp-agy patch --force-macos-app)\n\n`);
|
|
277
304
|
}
|
|
278
305
|
if (isPaseoRunning()) {
|
|
279
306
|
const isWin = process.platform === "win32";
|
package/dist/paseo-patcher.d.ts
CHANGED
|
@@ -58,7 +58,17 @@ export declare function ensurePaseoIntegration(options?: {
|
|
|
58
58
|
forceMacosApp?: boolean;
|
|
59
59
|
}): Promise<PatchResult>;
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Reads the Paseo version embedded inside an ASAR archive's package.json.
|
|
62
|
+
*/
|
|
63
|
+
export declare function resolveAsarArchiveVersion(archivePath: string): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the authoritative Paseo version of a target app.asar installation,
|
|
66
|
+
* preferring macOS bundle Info.plist (CFBundleShortVersionString) when inside an .app bundle.
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveTargetPaseoVersion(asarPath: string): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* Restores original unpatched Paseo files from backup (.bak) archives,
|
|
71
|
+
* enforcing strict version matching so an older backup can never downgrade a newer Paseo app.
|
|
62
72
|
*/
|
|
63
73
|
export declare function restorePaseoIntegration(): Promise<{
|
|
64
74
|
restored: string[];
|