agent-workflow-kit-cli 1.1.0 โ†’ 1.2.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.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import chalk from "chalk";
6
+ import { promises as fs } from "fs";
7
+ import path from "path";
8
+ import { renderTemplate, readStaticTemplateFile, getStackRules, getStackSkills, } from "../../core/renderer.js";
9
+ import { updateFileWithBlock, writeRuleWithChunking, } from "../../core/emitter.js";
10
+ import { analyzeModule } from "../../core/analyzer.js";
11
+ export async function runAdd(stack, options) {
12
+ const targetStack = stack.toLowerCase();
13
+ const validStacks = ["spring-boot", "react-ts", "fastapi", "python-ai"];
14
+ if (!validStacks.includes(targetStack)) {
15
+ console.error(chalk.red(`Error: Invalid stack '${stack}'. Supported stacks are: ${validStacks.join(", ")}`));
16
+ process.exit(1);
17
+ }
18
+ const targetDir = path.resolve(options.path);
19
+ console.log(chalk.bold.cyan("\n๐Ÿš€ Agent Workflow Kit - Adding Stack Pack..."));
20
+ console.log(chalk.dim("------------------------------------------"));
21
+ console.log(`${chalk.bold("Target Folder:")} ${chalk.green(targetDir)}`);
22
+ console.log(`${chalk.bold("Stack Pack:")} ${chalk.green(targetStack)}`);
23
+ console.log(`${chalk.bold("Agent Profile:")} ${chalk.green(options.agent)}`);
24
+ console.log(`${chalk.bold("Dry Run:")} ${options.dryRun ? chalk.yellow("Enabled ๐Ÿงช") : chalk.gray("Disabled")}`);
25
+ console.log(chalk.dim("------------------------------------------\n"));
26
+ // 1. Analyze the target directory for this specific stack
27
+ const analysis = await analyzeModule(targetDir, [targetStack]);
28
+ // 2. Render stack guidelines for AGENTS.md
29
+ let stackContent = "";
30
+ try {
31
+ const stackCtx = analysis[targetStack] || {};
32
+ const rendered = await renderTemplate(`${targetStack}/AGENTS.md.hbs`, stackCtx);
33
+ stackContent = rendered.trim();
34
+ }
35
+ catch (err) {
36
+ console.error(chalk.red(`Error loading template for stack '${targetStack}': ${err instanceof Error ? err.message : String(err)}`));
37
+ process.exit(1);
38
+ }
39
+ // 3. Write or Update AGENTS.md in the target directory
40
+ const agentsPath = path.join(targetDir, "AGENTS.md");
41
+ const moduleAgentsContent = await renderTemplate("common/AGENTS.md.hbs", {
42
+ stackContent,
43
+ });
44
+ if (options.dryRun) {
45
+ console.log(chalk.gray(`[Dry Run] Would write/update AGENTS.md at ${agentsPath}`));
46
+ }
47
+ else {
48
+ try {
49
+ await fs.access(agentsPath);
50
+ await updateFileWithBlock(agentsPath, "STACK_PACK", stackContent);
51
+ console.log(chalk.green(`โœ”๏ธ Updated STACK_PACK block in ${agentsPath}`));
52
+ }
53
+ catch {
54
+ await fs.mkdir(targetDir, { recursive: true });
55
+ await fs.writeFile(agentsPath, moduleAgentsContent, "utf8");
56
+ console.log(chalk.green(`โœ”๏ธ Created ${agentsPath}`));
57
+ }
58
+ }
59
+ // 4. Copy rules and skills for the target stack
60
+ const stackCtx = analysis[targetStack] || {};
61
+ // A. Copy Rules
62
+ const rules = await getStackRules(targetStack);
63
+ for (const rule of rules) {
64
+ if (rule === "microservice-style.md" && stackCtx.isMicroservice !== true) {
65
+ continue;
66
+ }
67
+ const relativeRulePath = `${targetStack}/rules/${rule}`;
68
+ try {
69
+ const ruleContent = await readStaticTemplateFile(relativeRulePath, stackCtx);
70
+ const targetRulePath = path.join(targetDir, ".agents", "rules", rule);
71
+ if (options.dryRun) {
72
+ console.log(chalk.gray(`[Dry Run] Would write rule to ${targetRulePath}`));
73
+ }
74
+ else {
75
+ await writeRuleWithChunking(targetRulePath, ruleContent);
76
+ console.log(chalk.green(`โœ”๏ธ Wrote rule ${rule} to ${targetRulePath}`));
77
+ }
78
+ }
79
+ catch (err) {
80
+ console.error(chalk.red(`Failed to copy rule ${rule}: ${err instanceof Error ? err.message : String(err)}`));
81
+ }
82
+ }
83
+ // B. Copy Skills
84
+ const skills = await getStackSkills(targetStack);
85
+ for (const skill of skills) {
86
+ const relativeSkillPath = `${targetStack}/skills/${skill}`;
87
+ try {
88
+ const skillContent = await readStaticTemplateFile(relativeSkillPath, stackCtx);
89
+ const targetSkillPath = path.join(targetDir, ".agents", "skills", skill);
90
+ if (options.dryRun) {
91
+ console.log(chalk.gray(`[Dry Run] Would write skill to ${targetSkillPath}`));
92
+ }
93
+ else {
94
+ await fs.mkdir(path.dirname(targetSkillPath), { recursive: true });
95
+ await fs.writeFile(targetSkillPath, skillContent, "utf8");
96
+ console.log(chalk.green(`โœ”๏ธ Wrote skill ${skill} to ${targetSkillPath}`));
97
+ }
98
+ }
99
+ catch (err) {
100
+ console.error(chalk.red(`Failed to copy skill ${skill}: ${err instanceof Error ? err.message : String(err)}`));
101
+ }
102
+ }
103
+ // C. Copy Common Skills if not present
104
+ try {
105
+ const commonSkills = await getStackSkills("common");
106
+ for (const skill of commonSkills) {
107
+ const relativeSkillPath = `common/skills/${skill}`;
108
+ try {
109
+ const skillContent = await readStaticTemplateFile(relativeSkillPath, {});
110
+ const targetSkillPath = path.join(targetDir, ".agents", "skills", skill);
111
+ if (options.dryRun) {
112
+ console.log(chalk.gray(`[Dry Run] Would ensure common skill ${skill} exists`));
113
+ }
114
+ else {
115
+ try {
116
+ await fs.access(targetSkillPath);
117
+ }
118
+ catch {
119
+ await fs.mkdir(path.dirname(targetSkillPath), { recursive: true });
120
+ await fs.writeFile(targetSkillPath, skillContent, "utf8");
121
+ console.log(chalk.green(`โœ”๏ธ Wrote common skill ${skill} to ${targetSkillPath}`));
122
+ }
123
+ }
124
+ }
125
+ catch (err) {
126
+ console.error(chalk.red(`Failed to copy common skill ${skill}: ${err instanceof Error ? err.message : String(err)}`));
127
+ }
128
+ }
129
+ }
130
+ catch {
131
+ // Ignore
132
+ }
133
+ console.log(chalk.bold.green("\n๐ŸŽ‰ Add completed successfully!"));
134
+ }
@@ -58,8 +58,9 @@ export function parseSkillFile(content, defaultName) {
58
58
  return { name, description, body: body.trim() };
59
59
  }
