@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 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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from "../src/cli.js";
4
+
5
+ run(process.argv.slice(2)).catch((error) => {
6
+ console.error(`Error: ${error.message}`);
7
+ process.exitCode = 1;
8
+ });
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
+ }