@herbertgao/pi-extensions 2026.9.3 → 2026.9.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/README.md +2 -1
- package/THIRD_PARTY_NOTICES.md +25 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +20 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/pi-antigravity/LICENSE +21 -0
- package/node_modules/pi-antigravity/README.md +194 -0
- package/node_modules/pi-antigravity/package.json +67 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
- package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
- package/node_modules/pi-antigravity/src/client/client.ts +561 -0
- package/node_modules/pi-antigravity/src/client/index.ts +1 -0
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/node_modules/pi-antigravity/src/image/image.ts +336 -0
- package/node_modules/pi-antigravity/src/image/index.ts +1 -0
- package/node_modules/pi-antigravity/src/index.ts +280 -0
- package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
- package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
- package/node_modules/pi-antigravity/src/models/index.ts +3 -0
- package/node_modules/pi-antigravity/src/models/models.ts +500 -0
- package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
- package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
- package/node_modules/pi-antigravity/src/types/index.ts +2 -0
- package/node_modules/pi-antigravity/src/types/types.ts +292 -0
- package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
- package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
- package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
- package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
- package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
- package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
- package/node_modules/pi-antigravity/tsconfig.json +21 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -10,8 +10,9 @@ pi install npm:@herbertgao/pi-extensions
|
|
|
10
10
|
|
|
11
11
|
Requires Node.js 24 or newer and Pi 0.84.4 or newer.
|
|
12
12
|
|
|
13
|
-
The package bundles
|
|
13
|
+
The package bundles 6 active `@herbertgao/*` child packages—`pi-bark`, `pi-cc-extensions`, `resume-from`, `pi-subagents`, `sol-pi`, and the maintained Antigravity provider mirror—plus the following upstream packages under their original names:
|
|
14
14
|
|
|
15
|
+
- `pi-antigravity@0.7.2`
|
|
15
16
|
- `@dietrichgebert/ponytail@4.9.0`
|
|
16
17
|
- `@juicesharp/rpiv-ask-user-question@2.8.0`
|
|
17
18
|
- `@luxusai/pi-hindsight@0.12.0`
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -87,3 +87,28 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
87
87
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
88
88
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
89
89
|
THE SOFTWARE.
|
|
90
|
+
|
|
91
|
+
## `pi-antigravity`
|
|
92
|
+
|
|
93
|
+
The aggregate bundles the MIT-licensed [`pi-antigravity`](https://github.com/Rahularya01/pi-antigravity) package version 0.7.2 under its original attribution:
|
|
94
|
+
|
|
95
|
+
MIT License
|
|
96
|
+
|
|
97
|
+
Copyright (c) 2026 Rahul Arya
|
|
98
|
+
|
|
99
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
100
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
101
|
+
in the Software without restriction, including without limitation the rights
|
|
102
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
103
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
104
|
+
|
|
105
|
+
The above copyright notice and this permission notice shall be included in all
|
|
106
|
+
copies or substantial portions of the Software.
|
|
107
|
+
|
|
108
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
109
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
110
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
111
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
112
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
113
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
114
|
+
THE SOFTWARE.
|
|
@@ -34,8 +34,8 @@ import {
|
|
|
34
34
|
import { renderRichToolResult } from "./tool/diff/index.ts";
|
|
35
35
|
import type { WriteExecutionMetadataStore } from "./tool/diff/write-execution.ts";
|
|
36
36
|
import {
|
|
37
|
+
formatDisplayPath,
|
|
37
38
|
insetComponent,
|
|
38
|
-
oneLine,
|
|
39
39
|
renderExpandedToolResult,
|
|
40
40
|
scheduleAnimation,
|
|
41
41
|
} from "./tool/result.ts";
|
|
@@ -552,7 +552,8 @@ function compactEditWriteLine(
|
|
|
552
552
|
visibleWidth(statsText) +
|
|
553
553
|
visibleWidth(hintText);
|
|
554
554
|
const pathWidth = Math.max(0, width - fixedWidth - (path ? 1 : 0));
|
|
555
|
-
const pathPart =
|
|
555
|
+
const pathPart =
|
|
556
|
+
pathWidth > 0 && path ? ` ${formatDisplayPath(path, component.cwd, pathWidth)}` : "";
|
|
556
557
|
const line = `${iconPart}${namePart}${theme.fg("toolTitle", pathPart)}${statsStyled}${hintText ? theme.fg("dim", hintText) : ""}`;
|
|
557
558
|
return ["", truncateToWidth(line, width, "")];
|
|
558
559
|
}
|
|
@@ -10,12 +10,13 @@ import { config, getToolDisplayConfig, type CompactStyleMode } from "../config/c
|
|
|
10
10
|
import { isToolCallHovered } from "./mouse/interaction.ts";
|
|
11
11
|
import {
|
|
12
12
|
countLines,
|
|
13
|
+
fitToolCallSummary,
|
|
13
14
|
hasExpandableDetail,
|
|
14
|
-
headTruncateToWidth,
|
|
15
15
|
insetComponent,
|
|
16
16
|
isToolExpanded,
|
|
17
17
|
oneLine,
|
|
18
18
|
outputLineCount,
|
|
19
|
+
pathSummary,
|
|
19
20
|
pendingIcon,
|
|
20
21
|
renderCollapsedToolResultToWidth,
|
|
21
22
|
renderExpandedToolResult,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
textFromResult,
|
|
27
28
|
toolIconColor,
|
|
28
29
|
toolViewportWidth,
|
|
30
|
+
type ToolCallSummary,
|
|
29
31
|
} from "./tool/result.ts";
|
|
30
32
|
import { showMoreHintText } from "./show-more-hint.ts";
|
|
31
33
|
import { renderRichToolResult, type WriteExecutionMetadataStore } from "./tool/diff/index.ts";
|
|
@@ -152,7 +154,8 @@ function singleToolCallSummary(
|
|
|
152
154
|
toolName: string,
|
|
153
155
|
label: string,
|
|
154
156
|
args: any,
|
|
155
|
-
|
|
157
|
+
cwd?: string,
|
|
158
|
+
): ToolCallSummary {
|
|
156
159
|
const title = label === toolName ? humanizeToolLabel(label) : label;
|
|
157
160
|
if (!args || typeof args !== "object") return { main: title, detail: "" };
|
|
158
161
|
const name = toolName.toLowerCase();
|
|
@@ -201,14 +204,17 @@ function singleToolCallSummary(
|
|
|
201
204
|
args.offset !== undefined ? `offset=${args.offset}` : "",
|
|
202
205
|
args.limit !== undefined ? `limit=${args.limit}` : "",
|
|
203
206
|
].filter(Boolean);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
207
|
+
const detail = details.length ? ` (${details.join(", ")})` : "";
|
|
208
|
+
if (typeof args.path === "string" && args.path) {
|
|
209
|
+
return pathSummary(title, args.path, cwd, detail);
|
|
210
|
+
}
|
|
211
|
+
return { main: title, detail };
|
|
212
|
+
}
|
|
213
|
+
const preferredPath = args.path ?? args.file_path;
|
|
214
|
+
if (typeof preferredPath === "string" && preferredPath) {
|
|
215
|
+
return pathSummary(title, preferredPath, cwd);
|
|
208
216
|
}
|
|
209
217
|
const preferred =
|
|
210
|
-
args.path ??
|
|
211
|
-
args.file_path ??
|
|
212
218
|
args.command ??
|
|
213
219
|
args.query ??
|
|
214
220
|
args.question ??
|
|
@@ -324,7 +330,7 @@ function createCcstyleTool(
|
|
|
324
330
|
visualState === "success"
|
|
325
331
|
? `${BRIGHT_GREEN}${rawIcon}${ANSI_FG_RESET}`
|
|
326
332
|
: theme.fg(toolIconColor(context), rawIcon);
|
|
327
|
-
const summary = singleToolCallSummary(toolName, label, args);
|
|
333
|
+
const summary = singleToolCallSummary(toolName, label, args, context?.cwd);
|
|
328
334
|
let cachedWidth: number | undefined;
|
|
329
335
|
let cachedLine: string | undefined;
|
|
330
336
|
return {
|
|
@@ -334,8 +340,7 @@ function createCcstyleTool(
|
|
|
334
340
|
const callWidth = Math.max(0, viewportWidth - visibleWidth(icon) - 2);
|
|
335
341
|
const mainWidth = Math.max(0, callWidth - visibleWidth(summary.detail));
|
|
336
342
|
cachedWidth = width;
|
|
337
|
-
|
|
338
|
-
const line = ` ${icon} ${theme.fg("toolTitle", headTruncateToWidth(summary.main, mainWidth))}${theme.fg("dim", summary.detail)}`;
|
|
343
|
+
const line = ` ${icon} ${theme.fg("toolTitle", fitToolCallSummary(summary, mainWidth))}${theme.fg("dim", summary.detail)}`;
|
|
339
344
|
cachedLine = truncateToWidth(line, viewportWidth, "");
|
|
340
345
|
return [cachedLine];
|
|
341
346
|
},
|
package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts
CHANGED
|
@@ -52,7 +52,7 @@ interface DiffTheme {
|
|
|
52
52
|
type DiffLineKind = "add" | "remove" | "context";
|
|
53
53
|
type DiffEntryKind = "line" | "meta" | "hunk" | "file";
|
|
54
54
|
|
|
55
|
-
interface DiffLineEntry {
|
|
55
|
+
export interface DiffLineEntry {
|
|
56
56
|
kind: "line";
|
|
57
57
|
lineKind: DiffLineKind;
|
|
58
58
|
oldLineNumber: number | null;
|
|
@@ -72,7 +72,7 @@ interface DiffMetaEntry {
|
|
|
72
72
|
|
|
73
73
|
type ParsedDiffEntry = DiffLineEntry | DiffMetaEntry;
|
|
74
74
|
|
|
75
|
-
interface ParsedDiff {
|
|
75
|
+
export interface ParsedDiff {
|
|
76
76
|
entries: ParsedDiffEntry[];
|
|
77
77
|
stats: DiffStats;
|
|
78
78
|
}
|
|
@@ -131,7 +131,9 @@ type CodeLineHighlighter = (line: string, entry: DiffLineEntry) => string;
|
|
|
131
131
|
|
|
132
132
|
const CANONICAL_LINE_PATTERN = /^([+\- ])(\s*\d+)\|(.*)$/;
|
|
133
133
|
const HASHLINE_ANCHOR_LINE_PATTERN = /^([+\- ])(\s*\d+)#([A-Za-z0-9]+| {2}):(.*)$/;
|
|
134
|
+
// Pi 仍使用空格分隔的行号格式;仅在无 hunk 头时可安全识别。
|
|
134
135
|
const LEGACY_LINE_PATTERN = /^([+\- ])(\s*\d+)\s(.*)$/;
|
|
136
|
+
const PI_OMISSION_LINE_PATTERN = /^ {3,}\.\.\.$/;
|
|
135
137
|
const HUNK_HEADER_PATTERN = /^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@(.*)$/;
|
|
136
138
|
const SPLIT_SEPARATOR = " │ ";
|
|
137
139
|
const MIN_LINE_NUMBER_WIDTH = 2;
|
|
@@ -374,7 +376,10 @@ function toParsedDiffLine(
|
|
|
374
376
|
return { lineKind: "context", lineNumber: normalizedLineNumber, content };
|
|
375
377
|
}
|
|
376
378
|
|
|
377
|
-
function parseCanonicalDiffLine(
|
|
379
|
+
function parseCanonicalDiffLine(
|
|
380
|
+
line: string,
|
|
381
|
+
allowPiFormat: boolean,
|
|
382
|
+
): {
|
|
378
383
|
lineKind: DiffLineKind;
|
|
379
384
|
lineNumber: string;
|
|
380
385
|
content: string;
|
|
@@ -393,7 +398,7 @@ function parseCanonicalDiffLine(line: string): {
|
|
|
393
398
|
}
|
|
394
399
|
|
|
395
400
|
const canonicalMatch = line.match(CANONICAL_LINE_PATTERN);
|
|
396
|
-
const legacyMatch = canonicalMatch ? null : line.match(LEGACY_LINE_PATTERN);
|
|
401
|
+
const legacyMatch = canonicalMatch || !allowPiFormat ? null : line.match(LEGACY_LINE_PATTERN);
|
|
397
402
|
const matched = canonicalMatch ?? legacyMatch;
|
|
398
403
|
if (!matched) {
|
|
399
404
|
return null;
|
|
@@ -486,7 +491,7 @@ function ensureImplicitHunk(currentHunk: number): number {
|
|
|
486
491
|
return currentHunk > 0 ? currentHunk : 1;
|
|
487
492
|
}
|
|
488
493
|
|
|
489
|
-
function parseDiff(diffText: string): ParsedDiff {
|
|
494
|
+
export function parseDiff(diffText: string): ParsedDiff {
|
|
490
495
|
const stats: DiffStats = {
|
|
491
496
|
added: 0,
|
|
492
497
|
removed: 0,
|
|
@@ -505,12 +510,14 @@ function parseDiff(diffText: string): ParsedDiff {
|
|
|
505
510
|
let oldLineCursor: number | null = null;
|
|
506
511
|
let newLineCursor: number | null = null;
|
|
507
512
|
let lineNumberDelta = 0;
|
|
513
|
+
let hasHunkHeader = false;
|
|
508
514
|
|
|
509
515
|
for (const rawLine of diffText.replace(/\r/g, "").split("\n")) {
|
|
510
516
|
stats.lines++;
|
|
511
517
|
|
|
512
518
|
const hunkMatch = rawLine.match(HUNK_HEADER_PATTERN);
|
|
513
519
|
if (hunkMatch) {
|
|
520
|
+
hasHunkHeader = true;
|
|
514
521
|
hunkIndex++;
|
|
515
522
|
stats.hunks = Math.max(stats.hunks, hunkIndex);
|
|
516
523
|
oldLineCursor = toNumber(hunkMatch[1]);
|
|
@@ -535,7 +542,14 @@ function parseDiff(diffText: string): ParsedDiff {
|
|
|
535
542
|
lineNumberDelta = 0;
|
|
536
543
|
}
|
|
537
544
|
|
|
538
|
-
|
|
545
|
+
// Pi 用空白行号填充省略上下文;它不是源码行,也不推进行号游标。
|
|
546
|
+
if (!hasHunkHeader && PI_OMISSION_LINE_PATTERN.test(rawLine)) {
|
|
547
|
+
entries.push(createMetaEntry(rawLine, hunkIndex));
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Pi 无 hunk 头时的数字行号与 unified hunk 中的数字源码文本存在歧义。
|
|
552
|
+
const canonical = parseCanonicalDiffLine(rawLine, !hasHunkHeader);
|
|
539
553
|
if (canonical) {
|
|
540
554
|
hunkIndex = ensureImplicitHunk(hunkIndex);
|
|
541
555
|
stats.hunks = Math.max(stats.hunks, hunkIndex);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "@earendil-works/pi-tui";
|
|
9
9
|
import { TOOL_LOADING_INTERVAL_MS, toolLoadingIcon } from "../../utils/tool-loading-icon.ts";
|
|
10
10
|
import { sanitizeToolResultText } from "../../utils/tool-result-sanitize.ts";
|
|
11
|
+
import { fitToolCallSummary, pathSummary, type ToolCallSummary } from "./result.ts";
|
|
11
12
|
import { isToolTuiFullscreen, showMoreHintText } from "../show-more-hint.ts";
|
|
12
13
|
|
|
13
14
|
const PATCH_KEY = Symbol.for("pi.ccstyle.tool-grouping-patch");
|
|
@@ -172,7 +173,7 @@ function humanizeToolName(name: string): string {
|
|
|
172
173
|
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
function toolSummary(tool: any):
|
|
176
|
+
function toolSummary(tool: any): ToolCallSummary {
|
|
176
177
|
const name = toolName(tool);
|
|
177
178
|
const lowerName = name.toLowerCase();
|
|
178
179
|
const args = tool?.args ?? {};
|
|
@@ -232,10 +233,11 @@ function toolSummary(tool: any): { main: string; detail: string } {
|
|
|
232
233
|
args.offset !== undefined ? `offset=${args.offset}` : "",
|
|
233
234
|
args.limit !== undefined ? `limit=${args.limit}` : "",
|
|
234
235
|
].filter(Boolean);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
236
|
+
const detail = details.length ? ` (${details.join(", ")})` : "";
|
|
237
|
+
if (typeof args.path === "string" && args.path) {
|
|
238
|
+
return pathSummary("Read", args.path, tool?.cwd, detail);
|
|
239
|
+
}
|
|
240
|
+
return { main: "Read ...", detail };
|
|
239
241
|
}
|
|
240
242
|
if (name === "bash") return { main: `Bash ${oneLine(args.command || "...")}`, detail: "" };
|
|
241
243
|
if (name === "grep") {
|
|
@@ -456,11 +458,14 @@ export class ToolGroupComponent extends Container {
|
|
|
456
458
|
const continuation = index === total - 1 ? " " : "│ ";
|
|
457
459
|
if (!this._expanded) {
|
|
458
460
|
const summary = toolSummary(tool);
|
|
461
|
+
const prefix = ` ${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `;
|
|
462
|
+
const detail = fg("dim", summary.detail);
|
|
463
|
+
const mainWidth = Math.max(0, width - visibleWidth(prefix) - visibleWidth(detail));
|
|
459
464
|
lines.push(
|
|
460
465
|
truncateToWidth(
|
|
461
|
-
|
|
466
|
+
`${prefix}${fg("toolTitle", fitToolCallSummary(summary, mainWidth))}${detail}`,
|
|
462
467
|
width,
|
|
463
|
-
"
|
|
468
|
+
"",
|
|
464
469
|
),
|
|
465
470
|
);
|
|
466
471
|
continue;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Text, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
|
+
import { posix, win32 } from "node:path";
|
|
2
3
|
import { inspect } from "node:util";
|
|
3
4
|
import { config } from "../../config/config.ts";
|
|
4
5
|
import { showMoreHintText } from "../show-more-hint.ts";
|
|
@@ -557,6 +558,103 @@ export function middleTruncateToWidth(text: string, width: number): string {
|
|
|
557
558
|
return `${left}…${right}`;
|
|
558
559
|
}
|
|
559
560
|
|
|
561
|
+
export type ToolCallSummary = {
|
|
562
|
+
main: string;
|
|
563
|
+
detail: string;
|
|
564
|
+
/** 路径摘要保留结构,供最终渲染按实际宽度优先保留文件名。 */
|
|
565
|
+
path?: { prefix: string; value: string };
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
function pathApi(value: string) {
|
|
569
|
+
if (win32.isAbsolute(value)) return win32;
|
|
570
|
+
if (posix.isAbsolute(value)) return posix;
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** cwd 内绝对路径转相对路径;cwd 外路径保持不变。 */
|
|
575
|
+
export function displayPath(value: unknown, cwd?: string): string {
|
|
576
|
+
const text = oneLine(value, 4096);
|
|
577
|
+
const base = cwd ? oneLine(cwd, 4096) : "";
|
|
578
|
+
const api = pathApi(text);
|
|
579
|
+
if (!api || !base || !api.isAbsolute(base)) return text;
|
|
580
|
+
const relative = api.relative(base, text);
|
|
581
|
+
if (!relative) return api.basename(text) || ".";
|
|
582
|
+
if (relative === ".." || relative.startsWith(`..${api.sep}`) || api.isAbsolute(relative)) {
|
|
583
|
+
return text;
|
|
584
|
+
}
|
|
585
|
+
return relative;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function headToWidth(text: string, width: number, ellipsis = ""): string {
|
|
589
|
+
if (visibleWidth(text) <= width) return text;
|
|
590
|
+
if (width <= 0) return "";
|
|
591
|
+
const suffix = visibleWidth(ellipsis) <= width ? ellipsis : "";
|
|
592
|
+
const contentWidth = width - visibleWidth(suffix);
|
|
593
|
+
let head = "";
|
|
594
|
+
for (const char of Array.from(text)) {
|
|
595
|
+
if (visibleWidth(head + char) > contentWidth) break;
|
|
596
|
+
head += char;
|
|
597
|
+
}
|
|
598
|
+
return head + suffix;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function tailToWidth(text: string, width: number): string {
|
|
602
|
+
if (width <= 0) return "";
|
|
603
|
+
let tail = "";
|
|
604
|
+
for (const char of Array.from(text).reverse()) {
|
|
605
|
+
if (visibleWidth(char + tail) > width) break;
|
|
606
|
+
tail = char + tail;
|
|
607
|
+
}
|
|
608
|
+
return tail;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** 中间截断路径:目录保留开头,末尾优先完整保留文件名。 */
|
|
612
|
+
export function truncatePathToWidth(path: string, width: number): string {
|
|
613
|
+
if (visibleWidth(path) <= width) return path;
|
|
614
|
+
if (width <= 1) return width === 1 ? "…" : "";
|
|
615
|
+
const separatorIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
616
|
+
const filename = separatorIndex >= 0 ? path.slice(separatorIndex + 1) : path;
|
|
617
|
+
const filenameWidth = visibleWidth(filename);
|
|
618
|
+
if (separatorIndex >= 0 && filenameWidth + 1 <= width) {
|
|
619
|
+
const separator = path[separatorIndex]!;
|
|
620
|
+
const suffix = `${separator}${filename}`;
|
|
621
|
+
const prefixWidth = width - visibleWidth(suffix) - 1;
|
|
622
|
+
const prefix = headToWidth(path.slice(0, separatorIndex), Math.max(0, prefixWidth));
|
|
623
|
+
return `${prefix}…${prefix ? suffix : filename}`;
|
|
624
|
+
}
|
|
625
|
+
const leftWidth = Math.max(1, Math.floor((width - 1) * 0.45));
|
|
626
|
+
const rightWidth = Math.max(0, width - leftWidth - 1);
|
|
627
|
+
return `${headToWidth(filename, leftWidth)}…${tailToWidth(filename, rightWidth)}`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** 路径展示的统一入口:相对化后按当前可用宽度中间截断。 */
|
|
631
|
+
export function formatDisplayPath(value: unknown, cwd: string | undefined, width: number): string {
|
|
632
|
+
return truncatePathToWidth(displayPath(value, cwd), Math.max(0, Math.floor(width)));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** 按最终终端宽度渲染摘要;路径摘要不会再被整行头部截断。 */
|
|
636
|
+
export function fitToolCallSummary(summary: ToolCallSummary, width: number): string {
|
|
637
|
+
if (!summary.path) return headTruncateToWidth(summary.main, width);
|
|
638
|
+
const prefix = summary.path.prefix;
|
|
639
|
+
const pathWidth = Math.max(0, width - visibleWidth(prefix) - 1);
|
|
640
|
+
if (pathWidth <= 0) return headTruncateToWidth(prefix, width);
|
|
641
|
+
return `${prefix} ${truncatePathToWidth(summary.path.value, pathWidth)}`;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function pathSummary(
|
|
645
|
+
prefix: string,
|
|
646
|
+
value: unknown,
|
|
647
|
+
cwd: string | undefined,
|
|
648
|
+
detail = "",
|
|
649
|
+
): ToolCallSummary {
|
|
650
|
+
const path = displayPath(value, cwd);
|
|
651
|
+
return {
|
|
652
|
+
main: `${prefix} ${path}`,
|
|
653
|
+
detail,
|
|
654
|
+
path: { prefix, value: path },
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
560
658
|
/** Pretty-print tool call args for the expanded Input section. */
|
|
561
659
|
export function formatToolInputArgs(args: unknown, maxChars = 8_000): string {
|
|
562
660
|
if (args === undefined || args === null) return "";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herbertgao/pi-cc-extensions",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
|
|
6
6
|
"type": "module",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
},
|
|
70
70
|
"x-upstream": {
|
|
71
71
|
"package": "pi-cc-extensions",
|
|
72
|
-
"version": "0.8.
|
|
72
|
+
"version": "0.8.70",
|
|
73
73
|
"repository": "https://github.com/minuque/pi-cc-extensions",
|
|
74
|
-
"commit": "
|
|
74
|
+
"commit": "9f200064951ffb148ecec2e1cf0dab7a2940f823"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rahul Arya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# pi-antigravity
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/pi-antigravity)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/sponsors/Rahularya01)
|
|
6
|
+
|
|
7
|
+
**pi-antigravity** is a [Pi Coding Agent](https://pi.dev) provider that lets Pi talk directly to Google Antigravity / Cloud Code Assist models — Gemini, plus the Claude and GPT-OSS models Antigravity also advertises. Sign in with Google, pick a model, and go. Under the hood it handles OAuth login, native streaming, model routing, and quota diagnostics itself, so it never shells out to an external Antigravity CLI.
|
|
8
|
+
|
|
9
|
+
Using [OpenCode](https://opencode.ai) instead of Pi? Install the companion plugin [`@rahularya01/opencode-antigravity`](https://www.npmjs.com/package/@rahularya01/opencode-antigravity).
|
|
10
|
+
|
|
11
|
+
Want to equip Pi with MCP, web access, subagents, and todos? Install [`pi-essentials`](https://github.com/Rahularya01/pi-essentials) (currently in beta), an all-in-one companion extension designed for Pi.
|
|
12
|
+
|
|
13
|
+
> **Unofficial integration.** This project is not affiliated with or endorsed by Google. Use it only with an account and services you are authorized to access, and review its source before granting OAuth permissions.
|
|
14
|
+
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- [Requirements](#requirements)
|
|
18
|
+
- [Install](#install)
|
|
19
|
+
- [Quick start](#quick-start)
|
|
20
|
+
- [Authentication and credential safety](#authentication-and-credential-safety)
|
|
21
|
+
- [Commands](#commands)
|
|
22
|
+
- [Models and routing](#models-and-routing)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [Troubleshooting](#troubleshooting)
|
|
25
|
+
- [Development](#development)
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Pi Coding Agent and Pi AI version **0.80.0 or later**
|
|
30
|
+
- A Google account that can use the relevant Cloud Code Assist / Antigravity services
|
|
31
|
+
- A browser to complete the Google sign-in. Same-machine is best (the browser hits the local callback automatically); on a remote/headless machine, complete sign-in anywhere and paste the resulting callback URL back into Pi (see [Troubleshooting](#troubleshooting)).
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Install from npm:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pi install npm:pi-antigravity
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or install the latest repository version:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pi install git:github.com/Rahularya01/pi-antigravity
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Restart Pi (or run `/reload`) after installation. To update the npm package later, use `pi update npm:pi-antigravity`.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
1. Start Pi and run `/login antigravity`.
|
|
52
|
+
2. Complete Google sign-in in your browser.
|
|
53
|
+
3. Select a model, for example:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
/model antigravity/gemini-3.8-flash
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
4. Start working. If a request fails, run `/antigravity.doctor` for sanitized diagnostics.
|
|
60
|
+
|
|
61
|
+
## Authentication and credential safety
|
|
62
|
+
|
|
63
|
+
The provider uses the OAuth 2.0 Authorization Code flow with PKCE, so credentials are only ever exchanged with Google — never typed into Pi.
|
|
64
|
+
|
|
65
|
+
1. `/login antigravity` opens Google sign-in and starts a temporary callback listener at `http://localhost:51121/oauth-callback`.
|
|
66
|
+
2. After you approve access, Pi exchanges the callback code for tokens and stores the provider credentials in Pi's auth store (normally `~/.pi/agent/auth.json`).
|
|
67
|
+
3. Pi refreshes access tokens automatically when they expire — you shouldn't need to sign in again unless a token is revoked.
|
|
68
|
+
|
|
69
|
+
The callback listener binds only to a loopback host, so it isn't reachable from outside your machine. The auth file it writes to contains sensitive access and refresh tokens: **do not commit it, paste it into issues, or share its contents.**
|
|
70
|
+
|
|
71
|
+
Signing in requests these Google OAuth scopes:
|
|
72
|
+
|
|
73
|
+
<!-- prettier-ignore -->
|
|
74
|
+
| Scope | Why it's needed |
|
|
75
|
+
| --- | --- |
|
|
76
|
+
| `aicode` | Access to the Cloud Code Assist / Antigravity model catalog and endpoints |
|
|
77
|
+
| `cloud-platform` | General Cloud Code Assist API access |
|
|
78
|
+
| `userinfo.email`, `userinfo.profile` | Identify the signed-in Google account |
|
|
79
|
+
| `cclog` | Cloud Code Assist logging/telemetry endpoints used by the API |
|
|
80
|
+
| `experimentsandconfigs` | Server-side experiment and config flags for the API |
|
|
81
|
+
|
|
82
|
+
Review these permissions before approving access. If your credentials expire or are revoked, just re-run `/login antigravity` to sign in again.
|
|
83
|
+
|
|
84
|
+
## Commands
|
|
85
|
+
|
|
86
|
+
<!-- prettier-ignore -->
|
|
87
|
+
| Command | Description |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `/login antigravity` | Sign in to Google and configure the provider. |
|
|
90
|
+
| `/model antigravity/<model-id>` | Choose a registered Antigravity model. |
|
|
91
|
+
| `/antigravity.usage` | Show the server-reported shared quota groups and reset times. |
|
|
92
|
+
| `/antigravity.models` | List available runtime models, remaining shared-pool quota, and capabilities. |
|
|
93
|
+
| `/antigravity.models all` | Include tab/chat models normally hidden from the model list. |
|
|
94
|
+
| `/antigravity.refresh` | Force refresh the dynamic model catalog from Antigravity. |
|
|
95
|
+
| `/antigravity.doctor` | Show sanitized provider diagnostics, including the endpoint, status, and resolved runtime model. |
|
|
96
|
+
| `/antigravity.image <prompt>` | Generate an image via Antigravity and save it under `.pi/generated-images/`. Optional `--ratio 16:9`, `--model`, `--path`. |
|
|
97
|
+
|
|
98
|
+
Model availability, entitlement, quota groups, and resets are returned by the service and can differ by account. The quota percentage shown for a model can represent a shared pool, not a private per-model allowance.
|
|
99
|
+
|
|
100
|
+
The extension also registers a `generate_image` tool the model can call. Images are written inside the project directory (default `.pi/generated-images/`). Image models such as `gemini-3-pro-image` are account-dependent; `/antigravity.image` falls back to other advertised Gemini image IDs on 404.
|
|
101
|
+
|
|
102
|
+
## Models and routing
|
|
103
|
+
|
|
104
|
+
After you sign in, the provider refreshes its catalog from Antigravity (`fetchAvailableModels`) and groups runtime thinking variants into public Pi model IDs. Newly enabled models — for example a new Gemini Flash generation — become selectable after that refresh without waiting for an extension release. A last-known-good cache is kept for offline/cold start; the static table below is only the conservative fallback and a routing reference.
|
|
105
|
+
|
|
106
|
+
Use `/antigravity.models` to see live availability and quota for your account. Runtime names such as `gemini-3.8-flash-low` / `-medium` / `-high` collapse to `gemini-3.8-flash` with those thinking levels. The conservative static entries remain selectable when an account's authenticated catalog omits them.
|
|
107
|
+
|
|
108
|
+
### Why Claude and GPT-OSS appear
|
|
109
|
+
|
|
110
|
+
Antigravity / Cloud Code Assist exposes a multi-provider catalog. Depending on your account, its Google-authenticated API can advertise Google Gemini models alongside Claude models served through Anthropic Vertex and GPT-OSS served through OpenAI Vertex. This extension intentionally exposes those advertised Claude and GPT-OSS models through the single `antigravity` provider; they are not separate Pi providers and do not use a separate Anthropic or OpenAI login.
|
|
111
|
+
|
|
112
|
+
The backend's display labels do not always match its runtime IDs. For example, `gemini-3.5-flash-extra-low`, `gemini-3.5-flash-low`, and `gemini-3-flash-agent` can be displayed as Gemini 3.5 Flash Low, Medium, and High. All supported models send integer `thinkingBudget` (Gemini 3.8/3.7/3.6: -1/4000/1000/0, Gemini 3.5: 10000/4000/1000/0, Gemini 3.1 Pro: 10001/1001/0, Claude: 1024/0, GPT-OSS: 8192/0) matching the official Antigravity CLI wire format.
|
|
113
|
+
|
|
114
|
+
<!-- prettier-ignore -->
|
|
115
|
+
| Public model ID | Input | Thinking levels shown | Max output tokens | Request routing |
|
|
116
|
+
| --- | --- | --- | --- | --- |
|
|
117
|
+
| `gemini-3.8-flash` | Text, image | Low, Medium, High | 65,536 | low → `gemini-3.8-flash-low`; medium → `gemini-3.8-flash-medium`; high → `gemini-3.8-flash-high` |
|
|
118
|
+
| `gemini-3.7-flash` | Text, image | Low, Medium, High | 65,536 | low → `gemini-3.7-flash-low`; medium → `gemini-3.7-flash-medium`; high → `gemini-3.7-flash-high` |
|
|
119
|
+
| `gemini-3.6-flash` | Text, image | Low, Medium, High | 65,536 | low → `gemini-3.6-flash-low`; medium → `gemini-3.6-flash-medium`; high → `gemini-3.6-flash-high` |
|
|
120
|
+
| `gemini-3.5-flash` | Text, image | Low, Medium, High | 65,536 | low → `gemini-3.5-flash-extra-low`; medium → `gemini-3.5-flash-low`; high → `gemini-3-flash-agent` |
|
|
121
|
+
| `gemini-3.1-pro` | Text, image | Low, High | 65,535 | low → `gemini-3.1-pro-low`; high → `gemini-pro-agent` |
|
|
122
|
+
| `claude-sonnet-4-6` | Text, image | High | 64,000 | high → `claude-sonnet-4-6` |
|
|
123
|
+
| `claude-opus-4-6` | Text, image | High | 64,000 | high → `claude-opus-4-6-thinking` |
|
|
124
|
+
| `gpt-oss-120b` | Text | Medium | 32,768 | medium → `gpt-oss-120b-medium` |
|
|
125
|
+
|
|
126
|
+
To limit which models Pi cycles through, enable specific entries in `~/.pi/agent/settings.json`:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"models": {
|
|
131
|
+
"antigravity/gemini-3.8-flash": { "enabled": true },
|
|
132
|
+
"antigravity/gemini-3.7-flash": { "enabled": true },
|
|
133
|
+
"antigravity/gemini-3.6-flash": { "enabled": true },
|
|
134
|
+
"antigravity/gemini-3.5-flash": { "enabled": true },
|
|
135
|
+
"antigravity/gemini-3.1-pro": { "enabled": true },
|
|
136
|
+
"antigravity/claude-sonnet-4-6": { "enabled": true }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
All primary environment variables start with `ANTIGRAVITY_`. The legacy `NOAGY_` prefix is also accepted for compatibility.
|
|
144
|
+
|
|
145
|
+
<!-- prettier-ignore -->
|
|
146
|
+
| Variable | Purpose |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `ANTIGRAVITY_BASE_URL` | Override the API base URL. It must be HTTPS, contain no URL credentials, and target an allowed Google APIs host. |
|
|
149
|
+
| `ANTIGRAVITY_PROJECT_ID` | Use a specific Cloud Code Assist project ID instead of discovery or the stable account fallback. |
|
|
150
|
+
| `ANTIGRAVITY_CALLBACK_HOST` | Bind OAuth callback to `127.0.0.1`, `::1`, or `localhost` only. Defaults to `127.0.0.1`. |
|
|
151
|
+
| `ANTIGRAVITY_USER_AGENT` | Override the request user-agent. |
|
|
152
|
+
| `ANTIGRAVITY_RUNTIME_MODEL` | Pin requests to a runtime model ID, bypassing discovered/fallback routing. |
|
|
153
|
+
| `ANTIGRAVITY_CATALOG_REFRESH_INTERVAL_MS` | Override catalog refresh TTL in ms. Defaults to 4 hours (`14400000`). Set to `0` to always refresh. |
|
|
154
|
+
| `ANTIGRAVITY_CLIENT_ID` | Use a custom Google OAuth client ID. |
|
|
155
|
+
| `ANTIGRAVITY_CLIENT_SECRET` | Use a custom Google OAuth client secret. Keep it out of source control and shell history. |
|
|
156
|
+
| `ANTIGRAVITY_NO_KEEPALIVE` | Set to `1` to skip the keep-alive connection pool. |
|
|
157
|
+
| `ANTIGRAVITY_NO_PREWARM` | Set to `1` to skip the TLS pre-warm request made when the extension loads. |
|
|
158
|
+
|
|
159
|
+
By default, the provider tries `https://daily-cloudcode-pa.googleapis.com`, then the sandbox host, then `https://cloudcode-pa.googleapis.com`. Prefer the built-in OAuth client unless you have a reason to use your own credentials.
|
|
160
|
+
|
|
161
|
+
### Latency
|
|
162
|
+
|
|
163
|
+
Provider requests reuse a keep-alive connection pool when the runtime supports it, so consecutive turns do not repeat the DNS, TCP, and TLS handshake. When `HTTP_PROXY`, `HTTPS_PROXY`, or `ALL_PROXY` is set, that pool is skipped so Pi's proxy-aware dispatcher is used instead. The connection is also opened when the extension loads so the first message of a session skips the handshake too. For the lowest time-to-first-token, pick a fast runtime: `gemini-3.8-flash` with reasoning off routes to `gemini-3.8-flash-low` at thinking level `LOW`. Setting `ANTIGRAVITY_PROJECT_ID` also removes the project-discovery round-trip when credentials do not already carry a project ID.
|
|
164
|
+
|
|
165
|
+
## Troubleshooting
|
|
166
|
+
|
|
167
|
+
- **No credentials / 401 / 403:** Run `/login antigravity` again, then check `/antigravity.doctor`.
|
|
168
|
+
- **Remote/headless machine — browser can't reach `localhost:51121`:** The callback binds to loopback only, so a browser on another machine can't hit it. You have two options:
|
|
169
|
+
- **Paste (no extra setup):** Run `/login antigravity`, open the shown URL and complete Google sign-in in _any_ browser. When it redirects to `http://localhost:51121/oauth-callback?…` and fails to load, copy that full URL from the address bar and paste it into the prompt Pi shows. The code is single-use and expires quickly, so paste promptly.
|
|
170
|
+
- **SSH tunnel (reusable):** From the machine with the browser, run `ssh -N -L 51121:127.0.0.1:51121 <user>@<server>` and keep it open, then run `/login antigravity` on the server. The redirect to `localhost:51121` tunnels through to the local callback automatically.
|
|
171
|
+
- **OAuth callback will not start:** Ensure port `51121` is free and `ANTIGRAVITY_CALLBACK_HOST` is a permitted loopback address.
|
|
172
|
+
- **Model is unavailable:** Run `/antigravity.models`; availability is account- and service-dependent.
|
|
173
|
+
- **Claude/GPT tool-call schema error:** Upgrade to the latest package release. The provider adapts Pi's JSON Schema tool definitions for the Cloud Code Assist custom-tool bridge.
|
|
174
|
+
- **Quota or rate limit:** Run `/antigravity.usage`. A `429` response usually indicates quota or rate limiting; changing models may still draw from the same shared pool.
|
|
175
|
+
- **Need a safe diagnostic:** `/antigravity.doctor` redacts recognized secrets from its error output. Still review output before sharing it publicly.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
This repo uses [Bun](https://bun.sh) for install, scripts, and CI. The published extension itself runs on Node (Pi's CLI).
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
bun install
|
|
183
|
+
bun run check
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The package declares its Pi extension in `package.json` under `pi.extensions`. See the [Pi package documentation](https://pi.dev/docs/latest/packages) for package installation, manifest, and gallery conventions.
|
|
187
|
+
|
|
188
|
+
## Support the project
|
|
189
|
+
|
|
190
|
+
If `pi-antigravity` is useful to you, consider [sponsoring the project on GitHub](https://github.com/sponsors/Rahularya01).
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
[MIT](LICENSE)
|