60
60
  export async function runExport(target, options) {
61
- if (target.toLowerCase() !== "antigravity") {
62
- console.warn(chalk.yellow(`Warning: Currently, only 'antigravity' target is officially optimized, but trying to export custom rules anyway.`));
61
+ const normTarget = target.toLowerCase();
62
+ if (normTarget !== "antigravity" && normTarget !== "codex") {
63
+ console.warn(chalk.yellow(`Warning: Currently, only 'antigravity' and 'codex' targets are officially optimized, but trying to export custom rules anyway.`));
63
64
  }
64
65
  const cwd = process.cwd();
65
66
  const skillsDir = path.join(cwd, ".agents", "skills");
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { Command } from "commander";
6
6
  import chalk from "chalk";
7
7
  import { runInit } from "./commands/init.js";
8
+ import { runAdd } from "./commands/add.js";
8
9
  import { runSync } from "./commands/sync.js";
9
10
  import { runDoctor } from "./commands/doctor.js";
10
11
  import { runExport } from "./commands/export.js";
@@ -13,7 +14,7 @@ export function runCli() {
13
14
  program
14
15
  .name("agent-workflow-kit")
15
16
  .description("Generate AI coding workflows/rules/templates for Codex and Antigravity")
16
- .version("1.1.0");
17
+ .version("1.2.0");
17
18
  program
18
19
  .command("init")
19
20
  .description("Initialize agent guidelines and skills for the repository")
@@ -29,6 +30,21 @@ export function runCli() {
29
30
  process.exit(1);
30
31
  }
31
32
  });
33
+ program
34
+ .command("add <stack>")
35
+ .description("Manually add/install a stack pack to a specific folder")
36
+ .option("--path <path>", "Target folder path to install the guidelines", ".")
37
+ .option("--agent <agent>", "Specify target agent profile: both | codex | antigravity", "both")
38
+ .option("--dry-run", "Output actions to console without writing any files", false)
39
+ .action(async (stack, options) => {
40
+ try {
41
+ await runAdd(stack, options);
42
+ }
43
+ catch (err) {
44
+ console.error(chalk.red(`Error running add: ${err instanceof Error ? err.message : String(err)}`));
45
+ process.exit(1);
46
+ }
47
+ });
32
48
  program
33
49
  .command("sync")
34
50
  .description("Sync generated guidelines and skills inside managed blocks")
@@ -87,73 +87,84 @@ async function detectProjectStackDirect(cwd) {
87
87
  }
88
88
  }
