@ianyian/myskills 0.1.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/LICENSE +21 -0
- package/README.md +101 -0
- package/bin/myskills.js +8 -0
- package/package.json +38 -0
- package/src/cli.js +36 -0
- package/src/model-client.js +38 -0
- package/src/shared.js +31 -0
- package/src/skills/idea-to-execution.js +27 -0
- package/src/skills/repo-explorer.js +57 -0
- package/src/skills/research-brief.js +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ianyian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @ianyian/myskills
|
|
2
|
+
|
|
3
|
+
Three practical AI skills that turn a Node.js CLI into a small project assistant:
|
|
4
|
+
|
|
5
|
+
- **`repo-explorer`** understands a local repository and writes an architecture and onboarding guide.
|
|
6
|
+
- **`research-brief`** turns a topic and optional URLs into a cited Markdown research brief.
|
|
7
|
+
- **`idea-to-execution`** converts an idea into an implementation plan with tasks and acceptance criteria.
|
|
8
|
+
|
|
9
|
+
Repository: <https://github.com/ianyian/mySKILLs>
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js 18 or newer
|
|
14
|
+
- An OpenAI-compatible API key
|
|
15
|
+
|
|
16
|
+
The CLI uses the OpenAI-compatible Chat Completions API. It works with OpenAI by default and can be pointed at another compatible provider with `OPENAI_BASE_URL`.
|
|
17
|
+
|
|
18
|
+
## Install and configure
|
|
19
|
+
|
|
20
|
+
Run without installing:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @ianyian/myskills --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or install globally:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --global @ianyian/myskills
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Configure the model client:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export OPENAI_API_KEY="your-api-key"
|
|
36
|
+
# Optional:
|
|
37
|
+
export OPENAI_MODEL="gpt-4o-mini"
|
|
38
|
+
export OPENAI_BASE_URL="https://api.openai.com/v1"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
Analyze the current repository:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx @ianyian/myskills repo-explorer .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This creates `PROJECT_OVERVIEW.md`. Choose another output path with `--output`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @ianyian/myskills repo-explorer ./my-project --output docs/onboarding.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Create a research brief:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx @ianyian/myskills research-brief "Compare PostgreSQL and MongoDB"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Add source URLs. The skill fetches their text and asks the model to cite them:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx @ianyian/myskills research-brief \
|
|
65
|
+
"Best practices for Node.js error handling" \
|
|
66
|
+
--url https://nodejs.org/en/learn/getting-started/introduction-to-nodejs \
|
|
67
|
+
--output docs/node-research.md
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Turn a product idea into an execution plan:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx @ianyian/myskills idea-to-execution \
|
|
74
|
+
"Add dark mode to my web application" \
|
|
75
|
+
--output implementation-plan.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install
|
|
82
|
+
npm test
|
|
83
|
+
npm pack --dry-run
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The package contains no runtime dependencies; it uses Node.js built-ins and `fetch`.
|
|
87
|
+
|
|
88
|
+
## Publish
|
|
89
|
+
|
|
90
|
+
After logging in to npm, publish a new version:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm login
|
|
94
|
+
npm publish --access public
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Every later publish needs a new version, for example `npm version patch && npm publish`.
|
|
98
|
+
|
|
99
|
+
## Security and privacy
|
|
100
|
+
|
|
101
|
+
The CLI sends the repository excerpts or fetched source text to the configured model provider. Review your provider's data policy before using it with private code. API keys are read from environment variables and are never written to generated files.
|
package/bin/myskills.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ianyian/myskills",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Practical AI skills for repository analysis, research, and execution planning",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"myskills": "bin/myskills.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test",
|
|
17
|
+
"start": "node ./bin/myskills.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/ianyian/mySKILLs.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/ianyian/mySKILLs",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/ianyian/mySKILLs/issues"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"ai",
|
|
32
|
+
"agents",
|
|
33
|
+
"skills",
|
|
34
|
+
"cli",
|
|
35
|
+
"automation"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { runRepoExplorer } from "./skills/repo-explorer.js";
|
|
2
|
+
import { runResearchBrief } from "./skills/research-brief.js";
|
|
3
|
+
import { runIdeaToExecution } from "./skills/idea-to-execution.js";
|
|
4
|
+
|
|
5
|
+
const HELP = `@ianyian/myskills - practical AI skills for projects
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
myskills <command> [arguments] [options]
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
repo-explorer [directory] Analyze a repository
|
|
12
|
+
research-brief <topic> Create a research brief
|
|
13
|
+
idea-to-execution <idea> Create an implementation plan
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--output <file> Output Markdown path
|
|
17
|
+
--url <url> Source URL (repeatable, research-brief only)
|
|
18
|
+
--help Show this help
|
|
19
|
+
|
|
20
|
+
Environment:
|
|
21
|
+
OPENAI_API_KEY Required model provider API key
|
|
22
|
+
OPENAI_MODEL Optional model (default: gpt-4o-mini)
|
|
23
|
+
OPENAI_BASE_URL Optional OpenAI-compatible API base URL`;
|
|
24
|
+
|
|
25
|
+
export async function run(args) {
|
|
26
|
+
const [command, ...rest] = args;
|
|
27
|
+
if (!command || command === "--help" || command === "-h") {
|
|
28
|
+
console.log(HELP);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (command === "repo-explorer") return runRepoExplorer(rest);
|
|
33
|
+
if (command === "research-brief") return runResearchBrief(rest);
|
|
34
|
+
if (command === "idea-to-execution") return runIdeaToExecution(rest);
|
|
35
|
+
throw new Error(`Unknown command "${command}". Run "myskills --help" for usage.`);
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
|
2
|
+
const DEFAULT_MODEL = "gpt-4o-mini";
|
|
3
|
+
|
|
4
|
+
export async function complete({ system, user }) {
|
|
5
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
throw new Error("OPENAI_API_KEY is not set. Export an OpenAI-compatible API key and try again.");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const baseUrl = (process.env.OPENAI_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
11
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: {
|
|
14
|
+
"content-type": "application/json",
|
|
15
|
+
authorization: `Bearer ${apiKey}`,
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
model: process.env.OPENAI_MODEL || DEFAULT_MODEL,
|
|
19
|
+
temperature: 0.2,
|
|
20
|
+
messages: [
|
|
21
|
+
{ role: "system", content: system },
|
|
22
|
+
{ role: "user", content: user },
|
|
23
|
+
],
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const body = await response.json().catch(() => ({}));
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const detail = body?.error?.message || response.statusText;
|
|
30
|
+
throw new Error(`Model request failed (${response.status}): ${detail}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const content = body?.choices?.[0]?.message?.content;
|
|
34
|
+
if (typeof content !== "string" || content.trim() === "") {
|
|
35
|
+
throw new Error("Model response did not contain text.");
|
|
36
|
+
}
|
|
37
|
+
return content.trim();
|
|
38
|
+
}
|
package/src/shared.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function writeOutput(filePath, content) {
|
|
5
|
+
const absolutePath = path.resolve(filePath);
|
|
6
|
+
await mkdir(path.dirname(absolutePath), { recursive: true });
|
|
7
|
+
await writeFile(absolutePath, `${content.trim()}\n`, "utf8");
|
|
8
|
+
return absolutePath;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function optionValue(args, name, fallback) {
|
|
12
|
+
const index = args.indexOf(name);
|
|
13
|
+
return index === -1 ? fallback : args[index + 1];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function optionValues(args, name) {
|
|
17
|
+
const values = [];
|
|
18
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
19
|
+
if (args[index] === name && args[index + 1]) values.push(args[index + 1]);
|
|
20
|
+
}
|
|
21
|
+
return values;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function removeOptions(args, names) {
|
|
25
|
+
const result = [];
|
|
26
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
27
|
+
if (names.includes(args[index])) index += 1;
|
|
28
|
+
else result.push(args[index]);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { complete } from "../model-client.js";
|
|
2
|
+
import { optionValue, removeOptions, writeOutput } from "../shared.js";
|
|
3
|
+
|
|
4
|
+
export async function runIdeaToExecution(args) {
|
|
5
|
+
const output = optionValue(args, "--output", "implementation-plan.md");
|
|
6
|
+
const idea = removeOptions(args, ["--output"]).join(" ").trim();
|
|
7
|
+
if (!idea) throw new Error("Provide an idea to turn into an execution plan.");
|
|
8
|
+
|
|
9
|
+
const result = await complete({
|
|
10
|
+
system: "You are a pragmatic technical product manager and software architect. Make assumptions explicit and keep the plan implementable.",
|
|
11
|
+
user: `Turn this idea into an execution-ready Markdown plan:
|
|
12
|
+
|
|
13
|
+
${idea}
|
|
14
|
+
|
|
15
|
+
Include:
|
|
16
|
+
1. Problem and goals
|
|
17
|
+
2. Assumptions and non-goals
|
|
18
|
+
3. Proposed user experience
|
|
19
|
+
4. Technical approach
|
|
20
|
+
5. Ordered implementation tasks with likely files or modules
|
|
21
|
+
6. Acceptance criteria
|
|
22
|
+
7. Testing strategy
|
|
23
|
+
8. Risks and open questions`,
|
|
24
|
+
});
|
|
25
|
+
const written = await writeOutput(output, result);
|
|
26
|
+
console.log(`Implementation plan written to ${written}`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { complete } from "../model-client.js";
|
|
4
|
+
import { optionValue, writeOutput } from "../shared.js";
|
|
5
|
+
|
|
6
|
+
const IGNORED = new Set([".git", "node_modules", "dist", "build", "coverage", ".next"]);
|
|
7
|
+
const MAX_FILES = 80;
|
|
8
|
+
const MAX_CHARS_PER_FILE = 5000;
|
|
9
|
+
|
|
10
|
+
async function collectFiles(directory, relative = "") {
|
|
11
|
+
if (relative.split(path.sep).some((part) => IGNORED.has(part))) return [];
|
|
12
|
+
const entries = await readdir(path.join(directory, relative), { withFileTypes: true });
|
|
13
|
+
const files = [];
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const child = path.join(relative, entry.name);
|
|
16
|
+
if (entry.isDirectory()) files.push(...await collectFiles(directory, child));
|
|
17
|
+
else if (files.length < MAX_FILES && !entry.name.startsWith(".env")) files.push(child);
|
|
18
|
+
if (files.length >= MAX_FILES) break;
|
|
19
|
+
}
|
|
20
|
+
return files;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function runRepoExplorer(args) {
|
|
24
|
+
const directory = args[0] && !args[0].startsWith("--") ? args[0] : ".";
|
|
25
|
+
const output = optionValue(args, "--output", "PROJECT_OVERVIEW.md");
|
|
26
|
+
const root = path.resolve(directory);
|
|
27
|
+
if (!(await stat(root)).isDirectory()) throw new Error(`${directory} is not a directory.`);
|
|
28
|
+
|
|
29
|
+
const files = await collectFiles(root);
|
|
30
|
+
const excerpts = [];
|
|
31
|
+
for (const relative of files) {
|
|
32
|
+
try {
|
|
33
|
+
const content = await readFile(path.join(root, relative), "utf8");
|
|
34
|
+
excerpts.push(`### ${relative}\n${content.slice(0, MAX_CHARS_PER_FILE)}`);
|
|
35
|
+
} catch {
|
|
36
|
+
// Binary and unreadable files are represented by the file list only.
|
|
37
|
+
excerpts.push(`### ${relative}\n[content unavailable]`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = await complete({
|
|
42
|
+
system: "You are a senior software architect. Produce accurate, practical Markdown. Do not invent files, dependencies, or behavior that is not supported by the supplied repository evidence.",
|
|
43
|
+
user: `Analyze this repository and write an onboarding document with these sections:
|
|
44
|
+
1. Executive summary
|
|
45
|
+
2. Architecture and data flow
|
|
46
|
+
3. Important directories and files
|
|
47
|
+
4. How to run, test, and extend it
|
|
48
|
+
5. Dependencies and configuration
|
|
49
|
+
6. Risks, unknowns, and recommended next steps
|
|
50
|
+
|
|
51
|
+
Repository: ${root}
|
|
52
|
+
File excerpts:
|
|
53
|
+
${excerpts.join("\n\n")}`,
|
|
54
|
+
});
|
|
55
|
+
const written = await writeOutput(output, result);
|
|
56
|
+
console.log(`Repository overview written to ${written}`);
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { complete } from "../model-client.js";
|
|
2
|
+
import { optionValue, optionValues, removeOptions, writeOutput } from "../shared.js";
|
|
3
|
+
|
|
4
|
+
export async function runResearchBrief(args) {
|
|
5
|
+
const urls = optionValues(args, "--url");
|
|
6
|
+
const output = optionValue(args, "--output", "research-brief.md");
|
|
7
|
+
const topicArgs = removeOptions(args, ["--url", "--output"]);
|
|
8
|
+
const topic = topicArgs.join(" ").trim();
|
|
9
|
+
if (!topic) throw new Error("Provide a research topic.");
|
|
10
|
+
|
|
11
|
+
const sources = [];
|
|
12
|
+
for (const url of urls) {
|
|
13
|
+
let response;
|
|
14
|
+
try {
|
|
15
|
+
response = await fetch(url);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error(`Could not fetch ${url}: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
if (!response.ok) throw new Error(`Could not fetch ${url} (${response.status}).`);
|
|
20
|
+
sources.push(`SOURCE: ${url}\n${(await response.text()).slice(0, 12000)}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result = await complete({
|
|
24
|
+
system: "You are a careful research analyst. Distinguish facts from interpretation, cite supplied URLs inline as [1], [2], and never claim to have consulted sources that were not supplied.",
|
|
25
|
+
user: `Create a concise Markdown research brief about: ${topic}
|
|
26
|
+
|
|
27
|
+
Include: an executive summary, key findings, comparison or trade-offs where relevant, practical recommendations, and a Sources section. If no URLs are supplied, clearly label claims that need verification.
|
|
28
|
+
|
|
29
|
+
${sources.length ? sources.join("\n\n") : "No source URLs were supplied."}`,
|
|
30
|
+
});
|
|
31
|
+
const written = await writeOutput(output, result);
|
|
32
|
+
console.log(`Research brief written to ${written}`);
|
|
33
|
+
}
|