@liuovo/skills 0.0.0-stage → 0.1.2
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 +73 -2
- package/dist/args.d.ts +10 -0
- package/dist/args.js +72 -0
- package/dist/catalog.d.ts +6 -0
- package/dist/catalog.js +155 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -0
- package/dist/install.d.ts +5 -0
- package/dist/install.js +58 -0
- package/dist/picker.d.ts +21 -0
- package/dist/picker.js +89 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/dist/ui.d.ts +5 -0
- package/dist/ui.js +25 -0
- package/package.json +210 -3
- package/skills/.gitkeep +1 -0
package/README.md
CHANGED
|
@@ -1,3 +1,74 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @liuovo/skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一份精选的 Skills 清单,用交互式向导按需安装到 Codex、Claude Code 或 OpenCode。
|
|
4
|
+
|
|
5
|
+
## 使用
|
|
6
|
+
|
|
7
|
+
需要 Node.js >= 22.20.0、pnpm,以及可访问 GitHub 的 Git / 网络环境。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpx @liuovo/skills
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
这里只选择清单里的 Skills;随后由原生 `skills` 处理 Agent、安装范围、确认和安装结果。输入关键词搜索,`Tab` 多选,`Enter` 确认,`Esc` 取消清单选择。多个来源会分别进入原生安装流程。
|
|
14
|
+
|
|
15
|
+
取消原生安装时遵循其退出码:返回非零才停止后续来源;若原生把取消视为正常结束并返回 `0`,仍会继续下一个来源。因此,在原生提示中按 `Ctrl+C` 或 `Esc` 不保证结束整轮操作。
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 查看清单
|
|
19
|
+
pnpx @liuovo/skills list
|
|
20
|
+
|
|
21
|
+
# 指定清单项,继续原生交互
|
|
22
|
+
pnpx @liuovo/skills --skill handoff research
|
|
23
|
+
|
|
24
|
+
# 原生参数直接透传,不限制 Agent 名称
|
|
25
|
+
pnpx @liuovo/skills --skill handoff --global --agent codex
|
|
26
|
+
|
|
27
|
+
# -- 后的所有参数原样交给 skills add
|
|
28
|
+
pnpx @liuovo/skills --skill handoff -- --global --agent codex --copy --yes
|
|
29
|
+
|
|
30
|
+
# 只查看将执行的命令参数,不下载、不安装
|
|
31
|
+
pnpx @liuovo/skills --skill handoff --dry-run
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
安装时优先使用 `PATH` 中已有的 `skills`;没有时执行 `pnpx skills@latest`。已有安装器不会被自动升级。
|
|
35
|
+
|
|
36
|
+
**同名 Skill 的处理遵循原生 `skills`,本工具不再默认跳过或自行保护已有目录。** 不自动添加 `--yes`;显式传入 `--yes` 可能覆盖已有内容和本地修改。原封装的 `--project`、`--overwrite` 不再作为自有选项;安装范围与覆盖行为以原生 CLI 为准。`--json` 在 `list` 中输出清单 JSON,在安装命令中则直接透传给原生。
|
|
37
|
+
|
|
38
|
+
原生 `skills` 也可能根据运行环境(例如编码 Agent)自行进入非交互模式,本工具不改写这些规则。
|
|
39
|
+
|
|
40
|
+
更新、卸载也直接使用原生命令,例如:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpx skills update --global
|
|
44
|
+
pnpx skills update --project
|
|
45
|
+
pnpx skills remove handoff --global
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
更新可能覆盖本地修改;不会自动安装本清单新增的条目。
|
|
49
|
+
|
|
50
|
+
## Skills
|
|
51
|
+
|
|
52
|
+
当前包含 19 个外部 Skills 引用,安装时从各自上游获取,未复制上游正文。
|
|
53
|
+
|
|
54
|
+
| Skill | 用途 | 来源 |
|
|
55
|
+
| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------ |
|
|
56
|
+
| [grill-me](https://github.com/mattpocock/skills/blob/HEAD/skills/productivity/grill-me/SKILL.md) | 通过连续追问,把模糊方案打磨清楚。 | mattpocock/skills |
|
|
57
|
+
| [grilling](https://github.com/mattpocock/skills/blob/HEAD/skills/productivity/grilling/SKILL.md) | 质询计划与决策,找出遗漏和薄弱环节。 | mattpocock/skills |
|
|
58
|
+
| [handoff](https://github.com/mattpocock/skills/blob/HEAD/skills/productivity/handoff/SKILL.md) | 把当前对话整理成可接续的交接文档。 | mattpocock/skills |
|
|
59
|
+
| [codebase-design](https://github.com/mattpocock/skills/blob/HEAD/skills/engineering/codebase-design/SKILL.md) | 用统一概念设计职责清晰、接口简洁的模块。 | mattpocock/skills |
|
|
60
|
+
| [writing-for-agents](https://github.com/mattpocock/skills/blob/HEAD/skills/productivity/writing-for-agents/SKILL.md) | 编写清晰、可执行的 Agent 文档与 Skills。 | mattpocock/skills |
|
|
61
|
+
| [to-questionnaire](https://github.com/mattpocock/skills/blob/HEAD/skills/productivity/to-questionnaire/SKILL.md) | 将难以独自定夺的决策整理成问卷,交给他人填写。 | mattpocock/skills |
|
|
62
|
+
| [code-review](https://github.com/mattpocock/skills/blob/HEAD/skills/engineering/code-review/SKILL.md) | 对照仓库规范与原始需求,审查指定基准后的改动。 | mattpocock/skills |
|
|
63
|
+
| [research](https://github.com/mattpocock/skills/blob/HEAD/skills/engineering/research/SKILL.md) | 基于高可信一手来源调研,并将结论写入仓库。 | mattpocock/skills |
|
|
64
|
+
| [improve](https://github.com/shadcn/improve/blob/HEAD/skills/improve/SKILL.md) | 审视代码库,按优先级生成可交付的改进计划。 | shadcn/improve |
|
|
65
|
+
| [dry-refactoring](https://github.com/kucherenko/jscpd/blob/HEAD/skills/dry-refactoring/SKILL.md) | 结合 jscpd 检测结果,逐步消除重复代码。 | kucherenko/jscpd |
|
|
66
|
+
| [i-have-adhd](https://github.com/ayghri/i-have-adhd/blob/HEAD/skills/i-have-adhd/SKILL.md) | 突出下一步、拆分步骤,减少阅读干扰。 | ayghri/i-have-adhd |
|
|
67
|
+
| [vue-best-practices](https://github.com/vuejs-ai/skills/blob/HEAD/skills/vue-best-practices/SKILL.md) | 用 TypeScript 和组合式 API 编写 Vue 3。 | vuejs-ai/skills |
|
|
68
|
+
| [vue-router-best-practices](https://github.com/vuejs-ai/skills/blob/HEAD/skills/vue-router-best-practices/SKILL.md) | 处理 Vue Router 路由、守卫与组件生命周期。 | vuejs-ai/skills |
|
|
69
|
+
| [vue-pinia-best-practices](https://github.com/vuejs-ai/skills/blob/HEAD/skills/vue-pinia-best-practices/SKILL.md) | 规范 Pinia 状态管理与响应式数据组织。 | vuejs-ai/skills |
|
|
70
|
+
| [vue-testing-best-practices](https://github.com/vuejs-ai/skills/blob/HEAD/skills/vue-testing-best-practices/SKILL.md) | 编写 Vue 组件测试、模拟依赖与端到端测试。 | vuejs-ai/skills |
|
|
71
|
+
| [vue-debug-guides](https://github.com/vuejs-ai/skills/blob/HEAD/skills/vue-debug-guides/SKILL.md) | 定位 Vue 运行时、异步与服务端渲染问题。 | vuejs-ai/skills |
|
|
72
|
+
| [create-adaptable-composable](https://github.com/vuejs-ai/skills/blob/HEAD/skills/create-adaptable-composable/SKILL.md) | 编写兼容普通值、ref 和 getter 的组合函数。 | vuejs-ai/skills |
|
|
73
|
+
| [frontend-design](https://github.com/anthropics/skills/blob/HEAD/skills/frontend-design/SKILL.md) | 打造有辨识度、视觉统一的前端界面。 | anthropics/skills |
|
|
74
|
+
| [antfu](https://github.com/antfu/skills/blob/HEAD/skills/antfu/SKILL.md) | 采用 Anthony Fu 的 JS/TS 工具链与工程约定。 | antfu/skills |
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface Options {
|
|
2
|
+
command: "install" | "list" | "help" | "version";
|
|
3
|
+
names: string[];
|
|
4
|
+
all: boolean;
|
|
5
|
+
dryRun: boolean;
|
|
6
|
+
json: boolean;
|
|
7
|
+
nativeArgs: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function parseArgs(args: string[]): Options;
|
|
10
|
+
export declare const help = "Usage: liuovo-skills [install] [catalog options] [native options]\n liuovo-skills list [--json]\n\nNo arguments: choose catalog skills, then continue in the native skills CLI.\n\n -s, --skill <names...> Select catalog names or owner/repo@name identifiers\n --all Select the entire curated catalog\n --dry-run Print command arguments only; no downloads or writes\n -h, --help Show this help\n -v, --version Show package version\n\nOther options are forwarded to skills add unchanged.\nUse -- to pass all remaining arguments directly to skills add.\nFor example: --global --agent codex, or -- --copy --yes.\nInstallation prompts, agents, scope, conflicts, and lock files belong to skills.\nNo --yes or --json is added automatically. Explicit --yes may overwrite skills.\n";
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function parseArgs(args) {
|
|
2
|
+
const options = {
|
|
3
|
+
command: "install",
|
|
4
|
+
names: [],
|
|
5
|
+
all: false,
|
|
6
|
+
dryRun: false,
|
|
7
|
+
json: false,
|
|
8
|
+
nativeArgs: [],
|
|
9
|
+
};
|
|
10
|
+
let i = 0;
|
|
11
|
+
if (["install", "list"].includes(args[0] ?? "")) {
|
|
12
|
+
options.command = args[0];
|
|
13
|
+
i++;
|
|
14
|
+
}
|
|
15
|
+
for (; i < args.length; i++) {
|
|
16
|
+
const arg = args[i];
|
|
17
|
+
if (arg === "--") {
|
|
18
|
+
options.nativeArgs.push(...args.slice(i + 1));
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
if (arg === "--help" || arg === "-h")
|
|
22
|
+
options.command = "help";
|
|
23
|
+
else if (arg === "--version" || arg === "-v")
|
|
24
|
+
options.command = "version";
|
|
25
|
+
else if (arg === "--all")
|
|
26
|
+
options.all = true;
|
|
27
|
+
else if (arg === "--dry-run")
|
|
28
|
+
options.dryRun = true;
|
|
29
|
+
else if (arg === "--json" && options.command === "list")
|
|
30
|
+
options.json = true;
|
|
31
|
+
else if (arg === "--skill" || arg === "-s") {
|
|
32
|
+
const start = options.names.length;
|
|
33
|
+
while (args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
34
|
+
options.names.push(args[++i]);
|
|
35
|
+
}
|
|
36
|
+
if (options.names.length === start) {
|
|
37
|
+
throw new Error(`${arg} requires at least one value.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
options.nativeArgs.push(arg);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (options.all && options.names.length) {
|
|
45
|
+
throw new Error("--all and --skill are mutually exclusive.");
|
|
46
|
+
}
|
|
47
|
+
if (options.command === "list" &&
|
|
48
|
+
(options.all ||
|
|
49
|
+
options.names.length ||
|
|
50
|
+
options.dryRun ||
|
|
51
|
+
options.nativeArgs.length)) {
|
|
52
|
+
throw new Error("list only accepts --json.");
|
|
53
|
+
}
|
|
54
|
+
return options;
|
|
55
|
+
}
|
|
56
|
+
export const help = `Usage: liuovo-skills [install] [catalog options] [native options]
|
|
57
|
+
liuovo-skills list [--json]
|
|
58
|
+
|
|
59
|
+
No arguments: choose catalog skills, then continue in the native skills CLI.
|
|
60
|
+
|
|
61
|
+
-s, --skill <names...> Select catalog names or owner/repo@name identifiers
|
|
62
|
+
--all Select the entire curated catalog
|
|
63
|
+
--dry-run Print command arguments only; no downloads or writes
|
|
64
|
+
-h, --help Show this help
|
|
65
|
+
-v, --version Show package version
|
|
66
|
+
|
|
67
|
+
Other options are forwarded to skills add unchanged.
|
|
68
|
+
Use -- to pass all remaining arguments directly to skills add.
|
|
69
|
+
For example: --global --agent codex, or -- --copy --yes.
|
|
70
|
+
Installation prompts, agents, scope, conflicts, and lock files belong to skills.
|
|
71
|
+
No --yes or --json is added automatically. Explicit --yes may overwrite skills.
|
|
72
|
+
`;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Skill } from "./types.js";
|
|
2
|
+
export declare const packageRoot: string;
|
|
3
|
+
export declare function validateName(name: unknown): asserts name is string;
|
|
4
|
+
export declare function loadCatalog(root?: string): Promise<Skill[]>;
|
|
5
|
+
export declare function groupByCategory(catalog: Skill[]): Map<string, Skill[]>;
|
|
6
|
+
export declare function selectSkills(catalog: Skill[], names: string[]): Skill[];
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
export const packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
6
|
+
function record(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
export function validateName(name) {
|
|
10
|
+
if (typeof name !== "string" ||
|
|
11
|
+
!/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/.test(name) ||
|
|
12
|
+
name.length > 64) {
|
|
13
|
+
throw new Error(`Invalid skill name: ${String(name)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function validateSource(source) {
|
|
17
|
+
if (typeof source !== "string" ||
|
|
18
|
+
!/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/.test(source)) {
|
|
19
|
+
throw new Error(`Expected a GitHub owner/repo source: ${String(source)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function loadCatalog(root = packageRoot) {
|
|
23
|
+
const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
24
|
+
if (!record(pkg) ||
|
|
25
|
+
!Array.isArray(pkg.skills) ||
|
|
26
|
+
!record(pkg.skillsPack) ||
|
|
27
|
+
!record(pkg.skillsPack.metadata)) {
|
|
28
|
+
throw new Error("Invalid package.json skills / skillsPack configuration.");
|
|
29
|
+
}
|
|
30
|
+
const metadata = pkg.skillsPack.metadata;
|
|
31
|
+
const categories = pkg.skillsPack.categories ?? [];
|
|
32
|
+
if (!Array.isArray(categories) ||
|
|
33
|
+
categories.some((category) => typeof category !== "string" || !category.trim()) ||
|
|
34
|
+
new Set(categories).size !== categories.length) {
|
|
35
|
+
throw new Error("skillsPack.categories must be an array of unique, non-empty category names.");
|
|
36
|
+
}
|
|
37
|
+
const categoryOrder = ["自有 Skills", ...categories, "其他"];
|
|
38
|
+
validateSource(pkg.skillsPack.ownSource);
|
|
39
|
+
const ownSource = pkg.skillsPack.ownSource;
|
|
40
|
+
const catalog = [];
|
|
41
|
+
for (const group of pkg.skills) {
|
|
42
|
+
if (!record(group) || !Array.isArray(group.skills)) {
|
|
43
|
+
throw new Error("Each skills entry needs a source and a skills array.");
|
|
44
|
+
}
|
|
45
|
+
validateSource(group.source);
|
|
46
|
+
for (const name of group.skills) {
|
|
47
|
+
validateName(name);
|
|
48
|
+
const id = `${group.source}@${name}`;
|
|
49
|
+
const meta = metadata[id];
|
|
50
|
+
if (!record(meta) ||
|
|
51
|
+
typeof meta.description !== "string" ||
|
|
52
|
+
!meta.description.trim() ||
|
|
53
|
+
typeof meta.path !== "string" ||
|
|
54
|
+
!/^skills\/[a-zA-Z0-9_/-]+$/.test(meta.path) ||
|
|
55
|
+
meta.path.split("/").some((part) => !part)) {
|
|
56
|
+
throw new Error(`Missing or invalid display metadata for ${id}.`);
|
|
57
|
+
}
|
|
58
|
+
const category = meta.category ?? "其他";
|
|
59
|
+
if (typeof category !== "string" || !category.trim()) {
|
|
60
|
+
throw new Error(`Category must be a non-empty string for ${id}.`);
|
|
61
|
+
}
|
|
62
|
+
if (categories.length && !categoryOrder.includes(category)) {
|
|
63
|
+
throw new Error(`Category for ${id} is not declared in skillsPack.categories: ${category}`);
|
|
64
|
+
}
|
|
65
|
+
catalog.push({
|
|
66
|
+
id,
|
|
67
|
+
name,
|
|
68
|
+
source: group.source,
|
|
69
|
+
kind: "external",
|
|
70
|
+
description: meta.description,
|
|
71
|
+
category,
|
|
72
|
+
url: `https://github.com/${group.source}/blob/HEAD/${meta.path}/SKILL.md`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function walk(directory) {
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error.code === "ENOENT")
|
|
83
|
+
return;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
87
|
+
if (entries.some((entry) => entry.name === "SKILL.md" && entry.isFile())) {
|
|
88
|
+
const text = await readFile(join(directory, "SKILL.md"), "utf8");
|
|
89
|
+
const header = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1];
|
|
90
|
+
const data = header === undefined ? undefined : parse(header);
|
|
91
|
+
if (!record(data) ||
|
|
92
|
+
typeof data.description !== "string" ||
|
|
93
|
+
!data.description.trim()) {
|
|
94
|
+
throw new Error(`Invalid skill frontmatter: ${directory}/SKILL.md`);
|
|
95
|
+
}
|
|
96
|
+
validateName(data.name);
|
|
97
|
+
catalog.push({
|
|
98
|
+
id: `${ownSource}@${data.name}`,
|
|
99
|
+
name: data.name,
|
|
100
|
+
description: data.description,
|
|
101
|
+
category: "自有 Skills",
|
|
102
|
+
source: ownSource,
|
|
103
|
+
kind: "own",
|
|
104
|
+
url: `https://github.com/${ownSource}/blob/HEAD/${relative(root, directory).split(sep).join("/")}/SKILL.md`,
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entry.isDirectory() &&
|
|
110
|
+
!entry.name.startsWith(".") &&
|
|
111
|
+
entry.name !== "node_modules") {
|
|
112
|
+
await walk(join(directory, entry.name));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await walk(join(root, "skills"));
|
|
117
|
+
const ids = new Set();
|
|
118
|
+
for (const skill of catalog) {
|
|
119
|
+
if (ids.has(skill.id))
|
|
120
|
+
throw new Error(`Duplicate catalog entry: ${skill.id}`);
|
|
121
|
+
ids.add(skill.id);
|
|
122
|
+
}
|
|
123
|
+
const categoryRank = (category) => {
|
|
124
|
+
const rank = categoryOrder.indexOf(category);
|
|
125
|
+
return rank === -1 ? categoryOrder.length : rank;
|
|
126
|
+
};
|
|
127
|
+
return catalog.sort((a, b) => categoryRank(a.category) - categoryRank(b.category) ||
|
|
128
|
+
a.category.localeCompare(b.category, "zh-CN") ||
|
|
129
|
+
a.source.localeCompare(b.source) ||
|
|
130
|
+
a.name.localeCompare(b.name));
|
|
131
|
+
}
|
|
132
|
+
export function groupByCategory(catalog) {
|
|
133
|
+
const groups = new Map();
|
|
134
|
+
for (const skill of catalog) {
|
|
135
|
+
const group = groups.get(skill.category) ?? [];
|
|
136
|
+
group.push(skill);
|
|
137
|
+
groups.set(skill.category, group);
|
|
138
|
+
}
|
|
139
|
+
return groups;
|
|
140
|
+
}
|
|
141
|
+
export function selectSkills(catalog, names) {
|
|
142
|
+
const selected = names.map((name) => {
|
|
143
|
+
const matches = catalog.filter((skill) => skill.id === name || skill.name === name);
|
|
144
|
+
if (matches.length === 0)
|
|
145
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
146
|
+
if (matches.length > 1) {
|
|
147
|
+
throw new Error(`Ambiguous skill "${name}". Use one of: ${matches.map((s) => s.id).join(", ")}`);
|
|
148
|
+
}
|
|
149
|
+
return matches[0];
|
|
150
|
+
});
|
|
151
|
+
const unique = [
|
|
152
|
+
...new Map(selected.map((skill) => [skill.id, skill])).values(),
|
|
153
|
+
];
|
|
154
|
+
return unique;
|
|
155
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { help, parseArgs } from "./args.js";
|
|
5
|
+
import { groupByCategory, loadCatalog, packageRoot } from "./catalog.js";
|
|
6
|
+
import { buildCommands, installSkills } from "./install.js";
|
|
7
|
+
import { Cancelled, choose } from "./ui.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const options = parseArgs(process.argv.slice(2));
|
|
10
|
+
if (options.command === "help") {
|
|
11
|
+
console.log(help);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (options.command === "version") {
|
|
15
|
+
const pkg = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
16
|
+
console.log(`${pkg.name} ${pkg.version}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const catalog = await loadCatalog();
|
|
20
|
+
if (options.command === "list") {
|
|
21
|
+
if (options.json)
|
|
22
|
+
console.log(JSON.stringify(catalog, null, 2));
|
|
23
|
+
else {
|
|
24
|
+
for (const [category, skills] of groupByCategory(catalog)) {
|
|
25
|
+
console.log(`\n${category} (${skills.length} skills)`);
|
|
26
|
+
for (const skill of skills) {
|
|
27
|
+
console.log(` ${skill.name} — ${skill.description}\n Source: ${skill.source}${skill.kind === "own" ? " (own)" : ""}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
console.log(`\n${catalog.length} skills.`);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const skills = await choose(options, catalog, Boolean(process.stdin.isTTY && process.stdout.isTTY));
|
|
35
|
+
const commands = buildCommands(skills, options.nativeArgs);
|
|
36
|
+
if (options.dryRun) {
|
|
37
|
+
console.log(JSON.stringify(commands.map((args) => ["skills", ...args]), null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
process.exitCode = await installSkills(commands, {
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
env: process.env,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
main().catch((error) => {
|
|
46
|
+
console.error(`${error instanceof Cancelled ? "" : "Error: "}${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
+
process.exitCode = error instanceof Cancelled ? 130 : 1;
|
|
48
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Context, Skill } from "./types.js";
|
|
2
|
+
export type Runner = (args: string[], context: Context, signal?: AbortSignal) => Promise<number>;
|
|
3
|
+
export declare const runSkills: Runner;
|
|
4
|
+
export declare function buildCommands(skills: Skill[], nativeArgs: string[]): string[][];
|
|
5
|
+
export declare function installSkills(commands: string[][], context: Context, run?: Runner): Promise<number>;
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { constants } from "node:os";
|
|
3
|
+
async function runCommand(command, args, context, signal) {
|
|
4
|
+
return await new Promise((accept, reject) => {
|
|
5
|
+
const child = spawn(command, args, {
|
|
6
|
+
cwd: context.cwd,
|
|
7
|
+
env: context.env,
|
|
8
|
+
stdio: "inherit",
|
|
9
|
+
...(signal ? { signal } : {}),
|
|
10
|
+
});
|
|
11
|
+
child.once("error", reject);
|
|
12
|
+
child.once("close", (code, childSignal) => {
|
|
13
|
+
accept(code ?? (childSignal ? 128 + constants.signals[childSignal] : 1));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export const runSkills = async (args, context, signal) => {
|
|
18
|
+
try {
|
|
19
|
+
return await runCommand("skills", args, context, signal);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// Only a missing executable permits fallback; never retry a started install.
|
|
23
|
+
if (error.code !== "ENOENT")
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await runCommand("pnpx", ["skills@latest", ...args], context, signal);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") {
|
|
31
|
+
throw new Error("Neither skills nor pnpx was found on PATH. Install skills or pnpm, then retry.");
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export function buildCommands(skills, nativeArgs) {
|
|
37
|
+
const groups = new Map();
|
|
38
|
+
for (const skill of skills) {
|
|
39
|
+
const names = groups.get(skill.source) ?? [];
|
|
40
|
+
names.push(skill.name);
|
|
41
|
+
groups.set(skill.source, names);
|
|
42
|
+
}
|
|
43
|
+
return [...groups].map(([source, names]) => [
|
|
44
|
+
"add",
|
|
45
|
+
source,
|
|
46
|
+
"--skill",
|
|
47
|
+
...names,
|
|
48
|
+
...nativeArgs,
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
export async function installSkills(commands, context, run = runSkills) {
|
|
52
|
+
for (const args of commands) {
|
|
53
|
+
const code = await run(args, context);
|
|
54
|
+
if (code !== 0)
|
|
55
|
+
return code;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
package/dist/picker.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AutocompletePrompt } from "@clack/core";
|
|
2
|
+
export interface PickerOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
category: string;
|
|
7
|
+
source?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function matchesOption(query: string, option: PickerOption): boolean;
|
|
10
|
+
export interface PickerFrame {
|
|
11
|
+
message: string;
|
|
12
|
+
state: AutocompletePrompt<PickerOption>["state"];
|
|
13
|
+
options: PickerOption[];
|
|
14
|
+
filtered: PickerOption[];
|
|
15
|
+
selected: string[];
|
|
16
|
+
cursor: number;
|
|
17
|
+
input: string;
|
|
18
|
+
error: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function renderPicker(frame: PickerFrame): string;
|
|
21
|
+
export declare function searchMultiselect(message: string, options: PickerOption[]): Promise<string[] | symbol>;
|
package/dist/picker.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AutocompletePrompt, isCancel } from "@clack/core";
|
|
2
|
+
import { limitOptions, symbol } from "@clack/prompts";
|
|
3
|
+
import { styleText } from "node:util";
|
|
4
|
+
export function matchesOption(query, option) {
|
|
5
|
+
const terms = query.trim().toLocaleLowerCase().split(/\s+/);
|
|
6
|
+
const text = [
|
|
7
|
+
option.value,
|
|
8
|
+
option.label,
|
|
9
|
+
option.description,
|
|
10
|
+
option.category,
|
|
11
|
+
option.source,
|
|
12
|
+
]
|
|
13
|
+
.join(" ")
|
|
14
|
+
.toLocaleLowerCase();
|
|
15
|
+
return terms.every((term) => text.includes(term));
|
|
16
|
+
}
|
|
17
|
+
// Clack owns keyboard input and selection state; this renderer adds category
|
|
18
|
+
// headings and always-visible descriptions without making headings selectable.
|
|
19
|
+
export function renderPicker(frame) {
|
|
20
|
+
const heading = `${symbol(frame.state)} ${frame.message}`;
|
|
21
|
+
const chosen = frame.options
|
|
22
|
+
.filter((option) => frame.selected.includes(option.value))
|
|
23
|
+
.map((option) => option.label);
|
|
24
|
+
if (frame.state === "submit") {
|
|
25
|
+
return `${heading}\n│ ${chosen.length} items selected: ${chosen.join(", ")}`;
|
|
26
|
+
}
|
|
27
|
+
if (frame.state === "cancel")
|
|
28
|
+
return `${heading}\n│ Cancelled.`;
|
|
29
|
+
const focused = frame.filtered[frame.cursor];
|
|
30
|
+
const lines = [
|
|
31
|
+
heading,
|
|
32
|
+
`│ Search: ${frame.input || styleText("dim", "Name / description / category / source")}`,
|
|
33
|
+
`│ ${frame.filtered.length} matches · ${frame.selected.length} selected${focused ? ` · Category: ${focused.category}` : ""}`,
|
|
34
|
+
];
|
|
35
|
+
if (frame.state === "error")
|
|
36
|
+
lines.push(`│ ${styleText("yellow", frame.error)}`);
|
|
37
|
+
if (!frame.filtered.length)
|
|
38
|
+
lines.push("│ No matches found. Try another keyword.");
|
|
39
|
+
let previousCategory;
|
|
40
|
+
const rows = limitOptions({
|
|
41
|
+
options: frame.filtered,
|
|
42
|
+
cursor: frame.cursor,
|
|
43
|
+
maxItems: 10,
|
|
44
|
+
columnPadding: 3,
|
|
45
|
+
rowPadding: lines.length + 4,
|
|
46
|
+
style: (option, active) => {
|
|
47
|
+
const category = previousCategory === option.category
|
|
48
|
+
? ""
|
|
49
|
+
: `${styleText("cyan", `── ${option.category} ──`)}\n`;
|
|
50
|
+
previousCategory = option.category;
|
|
51
|
+
const checkbox = frame.selected.includes(option.value) ? "[✓]" : "[ ]";
|
|
52
|
+
const name = active ? styleText("bold", option.label) : option.label;
|
|
53
|
+
return `${category}${active ? "›" : " "} ${checkbox} ${name} ${styleText("dim", option.description)}`;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
lines.push(...rows.map((row) => `│ ${row}`));
|
|
57
|
+
if (focused?.source)
|
|
58
|
+
lines.push(`│ Source: ${focused.source}`);
|
|
59
|
+
lines.push("│ ↑/↓ to navigate · Tab: select · Enter: confirm", "│ Type to search; selections are preserved · Esc: cancel", "└");
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
export async function searchMultiselect(message, options) {
|
|
63
|
+
const prompt = new AutocompletePrompt({
|
|
64
|
+
options,
|
|
65
|
+
multiple: true,
|
|
66
|
+
filter: matchesOption,
|
|
67
|
+
validate: () => prompt.selectedValues.length
|
|
68
|
+
? undefined
|
|
69
|
+
: "Please select at least one item.",
|
|
70
|
+
render() {
|
|
71
|
+
return renderPicker({
|
|
72
|
+
message,
|
|
73
|
+
state: this.state,
|
|
74
|
+
options: this.options,
|
|
75
|
+
filtered: this.filteredOptions,
|
|
76
|
+
selected: this.selectedValues,
|
|
77
|
+
cursor: this.cursor,
|
|
78
|
+
input: this.userInput ? this.userInputWithCursor : "",
|
|
79
|
+
error: this.error,
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const answer = await prompt.prompt();
|
|
84
|
+
if (isCancel(answer))
|
|
85
|
+
return answer;
|
|
86
|
+
if (!Array.isArray(answer))
|
|
87
|
+
throw new Error("Invalid selection result. Please try again.");
|
|
88
|
+
return answer;
|
|
89
|
+
}
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/ui.d.ts
ADDED
package/dist/ui.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { selectSkills } from "./catalog.js";
|
|
2
|
+
import { searchMultiselect } from "./picker.js";
|
|
3
|
+
export class Cancelled extends Error {
|
|
4
|
+
}
|
|
5
|
+
export async function choose(options, catalog, interactive) {
|
|
6
|
+
if (!catalog.length)
|
|
7
|
+
throw new Error("The catalog is empty.");
|
|
8
|
+
if (options.all)
|
|
9
|
+
return selectSkills(catalog, catalog.map((skill) => skill.id));
|
|
10
|
+
if (options.names.length)
|
|
11
|
+
return selectSkills(catalog, options.names);
|
|
12
|
+
if (!interactive) {
|
|
13
|
+
throw new Error("Non-interactive catalog selection requires --skill <names...> or --all.");
|
|
14
|
+
}
|
|
15
|
+
const names = await searchMultiselect("Which skills? Type to search; Tab to select.", catalog.map((skill) => ({
|
|
16
|
+
value: skill.id,
|
|
17
|
+
label: skill.name,
|
|
18
|
+
description: skill.description,
|
|
19
|
+
category: skill.category,
|
|
20
|
+
source: skill.kind === "own" ? `${skill.source} (own)` : skill.source,
|
|
21
|
+
})));
|
|
22
|
+
if (typeof names === "symbol")
|
|
23
|
+
throw new Cancelled("Cancelled.");
|
|
24
|
+
return selectSkills(catalog, names);
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,213 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liuovo/skills",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Personal skills and curated upstream references, installed through one interactive CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/liuc-c/skills.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"liuovo-skills": "dist/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"skills"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.20.0"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@clack/core": "1.5.1",
|
|
27
|
+
"@clack/prompts": "1.8.1",
|
|
28
|
+
"yaml": "2.9.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "22.20.5",
|
|
32
|
+
"bumpp": "12.3.0",
|
|
33
|
+
"prettier": "3.9.10",
|
|
34
|
+
"skills": "latest",
|
|
35
|
+
"tsx": "4.23.15",
|
|
36
|
+
"typescript": "5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"skills": [
|
|
39
|
+
{
|
|
40
|
+
"source": "mattpocock/skills",
|
|
41
|
+
"skills": [
|
|
42
|
+
"grill-me",
|
|
43
|
+
"grilling",
|
|
44
|
+
"handoff",
|
|
45
|
+
"codebase-design",
|
|
46
|
+
"writing-for-agents",
|
|
47
|
+
"to-questionnaire",
|
|
48
|
+
"code-review",
|
|
49
|
+
"research"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"source": "shadcn/improve",
|
|
54
|
+
"skills": [
|
|
55
|
+
"improve"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"source": "kucherenko/jscpd",
|
|
60
|
+
"skills": [
|
|
61
|
+
"dry-refactoring"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"source": "ayghri/i-have-adhd",
|
|
66
|
+
"skills": [
|
|
67
|
+
"i-have-adhd"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"source": "vuejs-ai/skills",
|
|
72
|
+
"skills": [
|
|
73
|
+
"vue-best-practices",
|
|
74
|
+
"vue-router-best-practices",
|
|
75
|
+
"vue-pinia-best-practices",
|
|
76
|
+
"vue-testing-best-practices",
|
|
77
|
+
"vue-debug-guides",
|
|
78
|
+
"create-adaptable-composable"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"source": "anthropics/skills",
|
|
83
|
+
"skills": [
|
|
84
|
+
"frontend-design"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"source": "antfu/skills",
|
|
89
|
+
"skills": [
|
|
90
|
+
"antfu"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"skillsPack": {
|
|
95
|
+
"ownSource": "liuc-c/skills",
|
|
96
|
+
"metadata": {
|
|
97
|
+
"mattpocock/skills@grill-me": {
|
|
98
|
+
"description": "通过连续追问,把模糊方案打磨清楚。",
|
|
99
|
+
"path": "skills/productivity/grill-me",
|
|
100
|
+
"category": "方案推敲"
|
|
101
|
+
},
|
|
102
|
+
"mattpocock/skills@grilling": {
|
|
103
|
+
"description": "质询计划与决策,找出遗漏和薄弱环节。",
|
|
104
|
+
"path": "skills/productivity/grilling",
|
|
105
|
+
"category": "方案推敲"
|
|
106
|
+
},
|
|
107
|
+
"mattpocock/skills@handoff": {
|
|
108
|
+
"description": "把当前对话整理成可接续的交接文档。",
|
|
109
|
+
"path": "skills/productivity/handoff",
|
|
110
|
+
"category": "协作与表达"
|
|
111
|
+
},
|
|
112
|
+
"mattpocock/skills@codebase-design": {
|
|
113
|
+
"description": "用统一概念设计职责清晰、接口简洁的模块。",
|
|
114
|
+
"path": "skills/engineering/codebase-design",
|
|
115
|
+
"category": "代码设计与重构"
|
|
116
|
+
},
|
|
117
|
+
"mattpocock/skills@writing-for-agents": {
|
|
118
|
+
"description": "编写清晰、可执行的 Agent 文档与 Skills。",
|
|
119
|
+
"path": "skills/productivity/writing-for-agents",
|
|
120
|
+
"category": "协作与表达"
|
|
121
|
+
},
|
|
122
|
+
"mattpocock/skills@to-questionnaire": {
|
|
123
|
+
"description": "将难以独自定夺的决策整理成问卷,交给他人填写。",
|
|
124
|
+
"path": "skills/productivity/to-questionnaire",
|
|
125
|
+
"category": "方案推敲"
|
|
126
|
+
},
|
|
127
|
+
"mattpocock/skills@code-review": {
|
|
128
|
+
"description": "对照仓库规范与原始需求,审查指定基准后的改动。",
|
|
129
|
+
"path": "skills/engineering/code-review",
|
|
130
|
+
"category": "代码设计与重构"
|
|
131
|
+
},
|
|
132
|
+
"mattpocock/skills@research": {
|
|
133
|
+
"description": "基于高可信一手来源调研,并将结论写入仓库。",
|
|
134
|
+
"path": "skills/engineering/research",
|
|
135
|
+
"category": "协作与表达"
|
|
136
|
+
},
|
|
137
|
+
"shadcn/improve@improve": {
|
|
138
|
+
"description": "审视代码库,按优先级生成可交付的改进计划。",
|
|
139
|
+
"path": "skills/improve",
|
|
140
|
+
"category": "代码设计与重构"
|
|
141
|
+
},
|
|
142
|
+
"kucherenko/jscpd@dry-refactoring": {
|
|
143
|
+
"description": "结合 jscpd 检测结果,逐步消除重复代码。",
|
|
144
|
+
"path": "skills/dry-refactoring",
|
|
145
|
+
"category": "代码设计与重构"
|
|
146
|
+
},
|
|
147
|
+
"ayghri/i-have-adhd@i-have-adhd": {
|
|
148
|
+
"description": "突出下一步、拆分步骤,减少阅读干扰。",
|
|
149
|
+
"path": "skills/i-have-adhd",
|
|
150
|
+
"category": "协作与表达"
|
|
151
|
+
},
|
|
152
|
+
"vuejs-ai/skills@vue-best-practices": {
|
|
153
|
+
"description": "用 TypeScript 和组合式 API 编写 Vue 3。",
|
|
154
|
+
"path": "skills/vue-best-practices",
|
|
155
|
+
"category": "Vue 开发"
|
|
156
|
+
},
|
|
157
|
+
"vuejs-ai/skills@vue-router-best-practices": {
|
|
158
|
+
"description": "处理 Vue Router 路由、守卫与组件生命周期。",
|
|
159
|
+
"path": "skills/vue-router-best-practices",
|
|
160
|
+
"category": "Vue 开发"
|
|
161
|
+
},
|
|
162
|
+
"vuejs-ai/skills@vue-pinia-best-practices": {
|
|
163
|
+
"description": "规范 Pinia 状态管理与响应式数据组织。",
|
|
164
|
+
"path": "skills/vue-pinia-best-practices",
|
|
165
|
+
"category": "Vue 开发"
|
|
166
|
+
},
|
|
167
|
+
"vuejs-ai/skills@vue-testing-best-practices": {
|
|
168
|
+
"description": "编写 Vue 组件测试、模拟依赖与端到端测试。",
|
|
169
|
+
"path": "skills/vue-testing-best-practices",
|
|
170
|
+
"category": "Vue 开发"
|
|
171
|
+
},
|
|
172
|
+
"vuejs-ai/skills@vue-debug-guides": {
|
|
173
|
+
"description": "定位 Vue 运行时、异步与服务端渲染问题。",
|
|
174
|
+
"path": "skills/vue-debug-guides",
|
|
175
|
+
"category": "Vue 开发"
|
|
176
|
+
},
|
|
177
|
+
"vuejs-ai/skills@create-adaptable-composable": {
|
|
178
|
+
"description": "编写兼容普通值、ref 和 getter 的组合函数。",
|
|
179
|
+
"path": "skills/create-adaptable-composable",
|
|
180
|
+
"category": "Vue 开发"
|
|
181
|
+
},
|
|
182
|
+
"anthropics/skills@frontend-design": {
|
|
183
|
+
"description": "打造有辨识度、视觉统一的前端界面。",
|
|
184
|
+
"path": "skills/frontend-design",
|
|
185
|
+
"category": "前端设计"
|
|
186
|
+
},
|
|
187
|
+
"antfu/skills@antfu": {
|
|
188
|
+
"description": "采用 Anthony Fu 的 JS/TS 工具链与工程约定。",
|
|
189
|
+
"path": "skills/antfu",
|
|
190
|
+
"category": "代码设计与重构"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"categories": [
|
|
194
|
+
"方案推敲",
|
|
195
|
+
"协作与表达",
|
|
196
|
+
"代码设计与重构",
|
|
197
|
+
"前端设计",
|
|
198
|
+
"Vue 开发"
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
"scripts": {
|
|
202
|
+
"dev": "tsx src/cli.ts",
|
|
203
|
+
"build": "tsc",
|
|
204
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
205
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
206
|
+
"format": "prettier --write .",
|
|
207
|
+
"format:check": "prettier --check .",
|
|
208
|
+
"check": "pnpm typecheck && pnpm test && pnpm format:check",
|
|
209
|
+
"preversion": "git rev-parse --verify @{upstream} && pnpm check",
|
|
210
|
+
"release:check": "pnpm check && npm pack --dry-run",
|
|
211
|
+
"release": "bumpp && pnpm publish"
|
|
212
|
+
}
|
|
6
213
|
}
|
package/skills/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|