pi-image-tools 1.3.0 → 1.3.1

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.
@@ -72,49 +72,51 @@ const LEGACY_KEYBINDING_NAME_MIGRATIONS: Record<string, string> = {
72
72
  deleteSessionNoninvasive: "app.session.deleteNoninvasive",
73
73
  };
74
74
 
75
- const APP_KEYBINDING_DEFAULTS: KeybindingDefaults = {
76
- "app.interrupt": "escape",
77
- "app.clear": "ctrl+c",
78
- "app.exit": "ctrl+d",
79
- "app.suspend": process.platform === "win32" ? [] : "ctrl+z",
80
- "app.thinking.cycle": "shift+tab",
81
- "app.model.cycleForward": "ctrl+p",
82
- "app.model.cycleBackward": "shift+ctrl+p",
83
- "app.model.select": "ctrl+l",
84
- "app.tools.expand": "ctrl+o",
85
- "app.thinking.toggle": "ctrl+t",
86
- "app.session.toggleNamedFilter": "ctrl+n",
87
- "app.editor.external": "ctrl+g",
88
- "app.message.followUp": "alt+enter",
89
- "app.message.dequeue": "alt+up",
90
- "app.clipboard.pasteImage": process.platform === "win32" ? "alt+v" : "ctrl+v",
91
- "app.session.new": [],
92
- "app.session.tree": [],
93
- "app.session.fork": [],
94
- "app.session.resume": [],
95
- "app.tree.foldOrUp": ["ctrl+left", "alt+left"],
96
- "app.tree.unfoldOrDown": ["ctrl+right", "alt+right"],
97
- "app.tree.editLabel": "shift+l",
98
- "app.tree.toggleLabelTimestamp": "shift+t",
99
- "app.session.togglePath": "ctrl+p",
100
- "app.session.toggleSort": "ctrl+s",
101
- "app.session.rename": "ctrl+r",
102
- "app.session.delete": "ctrl+d",
103
- "app.session.deleteNoninvasive": "ctrl+backspace",
104
- "app.models.save": "ctrl+s",
105
- "app.models.enableAll": "ctrl+a",
106
- "app.models.clearAll": "ctrl+x",
107
- "app.models.toggleProvider": "ctrl+p",
108
- "app.models.reorderUp": "alt+up",
109
- "app.models.reorderDown": "alt+down",
110
- "app.tree.filter.default": "ctrl+d",
111
- "app.tree.filter.noTools": "ctrl+t",
112
- "app.tree.filter.userOnly": "ctrl+u",
113
- "app.tree.filter.labeledOnly": "ctrl+l",
114
- "app.tree.filter.all": "ctrl+a",
115
- "app.tree.filter.cycleForward": "ctrl+o",
116
- "app.tree.filter.cycleBackward": "shift+ctrl+o",
117
- };
75
+ function getAppKeybindingDefaults(platform: NodeJS.Platform): KeybindingDefaults {
76
+ return {
77
+ "app.interrupt": "escape",
78
+ "app.clear": "ctrl+c",
79
+ "app.exit": "ctrl+d",
80
+ "app.suspend": platform === "win32" ? [] : "ctrl+z",
81
+ "app.thinking.cycle": "shift+tab",
82
+ "app.model.cycleForward": "ctrl+p",
83
+ "app.model.cycleBackward": "shift+ctrl+p",
84
+ "app.model.select": "ctrl+l",
85
+ "app.tools.expand": "ctrl+o",
86
+ "app.thinking.toggle": "ctrl+t",
87
+ "app.session.toggleNamedFilter": "ctrl+n",
88
+ "app.editor.external": "ctrl+g",
89
+ "app.message.followUp": "alt+enter",
90
+ "app.message.dequeue": "alt+up",
91
+ "app.clipboard.pasteImage": platform === "win32" ? "alt+v" : "ctrl+v",
92
+ "app.session.new": [],
93
+ "app.session.tree": [],
94
+ "app.session.fork": [],
95
+ "app.session.resume": [],
96
+ "app.tree.foldOrUp": ["ctrl+left", "alt+left"],
97
+ "app.tree.unfoldOrDown": ["ctrl+right", "alt+right"],
98
+ "app.tree.editLabel": "shift+l",
99
+ "app.tree.toggleLabelTimestamp": "shift+t",
100
+ "app.session.togglePath": "ctrl+p",
101
+ "app.session.toggleSort": "ctrl+s",
102
+ "app.session.rename": "ctrl+r",
103
+ "app.session.delete": "ctrl+d",
104
+ "app.session.deleteNoninvasive": "ctrl+backspace",
105
+ "app.models.save": "ctrl+s",
106
+ "app.models.enableAll": "ctrl+a",
107
+ "app.models.clearAll": "ctrl+x",
108
+ "app.models.toggleProvider": "ctrl+p",
109
+ "app.models.reorderUp": "alt+up",
110
+ "app.models.reorderDown": "alt+down",
111
+ "app.tree.filter.default": "ctrl+d",
112
+ "app.tree.filter.noTools": "ctrl+t",
113
+ "app.tree.filter.userOnly": "ctrl+u",
114
+ "app.tree.filter.labeledOnly": "ctrl+l",
115
+ "app.tree.filter.all": "ctrl+a",
116
+ "app.tree.filter.cycleForward": "ctrl+o",
117
+ "app.tree.filter.cycleBackward": "shift+ctrl+o",
118
+ };
119
+ }
118
120
 
