@tanstack/intent 0.0.1
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 +66 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +327 -0
- package/dist/feedback-DKreHfB1.mjs +300 -0
- package/dist/feedback-FIUBOL0g.mjs +3 -0
- package/dist/index.d.mts +61 -0
- package/dist/index.mjs +8 -0
- package/dist/init-DEzzXm9j.mjs +3 -0
- package/dist/init-DNxmjQfU.mjs +70 -0
- package/dist/intent-library.d.mts +1 -0
- package/dist/intent-library.mjs +123 -0
- package/dist/library-scanner-BrznE00j.mjs +111 -0
- package/dist/library-scanner.d.mts +16 -0
- package/dist/library-scanner.mjs +4 -0
- package/dist/scanner-BuWPDJ4P.mjs +4 -0
- package/dist/scanner-CpsJAHXT.mjs +147 -0
- package/dist/setup-CNGz26qL.mjs +116 -0
- package/dist/setup-N5dttGp_.d.mts +10 -0
- package/dist/setup.d.mts +2 -0
- package/dist/setup.mjs +3 -0
- package/dist/staleness-CnomT9Hm.mjs +72 -0
- package/dist/staleness-DyhsrqQ5.mjs +4 -0
- package/dist/types-kbQfN_is.d.mts +70 -0
- package/dist/utils-DjkEPBxu.mjs +39 -0
- package/meta/domain-discovery/SKILL.md +681 -0
- package/meta/generate-skill/SKILL.md +419 -0
- package/meta/skill-staleness-check/SKILL.md +282 -0
- package/meta/templates/oz/domain-discovery.md +53 -0
- package/meta/templates/oz/feedback-collection.md +69 -0
- package/meta/templates/oz/skill-update.md +47 -0
- package/meta/templates/oz/tree-generation.md +48 -0
- package/meta/templates/workflows/generate-skills-oz.yml +86 -0
- package/meta/templates/workflows/notify-playbooks.yml +52 -0
- package/meta/templates/workflows/update-skills-oz.yml +98 -0
- package/meta/templates/workflows/validate-skills.yml +52 -0
- package/meta/tree-generator/SKILL.md +859 -0
- package/package.json +38 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { a as IntentProjectConfig, c as ScanResult, d as StalenessReport, i as IntentPackage, l as SkillEntry, n as FeedbackPayload, o as MetaFeedbackPayload, r as IntentConfig, s as MetaSkillName, t as AgentName, u as SkillStaleness } from "./types-kbQfN_is.mjs";
|
|
2
|
+
import { n as runSetup } from "./setup-N5dttGp_.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/scanner.d.ts
|
|
5
|
+
declare function scanForIntents(root?: string): Promise<ScanResult>;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/staleness.d.ts
|
|
8
|
+
declare function checkStaleness(packageDir: string, packageName?: string): Promise<StalenessReport>;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/feedback.d.ts
|
|
11
|
+
declare function containsSecrets(text: string): boolean;
|
|
12
|
+
declare function hasGhCli(): boolean;
|
|
13
|
+
declare function resolveFrequency(root: string): string;
|
|
14
|
+
declare function validatePayload(payload: unknown): {
|
|
15
|
+
valid: boolean;
|
|
16
|
+
errors: string[];
|
|
17
|
+
};
|
|
18
|
+
declare function validateMetaPayload(payload: unknown): {
|
|
19
|
+
valid: boolean;
|
|
20
|
+
errors: string[];
|
|
21
|
+
};
|
|
22
|
+
declare function metaToMarkdown(payload: MetaFeedbackPayload): string;
|
|
23
|
+
declare function toMarkdown(payload: FeedbackPayload): string;
|
|
24
|
+
interface SubmitResult {
|
|
25
|
+
method: 'gh' | 'file' | 'stdout';
|
|
26
|
+
detail: string;
|
|
27
|
+
}
|
|
28
|
+
declare function submitFeedback(payload: FeedbackPayload, repo: string, opts: {
|
|
29
|
+
ghAvailable: boolean;
|
|
30
|
+
outputPath?: string;
|
|
31
|
+
}): SubmitResult;
|
|
32
|
+
declare function submitMetaFeedback(payload: MetaFeedbackPayload, opts: {
|
|
33
|
+
ghAvailable: boolean;
|
|
34
|
+
outputPath?: string;
|
|
35
|
+
}): SubmitResult;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/init.d.ts
|
|
38
|
+
declare function detectAgentConfigs(root: string): string[];
|
|
39
|
+
declare function hasIntentBlock(filePath: string): boolean;
|
|
40
|
+
declare function injectIntentBlock(filePath: string): boolean;
|
|
41
|
+
declare function writeProjectConfig(root: string): string;
|
|
42
|
+
declare function readProjectConfig(root: string): IntentProjectConfig | null;
|
|
43
|
+
interface InitResult {
|
|
44
|
+
injected: string[];
|
|
45
|
+
skipped: string[];
|
|
46
|
+
created: string[];
|
|
47
|
+
configPath: string;
|
|
48
|
+
}
|
|
49
|
+
declare function runInit(root: string): InitResult;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/utils.d.ts
|
|
52
|
+
/**
|
|
53
|
+
* Recursively find all SKILL.md files under a directory.
|
|
54
|
+
*/
|
|
55
|
+
declare function findSkillFiles(dir: string): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Parse YAML frontmatter from a file. Returns null if no frontmatter or on error.
|
|
58
|
+
*/
|
|
59
|
+
declare function parseFrontmatter(filePath: string): Record<string, unknown> | null;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { type AgentName, type FeedbackPayload, type IntentConfig, type IntentPackage, type IntentProjectConfig, type MetaFeedbackPayload, type MetaSkillName, type ScanResult, type SkillEntry, type SkillStaleness, type StalenessReport, checkStaleness, containsSecrets, detectAgentConfigs, findSkillFiles, hasGhCli, hasIntentBlock, injectIntentBlock, metaToMarkdown, parseFrontmatter, readProjectConfig, resolveFrequency, runInit, runSetup, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload, writeProjectConfig };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { n as parseFrontmatter, t as findSkillFiles } from "./utils-DjkEPBxu.mjs";
|
|
2
|
+
import { t as scanForIntents } from "./scanner-CpsJAHXT.mjs";
|
|
3
|
+
import { t as checkStaleness } from "./staleness-CnomT9Hm.mjs";
|
|
4
|
+
import { c as toMarkdown, i as resolveFrequency, l as validateMetaPayload, n as hasGhCli, o as submitFeedback, r as metaToMarkdown, s as submitMetaFeedback, t as containsSecrets, u as validatePayload } from "./feedback-DKreHfB1.mjs";
|
|
5
|
+
import { a as runInit, i as readProjectConfig, n as hasIntentBlock, o as writeProjectConfig, r as injectIntentBlock, t as detectAgentConfigs } from "./init-DNxmjQfU.mjs";
|
|
6
|
+
import { t as runSetup } from "./setup-CNGz26qL.mjs";
|
|
7
|
+
|
|
8
|
+
export { checkStaleness, containsSecrets, detectAgentConfigs, findSkillFiles, hasGhCli, hasIntentBlock, injectIntentBlock, metaToMarkdown, parseFrontmatter, readProjectConfig, resolveFrequency, runInit, runSetup, scanForIntents, submitFeedback, submitMetaFeedback, toMarkdown, validateMetaPayload, validatePayload, writeProjectConfig };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/init.ts
|
|
5
|
+
const AGENT_CONFIG_FILES = [
|
|
6
|
+
"AGENTS.md",
|
|
7
|
+
"CLAUDE.md",
|
|
8
|
+
".cursorrules",
|
|
9
|
+
".github/copilot-instructions.md"
|
|
10
|
+
];
|
|
11
|
+
const INTENT_BLOCK_MARKER = "## Intent Skills";
|
|
12
|
+
const INTENT_BLOCK = `## Intent Skills
|
|
13
|
+
|
|
14
|
+
This project uses TanStack Intent. Run \`npx intent list\` to discover
|
|
15
|
+
available AI coding skills. Before working with a library that has skills,
|
|
16
|
+
read the relevant SKILL.md file at the path shown in the list output.
|
|
17
|
+
`;
|
|
18
|
+
const DEFAULT_CONFIG = { feedback: { frequency: "every-5" } };
|
|
19
|
+
function detectAgentConfigs(root) {
|
|
20
|
+
return AGENT_CONFIG_FILES.map((f) => join(root, f)).filter((f) => existsSync(f));
|
|
21
|
+
}
|
|
22
|
+
function hasIntentBlock(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
return readFileSync(filePath, "utf8").includes(INTENT_BLOCK_MARKER);
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function injectIntentBlock(filePath) {
|
|
30
|
+
if (hasIntentBlock(filePath)) return false;
|
|
31
|
+
let content;
|
|
32
|
+
try {
|
|
33
|
+
content = readFileSync(filePath, "utf8");
|
|
34
|
+
} catch {
|
|
35
|
+
content = "";
|
|
36
|
+
}
|
|
37
|
+
const separator = content.length > 0 && !content.endsWith("\n\n") ? "\n\n" : "";
|
|
38
|
+
writeFileSync(filePath, content + separator + INTENT_BLOCK);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
function writeProjectConfig(root) {
|
|
42
|
+
const configPath = join(root, "intent.config.json");
|
|
43
|
+
if (!existsSync(configPath)) writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
|
|
44
|
+
return configPath;
|
|
45
|
+
}
|
|
46
|
+
function readProjectConfig(root) {
|
|
47
|
+
const configPath = join(root, "intent.config.json");
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(readFileSync(configPath, "utf8"));
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function runInit(root) {
|
|
55
|
+
const detected = detectAgentConfigs(root);
|
|
56
|
+
const injected = [];
|
|
57
|
+
const skipped = [];
|
|
58
|
+
const created = [];
|
|
59
|
+
for (const filePath of detected) if (injectIntentBlock(filePath)) injected.push(filePath);
|
|
60
|
+
else skipped.push(filePath);
|
|
61
|
+
return {
|
|
62
|
+
injected,
|
|
63
|
+
skipped,
|
|
64
|
+
created,
|
|
65
|
+
configPath: writeProjectConfig(root)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { runInit as a, readProjectConfig as i, hasIntentBlock as n, writeProjectConfig as o, injectIntentBlock as r, detectAgentConfigs as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./utils-DjkEPBxu.mjs";
|
|
3
|
+
import { t as scanLibrary } from "./library-scanner-BrznE00j.mjs";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { release } from "node:os";
|
|
6
|
+
|
|
7
|
+
//#region src/intent-library.ts
|
|
8
|
+
async function cmdList() {
|
|
9
|
+
let result;
|
|
10
|
+
try {
|
|
11
|
+
result = await scanLibrary(process.argv[1]);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error(err.message);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
if (result.packages.length === 0) {
|
|
17
|
+
console.log("No intent-enabled packages found.");
|
|
18
|
+
if (result.warnings.length > 0) {
|
|
19
|
+
console.log("\nWarnings:");
|
|
20
|
+
for (const w of result.warnings) console.log(` ⚠ ${w}`);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const pkg of result.packages) {
|
|
25
|
+
const header = pkg.description ? `${pkg.name} v${pkg.version} — ${pkg.description}` : `${pkg.name} v${pkg.version}`;
|
|
26
|
+
console.log(header);
|
|
27
|
+
for (const skill of pkg.skills) {
|
|
28
|
+
const name = skill.name.padEnd(28);
|
|
29
|
+
const desc = (skill.description || "").padEnd(52);
|
|
30
|
+
console.log(` ${name}${desc}${skill.path}`);
|
|
31
|
+
}
|
|
32
|
+
console.log();
|
|
33
|
+
}
|
|
34
|
+
if (result.warnings.length > 0) {
|
|
35
|
+
console.log("Warnings:");
|
|
36
|
+
for (const w of result.warnings) console.log(` ⚠ ${w}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function cmdInstall() {
|
|
40
|
+
function tryCopyToClipboard(text) {
|
|
41
|
+
const platform = process.platform;
|
|
42
|
+
const isWsl = platform === "linux" && (Boolean(process.env.WSL_DISTRO_NAME) || Boolean(process.env.WSL_INTEROP) || release().toLowerCase().includes("microsoft"));
|
|
43
|
+
const tryCommand = (command$1, args = []) => {
|
|
44
|
+
return spawnSync(command$1, args, { input: text }).status === 0;
|
|
45
|
+
};
|
|
46
|
+
if (platform === "darwin") return tryCommand("pbcopy");
|
|
47
|
+
if (platform === "win32") return tryCommand("clip");
|
|
48
|
+
if (isWsl) return tryCommand("clip.exe");
|
|
49
|
+
return tryCommand("wl-copy") || tryCommand("xclip", ["-selection", "clipboard"]) || tryCommand("xsel", ["--clipboard", "--input"]);
|
|
50
|
+
}
|
|
51
|
+
const prompt = `You are an AI assistant helping a developer set up skill-to-task mappings for their project.
|
|
52
|
+
|
|
53
|
+
Follow these steps in order:
|
|
54
|
+
|
|
55
|
+
1. CHECK FOR EXISTING MAPPINGS
|
|
56
|
+
Search the project's agent config files (CLAUDE.md, AGENTS.md, .cursorrules,
|
|
57
|
+
.github/copilot-instructions.md) for a block delimited by:
|
|
58
|
+
<!-- intent-skills:start -->
|
|
59
|
+
<!-- intent-skills:end -->
|
|
60
|
+
- If found: show the user the current mappings and ask "What would you like to update?"
|
|
61
|
+
Then skip to step 4 with their requested changes.
|
|
62
|
+
- If not found: continue to step 2.
|
|
63
|
+
|
|
64
|
+
2. DISCOVER AVAILABLE SKILLS
|
|
65
|
+
Run: intent list
|
|
66
|
+
This outputs each skill's name, description, and full path — grouped by package.
|
|
67
|
+
|
|
68
|
+
3. SCAN THE REPOSITORY
|
|
69
|
+
Build a picture of the project's structure and patterns:
|
|
70
|
+
- Read package.json for library dependencies
|
|
71
|
+
- Survey the directory layout (src/, app/, routes/, components/, api/, etc.)
|
|
72
|
+
- Note recurring patterns (routing, data fetching, auth, UI components, etc.)
|
|
73
|
+
|
|
74
|
+
Based on this, propose 3–5 skill-to-task mappings. For each one explain:
|
|
75
|
+
- The task or code area (in plain language the user would recognise)
|
|
76
|
+
- Which skill applies and why
|
|
77
|
+
|
|
78
|
+
Then ask: "What other tasks do you commonly use AI coding agents for?
|
|
79
|
+
I'll create mappings for those too."
|
|
80
|
+
|
|
81
|
+
4. WRITE THE MAPPINGS BLOCK
|
|
82
|
+
Once you have the full set of mappings, write or update the agent config file
|
|
83
|
+
(prefer CLAUDE.md; create it if none exists) with this exact block:
|
|
84
|
+
|
|
85
|
+
<!-- intent-skills:start -->
|
|
86
|
+
# Skill mappings — when working in these areas, load the linked skill file into context.
|
|
87
|
+
skills:
|
|
88
|
+
- task: "describe the task or code area here"
|
|
89
|
+
load: "node_modules/package-name/skills/skill-name/SKILL.md"
|
|
90
|
+
<!-- intent-skills:end -->
|
|
91
|
+
|
|
92
|
+
Rules:
|
|
93
|
+
- Use the user's own words for task descriptions
|
|
94
|
+
- Include the exact path from \`intent list\` output so agents can load it directly
|
|
95
|
+
- Keep entries concise — this block is read on every agent task
|
|
96
|
+
- Preserve all content outside the block tags unchanged`;
|
|
97
|
+
console.log("🚀 Intent Install");
|
|
98
|
+
console.log("✨ Copy the prompt below into your AI agent:\n");
|
|
99
|
+
console.log(prompt);
|
|
100
|
+
if (tryCopyToClipboard(prompt)) console.log("\n✅ Copied prompt to clipboard");
|
|
101
|
+
else console.log("\n⚠ Tip: Manually copy the prompt above into your agent");
|
|
102
|
+
}
|
|
103
|
+
const USAGE = `TanStack Intent
|
|
104
|
+
|
|
105
|
+
Usage:
|
|
106
|
+
intent list List all available skills from this library and its dependencies
|
|
107
|
+
intent install Set up skill-to-task mappings in your agent config`;
|
|
108
|
+
const command = process.argv[2];
|
|
109
|
+
switch (command) {
|
|
110
|
+
case "list":
|
|
111
|
+
case void 0:
|
|
112
|
+
await cmdList();
|
|
113
|
+
break;
|
|
114
|
+
case "install":
|
|
115
|
+
cmdInstall();
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
console.log(USAGE);
|
|
119
|
+
process.exit(command ? 1 : 0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { n as parseFrontmatter } from "./utils-DjkEPBxu.mjs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/library-scanner.ts
|
|
6
|
+
function readPkgJson(dir) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function findHomeDir(scriptPath) {
|
|
14
|
+
let dir = dirname(scriptPath);
|
|
15
|
+
while (true) {
|
|
16
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
17
|
+
const parent = dirname(dir);
|
|
18
|
+
if (parent === dir) return null;
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function hasIntentBin(pkg) {
|
|
23
|
+
const bin = pkg.bin;
|
|
24
|
+
if (!bin || typeof bin !== "object") return false;
|
|
25
|
+
return "intent" in bin;
|
|
26
|
+
}
|
|
27
|
+
function getDeps(pkg) {
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
for (const field of ["dependencies", "peerDependencies"]) {
|
|
30
|
+
const d = pkg[field];
|
|
31
|
+
if (d && typeof d === "object") for (const name of Object.keys(d)) seen.add(name);
|
|
32
|
+
}
|
|
33
|
+
return [...seen];
|
|
34
|
+
}
|
|
35
|
+
function discoverSkills(skillsDir) {
|
|
36
|
+
const skills = [];
|
|
37
|
+
function walk(dir) {
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (!entry.isDirectory()) continue;
|
|
46
|
+
const childDir = join(dir, entry.name);
|
|
47
|
+
const skillFile = join(childDir, "SKILL.md");
|
|
48
|
+
if (existsSync(skillFile)) {
|
|
49
|
+
const fm = parseFrontmatter(skillFile);
|
|
50
|
+
const relName = relative(skillsDir, childDir).split(sep).join("/");
|
|
51
|
+
skills.push({
|
|
52
|
+
name: typeof fm?.name === "string" ? fm.name : relName,
|
|
53
|
+
path: skillFile,
|
|
54
|
+
description: typeof fm?.description === "string" ? fm.description.replace(/\s+/g, " ").trim() : "",
|
|
55
|
+
type: typeof fm?.type === "string" ? fm.type : void 0,
|
|
56
|
+
framework: typeof fm?.framework === "string" ? fm.framework : void 0
|
|
57
|
+
});
|
|
58
|
+
walk(childDir);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
walk(skillsDir);
|
|
63
|
+
return skills;
|
|
64
|
+
}
|
|
65
|
+
async function scanLibrary(scriptPath, projectRoot) {
|
|
66
|
+
const nodeModulesDir = join(projectRoot ?? process.cwd(), "node_modules");
|
|
67
|
+
const packages = [];
|
|
68
|
+
const warnings = [];
|
|
69
|
+
const visited = /* @__PURE__ */ new Set();
|
|
70
|
+
const homeDir = findHomeDir(scriptPath);
|
|
71
|
+
if (!homeDir) return {
|
|
72
|
+
packages,
|
|
73
|
+
warnings: ["Could not determine home package directory"]
|
|
74
|
+
};
|
|
75
|
+
const homePkg = readPkgJson(homeDir);
|
|
76
|
+
if (!homePkg) return {
|
|
77
|
+
packages,
|
|
78
|
+
warnings: ["Could not read home package.json"]
|
|
79
|
+
};
|
|
80
|
+
const homeName = typeof homePkg.name === "string" ? homePkg.name : "";
|
|
81
|
+
function processPackage(name, dir) {
|
|
82
|
+
if (visited.has(name)) return;
|
|
83
|
+
visited.add(name);
|
|
84
|
+
const pkg = readPkgJson(dir);
|
|
85
|
+
if (!pkg) {
|
|
86
|
+
warnings.push(`Could not read package.json for ${name}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const skillsDir = join(dir, "skills");
|
|
90
|
+
packages.push({
|
|
91
|
+
name,
|
|
92
|
+
version: typeof pkg.version === "string" ? pkg.version : "0.0.0",
|
|
93
|
+
description: typeof pkg.description === "string" ? pkg.description : "",
|
|
94
|
+
skills: existsSync(skillsDir) ? discoverSkills(skillsDir) : []
|
|
95
|
+
});
|
|
96
|
+
for (const depName of getDeps(pkg)) {
|
|
97
|
+
const depDir = join(nodeModulesDir, depName);
|
|
98
|
+
if (!existsSync(depDir)) continue;
|
|
99
|
+
const depPkg = readPkgJson(depDir);
|
|
100
|
+
if (depPkg && hasIntentBin(depPkg)) processPackage(depName, depDir);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
processPackage(homeName, homeDir);
|
|
104
|
+
return {
|
|
105
|
+
packages,
|
|
106
|
+
warnings
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
export { scanLibrary as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { l as SkillEntry } from "./types-kbQfN_is.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/library-scanner.d.ts
|
|
4
|
+
interface LibraryPackage {
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
description: string;
|
|
8
|
+
skills: SkillEntry[];
|
|
9
|
+
}
|
|
10
|
+
interface LibraryScanResult {
|
|
11
|
+
packages: LibraryPackage[];
|
|
12
|
+
warnings: string[];
|
|
13
|
+
}
|
|
14
|
+
declare function scanLibrary(scriptPath: string, projectRoot?: string): Promise<LibraryScanResult>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { LibraryPackage, LibraryScanResult, scanLibrary };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { n as parseFrontmatter } from "./utils-DjkEPBxu.mjs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join, relative, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/scanner.ts
|
|
6
|
+
function detectPackageManager(root) {
|
|
7
|
+
if (existsSync(join(root, ".pnp.cjs")) || existsSync(join(root, ".pnp.js"))) throw new Error("Yarn PnP is not yet supported. Add `nodeLinker: node-modules` to your .yarnrc.yml to use intent.");
|
|
8
|
+
if (existsSync(join(root, "deno.json")) && !existsSync(join(root, "node_modules"))) throw new Error("Deno without node_modules is not yet supported. Add `\"nodeModulesDir\": \"auto\"` to your deno.json to use intent.");
|
|
9
|
+
if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
10
|
+
if (existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock"))) return "bun";
|
|
11
|
+
if (existsSync(join(root, "yarn.lock"))) return "yarn";
|
|
12
|
+
if (existsSync(join(root, "package-lock.json"))) return "npm";
|
|
13
|
+
return "unknown";
|
|
14
|
+
}
|
|
15
|
+
function validateIntentField(pkgName, intent) {
|
|
16
|
+
if (!intent || typeof intent !== "object") return null;
|
|
17
|
+
const pb = intent;
|
|
18
|
+
if (pb.version !== 1) return null;
|
|
19
|
+
if (typeof pb.repo !== "string" || !pb.repo) return null;
|
|
20
|
+
if (typeof pb.docs !== "string" || !pb.docs) return null;
|
|
21
|
+
const requires = Array.isArray(pb.requires) ? pb.requires.filter((r) => typeof r === "string") : void 0;
|
|
22
|
+
return {
|
|
23
|
+
version: 1,
|
|
24
|
+
repo: pb.repo,
|
|
25
|
+
docs: pb.docs,
|
|
26
|
+
requires
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function discoverSkills(skillsDir, baseName) {
|
|
30
|
+
const skills = [];
|
|
31
|
+
function walk(dir) {
|
|
32
|
+
let entries;
|
|
33
|
+
try {
|
|
34
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
35
|
+
} catch {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (!entry.isDirectory()) continue;
|
|
40
|
+
const childDir = join(dir, entry.name);
|
|
41
|
+
const skillFile = join(childDir, "SKILL.md");
|
|
42
|
+
if (existsSync(skillFile)) {
|
|
43
|
+
const fm = parseFrontmatter(skillFile);
|
|
44
|
+
const relName = relative(skillsDir, childDir).split(sep).join("/");
|
|
45
|
+
const desc = typeof fm?.description === "string" ? fm.description.replace(/\s+/g, " ").trim() : "";
|
|
46
|
+
skills.push({
|
|
47
|
+
name: typeof fm?.name === "string" ? fm.name : relName,
|
|
48
|
+
path: skillFile,
|
|
49
|
+
description: desc,
|
|
50
|
+
type: typeof fm?.type === "string" ? fm.type : void 0,
|
|
51
|
+
framework: typeof fm?.framework === "string" ? fm.framework : void 0
|
|
52
|
+
});
|
|
53
|
+
walk(childDir);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
walk(skillsDir);
|
|
58
|
+
return skills;
|
|
59
|
+
}
|
|
60
|
+
function topoSort(packages) {
|
|
61
|
+
const byName = new Map(packages.map((p) => [p.name, p]));
|
|
62
|
+
const visited = /* @__PURE__ */ new Set();
|
|
63
|
+
const sorted = [];
|
|
64
|
+
function visit(name) {
|
|
65
|
+
if (visited.has(name)) return;
|
|
66
|
+
visited.add(name);
|
|
67
|
+
const pkg = byName.get(name);
|
|
68
|
+
if (!pkg) return;
|
|
69
|
+
for (const dep of pkg.intent.requires ?? []) visit(dep);
|
|
70
|
+
sorted.push(pkg);
|
|
71
|
+
}
|
|
72
|
+
for (const pkg of packages) visit(pkg.name);
|
|
73
|
+
return sorted;
|
|
74
|
+
}
|
|
75
|
+
async function scanForIntents(root) {
|
|
76
|
+
const projectRoot = root ?? process.cwd();
|
|
77
|
+
const packageManager = detectPackageManager(projectRoot);
|
|
78
|
+
const nodeModulesDir = join(projectRoot, "node_modules");
|
|
79
|
+
const packages = [];
|
|
80
|
+
const warnings = [];
|
|
81
|
+
if (!existsSync(nodeModulesDir)) return {
|
|
82
|
+
packageManager,
|
|
83
|
+
packages,
|
|
84
|
+
warnings
|
|
85
|
+
};
|
|
86
|
+
const packageDirs = [];
|
|
87
|
+
let topEntries;
|
|
88
|
+
try {
|
|
89
|
+
topEntries = readdirSync(nodeModulesDir, { withFileTypes: true });
|
|
90
|
+
} catch {
|
|
91
|
+
return {
|
|
92
|
+
packageManager,
|
|
93
|
+
packages,
|
|
94
|
+
warnings
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
for (const entry of topEntries) {
|
|
98
|
+
if (!entry.isDirectory()) continue;
|
|
99
|
+
const dirPath = join(nodeModulesDir, entry.name);
|
|
100
|
+
if (entry.name.startsWith("@")) {
|
|
101
|
+
let scopedEntries;
|
|
102
|
+
try {
|
|
103
|
+
scopedEntries = readdirSync(dirPath, { withFileTypes: true });
|
|
104
|
+
} catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
for (const scoped of scopedEntries) {
|
|
108
|
+
if (!scoped.isDirectory()) continue;
|
|
109
|
+
packageDirs.push({ dirPath: join(dirPath, scoped.name) });
|
|
110
|
+
}
|
|
111
|
+
} else if (!entry.name.startsWith(".")) packageDirs.push({ dirPath });
|
|
112
|
+
}
|
|
113
|
+
for (const { dirPath } of packageDirs) {
|
|
114
|
+
const skillsDir = join(dirPath, "skills");
|
|
115
|
+
if (!existsSync(skillsDir)) continue;
|
|
116
|
+
const pkgJsonPath = join(dirPath, "package.json");
|
|
117
|
+
let pkgJson;
|
|
118
|
+
try {
|
|
119
|
+
pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
120
|
+
} catch {
|
|
121
|
+
warnings.push(`Could not read package.json for ${dirPath}`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const pkgName = typeof pkgJson.name === "string" ? pkgJson.name : "unknown";
|
|
125
|
+
const pkgVersion = typeof pkgJson.version === "string" ? pkgJson.version : "0.0.0";
|
|
126
|
+
const intent = validateIntentField(pkgName, pkgJson.intent);
|
|
127
|
+
if (!intent) {
|
|
128
|
+
warnings.push(`${pkgName} has a skills/ directory but missing or invalid "intent" field in package.json`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const skills = discoverSkills(skillsDir, pkgName);
|
|
132
|
+
packages.push({
|
|
133
|
+
name: pkgName,
|
|
134
|
+
version: pkgVersion,
|
|
135
|
+
intent,
|
|
136
|
+
skills
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
packageManager,
|
|
141
|
+
packages: topoSort(packages),
|
|
142
|
+
warnings
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
export { scanForIntents as t };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/setup.ts
|
|
5
|
+
function detectVars(root) {
|
|
6
|
+
const pkgPath = join(root, "package.json");
|
|
7
|
+
let pkgJson = {};
|
|
8
|
+
try {
|
|
9
|
+
pkgJson = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
10
|
+
} catch {}
|
|
11
|
+
const name = typeof pkgJson.name === "string" ? pkgJson.name : "unknown";
|
|
12
|
+
const intent = pkgJson.intent;
|
|
13
|
+
const repo = typeof intent?.repo === "string" ? intent.repo : name.replace(/^@/, "").replace(/\//, "/");
|
|
14
|
+
const docs = typeof intent?.docs === "string" ? intent.docs : "docs/";
|
|
15
|
+
let srcPath = `packages/${name.replace(/^@[^/]+\//, "")}/src/**`;
|
|
16
|
+
if (existsSync(join(root, "src"))) srcPath = "src/**";
|
|
17
|
+
return {
|
|
18
|
+
PACKAGE_NAME: name,
|
|
19
|
+
REPO: repo,
|
|
20
|
+
DOCS_PATH: docs.endsWith("**") ? docs : docs.replace(/\/$/, "") + "/**",
|
|
21
|
+
SRC_PATH: srcPath
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function applyVars(content, vars) {
|
|
25
|
+
return content.replace(/\{\{PACKAGE_NAME\}\}/g, vars.PACKAGE_NAME).replace(/\{\{REPO\}\}/g, vars.REPO).replace(/\{\{DOCS_PATH\}\}/g, vars.DOCS_PATH).replace(/\{\{SRC_PATH\}\}/g, vars.SRC_PATH);
|
|
26
|
+
}
|
|
27
|
+
function copyTemplates(srcDir, destDir, vars) {
|
|
28
|
+
const copied = [];
|
|
29
|
+
const skipped = [];
|
|
30
|
+
if (!existsSync(srcDir)) return {
|
|
31
|
+
copied,
|
|
32
|
+
skipped
|
|
33
|
+
};
|
|
34
|
+
mkdirSync(destDir, { recursive: true });
|
|
35
|
+
for (const entry of readdirSync(srcDir)) {
|
|
36
|
+
const srcPath = join(srcDir, entry);
|
|
37
|
+
const destPath = join(destDir, entry);
|
|
38
|
+
if (existsSync(destPath)) {
|
|
39
|
+
skipped.push(destPath);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
writeFileSync(destPath, applyVars(readFileSync(srcPath, "utf8"), vars));
|
|
43
|
+
copied.push(destPath);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
copied,
|
|
47
|
+
skipped
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const SHIM_CONTENT = `#!/usr/bin/env node
|
|
51
|
+
// Auto-generated by @tanstack/intent setup
|
|
52
|
+
// Exposes the intent end-user CLI for consumers of this library.
|
|
53
|
+
// Commit this file, then add to your package.json:
|
|
54
|
+
// "bin": { "intent": "./bin/intent.js" }
|
|
55
|
+
await import('@tanstack/intent/intent-library')
|
|
56
|
+
`;
|
|
57
|
+
function generateShim(root, result) {
|
|
58
|
+
const shimPath = join(root, "bin", "intent.js");
|
|
59
|
+
if (existsSync(shimPath)) {
|
|
60
|
+
result.skipped.push(shimPath);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
mkdirSync(join(root, "bin"), { recursive: true });
|
|
64
|
+
writeFileSync(shimPath, SHIM_CONTENT);
|
|
65
|
+
result.shim = shimPath;
|
|
66
|
+
}
|
|
67
|
+
function runSetup(root, metaDir, args) {
|
|
68
|
+
const doAll = args.includes("--all");
|
|
69
|
+
const doWorkflows = doAll || args.includes("--workflows");
|
|
70
|
+
const doOz = doAll || args.includes("--oz");
|
|
71
|
+
const doShim = doAll || args.includes("--shim");
|
|
72
|
+
const defaultAll = !doWorkflows && !doOz && !doShim;
|
|
73
|
+
const installWorkflows = doWorkflows || defaultAll;
|
|
74
|
+
const installOz = doOz || defaultAll;
|
|
75
|
+
const installShim = doShim || defaultAll;
|
|
76
|
+
const vars = detectVars(root);
|
|
77
|
+
const result = {
|
|
78
|
+
workflows: [],
|
|
79
|
+
oz: [],
|
|
80
|
+
skipped: [],
|
|
81
|
+
shim: null
|
|
82
|
+
};
|
|
83
|
+
const templatesDir = join(metaDir, "templates");
|
|
84
|
+
if (installWorkflows) {
|
|
85
|
+
const { copied, skipped } = copyTemplates(join(templatesDir, "workflows"), join(root, ".github", "workflows"), vars);
|
|
86
|
+
result.workflows = copied;
|
|
87
|
+
result.skipped.push(...skipped);
|
|
88
|
+
}
|
|
89
|
+
if (installOz) {
|
|
90
|
+
const { copied, skipped } = copyTemplates(join(templatesDir, "oz"), join(root, ".intent", "oz"), vars);
|
|
91
|
+
result.oz = copied;
|
|
92
|
+
result.skipped.push(...skipped);
|
|
93
|
+
}
|
|
94
|
+
if (installShim) generateShim(root, result);
|
|
95
|
+
for (const f of result.workflows) console.log(`✓ Copied workflow: ${f}`);
|
|
96
|
+
for (const f of result.oz) console.log(`✓ Copied Oz prompt: ${f}`);
|
|
97
|
+
for (const f of result.skipped) console.log(` Already exists: ${f}`);
|
|
98
|
+
if (result.shim) {
|
|
99
|
+
console.log(`✓ Generated intent shim: ${result.shim}`);
|
|
100
|
+
console.log(`\n Add to your package.json:`);
|
|
101
|
+
console.log(` "bin": { "intent": "./bin/intent.js" }`);
|
|
102
|
+
console.log(`\n Add bin/intent.js to your package.json "files" array.`);
|
|
103
|
+
}
|
|
104
|
+
if (result.workflows.length === 0 && result.oz.length === 0 && result.shim === null && result.skipped.length === 0) console.log("No templates directory found. Is @tanstack/intent installed?");
|
|
105
|
+
else if (result.workflows.length > 0 || result.oz.length > 0) {
|
|
106
|
+
console.log(`\nTemplate variables applied:`);
|
|
107
|
+
console.log(` Package: ${vars.PACKAGE_NAME}`);
|
|
108
|
+
console.log(` Repo: ${vars.REPO}`);
|
|
109
|
+
console.log(` Docs: ${vars.DOCS_PATH}`);
|
|
110
|
+
console.log(` Src: ${vars.SRC_PATH}`);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
export { runSetup as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/setup.d.ts
|
|
2
|
+
interface SetupResult {
|
|
3
|
+
workflows: string[];
|
|
4
|
+
oz: string[];
|
|
5
|
+
skipped: string[];
|
|
6
|
+
shim: string | null;
|
|
7
|
+
}
|
|
8
|
+
declare function runSetup(root: string, metaDir: string, args: string[]): SetupResult;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { runSetup as n, SetupResult as t };
|