pi-ui-extend 0.1.72 → 0.1.77
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 +45 -3
- package/dist/app/cli/install.d.ts +20 -2
- package/dist/app/cli/install.js +128 -13
- package/dist/app/cli/startup-checks.d.ts +1 -0
- package/dist/app/cli/startup-checks.js +16 -0
- package/dist/app/commands/command-model-actions.js +4 -4
- package/dist/app/input/autocomplete-controller.js +1 -1
- package/dist/app/input/prompt-enhancer-controller.js +1 -1
- package/dist/app/runtime.d.ts +3 -1
- package/dist/app/runtime.js +24 -7
- package/external/pi-tools-suite/README.md +41 -7
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +20 -6
- package/external/pi-tools-suite/src/opencode-import/commands.ts +8 -5
- package/external/pi-tools-suite/src/opencode-import/importer.ts +63 -22
- package/external/pi-tools-suite/src/opencode-import/index.ts +14 -3
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/external/pi-tools-suite/src/web-search/index.ts +394 -29
- package/package.json +4 -4
- package/skills/context7/SKILL.md +10 -0
- package/skills/context7/scripts/context7.sh +3 -2
|
@@ -72,15 +72,18 @@ export function formatOpencodeImportResult(result: OpencodeImportResult): string
|
|
|
72
72
|
if (antigravity) providerLines.push(`- ${antigravity}`);
|
|
73
73
|
|
|
74
74
|
if (providerLines.length === 0) {
|
|
75
|
-
return `No
|
|
75
|
+
return `No OpenCode credentials were imported. Checked ${result.sourcePath}${result.antigravitySourcePath ? ` and ${result.antigravitySourcePath}` : ""}.`;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
const suffix = missingCount > 0 ? `\nMissing ${missingCount} known opencode provider entr${missingCount === 1 ? "y" : "ies"}.` : "";
|
|
79
|
-
|
|
79
|
+
const heading = result.wroteAuth ? `Imported OpenCode credentials into ${result.authPath}:` : `OpenCode credential check for ${result.authPath}:`;
|
|
80
|
+
return `${heading}\n${providerLines.join("\n")}${suffix}`;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
export function notificationLevel(result: OpencodeImportResult): "info" | "
|
|
83
|
+
export function notificationLevel(result: OpencodeImportResult): "info" | "warning" | "error" {
|
|
83
84
|
if (result.wroteAuth) return "info";
|
|
84
|
-
if (result.providers.some((provider) => provider.status === "auth-exists-use-force") || result.antigravity?.reason === "auth-exists-use-force") return "
|
|
85
|
-
return "error";
|
|
85
|
+
if (result.providers.some((provider) => provider.status === "auth-exists-use-force") || result.antigravity?.reason === "auth-exists-use-force") return "warning";
|
|
86
|
+
if (result.providers.some((provider) => provider.status === "invalid-source") || result.antigravity?.reason === "matching-account-not-found") return "error";
|
|
87
|
+
if (result.providers.some((provider) => provider.status === "already-imported") || result.antigravity?.reason === "already-imported") return "info";
|
|
88
|
+
return "warning";
|
|
86
89
|
}
|
|
@@ -55,16 +55,16 @@ export type OpencodeImportOptions = {
|
|
|
55
55
|
type Mapping = {
|
|
56
56
|
label: string;
|
|
57
57
|
sourceProvider: string;
|
|
58
|
-
targetProvider: string;
|
|
58
|
+
targetProvider: string | ((credential: OpencodeAuthCredential | undefined) => string);
|
|
59
59
|
transform: (credential: OpencodeAuthCredential) => PiAuthCredential | undefined;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
const AUTH_JSON_MAPPINGS: Mapping[] = [
|
|
63
63
|
{
|
|
64
|
-
label: "OpenAI
|
|
64
|
+
label: "OpenAI",
|
|
65
65
|
sourceProvider: "openai",
|
|
66
|
-
targetProvider: "openai-codex",
|
|
67
|
-
transform:
|
|
66
|
+
targetProvider: (credential) => (isOAuthCredential(credential) ? "openai-codex" : "openai"),
|
|
67
|
+
transform: transformOpenAiCredential,
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
label: "GitHub Copilot",
|
|
@@ -92,24 +92,45 @@ export function getDefaultOpencodeAuthPath(): string {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function transformOAuthCredential(credential: OpencodeAuthCredential): PiAuthCredential | undefined {
|
|
95
|
-
if (
|
|
95
|
+
if (
|
|
96
|
+
typeof credential.access !== "string" ||
|
|
97
|
+
!credential.access ||
|
|
98
|
+
typeof credential.refresh !== "string" ||
|
|
99
|
+
!credential.refresh ||
|
|
100
|
+
typeof credential.expires !== "number" ||
|
|
101
|
+
!Number.isFinite(credential.expires)
|
|
102
|
+
) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
96
105
|
return { ...credential, type: "oauth" };
|
|
97
106
|
}
|
|
98
107
|
|
|
99
108
|
function transformApiKeyCredential(credential: OpencodeAuthCredential): PiAuthCredential | undefined {
|
|
100
|
-
if (!credential.key) return undefined;
|
|
109
|
+
if (typeof credential.key !== "string" || !credential.key) return undefined;
|
|
101
110
|
return { ...credential, type: "api_key", key: credential.key };
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
function isOAuthCredential(credential: OpencodeAuthCredential | undefined): boolean {
|
|
114
|
+
return credential?.type === "oauth" || typeof credential?.access === "string" || typeof credential?.refresh === "string";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function transformOpenAiCredential(credential: OpencodeAuthCredential): PiAuthCredential | undefined {
|
|
118
|
+
return isOAuthCredential(credential) ? transformOAuthCredential(credential) : transformApiKeyCredential(credential);
|
|
119
|
+
}
|
|
120
|
+
|
|
104
121
|
function sameCredential(a: PiAuthCredential | undefined, b: PiAuthCredential | undefined): boolean {
|
|
105
122
|
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
function
|
|
125
|
+
function targetProvider(mapping: Mapping, credential: OpencodeAuthCredential | undefined): string {
|
|
126
|
+
return typeof mapping.targetProvider === "function" ? mapping.targetProvider(credential) : mapping.targetProvider;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function providerResult(mapping: Mapping, status: OpencodeProviderImportStatus, credential?: OpencodeAuthCredential): OpencodeProviderImportResult {
|
|
109
130
|
return {
|
|
110
131
|
label: mapping.label,
|
|
111
132
|
sourceProvider: mapping.sourceProvider,
|
|
112
|
-
targetProvider: mapping
|
|
133
|
+
targetProvider: targetProvider(mapping, credential),
|
|
113
134
|
status,
|
|
114
135
|
};
|
|
115
136
|
}
|
|
@@ -123,8 +144,15 @@ async function pathExists(path: string): Promise<boolean> {
|
|
|
123
144
|
}
|
|
124
145
|
}
|
|
125
146
|
|
|
147
|
+
async function readOpencodeAuth(sourcePath: string, environmentSource: string | undefined): Promise<OpencodeAuthData> {
|
|
148
|
+
if (environmentSource !== undefined) return parseOpencodeAuthContent(environmentSource);
|
|
149
|
+
if (!(await pathExists(sourcePath))) return {};
|
|
150
|
+
return readJsonFile<OpencodeAuthData>(sourcePath, {});
|
|
151
|
+
}
|
|
152
|
+
|
|
126
153
|
export async function importOpencodeAccounts(options: OpencodeImportOptions = {}): Promise<OpencodeImportResult> {
|
|
127
|
-
const
|
|
154
|
+
const environmentSource = options.sourcePath === undefined ? process.env.OPENCODE_AUTH_CONTENT : undefined;
|
|
155
|
+
const sourcePath = options.sourcePath ?? (environmentSource !== undefined ? "OPENCODE_AUTH_CONTENT" : getDefaultOpencodeAuthPath());
|
|
128
156
|
const authPath = options.authPath ?? getPiAuthPath();
|
|
129
157
|
const result: OpencodeImportResult = {
|
|
130
158
|
sourcePath,
|
|
@@ -138,8 +166,7 @@ export async function importOpencodeAccounts(options: OpencodeImportOptions = {}
|
|
|
138
166
|
const changedTargets = new Set<string>();
|
|
139
167
|
|
|
140
168
|
if (!options.skipAuthJson) {
|
|
141
|
-
const
|
|
142
|
-
const opencodeAuth = sourceExists ? await readJsonFile<OpencodeAuthData>(sourcePath, {}) : {};
|
|
169
|
+
const opencodeAuth = await readOpencodeAuth(sourcePath, environmentSource);
|
|
143
170
|
|
|
144
171
|
for (const mapping of AUTH_JSON_MAPPINGS) {
|
|
145
172
|
const sourceCredential = opencodeAuth[mapping.sourceProvider];
|
|
@@ -147,34 +174,35 @@ export async function importOpencodeAccounts(options: OpencodeImportOptions = {}
|
|
|
147
174
|
result.providers.push(providerResult(mapping, "source-missing"));
|
|
148
175
|
continue;
|
|
149
176
|
}
|
|
177
|
+
const resolvedTargetProvider = targetProvider(mapping, sourceCredential);
|
|
150
178
|
|
|
151
|
-
if (changedTargets.has(
|
|
152
|
-
result.providers.push(providerResult(mapping, "target-set-from-other-source"));
|
|
179
|
+
if (changedTargets.has(resolvedTargetProvider)) {
|
|
180
|
+
result.providers.push(providerResult(mapping, "target-set-from-other-source", sourceCredential));
|
|
153
181
|
continue;
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
const nextCredential = mapping.transform(sourceCredential);
|
|
157
185
|
if (!nextCredential) {
|
|
158
|
-
result.providers.push(providerResult(mapping, "invalid-source"));
|
|
186
|
+
result.providers.push(providerResult(mapping, "invalid-source", sourceCredential));
|
|
159
187
|
continue;
|
|
160
188
|
}
|
|
161
189
|
|
|
162
|
-
const existingCredential = piAuth[
|
|
190
|
+
const existingCredential = piAuth[resolvedTargetProvider];
|
|
163
191
|
if (sameCredential(existingCredential, nextCredential)) {
|
|
164
|
-
result.providers.push(providerResult(mapping, "already-imported"));
|
|
165
|
-
changedTargets.add(
|
|
192
|
+
result.providers.push(providerResult(mapping, "already-imported", sourceCredential));
|
|
193
|
+
changedTargets.add(resolvedTargetProvider);
|
|
166
194
|
continue;
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
if (existingCredential && !options.overwrite) {
|
|
170
|
-
result.providers.push(providerResult(mapping, "auth-exists-use-force"));
|
|
171
|
-
changedTargets.add(
|
|
198
|
+
result.providers.push(providerResult(mapping, "auth-exists-use-force", sourceCredential));
|
|
199
|
+
changedTargets.add(resolvedTargetProvider);
|
|
172
200
|
continue;
|
|
173
201
|
}
|
|
174
202
|
|
|
175
|
-
piAuth = { ...piAuth, [
|
|
176
|
-
changedTargets.add(
|
|
177
|
-
result.providers.push(providerResult(mapping, "imported"));
|
|
203
|
+
piAuth = { ...piAuth, [resolvedTargetProvider]: nextCredential };
|
|
204
|
+
changedTargets.add(resolvedTargetProvider);
|
|
205
|
+
result.providers.push(providerResult(mapping, "imported", sourceCredential));
|
|
178
206
|
}
|
|
179
207
|
|
|
180
208
|
if (result.providers.some((provider) => provider.status === "imported")) {
|
|
@@ -206,3 +234,16 @@ export async function importOpencodeAccounts(options: OpencodeImportOptions = {}
|
|
|
206
234
|
|
|
207
235
|
return result;
|
|
208
236
|
}
|
|
237
|
+
|
|
238
|
+
function parseOpencodeAuthContent(content: string): OpencodeAuthData {
|
|
239
|
+
let parsed: unknown;
|
|
240
|
+
try {
|
|
241
|
+
parsed = JSON.parse(content);
|
|
242
|
+
} catch {
|
|
243
|
+
throw new Error("OPENCODE_AUTH_CONTENT must contain a valid JSON object.");
|
|
244
|
+
}
|
|
245
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
246
|
+
throw new Error("OPENCODE_AUTH_CONTENT must contain a JSON object.");
|
|
247
|
+
}
|
|
248
|
+
return parsed as OpencodeAuthData;
|
|
249
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { formatOpencodeImportResult, notificationLevel, parseOpencodeImportCommandArgs } from "./commands";
|
|
3
3
|
import { importOpencodeAccounts } from "./importer";
|
|
4
4
|
|
|
@@ -8,10 +8,12 @@ export type { OpencodeImportOptions, OpencodeImportResult } from "./importer";
|
|
|
8
8
|
|
|
9
9
|
export default function opencodeImport(pi: ExtensionAPI): void {
|
|
10
10
|
pi.registerCommand("opencode-import", {
|
|
11
|
-
description: "Import
|
|
12
|
-
handler: async (args: string, ctx:
|
|
11
|
+
description: "Import supported OpenCode credentials and Antigravity accounts into Pi/Pix auth.json",
|
|
12
|
+
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
13
|
+
let wroteAuth = false;
|
|
13
14
|
try {
|
|
14
15
|
const result = await importOpencodeAccounts(parseOpencodeImportCommandArgs(args));
|
|
16
|
+
wroteAuth = result.wroteAuth;
|
|
15
17
|
const message = formatOpencodeImportResult(result);
|
|
16
18
|
if (ctx.ui?.notify) ctx.ui.notify(message, notificationLevel(result));
|
|
17
19
|
else console.log(message);
|
|
@@ -19,6 +21,15 @@ export default function opencodeImport(pi: ExtensionAPI): void {
|
|
|
19
21
|
const message = error instanceof Error ? error.message : String(error);
|
|
20
22
|
if (ctx.ui?.notify) ctx.ui.notify(message, "error");
|
|
21
23
|
else console.error(message);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!wroteAuth) return;
|
|
28
|
+
try {
|
|
29
|
+
await ctx.reload();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
console.error(`OpenCode credentials were imported, but resource reload failed: ${message}`);
|
|
22
33
|
}
|
|
23
34
|
},
|
|
24
35
|
});
|
|
@@ -246,8 +246,8 @@ export const WEB_SEARCH_TOOL_DESCRIPTIONS = {
|
|
|
246
246
|
name: "web_search",
|
|
247
247
|
label: "web-search",
|
|
248
248
|
description:
|
|
249
|
-
"Search the web for real-time information
|
|
250
|
-
promptSnippet: "Search the web for current or real-time information through
|
|
249
|
+
"Search the web for real-time information through Ollama, with automatic configured Tavily fallback. Credentials come from /web-credentials or environment variables. Supports per-call timeout_ms and PI_WEB_SEARCH_TIMEOUT_MS.",
|
|
250
|
+
promptSnippet: "Search the web for current or real-time information through Ollama, with Tavily fallback when configured.",
|
|
251
251
|
promptGuidelines: [
|
|
252
252
|
"Use web_search only for current public web information; keep queries focused and set max_results only when useful.",
|
|
253
253
|
"Never include secrets, tokens, or private repository data; do not use web_search for repo-local discovery—use repo_* or file/search tools.",
|
|
@@ -257,8 +257,8 @@ export const WEB_SEARCH_TOOL_DESCRIPTIONS = {
|
|
|
257
257
|
name: "web_fetch",
|
|
258
258
|
label: "web-fetch",
|
|
259
259
|
description:
|
|
260
|
-
"Fetch and extract text content from a web page URL
|
|
261
|
-
promptSnippet: "Fetch and extract text from a specific URL through
|
|
260
|
+
"Fetch and extract text content from a web page URL through Ollama, with automatic configured Tavily Extract fallback. Credentials come from /web-credentials or environment variables. Supports per-call timeout_ms and PI_WEB_SEARCH_TIMEOUT_MS.",
|
|
261
|
+
promptSnippet: "Fetch and extract text from a specific URL through Ollama, with Tavily Extract fallback when configured.",
|
|
262
262
|
promptGuidelines: [
|
|
263
263
|
"Use web_fetch for user-provided URLs or web_search results needing deeper reading; never pass secret/private-credential URLs, and use read for local files.",
|
|
264
264
|
],
|