@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.
Files changed (37) hide show
  1. package/README.md +66 -0
  2. package/dist/cli.d.mts +1 -0
  3. package/dist/cli.mjs +327 -0
  4. package/dist/feedback-DKreHfB1.mjs +300 -0
  5. package/dist/feedback-FIUBOL0g.mjs +3 -0
  6. package/dist/index.d.mts +61 -0
  7. package/dist/index.mjs +8 -0
  8. package/dist/init-DEzzXm9j.mjs +3 -0
  9. package/dist/init-DNxmjQfU.mjs +70 -0
  10. package/dist/intent-library.d.mts +1 -0
  11. package/dist/intent-library.mjs +123 -0
  12. package/dist/library-scanner-BrznE00j.mjs +111 -0
  13. package/dist/library-scanner.d.mts +16 -0
  14. package/dist/library-scanner.mjs +4 -0
  15. package/dist/scanner-BuWPDJ4P.mjs +4 -0
  16. package/dist/scanner-CpsJAHXT.mjs +147 -0
  17. package/dist/setup-CNGz26qL.mjs +116 -0
  18. package/dist/setup-N5dttGp_.d.mts +10 -0
  19. package/dist/setup.d.mts +2 -0
  20. package/dist/setup.mjs +3 -0
  21. package/dist/staleness-CnomT9Hm.mjs +72 -0
  22. package/dist/staleness-DyhsrqQ5.mjs +4 -0
  23. package/dist/types-kbQfN_is.d.mts +70 -0
  24. package/dist/utils-DjkEPBxu.mjs +39 -0
  25. package/meta/domain-discovery/SKILL.md +681 -0
  26. package/meta/generate-skill/SKILL.md +419 -0
  27. package/meta/skill-staleness-check/SKILL.md +282 -0
  28. package/meta/templates/oz/domain-discovery.md +53 -0
  29. package/meta/templates/oz/feedback-collection.md +69 -0
  30. package/meta/templates/oz/skill-update.md +47 -0
  31. package/meta/templates/oz/tree-generation.md +48 -0
  32. package/meta/templates/workflows/generate-skills-oz.yml +86 -0
  33. package/meta/templates/workflows/notify-playbooks.yml +52 -0
  34. package/meta/templates/workflows/update-skills-oz.yml +98 -0
  35. package/meta/templates/workflows/validate-skills.yml +52 -0
  36. package/meta/tree-generator/SKILL.md +859 -0
  37. package/package.json +38 -0
@@ -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,3 @@
1
+ import { a as runInit, i as readProjectConfig, n as hasIntentBlock, o as writeProjectConfig, r as injectIntentBlock, t as detectAgentConfigs } from "./init-DNxmjQfU.mjs";
2
+
3
+ export { detectAgentConfigs, runInit };
@@ -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,4 @@
1
+ import "./utils-DjkEPBxu.mjs";
2
+ import { t as scanLibrary } from "./library-scanner-BrznE00j.mjs";
3
+
4
+ export { scanLibrary };
@@ -0,0 +1,4 @@
1
+ import "./utils-DjkEPBxu.mjs";
2
+ import { t as scanForIntents } from "./scanner-CpsJAHXT.mjs";
3
+
4
+ export { scanForIntents };
@@ -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 };