@totoroyyb/amag 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.
Files changed (38) hide show
  1. package/README.md +141 -0
  2. package/dist/config.d.ts +28 -0
  3. package/dist/config.js +78 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +112 -0
  6. package/dist/installer.d.ts +12 -0
  7. package/dist/installer.js +268 -0
  8. package/dist/registry.d.ts +8 -0
  9. package/dist/registry.js +141 -0
  10. package/dist/utils.d.ts +14 -0
  11. package/dist/utils.js +110 -0
  12. package/package.json +49 -0
  13. package/templates/GEMINI.md +398 -0
  14. package/templates/resources/debug-escalation-template.md +34 -0
  15. package/templates/resources/plan-template.md +149 -0
  16. package/templates/rules/agentic-rules.md +18 -0
  17. package/templates/rules/code-quality.md +71 -0
  18. package/templates/rules/error-recovery.md +164 -0
  19. package/templates/rules/todo-enforcement.md +47 -0
  20. package/templates/skills/architecture-advisor/SKILL.md +111 -0
  21. package/templates/skills/browser-testing/SKILL.md +66 -0
  22. package/templates/skills/codebase-explorer/SKILL.md +128 -0
  23. package/templates/skills/deep-work/SKILL.md +75 -0
  24. package/templates/skills/external-cli-runner/SKILL.md +202 -0
  25. package/templates/skills/external-researcher/SKILL.md +99 -0
  26. package/templates/skills/frontend-ui-ux/SKILL.md +70 -0
  27. package/templates/skills/git-master/SKILL.md +72 -0
  28. package/templates/skills/plan-consultant/SKILL.md +218 -0
  29. package/templates/skills/plan-critic/SKILL.md +225 -0
  30. package/templates/skills/writing/SKILL.md +68 -0
  31. package/templates/workflows/debug-escalate.md +41 -0
  32. package/templates/workflows/debug.md +208 -0
  33. package/templates/workflows/explore.md +233 -0
  34. package/templates/workflows/init-deep.md +103 -0
  35. package/templates/workflows/plan.md +247 -0
  36. package/templates/workflows/resume.md +69 -0
  37. package/templates/workflows/start-work.md +189 -0
  38. package/templates/workflows/ultrawork.md +127 -0
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # AMAG
2
+
3
+ Agent orchestration for [Antigravity](https://antigravity.google). Curated rules, workflows, and skills that transform Antigravity's AI agent into a disciplined engineering partner.
4
+
5
+ Inspired by [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) — rebuilt from scratch for Antigravity's native tooling.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npm install -g @totoroyyb/amag
11
+
12
+ # Initialize in your project
13
+ cd /path/to/your/project
14
+ amag init
15
+ ```
16
+
17
+ Or install from source:
18
+
19
+ ```bash
20
+ git clone https://github.com/totoroyyb/amag.git
21
+ cd amag && npm install && npm run build && npm link
22
+ ```
23
+
24
+ That's it. Your project now has a `GEMINI.md` root prompt, always-on rules, slash-command workflows, and on-demand skills.
25
+
26
+ ## What You Get
27
+
28
+ ### Core System (`GEMINI.md`)
29
+
30
+ The root prompt that orchestrates everything:
31
+
32
+ - **Intent Gate** — classifies every request before acting (trivial, explicit, exploratory, open-ended)
33
+ - **True Intent Extraction** — understands what you *actually* want, not just what you typed
34
+ - **Auto-Ultrawork** — maximum rigor is always on: full verification, zero scope reduction
35
+ - **Codebase Assessment** — evaluates project maturity and adapts behavior accordingly
36
+ - **Verification Protocol** — evidence-based completion with a 6-step checklist (spec compliance → build → tests → debug scan)
37
+
38
+ ### Rules (Always-On)
39
+
40
+ Loaded into every conversation automatically.
41
+
42
+ | Rule | What it does |
43
+ |------|-------------|
44
+ | `code-quality` | Think-first coding, surgical changes, no AI slop |
45
+ | `error-recovery` | 3-failure escalation, blind retry prevention, hung command detection |
46
+ | `todo-enforcement` | Task breakdown, progress tracking, never abandon work |
47
+ | `agentic-rules` | Session start checks, active plan detection, auto-resume guidance |
48
+
49
+ ### Workflows (Slash Commands)
50
+
51
+ Type the command to activate.
52
+
53
+ | Command | What it does |
54
+ |---------|-------------|
55
+ | `/plan` | Planning interview with optional external consultant + critic review |
56
+ | `/start-work` | Execute a plan task-by-task with category-aware delegation |
57
+ | `/resume` | Cross-session resume from `.amag/active-plan.md` |
58
+ | `/debug` | 6-phase systematic debugging with external agent escalation |
59
+ | `/debug-escalate` | Immediately escalate to external CLI during an active `/debug` |
60
+ | `/explore` | Read-only multi-phase codebase exploration and architecture synthesis |
61
+ | `/ultrawork` | Maximum effort mode — 100% certainty before acting |
62
+ | `/init-deep` | Generate hierarchical `GEMINI.md` context files across the codebase |
63
+
64
+ ### Skills (On-Demand)
65
+
66
+ Auto-loaded when relevant, or loaded explicitly by workflows.
67
+
68
+ | Skill | What it does |
69
+ |-------|-------------|
70
+ | `deep-work` | Autonomous exploration — read extensively, build mental model, act decisively |
71
+ | `git-master` | Atomic commits, conventional format, rebasing, conflict resolution |
72
+ | `browser-testing` | Visual testing via Antigravity's `browser_subagent` |
73
+ | `frontend-ui-ux` | Design-first UI with bold aesthetics and responsive patterns |
74
+ | `writing` | Anti-AI-slop technical writing — plain words, human tone |
75
+ | `architecture-advisor` | Read-only design review with simplicity bias |
76
+ | `codebase-explorer` | Structured parallel codebase research and cross-validation |
77
+ | `external-researcher` | External library/API research — official docs and best practices |
78
+ | `external-cli-runner` | Unified runner for Claude, Codex, and Gemini CLIs with retry logic |
79
+ | `plan-consultant` | Pre-plan gap analysis — surface missing requirements before generation |
80
+ | `plan-critic` | Post-plan adversarial review — verify references and executability |
81
+
82
+ ## Typical Workflow
83
+
84
+ ```
85
+ You: /plan add OAuth login with Google and GitHub
86
+ Agent: [explores codebase → asks clarifying questions → drafts plan]
87
+ [runs plan through consultant + critic if configured]
88
+ → implementation_plan.md ready for review
89
+
90
+ You: /start-work
91
+ Agent: [executes plan task-by-task: build → type-check → test after each]
92
+ → all tasks complete, verified with evidence
93
+
94
+ You: /resume # pick up where you left off in a new session
95
+
96
+ You: /debug login redirects fail on Safari
97
+ Agent: [reproduce → hypothesize → instrument → root-cause → fix → verify]
98
+ [escalates to external CLI if stuck: /debug-escalate]
99
+
100
+ You: /explore how does the auth middleware chain work?
101
+ Agent: [structural scan → module deep-dives → synthesizes architecture doc]
102
+ → read-only, never modifies code
103
+ ```
104
+
105
+ ## External Agent Integration
106
+
107
+ AMAG can delegate to external CLI agents (Claude, Codex, Gemini) for plan review and debugging consultation. Configure via:
108
+
109
+ ```bash
110
+ amag config show # View current config
111
+ amag config set review.consultant.cli claude # Set plan consultant
112
+ amag config set debug.consultant.cli codex # Set debug consultant
113
+ amag config set review.critic.thinking high # Set thinking level
114
+ amag config reset # Reset to defaults
115
+ ```
116
+
117
+ Thinking levels: `max`, `high`, `medium`, `low`, `none`.
118
+
119
+ ## CLI Reference
120
+
121
+ ```bash
122
+ amag init # Install all components
123
+ amag update # Overwrite with latest templates
124
+ amag add <type> <name> # Install one component (rule, workflow, skill)
125
+ amag remove <type> <name> # Remove one component
126
+ amag uninstall # Remove all AMAG files
127
+ amag list # Show available components
128
+ amag doctor # Check installation status
129
+ amag config show|set|reset # Manage configuration
130
+ ```
131
+
132
+ ## Design Philosophy
133
+
134
+ 1. **System prompts are the product** — the CLI is just a delivery mechanism
135
+ 2. **Every template earns its place** — no bloat, curated for real engineering workflows
136
+ 3. **Antigravity-native** — built on AG's actual tools, not abstractions
137
+ 4. **Composable** — add only what you need, remove what you don't
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,28 @@
1
+ export type ExternalCLI = "codex" | "claude" | "gemini-cli";
2
+ export type ThinkingLevel = "max" | "high" | "medium" | "low" | "none";
3
+ export interface ExternalAgentConfig {
4
+ cli: ExternalCLI | null;
5
+ model: string | null;
6
+ thinking: ThinkingLevel;
7
+ }
8
+ export interface ReviewConfig {
9
+ consultant: ExternalAgentConfig;
10
+ critic: ExternalAgentConfig;
11
+ timeout_ms: number;
12
+ }
13
+ export interface DebugConfig {
14
+ consultant: ExternalAgentConfig;
15
+ timeout_ms: number;
16
+ }
17
+ export interface AmagConfig {
18
+ review: ReviewConfig;
19
+ debug: DebugConfig;
20
+ }
21
+ export declare function getDefaultConfig(): AmagConfig;
22
+ export declare function readConfig(targetDir: string): Promise<AmagConfig>;
23
+ export declare function writeConfig(targetDir: string, config: AmagConfig): Promise<void>;
24
+ /**
25
+ * Set a dot-path value in the config.
26
+ * e.g. "review.consultant.cli" → "codex"
27
+ */
28
+ export declare function setConfigValue(targetDir: string, dotPath: string, value: string): Promise<void>;
package/dist/config.js ADDED
@@ -0,0 +1,78 @@
1
+ import path from "node:path";
2
+ import fs from "fs-extra";
3
+ import { resolveProjectDir, log } from "./utils.js";
4
+ // --- Defaults ---
5
+ const DEFAULT_CONFIG = {
6
+ review: {
7
+ consultant: { cli: "claude", model: "claude-opus-4-6", thinking: "max" },
8
+ critic: { cli: "codex", model: "gpt-5.2", thinking: "medium" },
9
+ timeout_ms: 600000,
10
+ },
11
+ debug: {
12
+ consultant: { cli: "codex", model: "gpt-5.2", thinking: "high" },
13
+ timeout_ms: 600000,
14
+ },
15
+ };
16
+ // --- Functions ---
17
+ function configPath(projectDir) {
18
+ return path.join(projectDir, ".amag", "config.json");
19
+ }
20
+ export function getDefaultConfig() {
21
+ return structuredClone(DEFAULT_CONFIG);
22
+ }
23
+ export async function readConfig(targetDir) {
24
+ const projectDir = resolveProjectDir(targetDir);
25
+ const filePath = configPath(projectDir);
26
+ if (await fs.pathExists(filePath)) {
27
+ try {
28
+ const raw = await fs.readJson(filePath);
29
+ // Merge with defaults to handle missing keys from older configs
30
+ return {
31
+ review: {
32
+ consultant: { ...DEFAULT_CONFIG.review.consultant, ...raw?.review?.consultant },
33
+ critic: { ...DEFAULT_CONFIG.review.critic, ...raw?.review?.critic },
34
+ timeout_ms: raw?.review?.timeout_ms ?? DEFAULT_CONFIG.review.timeout_ms,
35
+ },
36
+ debug: {
37
+ consultant: { ...DEFAULT_CONFIG.debug.consultant, ...raw?.debug?.consultant },
38
+ timeout_ms: raw?.debug?.timeout_ms ?? DEFAULT_CONFIG.debug.timeout_ms,
39
+ },
40
+ };
41
+ }
42
+ catch {
43
+ log.warn("Invalid .amag/config.json — using defaults");
44
+ return getDefaultConfig();
45
+ }
46
+ }
47
+ return getDefaultConfig();
48
+ }
49
+ export async function writeConfig(targetDir, config) {
50
+ const projectDir = resolveProjectDir(targetDir);
51
+ const filePath = configPath(projectDir);
52
+ await fs.ensureDir(path.dirname(filePath));
53
+ await fs.writeJson(filePath, config, { spaces: 2 });
54
+ }
55
+ /**
56
+ * Set a dot-path value in the config.
57
+ * e.g. "review.consultant.cli" → "codex"
58
+ */
59
+ export async function setConfigValue(targetDir, dotPath, value) {
60
+ const config = await readConfig(targetDir);
61
+ const parts = dotPath.split(".");
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ let obj = config;
64
+ for (let i = 0; i < parts.length - 1; i++) {
65
+ if (obj[parts[i]] === undefined) {
66
+ throw new Error(`Invalid config path: ${dotPath}`);
67
+ }
68
+ obj = obj[parts[i]];
69
+ }
70
+ const lastKey = parts[parts.length - 1];
71
+ if (obj[lastKey] === undefined) {
72
+ throw new Error(`Invalid config path: ${dotPath}`);
73
+ }
74
+ // Coerce numeric values
75
+ const numericValue = Number(value);
76
+ obj[lastKey] = Number.isNaN(numericValue) ? (value === "null" ? null : value) : numericValue;
77
+ await writeConfig(targetDir, config);
78
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { initCommand } from "./installer.js";
4
+ import { listComponents } from "./registry.js";
5
+ import { doctorCheck } from "./utils.js";
6
+ import readline from "node:readline/promises";
7
+ const program = new Command();
8
+ program
9
+ .name("amag")
10
+ .description("OmO-style agent orchestration for Antigravity — install rules, workflows, and skills")
11
+ .version("0.1.0");
12
+ program
13
+ .command("init")
14
+ .description("Install all AMAG components (skips existing files with warning)")
15
+ .option("-t, --target <dir>", "Target project directory", ".")
16
+ .option("--no-gemini-md", "Skip generating GEMINI.md at project root")
17
+ .action(async (options) => {
18
+ await initCommand(options.target, { skipGeminiMd: !options.geminiMd });
19
+ });
20
+ program
21
+ .command("update")
22
+ .description("Overwrite all AMAG components with latest templates")
23
+ .option("-t, --target <dir>", "Target project directory", ".")
24
+ .option("--no-gemini-md", "Skip updating GEMINI.md at project root")
25
+ .action(async (options) => {
26
+ const { updateCommand } = await import("./installer.js");
27
+ await updateCommand(options.target, { skipGeminiMd: !options.geminiMd });
28
+ });
29
+ program
30
+ .command("add <type> <name>")
31
+ .description("Add a single component (rule, workflow, or skill)")
32
+ .option("-t, --target <dir>", "Target project directory", ".")
33
+ .action(async (type, name, options) => {
34
+ const { addComponent } = await import("./installer.js");
35
+ await addComponent(type, name, options.target);
36
+ });
37
+ program
38
+ .command("uninstall")
39
+ .description("Remove all AMAG-managed files from the target project")
40
+ .option("-t, --target <dir>", "Target project directory", ".")
41
+ .option("--keep-gemini-md", "Keep GEMINI.md at project root")
42
+ .option("-f, --force", "Skip confirmation prompt")
43
+ .action(async (options) => {
44
+ if (!options.force) {
45
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
46
+ const answer = await rl.question("This will remove all AMAG components. Continue? (y/N) ");
47
+ rl.close();
48
+ if (answer.toLowerCase() !== "y") {
49
+ console.log("Cancelled.");
50
+ return;
51
+ }
52
+ }
53
+ const { uninstallCommand } = await import("./installer.js");
54
+ await uninstallCommand(options.target, { keepGeminiMd: !!options.keepGeminiMd });
55
+ });
56
+ program
57
+ .command("remove <type> <name>")
58
+ .description("Remove a single component (rule, workflow, or skill)")
59
+ .option("-t, --target <dir>", "Target project directory", ".")
60
+ .action(async (type, name, options) => {
61
+ const { removeComponent } = await import("./installer.js");
62
+ await removeComponent(type, name, options.target);
63
+ });
64
+ program
65
+ .command("list")
66
+ .description("List all available components")
67
+ .action(() => {
68
+ listComponents();
69
+ });
70
+ program
71
+ .command("doctor")
72
+ .description("Check what AMAG components are installed in the target project")
73
+ .option("-t, --target <dir>", "Target project directory", ".")
74
+ .action(async (options) => {
75
+ await doctorCheck(options.target);
76
+ });
77
+ const configCmd = program
78
+ .command("config")
79
+ .description("Manage AMAG review configuration");
80
+ configCmd
81
+ .command("show")
82
+ .description("Show current review configuration")
83
+ .option("-t, --target <dir>", "Target project directory", ".")
84
+ .action(async (options) => {
85
+ const { readConfig } = await import("./config.js");
86
+ const config = await readConfig(options.target);
87
+ console.log(JSON.stringify(config, null, 2));
88
+ });
89
+ configCmd
90
+ .command("set <path> <value>")
91
+ .description("Set a config value (e.g. review.consultant.cli codex)")
92
+ .option("-t, --target <dir>", "Target project directory", ".")
93
+ .action(async (dotPath, value, options) => {
94
+ const { setConfigValue, readConfig } = await import("./config.js");
95
+ await setConfigValue(options.target, dotPath, value);
96
+ const config = await readConfig(options.target);
97
+ const { log } = await import("./utils.js");
98
+ log.success(`Set ${dotPath} = ${value}`);
99
+ console.log(JSON.stringify(config, null, 2));
100
+ });
101
+ configCmd
102
+ .command("reset")
103
+ .description("Reset configuration to defaults")
104
+ .option("-t, --target <dir>", "Target project directory", ".")
105
+ .action(async (options) => {
106
+ const { writeConfig, getDefaultConfig } = await import("./config.js");
107
+ await writeConfig(options.target, getDefaultConfig());
108
+ const { log } = await import("./utils.js");
109
+ log.success("Configuration reset to defaults");
110
+ console.log(JSON.stringify(getDefaultConfig(), null, 2));
111
+ });
112
+ program.parse();
@@ -0,0 +1,12 @@
1
+ export declare function initCommand(targetDir: string, options: {
2
+ skipGeminiMd: boolean;
3
+ force?: boolean;
4
+ }, confirmFn?: () => Promise<boolean>): Promise<void>;
5
+ export declare function updateCommand(targetDir: string, options: {
6
+ skipGeminiMd: boolean;
7
+ }): Promise<void>;
8
+ export declare function addComponent(type: string, name: string, targetDir: string): Promise<void>;
9
+ export declare function uninstallCommand(targetDir: string, options: {
10
+ keepGeminiMd: boolean;
11
+ }): Promise<void>;
12
+ export declare function removeComponent(type: string, name: string, targetDir: string): Promise<void>;
@@ -0,0 +1,268 @@
1
+ import { COMPONENTS } from "./registry.js";
2
+ import { resolveProjectDir, copyTemplate, removeIfExists, cleanEmptyDirs, log } from "./utils.js";
3
+ import path from "node:path";
4
+ import fs from "fs-extra";
5
+ const DEST_MAP = {
6
+ rule: ".agent/rules",
7
+ workflow: ".agent/workflows",
8
+ skill: ".agent/skills",
9
+ };
10
+ const SRC_MAP = {
11
+ rule: "rules",
12
+ workflow: "workflows",
13
+ skill: "skills",
14
+ };
15
+ function getTemplatesDir() {
16
+ // Resolve relative to this file's location (dist/ or src/)
17
+ return path.resolve(import.meta.dirname, "..", "templates");
18
+ }
19
+ function getDestPath(comp, projectDir) {
20
+ const destDir = path.join(projectDir, DEST_MAP[comp.type]);
21
+ if (comp.type === "skill") {
22
+ return path.join(destDir, comp.name, "SKILL.md");
23
+ }
24
+ return path.join(destDir, `${comp.name}.md`);
25
+ }
26
+ async function installComponent(comp, templatesDir, projectDir, options) {
27
+ const srcDir = path.join(templatesDir, SRC_MAP[comp.type]);
28
+ const destDir = path.join(projectDir, DEST_MAP[comp.type]);
29
+ if (comp.type === "skill") {
30
+ const srcSkillDir = path.join(srcDir, comp.name);
31
+ const destSkillDir = path.join(destDir, comp.name);
32
+ await fs.ensureDir(destSkillDir);
33
+ await copyTemplate(path.join(srcSkillDir, "SKILL.md"), path.join(destSkillDir, "SKILL.md"), comp.name, options);
34
+ }
35
+ else {
36
+ await fs.ensureDir(destDir);
37
+ await copyTemplate(path.join(srcDir, `${comp.name}.md`), path.join(destDir, `${comp.name}.md`), comp.name, options);
38
+ }
39
+ }
40
+ async function copyResources(templatesDir, projectDir, options) {
41
+ const srcDir = path.join(templatesDir, "resources");
42
+ const destDir = path.join(projectDir, ".agent", "resources");
43
+ let updated = 0;
44
+ let created = 0;
45
+ if (!(await fs.pathExists(srcDir)))
46
+ return { updated, created };
47
+ await fs.ensureDir(destDir);
48
+ for (const file of await fs.readdir(srcDir)) {
49
+ if (!file.endsWith(".md"))
50
+ continue;
51
+ const dest = path.join(destDir, file);
52
+ const existed = await fs.pathExists(dest);
53
+ await copyTemplate(path.join(srcDir, file), dest, `resource/${file}`, options);
54
+ if (existed)
55
+ updated++;
56
+ else
57
+ created++;
58
+ }
59
+ return { updated, created };
60
+ }
61
+ async function findExistingComponents(projectDir) {
62
+ const existing = [];
63
+ for (const comp of COMPONENTS) {
64
+ const dest = getDestPath(comp, projectDir);
65
+ if (await fs.pathExists(dest)) {
66
+ existing.push(`${comp.type}/${comp.name}`);
67
+ }
68
+ }
69
+ if (await fs.pathExists(path.join(projectDir, "GEMINI.md"))) {
70
+ existing.push("GEMINI.md");
71
+ }
72
+ if (await fs.pathExists(path.join(projectDir, ".amag", "config.json"))) {
73
+ existing.push(".amag/config.json");
74
+ }
75
+ return existing;
76
+ }
77
+ export async function initCommand(targetDir, options, confirmFn) {
78
+ const projectDir = resolveProjectDir(targetDir);
79
+ const templatesDir = getTemplatesDir();
80
+ log.header(`AMAG — Installing to ${projectDir}`);
81
+ // Check for existing files and warn
82
+ if (!options.force) {
83
+ const existing = await findExistingComponents(projectDir);
84
+ if (existing.length > 0) {
85
+ log.warn(`\n${existing.length} file(s) already exist and will be SKIPPED:`);
86
+ for (const name of existing) {
87
+ log.info(` • ${name}`);
88
+ }
89
+ console.log();
90
+ log.info("Use `amag update` to overwrite existing files with latest templates.");
91
+ console.log();
92
+ if (confirmFn) {
93
+ const proceed = await confirmFn();
94
+ if (!proceed) {
95
+ console.log("Cancelled.");
96
+ return;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ // Install all components
102
+ for (const comp of COMPONENTS) {
103
+ await installComponent(comp, templatesDir, projectDir, { force: options.force });
104
+ }
105
+ // Copy resource files (e.g., plan-template.md)
106
+ await copyResources(templatesDir, projectDir, { force: options.force });
107
+ // Generate GEMINI.md at project root
108
+ if (!options.skipGeminiMd) {
109
+ await copyTemplate(path.join(templatesDir, "GEMINI.md"), path.join(projectDir, "GEMINI.md"), "GEMINI.md", { force: options.force });
110
+ }
111
+ // Generate default .amag/config.json
112
+ const configPath = path.join(projectDir, ".amag", "config.json");
113
+ if (options.force || !(await fs.pathExists(configPath))) {
114
+ const { writeConfig, getDefaultConfig } = await import("./config.js");
115
+ await writeConfig(targetDir, getDefaultConfig());
116
+ log.success(".amag/config.json");
117
+ }
118
+ else {
119
+ log.warn(".amag/config.json (exists, skipped)");
120
+ }
121
+ log.success("\nDone! AMAG is installed.");
122
+ log.info("Run `amag doctor` to verify installation.");
123
+ }
124
+ export async function updateCommand(targetDir, options) {
125
+ const projectDir = resolveProjectDir(targetDir);
126
+ const templatesDir = getTemplatesDir();
127
+ log.header(`AMAG — Updating ${projectDir}`);
128
+ let updated = 0;
129
+ let created = 0;
130
+ for (const comp of COMPONENTS) {
131
+ const dest = getDestPath(comp, projectDir);
132
+ const existed = await fs.pathExists(dest);
133
+ await installComponent(comp, templatesDir, projectDir, { force: true });
134
+ if (existed)
135
+ updated++;
136
+ else
137
+ created++;
138
+ }
139
+ // Copy resource files
140
+ const resourceResult = await copyResources(templatesDir, projectDir, { force: true });
141
+ updated += resourceResult.updated;
142
+ created += resourceResult.created;
143
+ if (!options.skipGeminiMd) {
144
+ const geminiPath = path.join(projectDir, "GEMINI.md");
145
+ const existed = await fs.pathExists(geminiPath);
146
+ await copyTemplate(path.join(templatesDir, "GEMINI.md"), geminiPath, "GEMINI.md", { force: true });
147
+ if (existed)
148
+ updated++;
149
+ else
150
+ created++;
151
+ }
152
+ // Update .amag/config.json — merge with defaults to pick up new fields
153
+ const configFilePath = path.join(projectDir, ".amag", "config.json");
154
+ const { readConfig, writeConfig } = await import("./config.js");
155
+ if (await fs.pathExists(configFilePath)) {
156
+ // Re-read merges user overrides with latest defaults (picks up new fields)
157
+ const merged = await readConfig(targetDir);
158
+ await writeConfig(targetDir, merged);
159
+ log.success(".amag/config.json (merged with latest defaults)");
160
+ updated++;
161
+ }
162
+ else {
163
+ const { getDefaultConfig } = await import("./config.js");
164
+ await writeConfig(targetDir, getDefaultConfig());
165
+ log.success(".amag/config.json (created)");
166
+ created++;
167
+ }
168
+ console.log();
169
+ log.success(`Done! ${updated} updated, ${created} newly created.`);
170
+ }
171
+ export async function addComponent(type, name, targetDir) {
172
+ const validTypes = ["rule", "workflow", "skill"];
173
+ if (!validTypes.includes(type)) {
174
+ log.error(`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`);
175
+ process.exit(1);
176
+ }
177
+ const comp = COMPONENTS.find((c) => c.type === type && c.name === name);
178
+ if (!comp) {
179
+ log.error(`Unknown ${type} "${name}". Run \`amag list\` to see available components.`);
180
+ process.exit(1);
181
+ }
182
+ const projectDir = resolveProjectDir(targetDir);
183
+ const templatesDir = getTemplatesDir();
184
+ await installComponent(comp, templatesDir, projectDir);
185
+ log.success(`\nAdded ${type} "${name}" to ${projectDir}`);
186
+ }
187
+ export async function uninstallCommand(targetDir, options) {
188
+ const projectDir = resolveProjectDir(targetDir);
189
+ log.header(`AMAG — Uninstalling from ${projectDir}`);
190
+ let removed = 0;
191
+ let absent = 0;
192
+ // Remove all registered components
193
+ for (const comp of COMPONENTS) {
194
+ const destDir = path.join(projectDir, DEST_MAP[comp.type]);
195
+ let filePath;
196
+ if (comp.type === "skill") {
197
+ filePath = path.join(destDir, comp.name); // entire skill directory
198
+ }
199
+ else {
200
+ filePath = path.join(destDir, `${comp.name}.md`);
201
+ }
202
+ const wasRemoved = await removeIfExists(filePath, `${comp.type}/${comp.name}`);
203
+ if (wasRemoved)
204
+ removed++;
205
+ else
206
+ absent++;
207
+ }
208
+ // Clean up empty directories
209
+ const seenDirs = new Set();
210
+ for (const comp of COMPONENTS) {
211
+ const destDir = path.join(projectDir, DEST_MAP[comp.type]);
212
+ if (!seenDirs.has(destDir)) {
213
+ seenDirs.add(destDir);
214
+ await cleanEmptyDirs(destDir, projectDir);
215
+ }
216
+ }
217
+ // Remove GEMINI.md
218
+ if (!options.keepGeminiMd) {
219
+ const wasRemoved = await removeIfExists(path.join(projectDir, "GEMINI.md"), "GEMINI.md");
220
+ if (wasRemoved)
221
+ removed++;
222
+ else
223
+ absent++;
224
+ }
225
+ // Remove .amag/config.json
226
+ const configRemoved = await removeIfExists(path.join(projectDir, ".amag", "config.json"), ".amag/config.json");
227
+ if (configRemoved)
228
+ removed++;
229
+ else
230
+ absent++;
231
+ // Remove resource files
232
+ const resourcesDir = path.join(projectDir, ".agent", "resources");
233
+ if (await fs.pathExists(resourcesDir)) {
234
+ const wasRemoved = await removeIfExists(resourcesDir, ".agent/resources");
235
+ if (wasRemoved)
236
+ removed++;
237
+ else
238
+ absent++;
239
+ }
240
+ // Clean .amag directory if empty
241
+ await cleanEmptyDirs(path.join(projectDir, ".amag"), projectDir);
242
+ console.log();
243
+ log.success(`Done! ${removed} removed, ${absent} already absent.`);
244
+ }
245
+ export async function removeComponent(type, name, targetDir) {
246
+ const validTypes = ["rule", "workflow", "skill"];
247
+ if (!validTypes.includes(type)) {
248
+ log.error(`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`);
249
+ process.exit(1);
250
+ }
251
+ const comp = COMPONENTS.find((c) => c.type === type && c.name === name);
252
+ if (!comp) {
253
+ log.error(`Unknown ${type} "${name}". Run \`amag list\` to see available components.`);
254
+ process.exit(1);
255
+ }
256
+ const projectDir = resolveProjectDir(targetDir);
257
+ const componentType = type;
258
+ const destDir = path.join(projectDir, DEST_MAP[componentType]);
259
+ let filePath;
260
+ if (componentType === "skill") {
261
+ filePath = path.join(destDir, name);
262
+ }
263
+ else {
264
+ filePath = path.join(destDir, `${name}.md`);
265
+ }
266
+ await removeIfExists(filePath, `${type}/${name}`);
267
+ await cleanEmptyDirs(destDir, projectDir);
268
+ }
@@ -0,0 +1,8 @@
1
+ export type ComponentType = "rule" | "workflow" | "skill";
2
+ export interface Component {
3
+ type: ComponentType;
4
+ name: string;
5
+ description: string;
6
+ }
7
+ export declare const COMPONENTS: Component[];
8
+ export declare function listComponents(): void;