@oh-my-pi/pi-coding-agent 3.37.1 → 4.0.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/CHANGELOG.md +87 -0
- package/README.md +44 -3
- package/docs/extensions.md +29 -4
- package/docs/sdk.md +3 -3
- package/package.json +5 -5
- package/src/cli/args.ts +8 -0
- package/src/config.ts +5 -15
- package/src/core/agent-session.ts +193 -47
- package/src/core/auth-storage.ts +16 -3
- package/src/core/bash-executor.ts +79 -14
- package/src/core/custom-commands/types.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/export-html/index.ts +33 -1
- package/src/core/export-html/template.css +99 -0
- package/src/core/export-html/template.generated.ts +1 -1
- package/src/core/export-html/template.js +133 -8
- package/src/core/extensions/index.ts +22 -4
- package/src/core/extensions/loader.ts +152 -214
- package/src/core/extensions/runner.ts +139 -79
- package/src/core/extensions/types.ts +143 -19
- package/src/core/extensions/wrapper.ts +5 -8
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +2 -1
- package/src/core/keybindings.ts +4 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +35 -26
- package/src/core/sdk.ts +96 -76
- package/src/core/settings-manager.ts +45 -14
- package/src/core/system-prompt.ts +5 -15
- package/src/core/tools/bash.ts +115 -54
- package/src/core/tools/find.ts +86 -7
- package/src/core/tools/grep.ts +27 -6
- package/src/core/tools/index.ts +15 -6
- package/src/core/tools/ls.ts +49 -18
- package/src/core/tools/render-utils.ts +2 -1
- package/src/core/tools/task/worker.ts +35 -12
- package/src/core/tools/web-search/auth.ts +37 -32
- package/src/core/tools/web-search/providers/anthropic.ts +35 -22
- package/src/index.ts +101 -9
- package/src/main.ts +60 -20
- package/src/migrations.ts +47 -2
- package/src/modes/index.ts +2 -2
- package/src/modes/interactive/components/assistant-message.ts +25 -7
- package/src/modes/interactive/components/bash-execution.ts +5 -0
- package/src/modes/interactive/components/branch-summary-message.ts +5 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +5 -0
- package/src/modes/interactive/components/countdown-timer.ts +38 -0
- package/src/modes/interactive/components/custom-editor.ts +8 -0
- package/src/modes/interactive/components/custom-message.ts +5 -0
- package/src/modes/interactive/components/footer.ts +2 -5
- package/src/modes/interactive/components/hook-input.ts +29 -20
- package/src/modes/interactive/components/hook-selector.ts +52 -38
- package/src/modes/interactive/components/index.ts +39 -0
- package/src/modes/interactive/components/login-dialog.ts +160 -0
- package/src/modes/interactive/components/model-selector.ts +10 -2
- package/src/modes/interactive/components/session-selector.ts +5 -1
- package/src/modes/interactive/components/settings-defs.ts +9 -0
- package/src/modes/interactive/components/status-line/segments.ts +3 -3
- package/src/modes/interactive/components/tool-execution.ts +9 -16
- package/src/modes/interactive/components/tree-selector.ts +1 -6
- package/src/modes/interactive/interactive-mode.ts +466 -215
- package/src/modes/interactive/theme/theme.ts +50 -2
- package/src/modes/print-mode.ts +78 -31
- package/src/modes/rpc/rpc-mode.ts +186 -78
- package/src/modes/rpc/rpc-types.ts +10 -3
- package/src/prompts/system-prompt.md +36 -28
- package/src/utils/clipboard.ts +90 -50
- package/src/utils/image-convert.ts +1 -1
- package/src/utils/image-resize.ts +1 -1
- package/src/utils/tools-manager.ts +2 -2
package/src/utils/clipboard.ts
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
|
+
import { unlink } from "node:fs/promises";
|
|
1
2
|
import { platform } from "node:os";
|
|
2
3
|
import { nanoid } from "nanoid";
|
|
3
4
|
|
|
5
|
+
const PREFERRED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"] as const;
|
|
6
|
+
|
|
7
|
+
function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
8
|
+
return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function baseMimeType(mimeType: string): string {
|
|
12
|
+
const base = mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
|
|
13
|
+
return base === "image/jpg" ? "image/jpeg" : base;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function selectPreferredImageMimeType(mimeTypes: string[]): string | null {
|
|
17
|
+
const normalized = mimeTypes
|
|
18
|
+
.map((t) => t.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map((t) => ({ raw: t, base: baseMimeType(t) }));
|
|
21
|
+
|
|
22
|
+
for (const preferred of PREFERRED_IMAGE_MIME_TYPES) {
|
|
23
|
+
const match = normalized.find((t) => t.base === preferred);
|
|
24
|
+
if (match) {
|
|
25
|
+
return match.raw;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const anyImage = normalized.find((t) => t.base.startsWith("image/"));
|
|
30
|
+
return anyImage?.raw ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
4
33
|
async function spawnWithTimeout(cmd: string[], input: string, timeoutMs: number): Promise<void> {
|
|
5
34
|
const proc = Bun.spawn(cmd, { stdin: "pipe" });
|
|
6
35
|
|
|
@@ -22,15 +51,18 @@ async function spawnWithTimeout(cmd: string[], input: string, timeoutMs: number)
|
|
|
22
51
|
}
|
|
23
52
|
|
|
24
53
|
async function spawnAndRead(cmd: string[], timeoutMs: number): Promise<Buffer | null> {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
28
|
-
setTimeout(() => reject(new Error("Clipboard operation timed out")), timeoutMs);
|
|
29
|
-
});
|
|
54
|
+
let proc: ReturnType<typeof Bun.spawn> | null = null;
|
|
30
55
|
|
|
31
56
|
try {
|
|
57
|
+
proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
58
|
+
|
|
59
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
60
|
+
setTimeout(() => reject(new Error("Clipboard operation timed out")), timeoutMs);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const stdoutStream = proc.stdout as ReadableStream<Uint8Array>;
|
|
32
64
|
const [exitCode, stdout] = await Promise.race([
|
|
33
|
-
Promise.all([proc.exited, new Response(
|
|
65
|
+
Promise.all([proc.exited, new Response(stdoutStream).arrayBuffer()]),
|
|
34
66
|
timeoutPromise,
|
|
35
67
|
]);
|
|
36
68
|
|
|
@@ -42,7 +74,7 @@ async function spawnAndRead(cmd: string[], timeoutMs: number): Promise<Buffer |
|
|
|
42
74
|
} catch {
|
|
43
75
|
return null;
|
|
44
76
|
} finally {
|
|
45
|
-
proc
|
|
77
|
+
proc?.kill();
|
|
46
78
|
}
|
|
47
79
|
}
|
|
48
80
|
|
|
@@ -56,6 +88,19 @@ export async function copyToClipboard(text: string): Promise<void> {
|
|
|
56
88
|
} else if (p === "win32") {
|
|
57
89
|
await spawnWithTimeout(["clip"], text, timeout);
|
|
58
90
|
} else {
|
|
91
|
+
const wayland = isWaylandSession();
|
|
92
|
+
if (wayland) {
|
|
93
|
+
const wlCopyPath = Bun.which("wl-copy");
|
|
94
|
+
if (wlCopyPath) {
|
|
95
|
+
// Fire-and-forget: wl-copy may not exit promptly, so we unref to avoid blocking
|
|
96
|
+
const proc = Bun.spawn([wlCopyPath], { stdin: "pipe" });
|
|
97
|
+
proc.stdin.write(text);
|
|
98
|
+
proc.stdin.end();
|
|
99
|
+
proc.unref();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
59
104
|
// Linux - try xclip first, fall back to xsel
|
|
60
105
|
try {
|
|
61
106
|
await spawnWithTimeout(["xclip", "-selection", "clipboard"], text, timeout);
|
|
@@ -66,7 +111,8 @@ export async function copyToClipboard(text: string): Promise<void> {
|
|
|
66
111
|
} catch (error) {
|
|
67
112
|
const msg = error instanceof Error ? error.message : String(error);
|
|
68
113
|
if (p === "linux") {
|
|
69
|
-
|
|
114
|
+
const tools = isWaylandSession() ? "wl-copy, xclip, or xsel" : "xclip or xsel";
|
|
115
|
+
throw new Error(`Failed to copy to clipboard. Install ${tools}: ${msg}`);
|
|
70
116
|
}
|
|
71
117
|
throw new Error(`Failed to copy to clipboard: ${msg}`);
|
|
72
118
|
}
|
|
@@ -82,7 +128,7 @@ export interface ClipboardImage {
|
|
|
82
128
|
* Returns null if no image is in clipboard or clipboard access fails.
|
|
83
129
|
*
|
|
84
130
|
* Supported platforms:
|
|
85
|
-
* - Linux: requires xclip
|
|
131
|
+
* - Linux: requires wl-paste (Wayland) or xclip (X11)
|
|
86
132
|
* - macOS: uses osascript + pbpaste
|
|
87
133
|
* - Windows: uses PowerShell
|
|
88
134
|
*/
|
|
@@ -106,64 +152,59 @@ export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
|
|
|
106
152
|
}
|
|
107
153
|
|
|
108
154
|
async function readImageLinux(timeout: number): Promise<ClipboardImage | null> {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
155
|
+
const wayland = isWaylandSession();
|
|
156
|
+
if (wayland) {
|
|
157
|
+
const image = await readImageWayland(timeout);
|
|
158
|
+
if (image) return image;
|
|
159
|
+
}
|
|
112
160
|
|
|
113
161
|
return await readImageX11(timeout);
|
|
114
162
|
}
|
|
115
163
|
|
|
116
164
|
async function readImageWayland(timeout: number): Promise<ClipboardImage | null> {
|
|
117
|
-
// wl-paste --list-types shows available MIME types
|
|
118
165
|
const types = await spawnAndRead(["wl-paste", "--list-types"], timeout);
|
|
119
166
|
if (!types) return null;
|
|
120
167
|
|
|
121
|
-
const typeList = types
|
|
168
|
+
const typeList = types
|
|
169
|
+
.toString("utf-8")
|
|
170
|
+
.split(/\r?\n/)
|
|
171
|
+
.map((t) => t.trim())
|
|
172
|
+
.filter(Boolean);
|
|
122
173
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{ type: "image/png", mimeType: "image/png" },
|
|
126
|
-
{ type: "image/jpeg", mimeType: "image/jpeg" },
|
|
127
|
-
];
|
|
174
|
+
const selectedType = selectPreferredImageMimeType(typeList);
|
|
175
|
+
if (!selectedType) return null;
|
|
128
176
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const imageData = await spawnAndRead(["wl-paste", "--type", type], timeout);
|
|
132
|
-
if (imageData && imageData.length > 0) {
|
|
133
|
-
return {
|
|
134
|
-
data: imageData.toString("base64"),
|
|
135
|
-
mimeType,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
177
|
+
const imageData = await spawnAndRead(["wl-paste", "--type", selectedType, "--no-newline"], timeout);
|
|
178
|
+
if (!imageData || imageData.length === 0) return null;
|
|
140
179
|
|
|
141
|
-
return
|
|
180
|
+
return {
|
|
181
|
+
data: imageData.toString("base64"),
|
|
182
|
+
mimeType: baseMimeType(selectedType),
|
|
183
|
+
};
|
|
142
184
|
}
|
|
143
185
|
|
|
144
186
|
async function readImageX11(timeout: number): Promise<ClipboardImage | null> {
|
|
145
|
-
// Check available targets in clipboard
|
|
146
187
|
const targets = await spawnAndRead(["xclip", "-selection", "clipboard", "-t", "TARGETS", "-o"], timeout);
|
|
147
|
-
if (!targets) return null;
|
|
148
188
|
|
|
149
|
-
|
|
189
|
+
let candidateTypes: string[] = [];
|
|
190
|
+
if (targets) {
|
|
191
|
+
candidateTypes = targets
|
|
192
|
+
.toString("utf-8")
|
|
193
|
+
.split(/\r?\n/)
|
|
194
|
+
.map((t) => t.trim())
|
|
195
|
+
.filter(Boolean);
|
|
196
|
+
}
|
|
150
197
|
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
{ target: "image/png", mimeType: "image/png" },
|
|
154
|
-
{ target: "image/jpeg", mimeType: "image/jpeg" },
|
|
155
|
-
{ target: "image/jpg", mimeType: "image/jpeg" },
|
|
156
|
-
];
|
|
198
|
+
const preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;
|
|
199
|
+
const tryTypes = preferred ? [preferred, ...PREFERRED_IMAGE_MIME_TYPES] : [...PREFERRED_IMAGE_MIME_TYPES];
|
|
157
200
|
|
|
158
|
-
for (const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
};
|
|
166
|
-
}
|
|
201
|
+
for (const mimeType of tryTypes) {
|
|
202
|
+
const imageData = await spawnAndRead(["xclip", "-selection", "clipboard", "-t", mimeType, "-o"], timeout);
|
|
203
|
+
if (imageData && imageData.length > 0) {
|
|
204
|
+
return {
|
|
205
|
+
data: imageData.toString("base64"),
|
|
206
|
+
mimeType: baseMimeType(mimeType),
|
|
207
|
+
};
|
|
167
208
|
}
|
|
168
209
|
}
|
|
169
210
|
|
|
@@ -222,7 +263,6 @@ async function readImageMacOS(timeout: number): Promise<ClipboardImage | null> {
|
|
|
222
263
|
if (await file.exists()) {
|
|
223
264
|
const buffer = await file.arrayBuffer();
|
|
224
265
|
await Bun.write(tempFile, ""); // Clear file
|
|
225
|
-
const { unlink } = await import("fs/promises");
|
|
226
266
|
await unlink(tempFile).catch(() => {});
|
|
227
267
|
|
|
228
268
|
if (buffer.byteLength > 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
|
-
import { getImageDimensionsWithImageMagick, resizeWithImageMagick } from "./image-magick
|
|
2
|
+
import { getImageDimensionsWithImageMagick, resizeWithImageMagick } from "./image-magick";
|
|
3
3
|
|
|
4
4
|
export interface ImageResizeOptions {
|
|
5
5
|
maxWidth?: number; // Default: 2000
|
|
@@ -2,9 +2,9 @@ import { chmodSync, createWriteStream, existsSync, mkdirSync, renameSync, rmSync
|
|
|
2
2
|
import { arch, platform } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { APP_NAME,
|
|
5
|
+
import { APP_NAME, getBinDir } from "../config";
|
|
6
6
|
|
|
7
|
-
const TOOLS_DIR =
|
|
7
|
+
const TOOLS_DIR = getBinDir();
|
|
8
8
|
|
|
9
9
|
interface ToolConfig {
|
|
10
10
|
name: string;
|