119
121
  export interface RegisterImagePasteKeybindingsOptions {
120
122
  config: ImageToolsConfig;
@@ -186,7 +188,7 @@ function normalizeUserKeybindings(rawConfig: Record<string, unknown> | undefined
186
188
  return userKeybindings;
187
189
  }
188
190
 
189
- function getBuiltinKeybindingDefaults(): KeybindingDefaults {
191
+ function getBuiltinKeybindingDefaults(platform: NodeJS.Platform): KeybindingDefaults {
190
192
  const tuiDefaults: KeybindingDefaults = {};
191
193
  for (const [keybinding, definition] of Object.entries(TUI_KEYBINDINGS)) {
192
194
  tuiDefaults[keybinding] = definition.defaultKeys;
@@ -194,12 +196,12 @@ function getBuiltinKeybindingDefaults(): KeybindingDefaults {
194
196
 
195
197
  return {
196
198
  ...tuiDefaults,
197
- ...APP_KEYBINDING_DEFAULTS,
199
+ ...getAppKeybindingDefaults(platform),
198
200
  };
199
201
  }
200
202
 
201
- function getConfiguredBuiltinShortcuts(): Set<string> {
202
- const defaults = getBuiltinKeybindingDefaults();
203
+ function getConfiguredBuiltinShortcuts(platform: NodeJS.Platform): Set<string> {
204
+ const defaults = getBuiltinKeybindingDefaults(platform);
203
205
  const userKeybindings = normalizeUserKeybindings(readKeybindingsConfig());
204
206
  const builtinShortcuts = new Set<string>();
205
207
 
@@ -224,8 +226,8 @@ function getImagePasteShortcutCandidates(platform: NodeJS.Platform): KeyId[] {
224
226
  return ["ctrl+v", "alt+v", "ctrl+alt+v"];
225
227
  }
226
228
 
227
- function removeBuiltinConflicts(shortcuts: readonly KeyId[]): KeyId[] {
228
- const builtinShortcuts = getConfiguredBuiltinShortcuts();
229
+ function removeBuiltinConflicts(shortcuts: readonly KeyId[], platform: NodeJS.Platform): KeyId[] {
230
+ const builtinShortcuts = getConfiguredBuiltinShortcuts(platform);
229
231
 
230
232
  return shortcuts.filter((shortcut) => !builtinShortcuts.has(normalizeShortcutKey(shortcut)));
231
233
  }
@@ -238,7 +240,7 @@ export function getImagePasteShortcuts(
238
240
  config.shortcuts.avoidBuiltinConflicts || config.shortcuts.suppressBuiltinConflictWarnings;
239
241
  const candidates = config.shortcuts.pasteImage ?? getImagePasteShortcutCandidates(platform);
240
242
 
241
- return shouldAvoidBuiltinConflicts ? removeBuiltinConflicts(candidates) : [...candidates];
243
+ return shouldAvoidBuiltinConflicts ? removeBuiltinConflicts(candidates, platform) : [...candidates];
242
244
  }
243
245
 
244
246
  export function registerImagePasteKeybindings(
@@ -1,68 +1,68 @@
1
- import { spawnSync } from "node:child_process";
2
-
3
- import { isErrnoException } from "../errors.js";
4
-
5
- export const LIST_TYPES_TIMEOUT_MS = 1000;
6
- export const READ_TIMEOUT_MS = 5000;
7
- export const MAX_BUFFER_BYTES = 50 * 1024 * 1024;
8
-
9
- export interface CommandResult {
10
- ok: boolean;
11
- stdout: Buffer;
12
- stderr: Buffer;
13
- missingCommand: boolean;
14
- status: number | null;
15
- }
16
-
17
- export interface CommandRunOptions {
18
- environment?: NodeJS.ProcessEnv;
19
- maxBuffer?: number;
20
- timeout: number;
21
- windowsHide?: boolean;
22
- }
23
-
24
- export type CommandRunner = (
25
- command: string,
26
- args: readonly string[],
27
- options: CommandRunOptions,
28
- ) => CommandResult;
29
-
30
- function toBuffer(value: unknown): Buffer {
31
- if (Buffer.isBuffer(value)) {
32
- return value;
33
- }
34
-
35
- if (value instanceof Uint8Array) {
36
- return Buffer.from(value);
37
- }
38
-
39
- return Buffer.from(value === undefined || value === null ? "" : String(value), "utf8");
40
- }
41
-
42
- export const defaultCommandRunner: CommandRunner = (command, args, options) => {
43
- const result = spawnSync(command, [...args], {
44
- env: options.environment,
45
- maxBuffer: options.maxBuffer ?? MAX_BUFFER_BYTES,
46
- stdio: ["ignore", "pipe", "pipe"],
47
- timeout: options.timeout,
48
- windowsHide: options.windowsHide,
49
- });
50
-
51
- if (result.error) {
52
- return {
53
- ok: false,
54
- stdout: toBuffer(result.stdout),
55
- stderr: toBuffer(result.stderr),
56
- missingCommand: isErrnoException(result.error) && result.error.code === "ENOENT",
57
- status: result.status ?? null,
58
- };
59
- }
60
-
61
- return {
62
- ok: result.status === 0,
63
- stdout: toBuffer(result.stdout),
64
- stderr: toBuffer(result.stderr),
65
- missingCommand: false,
66
- status: result.status ?? null,
67
- };
68
- };
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ import { isErrnoException } from "../errors.js";
4
+
5
+ export const LIST_TYPES_TIMEOUT_MS = 1000;
6
+ export const READ_TIMEOUT_MS = 5000;
7
+ export const MAX_BUFFER_BYTES = 50 * 1024 * 1024;
8
+
9
+ export interface CommandResult {
10
+ ok: boolean;
11
+ stdout: Buffer;
12
+ stderr: Buffer;
13
+ missingCommand: boolean;
14
+ status: number | null;
15
+ }
16
+
17
+ export interface CommandRunOptions {
18
+ environment?: NodeJS.ProcessEnv;
19
+ maxBuffer?: number;
20
+ timeout: number;
21
+ windowsHide?: boolean;
22
+ }
23
+
24
+ export type CommandRunner = (
25
+ command: string,
26
+ args: readonly string[],
27
+ options: CommandRunOptions,
28
+ ) => CommandResult;
29
+
30
+ function toBuffer(value: unknown): Buffer {
31
+ if (Buffer.isBuffer(value)) {
32
+ return value;
33
+ }
34
+
35
+ if (value instanceof Uint8Array) {
36
+ return Buffer.from(value);
37
+ }
38
+
39
+ return Buffer.from(value === undefined || value === null ? "" : String(value), "utf8");
40
+ }
41
+
42
+ export const defaultCommandRunner: CommandRunner = (command, args, options) => {
43
+ const result = spawnSync(command, [...args], {
44
+ env: options.environment,
45
+ maxBuffer: options.maxBuffer ?? MAX_BUFFER_BYTES,
46
+ stdio: ["ignore", "pipe", "pipe"],
47
+ timeout: options.timeout,
48
+ windowsHide: options.windowsHide,
49
+ });
50
+
51
+ if (result.error) {
52
+ return {
53
+ ok: false,
54
+ stdout: toBuffer(result.stdout),
55
+ stderr: toBuffer(result.stderr),
56
+ missingCommand: isErrnoException(result.error) && result.error.code === "ENOENT",
57
+ status: result.status ?? null,
58
+ };
59
+ }
60
+
61
+ return {
62
+ ok: result.status === 0,
63
+ stdout: toBuffer(result.stdout),
64
+ stderr: toBuffer(result.stderr),
65
+ missingCommand: false,
66
+ status: result.status ?? null,
67
+ };
68
+ };
@@ -1,101 +1,101 @@
1
- import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
2
- import {
3
- defaultCommandRunner,
4
- MAX_BUFFER_BYTES,
5
- READ_TIMEOUT_MS,
6
- type CommandRunner,
7
- } from "./command-runner.js";
8
- import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
9
-
10
- const PNGF_SCRIPT = `try
11
- set imageData to the clipboard as «class PNGf»
12
- return imageData
13
- on error
14
- return ""
15
- end try`;
16
-
17
- export interface OsascriptPngfProviderOptions {
18
- priority?: number;
19
- commandRunner?: CommandRunner;
20
- commandExists?: CommandExists;
21
- }
22
-
23
- function parseAppleScriptPngfData(stdout: Buffer): Uint8Array | null {
24
- const text = stdout.toString("utf8").trim();
25
- if (text.length === 0) {
26
- return null;
27
- }
28
-
29
- const match = text.match(/«data\s+PNGf([0-9a-fA-F\s]+)»/i);
30
- if (!match) {
31
- return null;
32
- }
33
-
34
- const hex = match[1]?.replace(/\s+/g, "") ?? "";
35
- if (hex.length === 0 || hex.length % 2 !== 0) {
36
- return null;
37
- }
38
-
39
- const bytes = Buffer.from(hex, "hex");
40
- return bytes.length > 0 ? new Uint8Array(bytes) : null;
41
- }
42
-
43
- export class OsascriptPngfProvider implements ClipboardImageProvider {
44
- readonly capabilities;
45
- private readonly commandRunner: CommandRunner;
46
- private readonly commandExists: CommandExists;
47
-
48
- constructor(options: OsascriptPngfProviderOptions = {}) {
49
- this.capabilities = {
50
- id: "mac-osascript-pngf",
51
- name: "osascript PNGf",
52
- platforms: ["darwin"],
53
- priority: options.priority ?? 30,
54
- };
55
- this.commandRunner = options.commandRunner ?? defaultCommandRunner;
56
- this.commandExists = options.commandExists ?? defaultCommandExists;
57
- }
58
-
59
- isAvailable(context: ClipboardProviderContext): boolean {
60
- try {
61
- return this.commandExists("osascript", context);
62
- } catch {
63
- return false;
64
- }
65
- }
66
-
67
- read(context: ClipboardProviderContext): ClipboardReadResult {
68
- const wrapped = buildNamespaceWrappedCommand(
69
- "osascript",
70
- ["-e", PNGF_SCRIPT],
71
- context,
72
- this.commandExists,
73
- );
74
- const result = this.commandRunner(wrapped.command, wrapped.args, {
75
- environment: context.environment,
76
- maxBuffer: MAX_BUFFER_BYTES,
77
- timeout: READ_TIMEOUT_MS,
78
- });
79
-
80
- if (result.missingCommand) {
81
- return { available: false, image: null };
82
- }
83
-
84
- if (!result.ok || result.stdout.length === 0) {
85
- return { available: true, image: null };
86
- }
87
-
88
- const bytes = parseAppleScriptPngfData(result.stdout);
89
- if (!bytes) {
90
- return { available: true, image: null };
91
- }
92
-
93
- return {
94
- available: true,
95
- image: {
96
- bytes,
97
- mimeType: "image/png",
98
- },
99
- };
100
- }
101
- }
1
+ import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
2
+ import {
3
+ defaultCommandRunner,
4
+ MAX_BUFFER_BYTES,
5
+ READ_TIMEOUT_MS,
6
+ type CommandRunner,
7
+ } from "./command-runner.js";
8
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
9
+
10
+ const PNGF_SCRIPT = `try
11
+ set imageData to the clipboard as «class PNGf»
12
+ return imageData
13
+ on error
14
+ return ""
15
+ end try`;
16
+
17
+ export interface OsascriptPngfProviderOptions {
18
+ priority?: number;
19
+ commandRunner?: CommandRunner;
20
+ commandExists?: CommandExists;
21
+ }
22
+
23
+ function parseAppleScriptPngfData(stdout: Buffer): Uint8Array | null {
24
+ const text = stdout.toString("utf8").trim();
25
+ if (text.length === 0) {
26
+ return null;
27
+ }
28
+
29
+ const match = text.match(/«data\s+PNGf([0-9a-fA-F\s]+)»/i);
30
+ if (!match) {
31
+ return null;
32
+ }
33
+
34
+ const hex = match[1]?.replace(/\s+/g, "") ?? "";
35
+ if (hex.length === 0 || hex.length % 2 !== 0) {
36
+ return null;
37
+ }
38
+
39
+ const bytes = Buffer.from(hex, "hex");
40
+ return bytes.length > 0 ? new Uint8Array(bytes) : null;
41
+ }
42
+
43
+ export class OsascriptPngfProvider implements ClipboardImageProvider {
44
+ readonly capabilities;
45
+ private readonly commandRunner: CommandRunner;
46
+ private readonly commandExists: CommandExists;
47
+
48
+ constructor(options: OsascriptPngfProviderOptions = {}) {
49
+ this.capabilities = {
50
+ id: "mac-osascript-pngf",
51
+ name: "osascript PNGf",
52
+ platforms: ["darwin"],
53
+ priority: options.priority ?? 30,
54
+ };
55
+ this.commandRunner = options.commandRunner ?? defaultCommandRunner;
56
+ this.commandExists = options.commandExists ?? defaultCommandExists;
57
+ }
58
+
59
+ isAvailable(context: ClipboardProviderContext): boolean {
60
+ try {
61
+ return this.commandExists("osascript", context);
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ read(context: ClipboardProviderContext): ClipboardReadResult {
68
+ const wrapped = buildNamespaceWrappedCommand(
69
+ "osascript",
70
+ ["-e", PNGF_SCRIPT],
71
+ context,
72
+ this.commandExists,
73
+ );
74
+ const result = this.commandRunner(wrapped.command, wrapped.args, {
75
+ environment: context.environment,
76
+ maxBuffer: MAX_BUFFER_BYTES,
77
+ timeout: READ_TIMEOUT_MS,
78
+ });
79
+
80
+ if (result.missingCommand) {
81
+ return { available: false, image: null };
82
+ }
83
+
84
+ if (!result.ok || result.stdout.length === 0) {
85
+ return { available: true, image: null };
86
+ }
87
+
88
+ const bytes = parseAppleScriptPngfData(result.stdout);
89
+ if (!bytes) {
90
+ return { available: true, image: null };
91
+ }
92
+
93
+ return {
94
+ available: true,
95
+ image: {
96
+ bytes,
97
+ mimeType: "image/png",
98
+ },
99
+ };
100
+ }
101
+ }
@@ -1,77 +1,77 @@
1
- import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
2
- import {
3
- defaultCommandRunner,
4
- MAX_BUFFER_BYTES,
5
- READ_TIMEOUT_MS,
6
- type CommandRunner,
7
- } from "./command-runner.js";
8
- import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
9
-
10
- const PUBLIC_PNG_SCRIPT = `ObjC.import('AppKit');
11
- const pasteboard = $.NSPasteboard.generalPasteboard;
12
- const data = pasteboard.dataForType('public.png');
13
- if (!data) {
14
- $.exit(2);
15
- }
16
- $.NSFileHandle.fileHandleWithStandardOutput.writeData(data);`;
17
-
18
- export interface OsascriptPublicPngProviderOptions {
19
- priority?: number;
20
- commandRunner?: CommandRunner;
21
- commandExists?: CommandExists;
22
- }
23
-
24
- export class OsascriptPublicPngProvider implements ClipboardImageProvider {
25
- readonly capabilities;
26
- private readonly commandRunner: CommandRunner;
27
- private readonly commandExists: CommandExists;
28
-
29
- constructor(options: OsascriptPublicPngProviderOptions = {}) {
30
- this.capabilities = {
31
- id: "mac-osascript-public-png",
32
- name: "osascript public.png",
33
- platforms: ["darwin"],
34
- priority: options.priority ?? 20,
35
- };
36
- this.commandRunner = options.commandRunner ?? defaultCommandRunner;
37
- this.commandExists = options.commandExists ?? defaultCommandExists;
38
- }
39
-
40
- isAvailable(context: ClipboardProviderContext): boolean {
41
- try {
42
- return this.commandExists("osascript", context);
43
- } catch {
44
- return false;
45
- }
46
- }
47
-
48
- read(context: ClipboardProviderContext): ClipboardReadResult {
49
- const wrapped = buildNamespaceWrappedCommand(
50
- "osascript",
51
- ["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT],
52
- context,
53
- this.commandExists,
54
- );
55
- const result = this.commandRunner(wrapped.command, wrapped.args, {
56
- environment: context.environment,
57
- maxBuffer: MAX_BUFFER_BYTES,
58
- timeout: READ_TIMEOUT_MS,
59
- });
60
-
61
- if (result.missingCommand) {
62
- return { available: false, image: null };
63
- }
64
-
65
- if (!result.ok || result.stdout.length === 0) {
66
- return { available: true, image: null };
67
- }
68
-
69
- return {
70
- available: true,
71
- image: {
72
- bytes: new Uint8Array(result.stdout),
73
- mimeType: "image/png",
74
- },
75
- };
76
- }
77
- }
1
+ import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
2
+ import {
3
+ defaultCommandRunner,
4
+ MAX_BUFFER_BYTES,
5
+ READ_TIMEOUT_MS,
6
+ type CommandRunner,
7
+ } from "./command-runner.js";
8
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
9
+
10
+ const PUBLIC_PNG_SCRIPT = `ObjC.import('AppKit');
11
+ const pasteboard = $.NSPasteboard.generalPasteboard;
12
+ const data = pasteboard.dataForType('public.png');
13
+ if (!data) {
14
+ $.exit(2);
15
+ }
16
+ $.NSFileHandle.fileHandleWithStandardOutput.writeData(data);`;
17
+
18
+ export interface OsascriptPublicPngProviderOptions {
19
+ priority?: number;
20
+ commandRunner?: CommandRunner;
21
+ commandExists?: CommandExists;
22
+ }
23
+
24
+ export class OsascriptPublicPngProvider implements ClipboardImageProvider {
25
+ readonly capabilities;
26
+ private readonly commandRunner: CommandRunner;
27
+ private readonly commandExists: CommandExists;
28
+
29
+ constructor(options: OsascriptPublicPngProviderOptions = {}) {
30
+ this.capabilities = {
31
+ id: "mac-osascript-public-png",
32
+ name: "osascript public.png",
33
+ platforms: ["darwin"],
34
+ priority: options.priority ?? 20,
35
+ };
36
+ this.commandRunner = options.commandRunner ?? defaultCommandRunner;
37
+ this.commandExists = options.commandExists ?? defaultCommandExists;
38
+ }
39
+
40
+ isAvailable(context: ClipboardProviderContext): boolean {
41
+ try {
42
+ return this.commandExists("osascript", context);
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ read(context: ClipboardProviderContext): ClipboardReadResult {
49
+ const wrapped = buildNamespaceWrappedCommand(
50
+ "osascript",
51
+ ["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT],
52
+ context,
53
+ this.commandExists,
54
+ );
55
+ const result = this.commandRunner(wrapped.command, wrapped.args, {
56
+ environment: context.environment,
57
+ maxBuffer: MAX_BUFFER_BYTES,
58
+ timeout: READ_TIMEOUT_MS,
59
+ });
60
+
61
+ if (result.missingCommand) {
62
+ return { available: false, image: null };
63
+ }
64
+
65
+ if (!result.ok || result.stdout.length === 0) {
66
+ return { available: true, image: null };
67
+ }
68
+
69
+ return {
70
+ available: true,
71
+ image: {
72
+ bytes: new Uint8Array(result.stdout),
73
+ mimeType: "image/png",
74
+ },
75
+ };
76
+ }
77
+ }