@wangjq4214/pi-dollar-skills 0.0.0-stage → 0.5.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/README.md +57 -2
- package/README.zh-CN.md +58 -0
- package/dist/LICENSE +22 -0
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -5
package/README.md
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-dollar-skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.md) | [Simplified Chinese](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
An independent Pi extension for Codex-style skill selection. `/` keeps ordinary commands but hides skill candidates; `$` offers skills by name and description. Selecting a skill inserts `$skill-name` without sending. Submission injects the full skill file and its location into model context.
|
|
6
|
+
|
|
7
|
+
## Try it
|
|
8
|
+
|
|
9
|
+
Tested against Pi 1.4.0. Requires the public `addAutocompleteProvider` and custom trigger-character APIs. Older hosts without that UI API receive a warning; no editor patch is attempted. Host SDK peers are provided by Pi, not bundled.
|
|
10
|
+
|
|
11
|
+
From this repository root, preserving the current working directory:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pi -e ./packages/pi-dollar-skills/src/index.ts
|
|
15
|
+
# Or build and load the bundle:
|
|
16
|
+
bun run --filter @wangjq4214/pi-dollar-skills build
|
|
17
|
+
pi -e ./packages/pi-dollar-skills/dist/index.js
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The package is configured for public npm publication. Initial publication and Trusted Publisher setup are maintainer prerequisites, not actions performed by these commands. After publication, install with `pi install npm:@wangjq4214/pi-dollar-skills`. Do not load both source and bundle at once.
|
|
21
|
+
|
|
22
|
+
## Interaction
|
|
23
|
+
|
|
24
|
+
Type `$`, keep typing to filter names, then use the editor's normal selection keys (normally arrows, Tab or Enter). Accepting a candidate does not submit. Continue composing, then submit normally:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
Use $grimoire-review and $grimoire-test to inspect these changes
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- References start at the beginning of text or after whitespace; names match loaded skills exactly. Ordinary closing punctuation is accepted after a reference.
|
|
31
|
+
- Fenced/inline code and escaped dollar signs are literal. Unknown names are left unchanged. Use `\$skill-name` or inline code to quote a known name without loading it.
|
|
32
|
+
- Multiple distinct skills load in first-reference order; repeated references load once per submission. Full files are read fresh on submission, including frontmatter. Paths and relative-reference directories accompany each file. The original request and images pass through to Pi.
|
|
33
|
+
- Interactive multi-skill submissions show a separate native Pi skill card for each skill and a separate native User body. Click a card in fullscreen mode to expand it independently, or use Pi's normal expand-tools shortcut (normally Ctrl+O) to expand/collapse all. Open TUI's compact layout and User-box borders continue to apply to these native components.
|
|
34
|
+
- New multi-skill UI entries store the submitted snapshots, so replay does not reread changed skill files. Existing older transcript entries are not rewritten. Keep the extension loaded when resuming; without its renderer Pi falls back to showing the full stored content.
|
|
35
|
+
- Slash commands and `!` shell requests retain host semantics; dollar injection applies to ordinary prompts. Manual `/skill:name` still works, although hidden from suggestions.
|
|
36
|
+
- Available skills come from Pi's current session, including configured/package skills. Skill loading and automatic model matching remain unchanged; no settings are rewritten.
|
|
37
|
+
- A read failure stops submission instead of sending partial skill context. In the interactive editor an empty draft is restored without overwriting newer text. Images are not restored to the editor by this extension; if the host consumed an attachment before the failure, reattach it when retrying.
|
|
38
|
+
- Uses the host autocomplete composition API. It does not replace the native/Open TUI editor, patch prototypes, or alter installed Pi files. `/reload` uses Pi's normal extension/UI teardown.
|
|
39
|
+
|
|
40
|
+
## Integration notes
|
|
41
|
+
|
|
42
|
+
Interactive prompts with multiple distinct skills use one public custom session message, composed from Pi's publicly exported skill/User components. Pi converts its complete instructions, request and images into one model-facing User message, including when queued as steering or follow-up. The host's `before_agent_start` event identifies an idle submission as `messageType: "custom"`, and the extension consumes that input (`handled`). Extensions that require ordinary User-only input hooks or session entries must account for this. Single-skill prompts and non-TUI modes retain ordinary input transformation. No Open TUI runtime dependency or private host-component import is added.
|
|
43
|
+
|
|
44
|
+
## Development and evidence
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
bun run --filter @wangjq4214/pi-dollar-skills typecheck
|
|
48
|
+
bun run --filter @wangjq4214/pi-dollar-skills test
|
|
49
|
+
bun run --filter @wangjq4214/pi-dollar-skills build
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Tests cover parsing, non-destructive completion, real SDK/resource/extension submission into an offline provider (including images, failure handling, steering/follow-up and RPC), actual native/Open TUI editor key paths, native multi-card rendering/click/global expansion, snapshot replay, and Open TUI layout adaptation on regular/fullscreen host renderers (including borders, theme/padding/width changes and cleanup). Terminal scheduling and UI mounting are controlled; these are not pixel/IME tests of a live terminal. Open TUI is a test collaborator, not a package runtime dependency. Actual terminal appearance and IME behavior require a local smoke test.
|
|
53
|
+
|
|
54
|
+
Runtime contracts are in [spec 0015](../../.grimoire/spec/0015-dollar-skill-invocation.md) and [ADR 0026](../../.grimoire/adr/0026-independent-dollar-skill-extension.md). Public publication is separately authorized by [ADR 0030](../../.grimoire/adr/0030-public-workspace-trusted-publishing.md); see the repository [release guide](../../docs/releasing.md). No account configuration or actual publication is performed by this implementation.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
[MIT](../../LICENSE).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# pi-dollar-skills
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
独立的 Pi 扩展,提供类似 Codex 的 skill 选择方式:`/` 保留普通命令、隐藏 skill 候选;输入 `$` 列出 skill 名称及描述。选中后插入 `$skill-name`,不会立即发送;提交时将完整 skill 文件及其位置注入模型上下文。
|
|
6
|
+
|
|
7
|
+
## 试用
|
|
8
|
+
|
|
9
|
+
已针对 Pi 1.4.0 验证,使用公开的 `addAutocompleteProvider` 和自定义补全触发字符接口。缺少该 UI 接口的旧宿主会收到提示,不会尝试修补编辑器。SDK 由 Pi 提供,不打包进扩展。
|
|
10
|
+
|
|
11
|
+
在仓库根目录运行,保持当前工作目录:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pi -e ./packages/pi-dollar-skills/src/index.ts
|
|
15
|
+
# 或构建后加载:
|
|
16
|
+
bun run --filter @wangjq4214/pi-dollar-skills build
|
|
17
|
+
pi -e ./packages/pi-dollar-skills/dist/index.js
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
包已配置为可公开发布到 npm;首次发布和 Trusted Publisher 配置仍需维护者完成,以上命令不会执行这些操作。发布后可用 `pi install npm:@wangjq4214/pi-dollar-skills` 安装。不要同时加载源码与构建产物。
|
|
21
|
+
|
|
22
|
+
## 使用方式
|
|
23
|
+
|
|
24
|
+
输入 `$`,继续输入可按名称筛选,然后使用编辑器原有选择键(通常为方向键、Tab 或 Enter)。选中不发送,可以继续输入需求,再正常提交:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
用 $grimoire-review 和 $grimoire-test 检查这些修改
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- 引用位于文本开头或空白之后;skill 名精确匹配已加载的名称,后面可接常见结束标点。
|
|
31
|
+
- 代码块、行内代码和转义的 `$` 保持字面含义;未知名称原样保留。可以用 `\$skill-name` 或行内代码引用已知名称而不加载。
|
|
32
|
+
- 一条消息支持多个 skill,按首次出现顺序加载;同名引用在本次提交中只加载一次。提交时重新读取完整文件(含 frontmatter),附带文件位置和相对引用目录;保留原始需求与图片,由 Pi 继续处理。
|
|
33
|
+
- 交互模式下,多个 skill 各自显示原生 Pi skill 卡片,用户正文单独显示。全屏模式可点击单张卡片独立展开,也可用 Pi 原有展开快捷键(通常为 Ctrl+O)统一展开/折叠。Open TUI 的紧凑布局与 User 消息边框仍能作用于这些原生组件。
|
|
34
|
+
- 新的多 skill UI 消息保存提交时的快照,回放不重新读取已修改的 skill 文件;不改写旧版本已有的消息。恢复会话时请保持扩展已加载,否则 Pi 会退回显示完整存储内容。
|
|
35
|
+
- `/` 命令和 `!` shell 请求保留宿主语义,普通提示词才执行 `$` 注入。手动输入 `/skill:name` 仍然可用,只是不出现在候选列表。
|
|
36
|
+
- 使用 Pi 当前会话提供的 skill,包含配置路径和包提供的 skill;不卸载 skill,不改变模型自动匹配能力,不改写设置。
|
|
37
|
+
- 文件读取失败时停止本次提交,不发送半套上下文。交互编辑器为空时恢复需求文本,不覆盖较新的草稿。扩展不恢复编辑器里的图片;若宿主已消费附件,重试时需重新附加。
|
|
38
|
+
- 通过宿主补全组合接口工作,不替换原生/Open TUI 编辑器,不修补全局原型、不修改 Pi 安装文件。`/reload` 使用宿主原有扩展/UI 清理流程。
|
|
39
|
+
|
|
40
|
+
## 集成说明
|
|
41
|
+
|
|
42
|
+
交互模式的多个不同 skill 通过一条公开 custom 会话消息提交,由 Pi 公开导出的 skill/User 组件组合显示。Pi 将其中的完整指令、需求与图片转换为一条面向模型的 User 消息,steering/follow-up 排队也保持整体投递。空闲时提交会使宿主 `before_agent_start` 事件标记为 `messageType: "custom"`,扩展会消费该输入(`handled`);仅处理普通 User 输入钩子或会话条目的其他扩展需考虑这一差异。单 skill 和非 TUI 模式仍走普通输入变换流程。不增加 Open TUI 运行时依赖,也不导入宿主私有组件路径。
|
|
43
|
+
|
|
44
|
+
## 开发与验证边界
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
bun run --filter @wangjq4214/pi-dollar-skills typecheck
|
|
48
|
+
bun run --filter @wangjq4214/pi-dollar-skills test
|
|
49
|
+
bun run --filter @wangjq4214/pi-dollar-skills build
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
测试覆盖解析、无损补全、真实 SDK/资源加载/扩展提交流程向离线 provider 发送上下文(含图片、失败处理、steering/follow-up 与 RPC),原生/Open TUI 编辑器按键路径,原生多卡片渲染、独立点击/统一展开、快照回放,以及常规/全屏宿主渲染器中的 Open TUI 布局适配(含边框、主题/边距/宽度变化和清理)。测试控制终端调度与 UI 挂载,不等于真实终端的像素或输入法验证。Open TUI 仅作为测试协作者,不是运行时依赖;实际显示和输入法表现需本地试用。
|
|
53
|
+
|
|
54
|
+
运行时契约见 [spec 0015](../../.grimoire/spec/0015-dollar-skill-invocation.md) 和 [ADR 0026](../../.grimoire/adr/0026-independent-dollar-skill-extension.md)。公开发布由 [ADR 0030](../../.grimoire/adr/0030-public-workspace-trusted-publishing.md) 单独授权,操作步骤见仓库 [发布指南](../../docs/releasing.md)。本次实现不执行实际发布或修改账号配置。
|
|
55
|
+
|
|
56
|
+
## 许可证
|
|
57
|
+
|
|
58
|
+
[MIT](../../LICENSE)。
|
package/dist/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wangjq4214
|
|
4
|
+
Copyright (c) 2026 pi-open-tui contributors
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { SkillInvocationMessageComponent, UserMessageComponent, getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Container, Spacer } from "@earendil-works/pi-tui";
|
|
5
|
+
//#region src/skills.ts
|
|
6
|
+
function availableSkills(commands) {
|
|
7
|
+
return commands.filter((command) => command.source === "skill" && command.name.startsWith("skill:")).map((command) => ({
|
|
8
|
+
name: command.name.slice(6),
|
|
9
|
+
description: command.description,
|
|
10
|
+
path: command.sourceInfo.path
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
/** Mask code without changing offsets, including unfinished code while editing. */
|
|
14
|
+
function prose(text) {
|
|
15
|
+
let fence;
|
|
16
|
+
let inline = 0;
|
|
17
|
+
return text.split(/(?<=\n)/).map((line) => {
|
|
18
|
+
const marker = line.match(/^ {0,3}(`{3,}|~{3,})(.*)/);
|
|
19
|
+
if (fence) {
|
|
20
|
+
if (marker && marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) fence = void 0;
|
|
21
|
+
return line.replace(/[^\r\n]/g, " ");
|
|
22
|
+
}
|
|
23
|
+
if (!inline && marker) {
|
|
24
|
+
fence = marker[1];
|
|
25
|
+
return line.replace(/[^\r\n]/g, " ");
|
|
26
|
+
}
|
|
27
|
+
let result = "";
|
|
28
|
+
for (let i = 0; i < line.length;) if (line[i] === "\\" && !inline && i + 1 < line.length) {
|
|
29
|
+
result += " ";
|
|
30
|
+
i += 2;
|
|
31
|
+
} else if (line[i] === "`") {
|
|
32
|
+
let end = i + 1;
|
|
33
|
+
while (line[end] === "`") end++;
|
|
34
|
+
const length = end - i;
|
|
35
|
+
if (!inline) inline = length;
|
|
36
|
+
else if (inline === length) inline = 0;
|
|
37
|
+
result += " ".repeat(length);
|
|
38
|
+
i = end;
|
|
39
|
+
} else {
|
|
40
|
+
result += inline && !/[\r\n]/.test(line[i]) ? " " : line[i];
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}).join("");
|
|
45
|
+
}
|
|
46
|
+
/** Only whitespace-delimited dollar names in prose; not shell/path interpolation. */
|
|
47
|
+
function skillTokens(text) {
|
|
48
|
+
const visible = prose(text);
|
|
49
|
+
return [...text.matchAll(/(?<!\S)\$([a-z0-9-]*)(?=$|[\s,.;:!?,。;:!?)\]}])/g)].filter((match) => visible.slice(match.index, match.index + match[0].length) === match[0]).map((match) => ({
|
|
50
|
+
name: match[1],
|
|
51
|
+
start: match.index,
|
|
52
|
+
end: match.index + match[0].length
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
function completionToken(lines, line, col) {
|
|
56
|
+
const before = [...lines.slice(0, line), (lines[line] ?? "").slice(0, col)].join("\n");
|
|
57
|
+
const token = skillTokens(before).at(-1);
|
|
58
|
+
return token?.end === before.length ? token : void 0;
|
|
59
|
+
}
|
|
60
|
+
function attribute(text) {
|
|
61
|
+
return text.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
62
|
+
}
|
|
63
|
+
/** Read every selected skill before committing any input or UI change. */
|
|
64
|
+
async function loadSkills(text, skills) {
|
|
65
|
+
const catalog = new Map(skills.map((skill) => [skill.name, skill]));
|
|
66
|
+
const selected = [...new Set(skillTokens(text).map((token) => token.name))].map((name) => catalog.get(name)).filter((skill) => skill !== void 0);
|
|
67
|
+
return Promise.all(selected.map(async (skill) => {
|
|
68
|
+
try {
|
|
69
|
+
return {
|
|
70
|
+
...skill,
|
|
71
|
+
content: await readFile(skill.path, "utf8")
|
|
72
|
+
};
|
|
73
|
+
} catch (cause) {
|
|
74
|
+
throw new Error(`Cannot load $${skill.name} from ${skill.path}`, { cause });
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
function skillBlock(skill) {
|
|
79
|
+
return `<skill name="${attribute(skill.name)}" location="${attribute(skill.path)}">\nReferences are relative to ${dirname(skill.path)}.\n\n${skill.content}\n</skill>`;
|
|
80
|
+
}
|
|
81
|
+
function skillPrompt(text, skills) {
|
|
82
|
+
return skills.length ? `${skills.map(skillBlock).join("\n\n")}\n\n${text}` : text;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/autocomplete.ts
|
|
86
|
+
function skillAutocomplete(current, getSkills) {
|
|
87
|
+
return {
|
|
88
|
+
triggerCharacters: [.../* @__PURE__ */ new Set([...current.triggerCharacters ?? [], "$"])],
|
|
89
|
+
async getSuggestions(lines, cursorLine, cursorCol, options) {
|
|
90
|
+
if (options.signal.aborted) return null;
|
|
91
|
+
const token = /^\s*[!/]/.test(lines.join("\n")) ? void 0 : completionToken(lines, cursorLine, cursorCol);
|
|
92
|
+
if (token) {
|
|
93
|
+
const items = getSkills().filter((skill) => skill.name.includes(token.name)).map((skill) => ({
|
|
94
|
+
value: `$${skill.name}`,
|
|
95
|
+
label: `$${skill.name}`,
|
|
96
|
+
description: skill.description
|
|
97
|
+
}));
|
|
98
|
+
return items.length ? {
|
|
99
|
+
items,
|
|
100
|
+
prefix: `$${token.name}`
|
|
101
|
+
} : null;
|
|
102
|
+
}
|
|
103
|
+
const result = await current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
104
|
+
if (options.signal.aborted || !result) return null;
|
|
105
|
+
if (options.force || !/^\/[^\s/]*$/.test((lines[cursorLine]?.slice(0, cursorCol) ?? "").trimStart())) return result;
|
|
106
|
+
const skillCommands = new Set(getSkills().map((skill) => `skill:${skill.name}`));
|
|
107
|
+
const items = result.items.filter((item) => !skillCommands.has(item.value.replace(/^\//, "")));
|
|
108
|
+
return items.length ? {
|
|
109
|
+
...result,
|
|
110
|
+
items
|
|
111
|
+
} : null;
|
|
112
|
+
},
|
|
113
|
+
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
114
|
+
if (!prefix.startsWith("$")) return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
115
|
+
const line = lines[cursorLine] ?? "";
|
|
116
|
+
const before = line.slice(0, cursorCol - prefix.length);
|
|
117
|
+
const after = line.slice(cursorCol).replace(/^[a-z0-9-]*/, "");
|
|
118
|
+
const value = item.value + (after.startsWith(" ") ? "" : " ");
|
|
119
|
+
const next = [...lines];
|
|
120
|
+
next[cursorLine] = before + value + after;
|
|
121
|
+
return {
|
|
122
|
+
lines: next,
|
|
123
|
+
cursorLine,
|
|
124
|
+
cursorCol: before.length + value.length
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
|
|
128
|
+
return completionToken(lines, cursorLine, cursorCol) !== void 0 || (current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/skill-message.ts
|
|
134
|
+
const SKILL_MESSAGE = "dollar-skills";
|
|
135
|
+
/** One queued message keeps the skill snapshots, request and images atomic. */
|
|
136
|
+
function createSkillMessage(request, skills, images = []) {
|
|
137
|
+
return {
|
|
138
|
+
customType: SKILL_MESSAGE,
|
|
139
|
+
content: [
|
|
140
|
+
...skills.map((skill) => ({
|
|
141
|
+
type: "text",
|
|
142
|
+
text: `${skillBlock(skill)}\n\n`
|
|
143
|
+
})),
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: request
|
|
147
|
+
},
|
|
148
|
+
...images
|
|
149
|
+
],
|
|
150
|
+
display: true,
|
|
151
|
+
details: {
|
|
152
|
+
version: 1,
|
|
153
|
+
skills: skills.map(({ name, path }) => ({
|
|
154
|
+
name,
|
|
155
|
+
path
|
|
156
|
+
}))
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function registerSkillMessages(pi) {
|
|
161
|
+
const views = /* @__PURE__ */ new WeakMap();
|
|
162
|
+
pi.registerMessageRenderer(SKILL_MESSAGE, (message, options) => {
|
|
163
|
+
let view = views.get(message);
|
|
164
|
+
if (!view) {
|
|
165
|
+
const details = message.details;
|
|
166
|
+
const parts = message.content;
|
|
167
|
+
if (details?.version !== 1 || !Array.isArray(details.skills) || !details.skills.length || !Array.isArray(parts)) return void 0;
|
|
168
|
+
const request = parts[details.skills.length];
|
|
169
|
+
if (request?.type !== "text") return void 0;
|
|
170
|
+
const root = new Container();
|
|
171
|
+
const cards = [];
|
|
172
|
+
for (const [index, skill] of details.skills.entries()) {
|
|
173
|
+
const part = parts[index];
|
|
174
|
+
if (typeof skill?.name !== "string" || typeof skill.path !== "string" || part?.type !== "text" || !part.text.startsWith("<skill name=\"") || !part.text.endsWith("\n</skill>\n\n")) return void 0;
|
|
175
|
+
const content = part.text.slice(part.text.indexOf("\n") + 1, -11);
|
|
176
|
+
const card = new SkillInvocationMessageComponent({
|
|
177
|
+
name: skill.name,
|
|
178
|
+
location: skill.path,
|
|
179
|
+
content,
|
|
180
|
+
userMessage: void 0
|
|
181
|
+
}, getMarkdownTheme(), options.outputPad);
|
|
182
|
+
card.setExpanded(options.expanded);
|
|
183
|
+
if (cards.length) root.addChild(new Spacer(1));
|
|
184
|
+
root.addChild(card);
|
|
185
|
+
cards.push(card);
|
|
186
|
+
}
|
|
187
|
+
const user = new UserMessageComponent(request.text, getMarkdownTheme(), options.outputPad);
|
|
188
|
+
root.addChild(new Spacer(1));
|
|
189
|
+
root.addChild(user);
|
|
190
|
+
view = {
|
|
191
|
+
root,
|
|
192
|
+
cards,
|
|
193
|
+
user,
|
|
194
|
+
...options
|
|
195
|
+
};
|
|
196
|
+
views.set(message, view);
|
|
197
|
+
}
|
|
198
|
+
if (view.expanded !== options.expanded) {
|
|
199
|
+
for (const card of view.cards) card.setExpanded(options.expanded);
|
|
200
|
+
view.expanded = options.expanded;
|
|
201
|
+
}
|
|
202
|
+
if (view.outputPad !== options.outputPad) {
|
|
203
|
+
for (const card of view.cards) card.setOutputPad(options.outputPad);
|
|
204
|
+
view.user.setOutputPad(options.outputPad);
|
|
205
|
+
view.outputPad = options.outputPad;
|
|
206
|
+
}
|
|
207
|
+
return view.root;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/index.ts
|
|
212
|
+
function dollarSkills(pi) {
|
|
213
|
+
registerSkillMessages(pi);
|
|
214
|
+
const skills = () => availableSkills(pi.getCommands());
|
|
215
|
+
pi.on("session_start", (_event, ctx) => {
|
|
216
|
+
if (ctx.mode !== "tui") return;
|
|
217
|
+
if (typeof ctx.ui.addAutocompleteProvider !== "function") {
|
|
218
|
+
ctx.ui.notify("pi-dollar-skills requires Pi 1.4.0+ for dollar autocomplete. Update Pi; the editor was left unchanged.", "warning");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
ctx.ui.addAutocompleteProvider((current) => skillAutocomplete(current, skills));
|
|
222
|
+
});
|
|
223
|
+
pi.on("input", async (event, ctx) => {
|
|
224
|
+
if (/^\s*[!/]/.test(event.text)) return { action: "continue" };
|
|
225
|
+
try {
|
|
226
|
+
const loaded = await loadSkills(event.text, skills());
|
|
227
|
+
if (!loaded.length) return { action: "continue" };
|
|
228
|
+
if (ctx.mode === "tui" && loaded.length > 1) {
|
|
229
|
+
pi.sendMessage(createSkillMessage(event.text, loaded, event.images), {
|
|
230
|
+
triggerTurn: true,
|
|
231
|
+
deliverAs: event.streamingBehavior
|
|
232
|
+
});
|
|
233
|
+
return { action: "handled" };
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
action: "transform",
|
|
237
|
+
text: skillPrompt(event.text, loaded),
|
|
238
|
+
images: event.images
|
|
239
|
+
};
|
|
240
|
+
} catch (error) {
|
|
241
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
242
|
+
if (event.source === "interactive" && ctx.mode === "tui" && !ctx.ui.getEditorText()) ctx.ui.setEditorText(event.text);
|
|
243
|
+
return { action: "handled" };
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
//#endregion
|
|
248
|
+
export { dollarSkills as default };
|
|
249
|
+
|
|
250
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/skills.ts","../src/autocomplete.ts","../src/skill-message.ts","../src/index.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { SlashCommandInfo } from \"@earendil-works/pi-coding-agent\";\n\nexport interface Skill {\n\tname: string;\n\tdescription?: string;\n\tpath: string;\n}\n\nexport function availableSkills(commands: SlashCommandInfo[]): Skill[] {\n\treturn commands\n\t\t.filter(\n\t\t\t(command) =>\n\t\t\t\tcommand.source === \"skill\" && command.name.startsWith(\"skill:\"),\n\t\t)\n\t\t.map((command) => ({\n\t\t\tname: command.name.slice(6),\n\t\t\tdescription: command.description,\n\t\t\tpath: command.sourceInfo.path,\n\t\t}));\n}\n\n/** Mask code without changing offsets, including unfinished code while editing. */\nfunction prose(text: string): string {\n\tlet fence: string | undefined;\n\tlet inline = 0;\n\treturn text\n\t\t.split(/(?<=\\n)/)\n\t\t.map((line) => {\n\t\t\tconst marker = line.match(/^ {0,3}(`{3,}|~{3,})(.*)/);\n\t\t\tif (fence) {\n\t\t\t\tif (\n\t\t\t\t\tmarker &&\n\t\t\t\t\tmarker[1][0] === fence[0] &&\n\t\t\t\t\tmarker[1].length >= fence.length &&\n\t\t\t\t\t!marker[2].trim()\n\t\t\t\t)\n\t\t\t\t\tfence = undefined;\n\t\t\t\treturn line.replace(/[^\\r\\n]/g, \" \");\n\t\t\t}\n\t\t\tif (!inline && marker) {\n\t\t\t\tfence = marker[1];\n\t\t\t\treturn line.replace(/[^\\r\\n]/g, \" \");\n\t\t\t}\n\t\t\tlet result = \"\";\n\t\t\tfor (let i = 0; i < line.length; ) {\n\t\t\t\tif (line[i] === \"\\\\\" && !inline && i + 1 < line.length) {\n\t\t\t\t\tresult += \" \";\n\t\t\t\t\ti += 2;\n\t\t\t\t} else if (line[i] === \"`\") {\n\t\t\t\t\tlet end = i + 1;\n\t\t\t\t\twhile (line[end] === \"`\") end++;\n\t\t\t\t\tconst length = end - i;\n\t\t\t\t\tif (!inline) inline = length;\n\t\t\t\t\telse if (inline === length) inline = 0;\n\t\t\t\t\tresult += \" \".repeat(length);\n\t\t\t\t\ti = end;\n\t\t\t\t} else {\n\t\t\t\t\tresult += inline && !/[\\r\\n]/.test(line[i]) ? \" \" : line[i];\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t})\n\t\t.join(\"\");\n}\n\nexport interface SkillToken {\n\tname: string;\n\tstart: number;\n\tend: number;\n}\n\n/** Only whitespace-delimited dollar names in prose; not shell/path interpolation. */\nexport function skillTokens(text: string): SkillToken[] {\n\tconst visible = prose(text);\n\treturn [\n\t\t...text.matchAll(/(?<!\\S)\\$([a-z0-9-]*)(?=$|[\\s,.;:!?,。;:!?)\\]}])/g),\n\t]\n\t\t.filter(\n\t\t\t(match) =>\n\t\t\t\tvisible.slice(match.index, match.index + match[0].length) === match[0],\n\t\t)\n\t\t.map((match) => ({\n\t\t\tname: match[1],\n\t\t\tstart: match.index,\n\t\t\tend: match.index + match[0].length,\n\t\t}));\n}\n\nexport function completionToken(\n\tlines: string[],\n\tline: number,\n\tcol: number,\n): SkillToken | undefined {\n\tconst before = [\n\t\t...lines.slice(0, line),\n\t\t(lines[line] ?? \"\").slice(0, col),\n\t].join(\"\\n\");\n\tconst token = skillTokens(before).at(-1);\n\treturn token?.end === before.length ? token : undefined;\n}\n\nfunction attribute(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\");\n}\n\nexport interface LoadedSkill extends Skill {\n\tcontent: string;\n}\n\n/** Read every selected skill before committing any input or UI change. */\nexport async function loadSkills(\n\ttext: string,\n\tskills: Skill[],\n): Promise<LoadedSkill[]> {\n\tconst catalog = new Map(skills.map((skill) => [skill.name, skill]));\n\tconst selected = [...new Set(skillTokens(text).map((token) => token.name))]\n\t\t.map((name) => catalog.get(name))\n\t\t.filter((skill): skill is Skill => skill !== undefined);\n\treturn Promise.all(\n\t\tselected.map(async (skill) => {\n\t\t\ttry {\n\t\t\t\treturn { ...skill, content: await readFile(skill.path, \"utf8\") };\n\t\t\t} catch (cause) {\n\t\t\t\tthrow new Error(`Cannot load $${skill.name} from ${skill.path}`, {\n\t\t\t\t\tcause,\n\t\t\t\t});\n\t\t\t}\n\t\t}),\n\t);\n}\n\nexport function skillBlock(skill: LoadedSkill): string {\n\treturn `<skill name=\"${attribute(skill.name)}\" location=\"${attribute(skill.path)}\">\\nReferences are relative to ${dirname(skill.path)}.\\n\\n${skill.content}\\n</skill>`;\n}\n\nexport function skillPrompt(text: string, skills: LoadedSkill[]): string {\n\treturn skills.length\n\t\t? `${skills.map(skillBlock).join(\"\\n\\n\")}\\n\\n${text}`\n\t\t: text;\n}\n\nexport async function injectSkills(\n\ttext: string,\n\tskills: Skill[],\n): Promise<string> {\n\treturn skillPrompt(text, await loadSkills(text, skills));\n}\n","import type { AutocompleteProvider } from \"@earendil-works/pi-tui\";\nimport { completionToken, type Skill } from \"./skills.ts\";\n\nexport function skillAutocomplete(\n\tcurrent: AutocompleteProvider,\n\tgetSkills: () => Skill[],\n): AutocompleteProvider {\n\treturn {\n\t\ttriggerCharacters: [\n\t\t\t...new Set([...(current.triggerCharacters ?? []), \"$\"]),\n\t\t],\n\t\tasync getSuggestions(lines, cursorLine, cursorCol, options) {\n\t\t\tif (options.signal.aborted) return null;\n\t\t\tconst token = /^\\s*[!/]/.test(lines.join(\"\\n\"))\n\t\t\t\t? undefined\n\t\t\t\t: completionToken(lines, cursorLine, cursorCol);\n\t\t\tif (token) {\n\t\t\t\tconst items = getSkills()\n\t\t\t\t\t.filter((skill) => skill.name.includes(token.name))\n\t\t\t\t\t.map((skill) => ({\n\t\t\t\t\t\tvalue: `$${skill.name}`,\n\t\t\t\t\t\tlabel: `$${skill.name}`,\n\t\t\t\t\t\tdescription: skill.description,\n\t\t\t\t\t}));\n\t\t\t\treturn items.length ? { items, prefix: `$${token.name}` } : null;\n\t\t\t}\n\t\t\tconst result = await current.getSuggestions(\n\t\t\t\tlines,\n\t\t\t\tcursorLine,\n\t\t\t\tcursorCol,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tif (options.signal.aborted || !result) return null;\n\t\t\t// Filter only the command token, not command arguments or file completion.\n\t\t\tif (\n\t\t\t\toptions.force ||\n\t\t\t\t!/^\\/[^\\s/]*$/.test(\n\t\t\t\t\t(lines[cursorLine]?.slice(0, cursorCol) ?? \"\").trimStart(),\n\t\t\t\t)\n\t\t\t)\n\t\t\t\treturn result;\n\t\t\tconst skillCommands = new Set(\n\t\t\t\tgetSkills().map((skill) => `skill:${skill.name}`),\n\t\t\t);\n\t\t\tconst items = result.items.filter(\n\t\t\t\t(item) => !skillCommands.has(item.value.replace(/^\\//, \"\")),\n\t\t\t);\n\t\t\treturn items.length ? { ...result, items } : null;\n\t\t},\n\t\tapplyCompletion(lines, cursorLine, cursorCol, item, prefix) {\n\t\t\tif (!prefix.startsWith(\"$\"))\n\t\t\t\treturn current.applyCompletion(\n\t\t\t\t\tlines,\n\t\t\t\t\tcursorLine,\n\t\t\t\t\tcursorCol,\n\t\t\t\t\titem,\n\t\t\t\t\tprefix,\n\t\t\t\t);\n\t\t\tconst line = lines[cursorLine] ?? \"\";\n\t\t\tconst before = line.slice(0, cursorCol - prefix.length);\n\t\t\t// Replace the whole name even when accepting completion in the middle of a token.\n\t\t\tconst after = line.slice(cursorCol).replace(/^[a-z0-9-]*/, \"\");\n\t\t\tconst value = item.value + (after.startsWith(\" \") ? \"\" : \" \");\n\t\t\tconst next = [...lines];\n\t\t\tnext[cursorLine] = before + value + after;\n\t\t\treturn {\n\t\t\t\tlines: next,\n\t\t\t\tcursorLine,\n\t\t\t\tcursorCol: before.length + value.length,\n\t\t\t};\n\t\t},\n\t\tshouldTriggerFileCompletion(lines, cursorLine, cursorCol) {\n\t\t\t// Tab is also used to request dollar completion by the native editor.\n\t\t\treturn (\n\t\t\t\tcompletionToken(lines, cursorLine, cursorCol) !== undefined ||\n\t\t\t\t(current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ??\n\t\t\t\t\ttrue)\n\t\t\t);\n\t\t},\n\t};\n}\n","import type { ImageContent } from \"@earendil-works/pi-ai\";\nimport {\n\ttype ExtensionAPI,\n\tgetMarkdownTheme,\n\tSkillInvocationMessageComponent,\n\tUserMessageComponent,\n} from \"@earendil-works/pi-coding-agent\";\nimport { Container, Spacer } from \"@earendil-works/pi-tui\";\nimport { type LoadedSkill, skillBlock } from \"./skills.ts\";\n\nexport const SKILL_MESSAGE = \"dollar-skills\";\n\ninterface SkillMessageDetails {\n\tversion: 1;\n\tskills: { name: string; path: string }[];\n}\n\n/** One queued message keeps the skill snapshots, request and images atomic. */\nexport function createSkillMessage(\n\trequest: string,\n\tskills: LoadedSkill[],\n\timages: ImageContent[] = [],\n) {\n\treturn {\n\t\tcustomType: SKILL_MESSAGE,\n\t\tcontent: [\n\t\t\t...skills.map((skill) => ({\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: `${skillBlock(skill)}\\n\\n`,\n\t\t\t})),\n\t\t\t{ type: \"text\" as const, text: request },\n\t\t\t...images,\n\t\t],\n\t\tdisplay: true,\n\t\tdetails: {\n\t\t\tversion: 1,\n\t\t\tskills: skills.map(({ name, path }) => ({ name, path })),\n\t\t} satisfies SkillMessageDetails,\n\t};\n}\n\nexport function registerSkillMessages(pi: ExtensionAPI): void {\n\t// Keep native per-card mouse expansion through host invalidation/rebuilds.\n\t// Weak keys leave disposal, branch replacement and reload to the host.\n\tconst views = new WeakMap<\n\t\tobject,\n\t\t{\n\t\t\troot: Container;\n\t\t\tcards: SkillInvocationMessageComponent[];\n\t\t\tuser: UserMessageComponent;\n\t\t\texpanded: boolean;\n\t\t\toutputPad: number;\n\t\t}\n\t>();\n\tpi.registerMessageRenderer<SkillMessageDetails>(\n\t\tSKILL_MESSAGE,\n\t\t(message, options) => {\n\t\t\tlet view = views.get(message);\n\t\t\tif (!view) {\n\t\t\t\tconst details = message.details;\n\t\t\t\tconst parts = message.content;\n\t\t\t\tif (\n\t\t\t\t\tdetails?.version !== 1 ||\n\t\t\t\t\t!Array.isArray(details.skills) ||\n\t\t\t\t\t!details.skills.length ||\n\t\t\t\t\t!Array.isArray(parts)\n\t\t\t\t)\n\t\t\t\t\treturn undefined;\n\t\t\t\tconst request = parts[details.skills.length];\n\t\t\t\tif (request?.type !== \"text\") return undefined;\n\t\t\t\tconst root = new Container();\n\t\t\t\tconst cards: SkillInvocationMessageComponent[] = [];\n\t\t\t\tfor (const [index, skill] of details.skills.entries()) {\n\t\t\t\t\tconst part = parts[index];\n\t\t\t\t\tif (\n\t\t\t\t\t\ttypeof skill?.name !== \"string\" ||\n\t\t\t\t\t\ttypeof skill.path !== \"string\" ||\n\t\t\t\t\t\tpart?.type !== \"text\" ||\n\t\t\t\t\t\t!part.text.startsWith('<skill name=\"') ||\n\t\t\t\t\t\t!part.text.endsWith(\"\\n</skill>\\n\\n\")\n\t\t\t\t\t)\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t// Slice only our outer envelope, not tags/code inside skill files.\n\t\t\t\t\tconst content = part.text.slice(\n\t\t\t\t\t\tpart.text.indexOf(\"\\n\") + 1,\n\t\t\t\t\t\t-\"\\n</skill>\\n\\n\".length,\n\t\t\t\t\t);\n\t\t\t\t\tconst card = new SkillInvocationMessageComponent(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: skill.name,\n\t\t\t\t\t\t\tlocation: skill.path,\n\t\t\t\t\t\t\tcontent,\n\t\t\t\t\t\t\tuserMessage: undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tgetMarkdownTheme(),\n\t\t\t\t\t\toptions.outputPad,\n\t\t\t\t\t);\n\t\t\t\t\tcard.setExpanded(options.expanded);\n\t\t\t\t\tif (cards.length) root.addChild(new Spacer(1));\n\t\t\t\t\troot.addChild(card);\n\t\t\t\t\tcards.push(card);\n\t\t\t\t}\n\t\t\t\tconst user = new UserMessageComponent(\n\t\t\t\t\trequest.text,\n\t\t\t\t\tgetMarkdownTheme(),\n\t\t\t\t\toptions.outputPad,\n\t\t\t\t);\n\t\t\t\troot.addChild(new Spacer(1));\n\t\t\t\troot.addChild(user);\n\t\t\t\tview = { root, cards, user, ...options };\n\t\t\t\tviews.set(message, view);\n\t\t\t}\n\t\t\tif (view.expanded !== options.expanded) {\n\t\t\t\tfor (const card of view.cards) card.setExpanded(options.expanded);\n\t\t\t\tview.expanded = options.expanded;\n\t\t\t}\n\t\t\tif (view.outputPad !== options.outputPad) {\n\t\t\t\tfor (const card of view.cards) card.setOutputPad(options.outputPad);\n\t\t\t\tview.user.setOutputPad(options.outputPad);\n\t\t\t\tview.outputPad = options.outputPad;\n\t\t\t}\n\t\t\treturn view.root;\n\t\t},\n\t);\n}\n","import type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { skillAutocomplete } from \"./autocomplete.ts\";\nimport { createSkillMessage, registerSkillMessages } from \"./skill-message.ts\";\nimport { availableSkills, loadSkills, skillPrompt } from \"./skills.ts\";\n\nexport default function dollarSkills(pi: ExtensionAPI): void {\n\tregisterSkillMessages(pi);\n\tconst skills = () => availableSkills(pi.getCommands());\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tif (ctx.mode !== \"tui\") return;\n\t\tif (typeof ctx.ui.addAutocompleteProvider !== \"function\") {\n\t\t\tctx.ui.notify(\n\t\t\t\t\"pi-dollar-skills requires Pi 1.4.0+ for dollar autocomplete. Update Pi; the editor was left unchanged.\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.addAutocompleteProvider((current) =>\n\t\t\tskillAutocomplete(current, skills),\n\t\t);\n\t});\n\tpi.on(\"input\", async (event, ctx) => {\n\t\t// Commands and shell requests retain their host-owned semantics.\n\t\tif (/^\\s*[!/]/.test(event.text)) return { action: \"continue\" };\n\t\ttry {\n\t\t\tconst loaded = await loadSkills(event.text, skills());\n\t\t\tif (!loaded.length) return { action: \"continue\" };\n\t\t\tif (ctx.mode === \"tui\" && loaded.length > 1) {\n\t\t\t\t// The host parses only one skill per ordinary User message. A\n\t\t\t\t// public custom renderer composes native cards without UI patches.\n\t\t\t\tpi.sendMessage(createSkillMessage(event.text, loaded, event.images), {\n\t\t\t\t\ttriggerTurn: true,\n\t\t\t\t\tdeliverAs: event.streamingBehavior,\n\t\t\t\t});\n\t\t\t\treturn { action: \"handled\" };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\taction: \"transform\",\n\t\t\t\ttext: skillPrompt(event.text, loaded),\n\t\t\t\timages: event.images,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(\n\t\t\t\terror instanceof Error ? error.message : String(error),\n\t\t\t\t\"error\",\n\t\t\t);\n\t\t\t// Never send partially loaded skill context or overwrite a newer draft.\n\t\t\tif (\n\t\t\t\tevent.source === \"interactive\" &&\n\t\t\t\tctx.mode === \"tui\" &&\n\t\t\t\t!ctx.ui.getEditorText()\n\t\t\t)\n\t\t\t\tctx.ui.setEditorText(event.text);\n\t\t\treturn { action: \"handled\" };\n\t\t}\n\t});\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,65 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
2
|
+
"name": "@wangjq4214/pi-dollar-skills",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/wangjq4214/pi-delegate.git",
|
|
10
|
+
"directory": "packages/pi-dollar-skills"
|
|
11
|
+
},
|
|
12
|
+
"description": "Codex-style $skill selection and full instruction injection for Pi.",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"packageManager": "bun@1.4.1",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22.19.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"pi-package",
|
|
21
|
+
"pi",
|
|
22
|
+
"pi-extension",
|
|
23
|
+
"skills"
|
|
24
|
+
],
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"README.zh-CN.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "rolldown -c",
|
|
32
|
+
"build:watch": "rolldown -c --watch",
|
|
33
|
+
"prepack": "bun run build",
|
|
34
|
+
"dev": "pi -e ./src/index.ts",
|
|
35
|
+
"start": "pi -e ./dist/index.js",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "bun test ./tests"
|
|
38
|
+
},
|
|
39
|
+
"pi": {
|
|
40
|
+
"extensions": [
|
|
41
|
+
"./dist/index.js"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
46
|
+
"@earendil-works/pi-ai": "*",
|
|
47
|
+
"@earendil-works/pi-tui": "*"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@earendil-works/pi-coding-agent": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"@earendil-works/pi-ai": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"@earendil-works/pi-tui": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
62
|
+
"@earendil-works/pi-ai": "1.4.0",
|
|
63
|
+
"@earendil-works/pi-tui": "1.4.0"
|
|
64
|
+
}
|
|
65
|
+
}
|