89
89
  catch {
90
- // Ignore
91
- }
92
- }
93
- return stacks;
94
- }
95
- /**
96
- * Scans the workspace directory and subfolders (1-level deep) for monorepo detection.
97
- */
98
- export async function detectProjectStack(cwd) {
99
- const stacks = await detectProjectStackDirect(cwd);
100
- // Scan 1-level deep subdirectories for potential monorepos/multimodules
101
- try {
102
- const entries = await fs.readdir(cwd, { withFileTypes: true });
103
- for (const entry of entries) {
104
- if (entry.isDirectory() &&
105
- !entry.name.startsWith(".") &&
106
- entry.name !== "node_modules" &&
107
- entry.name !== "dist") {
108
- const subPath = path.join(cwd, entry.name);
109
- const subStacks = await detectProjectStackDirect(subPath);
110
- for (const stack of subStacks) {
111
- if (!stacks.includes(stack)) {
112
- stacks.push(stack);
113
- }
90
+ try {
91
+ const pipfilePath = path.join(cwd, "Pipfile");
92
+ const content = await fs.readFile(pipfilePath, "utf8");
93
+ if (content.includes("fastapi")) {
94
+ stacks.push("fastapi");
95
+ }
96
+ if (aiKeywords.some(kw => content.includes(kw))) {
97
+ stacks.push("python-ai");
114
98
  }
115
99
  }
100
+ catch {
101
+ // Ignore
102
+ }
116
103
  }
117
104
  }
118
- catch {
119
- // Ignore
120
- }
121
105
  return stacks;
122
106
  }
123
107
  /**
124
- * Detects stacks grouped by module directories (root + 1-level deep directories).
108
+ * Helper to recursively search for modules containing manifest configurations up to maxDepth.
125
109
  */
126
- export async function detectProjectModules(cwd) {
110
+ async function findModulesRecursively(baseDir, currentDir, maxDepth, currentDepth = 0) {
127
111
  const modules = [];
128
- const rootStacks = await detectProjectStackDirect(cwd);
129
- if (rootStacks.length > 0) {
112
+ // Skip standard build and internal directories to avoid deep scans
113
+ const dirName = path.basename(currentDir);
114
+ if (dirName.startsWith(".") ||
115
+ dirName === "node_modules" ||
116
+ dirName === "target" ||
117
+ dirName === "build" ||
118
+ dirName === "dist" ||
119
+ dirName === "bin" ||
120
+ dirName === "out" ||
121
+ dirName === "venv" ||
122
+ dirName === ".venv") {
123
+ return modules;
124
+ }
125
+ // Detect direct stack presets in this current directory
126
+ const directStacks = await detectProjectStackDirect(currentDir);
127
+ if (directStacks.length > 0) {
130
128
  modules.push({
131
- dir: cwd,
132
- name: ".",
133
- stacks: rootStacks,
129
+ dir: currentDir,
130
+ name: currentDir === baseDir ? "." : path.relative(baseDir, currentDir).replace(/\\/g, "/"),
131
+ stacks: directStacks,
134
132
  });
135
133
  }
136
- try {
137
- const entries = await fs.readdir(cwd, { withFileTypes: true });
138
- for (const entry of entries) {
139
- if (entry.isDirectory() &&
140
- !entry.name.startsWith(".") &&
141
- entry.name !== "node_modules" &&
142
- entry.name !== "dist") {
143
- const subPath = path.join(cwd, entry.name);
144
- const subStacks = await detectProjectStackDirect(subPath);
145
- if (subStacks.length > 0) {
146
- modules.push({
147
- dir: subPath,
148
- name: entry.name,
149
- stacks: subStacks,
150
- });
134
+ // If we haven't hit max depth, traverse subdirectories
135
+ if (currentDepth < maxDepth) {
136
+ try {
137
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
138
+ for (const entry of entries) {
139
+ if (entry.isDirectory()) {
140
+ const subPath = path.join(currentDir, entry.name);
141
+ const subModules = await findModulesRecursively(baseDir, subPath, maxDepth, currentDepth + 1);
142
+ modules.push(...subModules);
151
143
  }
152
144
  }
153
145
  }
154
- }
155
- catch {
156
- // Ignore
146
+ catch {
147
+ // Ignore directory read errors
148
+ }
157
149
  }
158
150
  return modules;
159
151
  }
152
+ /**
153
+ * Scans the workspace directory and subfolders (up to 3 levels deep) for monorepo detection.
154
+ */
155
+ export async function detectProjectStack(cwd) {
156
+ const modules = await detectProjectModules(cwd);
157
+ const stacks = new Set();
158
+ for (const mod of modules) {
159
+ for (const stack of mod.stacks) {
160
+ stacks.add(stack);
161
+ }
162
+ }
163
+ return Array.from(stacks);
164
+ }
165
+ /**
166
+ * Detects stacks grouped by module directories (root + up to 3 levels deep directories).
167
+ */
168
+ export async function detectProjectModules(cwd) {
169
+ return findModulesRecursively(cwd, cwd, 3);
170
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-workflow-kit-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "AI-Ready Repository Workflow Generator & Guideline Optimizer for Codex and Antigravity",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",