pi-image-tools 1.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 +7 -0
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/config/config.example.json +3 -0
- package/index.ts +3 -0
- package/package.json +52 -0
- package/src/clipboard.ts +117 -0
- package/src/commands.ts +79 -0
- package/src/image-preview.ts +469 -0
- package/src/index.ts +260 -0
- package/src/inline-user-preview.ts +345 -0
- package/src/keybindings.ts +15 -0
- package/src/recent-images.ts +437 -0
- package/src/temp-file.ts +82 -0
- package/src/types.ts +20 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MasuRii
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# pi-image-tools
|
|
2
|
+
|
|
3
|
+
Image attachment and preview extension for the **Pi coding agent**.
|
|
4
|
+
|
|
5
|
+
This extension focuses on one workflow: quickly attach a clipboard image (or pick a recent screenshot) to the message you are about to send, and then render an inline preview in the TUI chat.
|
|
6
|
+
|
|
7
|
+
> **Windows-only:** `pi-image-tools` only registers commands/shortcuts on Windows (`win32`). On macOS/Linux it does nothing.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Paste images into your next message:
|
|
14
|
+
- `/paste-image clipboard` (default) reads an image from the clipboard and queues it for the next send.
|
|
15
|
+
- `/paste-image recent` opens a picker for recent screenshots/images and queues the selected file.
|
|
16
|
+
- Keyboard shortcuts for fast paste:
|
|
17
|
+
- `alt+v`
|
|
18
|
+
- `ctrl+alt+v`
|
|
19
|
+
- Inline image preview in the chat after you send your message (up to **3** images previewed per message).
|
|
20
|
+
- Recent-images support:
|
|
21
|
+
- Searches common Windows screenshot locations by default.
|
|
22
|
+
- Caches images you pasted from clipboard so they also show up in the recent picker.
|
|
23
|
+
- Preview modes:
|
|
24
|
+
- **Sixel** (preferred on Windows) when the PowerShell `Sixel` module is available.
|
|
25
|
+
- **Native** fallback rendering when Sixel conversion is unavailable.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Local extension folder
|
|
30
|
+
|
|
31
|
+
Place this folder in:
|
|
32
|
+
|
|
33
|
+
- Global: `~/.pi/agent/extensions/pi-image-tools`
|
|
34
|
+
- Project: `.pi/extensions/pi-image-tools`
|
|
35
|
+
|
|
36
|
+
Pi auto-discovers these paths.
|
|
37
|
+
|
|
38
|
+
### As an npm package
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pi install npm:pi-image-tools
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or from git:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pi install git:github.com/MasuRii/pi-image-tools
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### Paste from clipboard
|
|
53
|
+
|
|
54
|
+
Command:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
/paste-image clipboard
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Notes:
|
|
61
|
+
|
|
62
|
+
- `/paste-image` (with no args) behaves the same as `/paste-image clipboard`.
|
|
63
|
+
- The extension inserts a marker into your draft (`[ Image Attached]`). When you send, that marker is removed and the queued image(s) are attached to the outgoing message.
|
|
64
|
+
- If you remove all markers from your draft before sending, the pending queued images are discarded.
|
|
65
|
+
|
|
66
|
+
### Paste from recent images
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
/paste-image recent
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This opens an interactive picker (requires Pi’s interactive TUI mode). The extension searches for recent images and shows a list like:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
01. Screenshot 2026-03-02 142233.png • 2m ago • 412 KB • C:\Users\...\Pictures\Screenshots\...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
After selection, the image is queued for your next send.
|
|
79
|
+
|
|
80
|
+
### Shortcuts
|
|
81
|
+
|
|
82
|
+
These shortcuts are equivalent to `/paste-image clipboard`:
|
|
83
|
+
|
|
84
|
+
- `alt+v`
|
|
85
|
+
- `ctrl+alt+v`
|
|
86
|
+
|
|
87
|
+
## Recent images: what gets searched
|
|
88
|
+
|
|
89
|
+
`/paste-image recent` searches Windows paths in this order:
|
|
90
|
+
|
|
91
|
+
1. The **recent cache directory** (images you pasted via clipboard are cached here).
|
|
92
|
+
2. If configured via environment, the directories from `PI_IMAGE_TOOLS_RECENT_DIRS`.
|
|
93
|
+
3. Otherwise, these defaults:
|
|
94
|
+
- `~/Pictures/Screenshots`
|
|
95
|
+
- `~/OneDrive/Pictures/Screenshots`
|
|
96
|
+
- `~/Desktop` (only files with screenshot-like names such as `Screenshot*`, `Snip*`, `IMG_*`, etc.)
|
|
97
|
+
|
|
98
|
+
Supported file types: `.png`, `.jpg`/`.jpeg`, `.webp`, `.gif`, `.bmp`.
|
|
99
|
+
|
|
100
|
+
### Environment variables
|
|
101
|
+
|
|
102
|
+
- `PI_IMAGE_TOOLS_RECENT_DIRS`
|
|
103
|
+
- Semicolon-separated list of directories to search (Windows-style):
|
|
104
|
+
- Example: `C:\Users\you\Pictures\Screenshots;D:\Shares\Screens`
|
|
105
|
+
- `PI_IMAGE_TOOLS_RECENT_CACHE_DIR`
|
|
106
|
+
- Overrides where clipboard-pasted images are cached.
|
|
107
|
+
- Default: `%TEMP%\pi-image-tools\recent-cache`
|
|
108
|
+
|
|
109
|
+
## Preview rendering (native vs Sixel)
|
|
110
|
+
|
|
111
|
+
When Pi displays a user message that contains image attachments, `pi-image-tools` renders an inline preview block under the message.
|
|
112
|
+
|
|
113
|
+
- **Sixel preview (Windows):**
|
|
114
|
+
- The extension tries to detect (and, if missing, install) the PowerShell module `Sixel` under the current user.
|
|
115
|
+
- It then converts the image to a Sixel escape sequence via PowerShell and renders it inline.
|
|
116
|
+
- **Native preview fallback:**
|
|
117
|
+
- If Sixel is unavailable or conversion fails, the extension renders via `@mariozechner/pi-tui`’s `Image` component.
|
|
118
|
+
- When a fallback is used, a warning line may be shown under the preview (and a one-time warning notification can appear on session start).
|
|
119
|
+
|
|
120
|
+
Limit: only the first **3** images in a message are previewed.
|
|
121
|
+
|
|
122
|
+
## Dependencies / PowerShell notes
|
|
123
|
+
|
|
124
|
+
### Clipboard access
|
|
125
|
+
|
|
126
|
+
- **Optional native module:** `@mariozechner/clipboard` (declared as an optional dependency).
|
|
127
|
+
- If it is available, `pi-image-tools` uses it first.
|
|
128
|
+
- **PowerShell fallback (Windows):** if the native module is unavailable, the extension calls `powershell.exe` to read an image from the clipboard using .NET (`System.Windows.Forms.Clipboard`).
|
|
129
|
+
|
|
130
|
+
### Sixel module
|
|
131
|
+
|
|
132
|
+
- Sixel preview uses the **PowerShell module** `Sixel`.
|
|
133
|
+
- `pi-image-tools` attempts to install it automatically (CurrentUser scope) using either `Install-Module` or `Install-PSResource` (depending on what your PowerShell supports).
|
|
134
|
+
|
|
135
|
+
If your environment blocks module installation, you can install it manually (in a PowerShell prompt):
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
Install-Module -Name Sixel -Scope CurrentUser -Force -AllowClobber
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
Runtime config is stored at:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
~/.pi/agent/extensions/pi-image-tools/config.json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A starter file is included as:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
config/config.example.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Currently the template only contains:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{ "enabled": true }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Troubleshooting
|
|
162
|
+
|
|
163
|
+
### Nothing happens on `/paste-image` or shortcuts
|
|
164
|
+
|
|
165
|
+
- This extension is **Windows-only**. On non-Windows platforms it does not register `/paste-image`.
|
|
166
|
+
|
|
167
|
+
### “/paste-image recent requires interactive TUI mode.”
|
|
168
|
+
|
|
169
|
+
- The recent picker uses an interactive selection UI. Run Pi in interactive mode (TUI) and retry.
|
|
170
|
+
|
|
171
|
+
### “No image found in clipboard.”
|
|
172
|
+
|
|
173
|
+
- Confirm you copied an actual image (not just a file path or text).
|
|
174
|
+
- If clipboard reads are failing in general, PowerShell may be restricted by policy or your environment may not allow access to `System.Windows.Forms.Clipboard`.
|
|
175
|
+
|
|
176
|
+
### Recent picker is empty
|
|
177
|
+
|
|
178
|
+
- By default only a few directories are searched. Configure additional directories via `PI_IMAGE_TOOLS_RECENT_DIRS`.
|
|
179
|
+
- Clipboard-pasted images are cached under `%TEMP%\pi-image-tools\recent-cache` (override with `PI_IMAGE_TOOLS_RECENT_CACHE_DIR`).
|
|
180
|
+
|
|
181
|
+
### Preview shows a warning about Sixel
|
|
182
|
+
|
|
183
|
+
- The extension falls back to native preview when the `Sixel` PowerShell module is missing or cannot be installed.
|
|
184
|
+
- Install the module manually (see above) and restart Pi.
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm run build
|
|
190
|
+
npm run lint
|
|
191
|
+
npm run test
|
|
192
|
+
npm run check
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Project layout
|
|
196
|
+
|
|
197
|
+
- `index.ts` - root Pi auto-discovery entrypoint
|
|
198
|
+
- `src/commands.ts` - `/paste-image` command registration and argument handling
|
|
199
|
+
- `src/keybindings.ts` - `alt+v` / `ctrl+alt+v` shortcut registration
|
|
200
|
+
- `src/clipboard.ts` - clipboard image read (optional native module + PowerShell fallback)
|
|
201
|
+
- `src/recent-images.ts` - recent discovery + cache management (`PI_IMAGE_TOOLS_RECENT_DIRS`, `PI_IMAGE_TOOLS_RECENT_CACHE_DIR`)
|
|
202
|
+
- `src/image-preview.ts` - preview item building, Sixel conversion, and message renderer
|
|
203
|
+
- `src/inline-user-preview.ts` - patches Pi TUI message rendering to show inline previews
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-image-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Image attachment and rendering extension for Pi TUI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"config/config.example.json",
|
|
14
|
+
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
|
|
20
|
+
"lint": "npm run build",
|
|
21
|
+
"test": "node --test",
|
|
22
|
+
"check": "npm run lint && npm run test"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"pi-package",
|
|
26
|
+
"pi",
|
|
27
|
+
"pi-extension",
|
|
28
|
+
"image",
|
|
29
|
+
"clipboard",
|
|
30
|
+
"windows"
|
|
31
|
+
],
|
|
32
|
+
"author": "MasuRii",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"pi": {
|
|
41
|
+
"extensions": [
|
|
42
|
+
"./index.ts"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
47
|
+
"@mariozechner/pi-tui": "*"
|
|
48
|
+
},
|
|
49
|
+
"optionalDependencies": {
|
|
50
|
+
"@mariozechner/clipboard": "*"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/clipboard.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
|
|
4
|
+
import type { ClipboardImage, ClipboardModule } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
let cachedClipboardModule: ClipboardModule | null | undefined;
|
|
9
|
+
|
|
10
|
+
function loadClipboardModule(): ClipboardModule | null {
|
|
11
|
+
if (cachedClipboardModule !== undefined) {
|
|
12
|
+
return cachedClipboardModule;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
cachedClipboardModule = require("@mariozechner/clipboard") as ClipboardModule;
|
|
17
|
+
} catch {
|
|
18
|
+
cachedClipboardModule = null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return cachedClipboardModule;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function readClipboardImageViaNativeModule(): Promise<ClipboardImage | null> {
|
|
25
|
+
const clipboard = loadClipboardModule();
|
|
26
|
+
if (!clipboard || !clipboard.hasImage()) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const imageData = await clipboard.getImageBinary();
|
|
31
|
+
if (!imageData || imageData.length === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
|
|
36
|
+
return { bytes, mimeType: "image/png" };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function encodePowerShell(script: string): string {
|
|
40
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readClipboardImageViaPowerShell(): ClipboardImage | null {
|
|
44
|
+
const script = `
|
|
45
|
+
$ErrorActionPreference = 'Stop'
|
|
46
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
47
|
+
Add-Type -AssemblyName System.Drawing
|
|
48
|
+
|
|
49
|
+
if (-not [System.Windows.Forms.Clipboard]::ContainsImage()) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
$image = [System.Windows.Forms.Clipboard]::GetImage()
|
|
54
|
+
if ($null -eq $image) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$stream = New-Object System.IO.MemoryStream
|
|
59
|
+
try {
|
|
60
|
+
$image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
61
|
+
[System.Convert]::ToBase64String($stream.ToArray())
|
|
62
|
+
} finally {
|
|
63
|
+
$stream.Dispose()
|
|
64
|
+
$image.Dispose()
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
const result = spawnSync(
|
|
69
|
+
"powershell.exe",
|
|
70
|
+
[
|
|
71
|
+
"-NoProfile",
|
|
72
|
+
"-NonInteractive",
|
|
73
|
+
"-ExecutionPolicy",
|
|
74
|
+
"Bypass",
|
|
75
|
+
"-STA",
|
|
76
|
+
"-EncodedCommand",
|
|
77
|
+
encodePowerShell(script),
|
|
78
|
+
],
|
|
79
|
+
{
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
timeout: 6000,
|
|
82
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
83
|
+
windowsHide: true,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (result.error || result.status !== 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const base64 = result.stdout.trim();
|
|
92
|
+
if (!base64) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const bytes = Buffer.from(base64, "base64");
|
|
98
|
+
if (bytes.length === 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return { bytes: new Uint8Array(bytes), mimeType: "image/png" };
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function readClipboardImage(platform: NodeJS.Platform = process.platform): Promise<ClipboardImage | null> {
|
|
108
|
+
if (platform !== "win32") {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
return (await readClipboardImageViaNativeModule()) ?? readClipboardImageViaPowerShell();
|
|
114
|
+
} catch {
|
|
115
|
+
return readClipboardImageViaPowerShell();
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/commands.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import type { PasteImageCommandHandlers } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const SUBCOMMAND_CLIPBOARD = "clipboard";
|
|
6
|
+
const SUBCOMMAND_RECENT = "recent";
|
|
7
|
+
|
|
8
|
+
const ARGUMENT_COMPLETIONS = [
|
|
9
|
+
{
|
|
10
|
+
value: SUBCOMMAND_CLIPBOARD,
|
|
11
|
+
label: SUBCOMMAND_CLIPBOARD,
|
|
12
|
+
description: "Attach image from clipboard",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
value: SUBCOMMAND_RECENT,
|
|
16
|
+
label: SUBCOMMAND_RECENT,
|
|
17
|
+
description: "Open recent images picker and attach selected image",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: "help",
|
|
21
|
+
label: "help",
|
|
22
|
+
description: "Show usage",
|
|
23
|
+
},
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
function parseArgs(args: string): string[] {
|
|
27
|
+
return args
|
|
28
|
+
.trim()
|
|
29
|
+
.split(/\s+/)
|
|
30
|
+
.map((token) => token.trim().toLowerCase())
|
|
31
|
+
.filter((token) => token.length > 0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function usageMessage(): string {
|
|
35
|
+
return "Usage: /paste-image [clipboard|recent]";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function registerPasteImageCommand(
|
|
39
|
+
pi: ExtensionAPI,
|
|
40
|
+
handlers: PasteImageCommandHandlers,
|
|
41
|
+
): void {
|
|
42
|
+
pi.registerCommand("paste-image", {
|
|
43
|
+
description: "Attach an image from clipboard or use a recent-image picker",
|
|
44
|
+
getArgumentCompletions: (argumentPrefix) => {
|
|
45
|
+
const normalized = argumentPrefix.trim().toLowerCase();
|
|
46
|
+
if (!normalized) {
|
|
47
|
+
return [...ARGUMENT_COMPLETIONS];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const matches = ARGUMENT_COMPLETIONS.filter((item) => item.value.startsWith(normalized));
|
|
51
|
+
return matches.length > 0 ? matches.map((item) => ({ ...item })) : null;
|
|
52
|
+
},
|
|
53
|
+
handler: async (args, ctx) => {
|
|
54
|
+
const tokens = parseArgs(args);
|
|
55
|
+
|
|
56
|
+
if (tokens.length === 0 || tokens[0] === SUBCOMMAND_CLIPBOARD) {
|
|
57
|
+
await handlers.fromClipboard(ctx);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (tokens[0] === SUBCOMMAND_RECENT) {
|
|
62
|
+
if (tokens.length > 1) {
|
|
63
|
+
ctx.ui.notify(usageMessage(), "warning");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await handlers.fromRecent(ctx);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (tokens[0] === "help") {
|
|
72
|
+
ctx.ui.notify(usageMessage(), "info");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ctx.ui.notify(usageMessage(), "warning");
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|