@mongoui/skills 0.1.4

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 ADDED
File without changes
package/dist/cli.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import { parseInstallOptions, runInstall, runUninstall } from "./commands/install.js";
3
+ import { supportedToolsText } from "./tools/index.js";
4
+ async function main() {
5
+ const args = process.argv.slice(2);
6
+ const [command, productName, ...restArgs] = args;
7
+ if (!isSupportedCommand(command)) {
8
+ printUsage(`Unsupported command: ${command ?? "(missing)"}`);
9
+ process.exitCode = 1;
10
+ return;
11
+ }
12
+ if (!productName) {
13
+ printUsage("Missing product name.");
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ try {
18
+ const options = parseInstallOptions(restArgs);
19
+ if (!options.tool) {
20
+ printUsage(`Missing required option: "--tool <${supportedToolsText()}>".`);
21
+ process.exitCode = 1;
22
+ return;
23
+ }
24
+ if (command === "install") {
25
+ await runInstall(productName, options);
26
+ return;
27
+ }
28
+ await runUninstall(productName, options);
29
+ }
30
+ catch (error) {
31
+ const message = error instanceof Error ? error.message : "Failed to parse command options.";
32
+ printUsage(message);
33
+ process.exitCode = 1;
34
+ }
35
+ }
36
+ function isSupportedCommand(command) {
37
+ return command === "install" || command === "uninstall";
38
+ }
39
+ function printUsage(errorMessage) {
40
+ if (errorMessage) {
41
+ console.error(`Error: ${errorMessage}`);
42
+ }
43
+ console.log("Usage:");
44
+ console.log(` msg-skills <install|uninstall> <product-name> --tool <${supportedToolsText()}> [--target-dir <path>]`);
45
+ }
46
+ void main();
@@ -0,0 +1,90 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { getProduct, listProducts } from "../products/index.js";
5
+ import { parseToolName, renderInstalledContent, resolveInstallTarget, supportedToolsText, } from "../tools/index.js";
6
+ export function parseInstallOptions(args) {
7
+ const options = {};
8
+ for (let index = 0; index < args.length; index += 1) {
9
+ const current = args[index];
10
+ if (current === "--tool") {
11
+ const nextValue = args[index + 1];
12
+ const parsedTool = parseToolName(nextValue);
13
+ if (parsedTool) {
14
+ options.tool = parsedTool;
15
+ index += 1;
16
+ continue;
17
+ }
18
+ throw new Error(`Invalid value for "--tool". Expected ${supportedToolsText()}.`);
19
+ }
20
+ if (current === "--target-dir") {
21
+ const nextValue = args[index + 1];
22
+ if (!nextValue) {
23
+ throw new Error('Missing value for "--target-dir".');
24
+ }
25
+ options.targetDir = nextValue;
26
+ index += 1;
27
+ continue;
28
+ }
29
+ throw new Error(`Unknown option: ${current}`);
30
+ }
31
+ return options;
32
+ }
33
+ export async function runInstall(productName, options) {
34
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
35
+ const product = requireProduct(productName);
36
+ if (!options.tool) {
37
+ throw new Error(`Missing required option: "--tool <${supportedToolsText()}>".`);
38
+ }
39
+ const installTarget = resolveInstallTarget(options.tool, options.targetDir);
40
+ const sourceSkillFile = path.join(packageRoot, product.skillFile);
41
+ const targetSkillFile = resolveTargetSkillFile(product.name, installTarget);
42
+ await fs.access(sourceSkillFile);
43
+ const sourceSkillContent = await fs.readFile(sourceSkillFile, "utf8");
44
+ const targetSkillContent = renderInstalledContent(installTarget.tool, product.name, product.displayName, sourceSkillContent);
45
+ await fs.mkdir(installTarget.targetDir, { recursive: true });
46
+ await fs.writeFile(targetSkillFile, targetSkillContent, "utf8");
47
+ console.log("Installed skill:");
48
+ console.log("- command: install");
49
+ console.log(`- product: ${product.name}`);
50
+ console.log(`- display name: ${product.displayName}`);
51
+ console.log(`- tool: ${installTarget.tool}`);
52
+ console.log(`- source skill file: ${sourceSkillFile}`);
53
+ console.log(`- target dir: ${installTarget.targetDir}`);
54
+ console.log(`- target source: ${installTarget.source}`);
55
+ console.log(`- target skill file: ${targetSkillFile}`);
56
+ console.log(`- target format: ${installTarget.fileExtension}`);
57
+ console.log("- status: installed");
58
+ }
59
+ export async function runUninstall(productName, options) {
60
+ const product = requireProduct(productName);
61
+ if (!options.tool) {
62
+ throw new Error(`Missing required option: "--tool <${supportedToolsText()}>".`);
63
+ }
64
+ const installTarget = resolveInstallTarget(options.tool, options.targetDir);
65
+ const targetSkillFile = resolveTargetSkillFile(product.name, installTarget);
66
+ await fs.rm(targetSkillFile, { force: true });
67
+ console.log("Uninstalled skill:");
68
+ console.log("- command: uninstall");
69
+ console.log(`- product: ${product.name}`);
70
+ console.log(`- display name: ${product.displayName}`);
71
+ console.log(`- tool: ${installTarget.tool}`);
72
+ console.log(`- target dir: ${installTarget.targetDir}`);
73
+ console.log(`- target source: ${installTarget.source}`);
74
+ console.log(`- target skill file: ${targetSkillFile}`);
75
+ console.log(`- target format: ${installTarget.fileExtension}`);
76
+ console.log("- status: removed");
77
+ }
78
+ function requireProduct(productName) {
79
+ const product = getProduct(productName);
80
+ if (!product) {
81
+ const supportedProducts = listProducts()
82
+ .map((item) => item.name)
83
+ .join(", ");
84
+ throw new Error(`Unknown product: ${productName}. Supported products: ${supportedProducts}`);
85
+ }
86
+ return product;
87
+ }
88
+ function resolveTargetSkillFile(productName, installTarget) {
89
+ return path.join(installTarget.targetDir, installTarget.fileName ?? `${productName}.${installTarget.fileExtension}`);
90
+ }
@@ -0,0 +1,55 @@
1
+ export function createGlinkAuthHeaders(accessToken) {
2
+ if (!accessToken.trim()) {
3
+ throw new Error("Missing Glink access token.");
4
+ }
5
+ return {
6
+ Authorization: `Bearer ${accessToken}`,
7
+ "Content-Type": "application/json",
8
+ };
9
+ }
10
+ export async function glinkPost(config, endpoint, payload) {
11
+ const response = await fetch(`${config.baseUrl}${endpoint}`, {
12
+ method: "POST",
13
+ headers: createGlinkAuthHeaders(config.accessToken),
14
+ body: JSON.stringify(payload),
15
+ });
16
+ if (!response.ok) {
17
+ const errorText = await response.text();
18
+ throw new Error(`Glink request failed: ${response.status} ${errorText}`);
19
+ }
20
+ return response.json();
21
+ }
22
+ export async function getHotelList(config, payload) {
23
+ return glinkPost(config, "/v1/hotel/list", payload);
24
+ }
25
+ async function main() {
26
+ const accessToken = process.env.GLINK_ACCESS_TOKEN ?? "";
27
+ const clientConfig = {
28
+ baseUrl: "https://api.glink.example.com",
29
+ accessToken,
30
+ };
31
+ const payload = {
32
+ cityCode: "BJS",
33
+ checkIn: "2026-05-01",
34
+ checkOut: "2026-05-03",
35
+ rooms: 1,
36
+ adults: 2,
37
+ starRating: [4, 5],
38
+ sortBy: "price",
39
+ page: 1,
40
+ pageSize: 20,
41
+ };
42
+ try {
43
+ const result = await getHotelList(clientConfig, payload);
44
+ console.log("Glink hotel list result:");
45
+ console.log(JSON.stringify(result, null, 2));
46
+ }
47
+ catch (error) {
48
+ const message = error instanceof Error ? error.message : "Unknown error while calling Glink API.";
49
+ console.error(message);
50
+ process.exitCode = 1;
51
+ }
52
+ }
53
+ if (import.meta.url === `file://${process.argv[1]}`) {
54
+ void main();
55
+ }
@@ -0,0 +1,23 @@
1
+ const PRODUCTS = {
2
+ "glink-hotel-api": {
3
+ name: "glink-hotel-api",
4
+ displayName: "Glink Hotel API",
5
+ skillFile: "skills/glink-hotel-api/SKILL.md",
6
+ },
7
+ "flink-flight-api": {
8
+ name: "flink-flight-api",
9
+ displayName: "Flink Flight API",
10
+ skillFile: "skills/flink-flight-api/SKILL.md",
11
+ },
12
+ "tmc-whitelabel": {
13
+ name: "tmc-whitelabel",
14
+ displayName: "TMC Whitelabel",
15
+ skillFile: "skills/tmc-whitelabel/SKILL.md",
16
+ },
17
+ };
18
+ export function getProduct(productName) {
19
+ return PRODUCTS[productName];
20
+ }
21
+ export function listProducts() {
22
+ return Object.values(PRODUCTS);
23
+ }
@@ -0,0 +1,162 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ const TOOL_ALIASES = {
4
+ codex: "codex",
5
+ cursor: "cursor",
6
+ kiro: "kiro",
7
+ claude: "claude-code",
8
+ "claude-code": "claude-code",
9
+ gemini: "gemini-cli",
10
+ "gemini-cli": "gemini-cli",
11
+ };
12
+ const TOOL_NAMES = ["codex", "cursor", "kiro", "claude-code", "gemini-cli"];
13
+ export function supportedToolsText() {
14
+ return [...new Set(Object.keys(TOOL_ALIASES))].join("|");
15
+ }
16
+ export function parseToolName(toolName) {
17
+ if (!toolName) {
18
+ return undefined;
19
+ }
20
+ return TOOL_ALIASES[toolName];
21
+ }
22
+ export function resolveInstallTarget(tool, targetDir) {
23
+ if (targetDir) {
24
+ return {
25
+ tool,
26
+ targetDir: path.resolve(targetDir),
27
+ source: "custom",
28
+ fileExtension: tool === "cursor" ? "mdc" : "md",
29
+ fileName: getToolFileName(tool),
30
+ };
31
+ }
32
+ switch (tool) {
33
+ case "codex": {
34
+ const codexHome = process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex");
35
+ return {
36
+ tool,
37
+ targetDir: path.join(codexHome, "skills"),
38
+ source: "default",
39
+ fileExtension: "md",
40
+ fileName: undefined,
41
+ };
42
+ }
43
+ case "cursor": {
44
+ const cursorRulesDir = process.env.CURSOR_RULES_DIR ?? path.join(process.cwd(), ".cursor", "rules");
45
+ return {
46
+ tool,
47
+ targetDir: cursorRulesDir,
48
+ source: "default",
49
+ fileExtension: "mdc",
50
+ fileName: undefined,
51
+ };
52
+ }
53
+ case "kiro": {
54
+ const kiroHome = process.env.KIRO_HOME ?? path.join(os.homedir(), ".kiro");
55
+ return {
56
+ tool,
57
+ targetDir: path.join(kiroHome, "skills"),
58
+ source: "default",
59
+ fileExtension: "md",
60
+ fileName: undefined,
61
+ };
62
+ }
63
+ case "claude-code": {
64
+ const claudeHome = process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude");
65
+ return {
66
+ tool,
67
+ targetDir: path.join(claudeHome, "agents"),
68
+ source: "default",
69
+ fileExtension: "md",
70
+ fileName: undefined,
71
+ };
72
+ }
73
+ case "gemini-cli": {
74
+ const geminiDir = process.env.GEMINI_HOME ?? process.cwd();
75
+ return {
76
+ tool,
77
+ targetDir: path.resolve(geminiDir),
78
+ source: "default",
79
+ fileExtension: "md",
80
+ fileName: "GEMINI.md",
81
+ };
82
+ }
83
+ }
84
+ }
85
+ export function renderInstalledContent(tool, productName, displayName, sourceContent) {
86
+ switch (tool) {
87
+ case "cursor": {
88
+ const skillBody = stripLeadingFrontmatter(sourceContent).trim();
89
+ return [
90
+ "---",
91
+ `description: Imported rule for ${displayName}`,
92
+ "alwaysApply: false",
93
+ "---",
94
+ "",
95
+ `# ${displayName}`,
96
+ "",
97
+ `Imported from msg-skills as the \`${productName}\` rule.`,
98
+ "",
99
+ skillBody,
100
+ "",
101
+ ].join("\n");
102
+ }
103
+ case "claude-code": {
104
+ const description = extractDescription(sourceContent) ?? `Use for ${displayName} tasks.`;
105
+ const skillBody = stripLeadingFrontmatter(sourceContent).trim();
106
+ return [
107
+ "---",
108
+ `name: ${productName}`,
109
+ `description: ${description}`,
110
+ "---",
111
+ "",
112
+ skillBody,
113
+ "",
114
+ ].join("\n");
115
+ }
116
+ case "gemini-cli": {
117
+ const skillBody = stripLeadingFrontmatter(sourceContent).trim();
118
+ return [
119
+ `# ${displayName}`,
120
+ "",
121
+ `Imported from msg-skills for \`${productName}\`.`,
122
+ "",
123
+ skillBody,
124
+ "",
125
+ ].join("\n");
126
+ }
127
+ default:
128
+ return sourceContent;
129
+ }
130
+ }
131
+ function stripLeadingFrontmatter(sourceContent) {
132
+ if (!sourceContent.startsWith("---\n")) {
133
+ return sourceContent;
134
+ }
135
+ const frontmatterEnd = sourceContent.indexOf("\n---\n", 4);
136
+ if (frontmatterEnd === -1) {
137
+ return sourceContent;
138
+ }
139
+ return sourceContent.slice(frontmatterEnd + 5);
140
+ }
141
+ function extractDescription(sourceContent) {
142
+ if (!sourceContent.startsWith("---\n")) {
143
+ return undefined;
144
+ }
145
+ const frontmatterEnd = sourceContent.indexOf("\n---\n", 4);
146
+ if (frontmatterEnd === -1) {
147
+ return undefined;
148
+ }
149
+ const frontmatter = sourceContent.slice(4, frontmatterEnd);
150
+ for (const line of frontmatter.split("\n")) {
151
+ if (line.startsWith("description:")) {
152
+ return line.slice("description:".length).trim();
153
+ }
154
+ }
155
+ return undefined;
156
+ }
157
+ function getToolFileName(tool) {
158
+ if (tool === "gemini-cli") {
159
+ return "GEMINI.md";
160
+ }
161
+ return undefined;
162
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@mongoui/skills",
3
+ "version": "0.1.4",
4
+ "description": "CLI for installing product skills into AI tools",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "bin": {
11
+ "msg-skills": "dist/cli.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "skills"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "dev": "tsx src/cli.ts"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^24.6.0",
23
+ "tsx": "^4.20.6",
24
+ "typescript": "^5.9.3"
25
+ }
26
+ }
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: flink-flight-api
3
+ description: Placeholder skill for Flink Flight API.
4
+ ---
5
+
6
+ # Flink Flight API
7
+
8
+ 内容待补充。
@@ -0,0 +1,175 @@
1
+ ---
2
+ name: glink-hotel-api
3
+ description: Glink 酒店 API 接入规范。支持鉴权、酒店列表查询、参数封装、错误处理。触发词:Glink、酒店接口、酒店查询、鉴权函数、hotel list。
4
+ ---
5
+
6
+ # Glink Hotel API
7
+
8
+ ## 目标
9
+
10
+ 当用户提到 Glink 酒店相关需求时,优先帮助用户完成以下任务:
11
+
12
+ - 实现 Glink API 的鉴权逻辑
13
+ - 封装酒店列表查询函数
14
+ - 生成 Node.js / TypeScript 调用示例
15
+ - 根据接口文档补全请求头、请求体、错误处理
16
+ - 在业务项目中封装可复用的 service / sdk 方法
17
+
18
+ 默认优先输出 TypeScript 示例,其次是 JavaScript。
19
+
20
+ ## 核心规则
21
+
22
+ - 所有请求默认使用 HTTPS
23
+ - 所有受保护接口必须携带 `Authorization: Bearer {access_token}`
24
+ - 请求头必须包含 `Content-Type: application/json`
25
+ - 日期字段使用 `YYYY-MM-DD`
26
+ - 示例代码优先使用 `fetch`
27
+ - 如果用户项目明显使用 `axios`,则跟随项目风格
28
+ - 如果用户要求“实现函数”,默认补全类型定义、请求函数、错误处理
29
+ - 如果用户没有说明运行环境,默认按 Node.js 18+ 处理
30
+ - 如果用户没有说明框架,默认给出独立可复用函数,不强绑定 NestJS/Express
31
+
32
+ ## 鉴权要求
33
+
34
+ 当用户提到“鉴权”“token”“Bearer”“access_token”时:
35
+
36
+ - 明确告诉用户需要在请求头中传递 Bearer Token
37
+ - 不要把 token 写死在代码里
38
+ - 优先从函数参数、环境变量或配置对象中读取 token
39
+ - 生成代码时,默认将鉴权逻辑封装为独立函数或统一请求客户端
40
+
41
+ 推荐模式:
42
+
43
+ 1. 先定义配置项
44
+ 2. 再封装请求方法
45
+ 3. 再封装业务接口方法
46
+
47
+ ## 常见接口示例
48
+
49
+ ### 查询酒店列表
50
+
51
+ ```http
52
+ POST /v1/hotel/list
53
+ Authorization: Bearer {access_token}
54
+ Content-Type: application/json
55
+
56
+ {
57
+ "cityCode": "BJS",
58
+ "checkIn": "2026-05-01",
59
+ "checkOut": "2026-05-03",
60
+ "rooms": 1,
61
+ "adults": 2,
62
+ "starRating": [4, 5],
63
+ "sortBy": "price",
64
+ "page": 1,
65
+ "pageSize": 20
66
+ }
67
+ ```
68
+
69
+ ## TypeScript 示例
70
+
71
+ ```ts
72
+ type HotelListRequest = {
73
+ cityCode: string;
74
+ checkIn: string;
75
+ checkOut: string;
76
+ rooms: number;
77
+ adults: number;
78
+ starRating?: number[];
79
+ sortBy?: string;
80
+ page?: number;
81
+ pageSize?: number;
82
+ };
83
+
84
+ type GlinkClientOptions = {
85
+ baseUrl: string;
86
+ accessToken: string;
87
+ };
88
+
89
+ async function glinkRequest<T>(
90
+ path: string,
91
+ options: GlinkClientOptions,
92
+ body: unknown,
93
+ ): Promise<T> {
94
+ const response = await fetch(`${options.baseUrl}${path}`, {
95
+ method: "POST",
96
+ headers: {
97
+ Authorization: `Bearer ${options.accessToken}`,
98
+ "Content-Type": "application/json",
99
+ },
100
+ body: JSON.stringify(body),
101
+ });
102
+
103
+ if (!response.ok) {
104
+ const errorText = await response.text();
105
+ throw new Error(`Glink request failed: ${response.status} ${errorText}`);
106
+ }
107
+
108
+ return response.json() as Promise<T>;
109
+ }
110
+
111
+ export async function getHotelList<T>(
112
+ options: GlinkClientOptions,
113
+ payload: HotelListRequest,
114
+ ): Promise<T> {
115
+ return glinkRequest<T>("/v1/hotel/list", options, payload);
116
+ }
117
+ ```
118
+
119
+ ## 当用户这样提问时
120
+
121
+ ### 用户说
122
+
123
+ `帮我实现 Glink 鉴权函数`
124
+
125
+ 应该优先输出:
126
+
127
+ - 一个可复用的鉴权请求封装
128
+ - 如何传入 `access_token`
129
+ - 如何设置 `Authorization: Bearer ...`
130
+ - 一个最小可运行示例
131
+
132
+ ### 用户说
133
+
134
+ `帮我调用 Glink 酒店列表接口`
135
+
136
+ 应该优先输出:
137
+
138
+ - 请求函数
139
+ - 请求参数类型
140
+ - 调用示例
141
+ - 错误处理
142
+ - 必要请求头
143
+
144
+ ### 用户说
145
+
146
+ `帮我封装成 service`
147
+
148
+ 应该优先输出:
149
+
150
+ - 一个类或模块化 service
151
+ - 构造函数接收 `baseUrl` 和 `accessToken`
152
+ - 业务方法如 `getHotelList`
153
+
154
+ ## 输出要求
155
+
156
+ - 优先输出完整可运行代码,而不是只解释思路
157
+ - 如果用户没有要求框架,优先输出纯 TypeScript 函数
158
+ - 如果接口字段不完整,明确标注“以下字段基于当前 Skills 示例推断”
159
+ - 如果缺少响应结构,不要编造复杂返回值类型,可先使用泛型或基础类型
160
+ - 如果用户要求接入现有项目,尽量贴近项目已有代码风格
161
+
162
+ ## 常见注意事项
163
+
164
+ - 不要把 `Authorization` 写成普通自定义字段
165
+ - 不要漏掉 `Content-Type: application/json`
166
+ - 不要把日期写成时间戳,除非用户明确要求
167
+ - 不要默认把所有字段都设成可选
168
+ - 不要把分页参数写死
169
+ - 如果用户要生产代码,提醒 token 不应硬编码
170
+
171
+ ## 回答风格
172
+
173
+ - 直接给代码
174
+ - 再简要说明关键点
175
+ - 优先面向接入开发,不做空泛概念解释
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: tmc-whitelabel
3
+ description: Placeholder skill for TMC Whitelabel.
4
+ ---
5
+
6
+ # TMC Whitelabel
7
+
8
+ 内容待补充。