@antomanc/aginit 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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +245 -0
  3. package/THIRD_PARTY_NOTICES.md +36 -0
  4. package/bin/aginit.js +8 -0
  5. package/dist/adapters/agents-md.d.ts +8 -0
  6. package/dist/adapters/agents-md.d.ts.map +1 -0
  7. package/dist/adapters/agents-md.js +121 -0
  8. package/dist/adapters/agents-md.js.map +1 -0
  9. package/dist/adapters/browser.d.ts +5 -0
  10. package/dist/adapters/browser.d.ts.map +1 -0
  11. package/dist/adapters/browser.js +42 -0
  12. package/dist/adapters/browser.js.map +1 -0
  13. package/dist/adapters/git.d.ts +5 -0
  14. package/dist/adapters/git.d.ts.map +1 -0
  15. package/dist/adapters/git.js +90 -0
  16. package/dist/adapters/git.js.map +1 -0
  17. package/dist/adapters/graft.d.ts +14 -0
  18. package/dist/adapters/graft.d.ts.map +1 -0
  19. package/dist/adapters/graft.js +56 -0
  20. package/dist/adapters/graft.js.map +1 -0
  21. package/dist/adapters/package-manager.d.ts +20 -0
  22. package/dist/adapters/package-manager.d.ts.map +1 -0
  23. package/dist/adapters/package-manager.js +89 -0
  24. package/dist/adapters/package-manager.js.map +1 -0
  25. package/dist/adapters/skills.d.ts +29 -0
  26. package/dist/adapters/skills.d.ts.map +1 -0
  27. package/dist/adapters/skills.js +80 -0
  28. package/dist/adapters/skills.js.map +1 -0
  29. package/dist/cli/doctor.d.ts +2 -0
  30. package/dist/cli/doctor.d.ts.map +1 -0
  31. package/dist/cli/doctor.js +212 -0
  32. package/dist/cli/doctor.js.map +1 -0
  33. package/dist/cli/init.d.ts +3 -0
  34. package/dist/cli/init.d.ts.map +1 -0
  35. package/dist/cli/init.js +32 -0
  36. package/dist/cli/init.js.map +1 -0
  37. package/dist/cli/main.d.ts +3 -0
  38. package/dist/cli/main.d.ts.map +1 -0
  39. package/dist/cli/main.js +162 -0
  40. package/dist/cli/main.js.map +1 -0
  41. package/dist/cli/new.d.ts +15 -0
  42. package/dist/cli/new.d.ts.map +1 -0
  43. package/dist/cli/new.js +161 -0
  44. package/dist/cli/new.js.map +1 -0
  45. package/dist/cli/wizard.d.ts +31 -0
  46. package/dist/cli/wizard.d.ts.map +1 -0
  47. package/dist/cli/wizard.js +260 -0
  48. package/dist/cli/wizard.js.map +1 -0
  49. package/dist/config/defaults.d.ts +8 -0
  50. package/dist/config/defaults.d.ts.map +1 -0
  51. package/dist/config/defaults.js +122 -0
  52. package/dist/config/defaults.js.map +1 -0
  53. package/dist/config/schema.d.ts +40 -0
  54. package/dist/config/schema.d.ts.map +1 -0
  55. package/dist/config/schema.js +4 -0
  56. package/dist/config/schema.js.map +1 -0
  57. package/dist/config/validation.d.ts +11 -0
  58. package/dist/config/validation.d.ts.map +1 -0
  59. package/dist/config/validation.js +99 -0
  60. package/dist/config/validation.js.map +1 -0
  61. package/dist/index.d.ts +16 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +16 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/presets/adr.d.ts +5 -0
  66. package/dist/presets/adr.d.ts.map +1 -0
  67. package/dist/presets/adr.js +33 -0
  68. package/dist/presets/adr.js.map +1 -0
  69. package/dist/presets/cli.d.ts +3 -0
  70. package/dist/presets/cli.d.ts.map +1 -0
  71. package/dist/presets/cli.js +126 -0
  72. package/dist/presets/cli.js.map +1 -0
  73. package/dist/presets/generic.d.ts +3 -0
  74. package/dist/presets/generic.d.ts.map +1 -0
  75. package/dist/presets/generic.js +29 -0
  76. package/dist/presets/generic.js.map +1 -0
  77. package/dist/presets/registry.d.ts +6 -0
  78. package/dist/presets/registry.d.ts.map +1 -0
  79. package/dist/presets/registry.js +20 -0
  80. package/dist/presets/registry.js.map +1 -0
  81. package/dist/presets/testing.d.ts +6 -0
  82. package/dist/presets/testing.d.ts.map +1 -0
  83. package/dist/presets/testing.js +46 -0
  84. package/dist/presets/testing.js.map +1 -0
  85. package/dist/presets/types.d.ts +14 -0
  86. package/dist/presets/types.d.ts.map +1 -0
  87. package/dist/presets/types.js +2 -0
  88. package/dist/presets/types.js.map +1 -0
  89. package/dist/presets/web.d.ts +3 -0
  90. package/dist/presets/web.d.ts.map +1 -0
  91. package/dist/presets/web.js +84 -0
  92. package/dist/presets/web.js.map +1 -0
  93. package/dist/utils/fs.d.ts +13 -0
  94. package/dist/utils/fs.d.ts.map +1 -0
  95. package/dist/utils/fs.js +60 -0
  96. package/dist/utils/fs.js.map +1 -0
  97. package/dist/utils/logger.d.ts +10 -0
  98. package/dist/utils/logger.d.ts.map +1 -0
  99. package/dist/utils/logger.js +20 -0
  100. package/dist/utils/logger.js.map +1 -0
  101. package/dist/utils/shell.d.ts +17 -0
  102. package/dist/utils/shell.d.ts.map +1 -0
  103. package/dist/utils/shell.js +87 -0
  104. package/dist/utils/shell.js.map +1 -0
  105. package/package.json +71 -0
  106. package/src/adapters/agents-md.ts +143 -0
  107. package/src/adapters/browser.ts +49 -0
  108. package/src/adapters/git.ts +100 -0
  109. package/src/adapters/graft.ts +80 -0
  110. package/src/adapters/package-manager.ts +116 -0
  111. package/src/adapters/skills.ts +117 -0
  112. package/src/cli/doctor.ts +244 -0
  113. package/src/cli/init.ts +34 -0
  114. package/src/cli/main.ts +191 -0
  115. package/src/cli/new.ts +200 -0
  116. package/src/cli/wizard.ts +300 -0
  117. package/src/config/defaults.ts +137 -0
  118. package/src/config/schema.ts +44 -0
  119. package/src/config/validation.ts +96 -0
  120. package/src/index.ts +15 -0
  121. package/src/presets/adr.ts +37 -0
  122. package/src/presets/cli.ts +136 -0
  123. package/src/presets/generic.ts +32 -0
  124. package/src/presets/registry.ts +24 -0
  125. package/src/presets/testing.ts +56 -0
  126. package/src/presets/types.ts +15 -0
  127. package/src/presets/web.ts +99 -0
  128. package/src/utils/fs.ts +79 -0
  129. package/src/utils/logger.ts +20 -0
  130. package/src/utils/shell.ts +116 -0
@@ -0,0 +1,117 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileExists, readJsonFile } from '../utils/fs.js';
4
+ import { runExecutable } from '../utils/shell.js';
5
+ import { logger } from '../utils/logger.js';
6
+ import { validateUpstreamArgument } from '../config/validation.js';
7
+ import { SkillSourceConfig } from '../config/schema.js';
8
+
9
+ export interface SkillsLockFile {
10
+ version: number;
11
+ skills: Record<
12
+ string,
13
+ {
14
+ source: string;
15
+ sourceType: string;
16
+ skillPath: string;
17
+ computedHash: string;
18
+ }
19
+ >;
20
+ }
21
+
22
+ export interface SkillInstallOptions {
23
+ agents?: string[];
24
+ dryRun?: boolean;
25
+ silent?: boolean;
26
+ }
27
+
28
+ export async function installSkillSource(
29
+ targetDir: string,
30
+ source: SkillSourceConfig,
31
+ options: SkillInstallOptions = {}
32
+ ): Promise<{ ok: boolean; message?: string }> {
33
+ const { agents = ['antigravity', 'codex'], dryRun = false, silent = false } = options;
34
+
35
+ validateUpstreamArgument(source.package, 'skill package');
36
+ for (const skill of source.skills) validateUpstreamArgument(skill, 'skill name');
37
+ for (const agent of agents) validateUpstreamArgument(agent, 'agent');
38
+ const args = [
39
+ '-y',
40
+ 'skills',
41
+ 'add',
42
+ source.package,
43
+ '--skill',
44
+ ...(source.skills.length ? source.skills : ['*']),
45
+ '--agent',
46
+ ...agents,
47
+ '-y'
48
+ ];
49
+
50
+ if (!silent) {
51
+ logger.dim(`Installing skills from ${source.package}: ${source.skills.join(', ')}...`);
52
+ }
53
+
54
+ const res = await runExecutable('npx', args, { cwd: targetDir, dryRun, silent: true });
55
+ if (!res.ok) {
56
+ logger.warn(`Failed installing skills from ${source.package}: ${res.stderr || res.stdout}`);
57
+ return { ok: false, message: res.stderr || res.stdout };
58
+ }
59
+
60
+ return { ok: true };
61
+ }
62
+
63
+ export async function installAllSkills(
64
+ targetDir: string,
65
+ sources: SkillSourceConfig[],
66
+ options: SkillInstallOptions = {}
67
+ ): Promise<{ installed: string[]; failed: string[] }> {
68
+ const installed: string[] = [];
69
+ const failed: string[] = [];
70
+
71
+ for (const source of sources) {
72
+ const res = await installSkillSource(targetDir, source, options);
73
+ if (res.ok) {
74
+ installed.push(...(source.skills.length ? source.skills : [`${source.package}:*`]));
75
+ } else {
76
+ failed.push(...(source.skills.length ? source.skills : [`${source.package}:*`]));
77
+ }
78
+ }
79
+
80
+ return { installed, failed };
81
+ }
82
+
83
+ export function getInstalledSkills(targetDir: string): string[] {
84
+ const lockPath = path.join(targetDir, 'skills-lock.json');
85
+ if (fileExists(lockPath)) {
86
+ const lock = readJsonFile<SkillsLockFile>(lockPath);
87
+ if (lock && lock.skills) {
88
+ return Object.keys(lock.skills);
89
+ }
90
+ }
91
+
92
+ // Check .agents/skills directory directly
93
+ const skillsDir = path.join(targetDir, '.agents', 'skills');
94
+ if (fileExists(skillsDir)) {
95
+ try {
96
+ return fs.readdirSync(skillsDir).filter((f) => !f.startsWith('.'));
97
+ } catch {
98
+ return [];
99
+ }
100
+ }
101
+
102
+ return [];
103
+ }
104
+
105
+ export async function updateSkills(
106
+ targetDir: string,
107
+ options: { dryRun?: boolean; silent?: boolean } = {}
108
+ ): Promise<boolean> {
109
+ const { dryRun = false, silent = false } = options;
110
+ if (!silent) logger.dim('Updating installed skills...');
111
+ const res = await runExecutable('npx', ['-y', 'skills', 'update', '-p', '-y'], {
112
+ cwd: targetDir,
113
+ dryRun,
114
+ silent: true
115
+ });
116
+ return res.ok;
117
+ }
@@ -0,0 +1,244 @@
1
+ import path from 'node:path';
2
+ import pc from 'picocolors';
3
+ import { runExecutable, commandExists } from '../utils/shell.js';
4
+ import { fileExists, readTextFile } from '../utils/fs.js';
5
+ import {
6
+ AGINIT_CONFIG_FILENAME,
7
+ LEGACY_CONFIG_FILENAME,
8
+ AginitConfig,
9
+ PackageManager
10
+ } from '../config/schema.js';
11
+ import { detectPackageManager } from '../adapters/package-manager.js';
12
+ import { getInstalledSkills } from '../adapters/skills.js';
13
+ import { readProjectConfig } from '../config/validation.js';
14
+ import { logger } from '../utils/logger.js';
15
+
16
+ interface CheckItem {
17
+ name: string;
18
+ category: 'Environment' | 'AI Tooling' | 'Project Config';
19
+ ok: boolean;
20
+ version?: string;
21
+ detail: string;
22
+ suggestion?: string;
23
+ }
24
+
25
+ export async function runDoctor(targetDir: string = process.cwd()): Promise<void> {
26
+ logger.banner('Aginit — System & Project Doctor', `Target: ${targetDir}`);
27
+
28
+ // Resolve project config early to detect configured package manager
29
+ let configPath = path.join(targetDir, AGINIT_CONFIG_FILENAME);
30
+ if (!fileExists(configPath)) {
31
+ const legacyPath = path.join(targetDir, LEGACY_CONFIG_FILENAME);
32
+ if (fileExists(legacyPath)) {
33
+ configPath = legacyPath;
34
+ }
35
+ }
36
+
37
+ const configExists = fileExists(configPath);
38
+ let projectConfig: AginitConfig | null = null;
39
+ let configError = '';
40
+ try {
41
+ projectConfig = readProjectConfig(targetDir);
42
+ } catch (error) {
43
+ configError = error instanceof Error ? error.message : String(error);
44
+ }
45
+ const activePm: PackageManager =
46
+ projectConfig?.packageManager || detectPackageManager(targetDir) || 'pnpm';
47
+
48
+ const checks: CheckItem[] = [];
49
+
50
+ // 1. Node.js check
51
+ const nodeRes = await runExecutable('node', ['-v'], { silent: true });
52
+ let nodeOk = nodeRes.ok;
53
+ let nodeDetail = 'Node.js is not found in PATH';
54
+ let nodeSuggestion: string | undefined =
55
+ 'Install Node.js (>= 22.12.0 LTS recommended) via nvm, fnm, or brew';
56
+ if (nodeRes.ok) {
57
+ const rawVer = nodeRes.stdout.replace(/^v/, '');
58
+ const [major = 0, minor = 0] = rawVer.split('.').map(Number);
59
+ const meetsEngine = major > 22 || (major === 22 && minor >= 12);
60
+ if (meetsEngine) {
61
+ nodeDetail = `Installed (${nodeRes.stdout})`;
62
+ nodeSuggestion = undefined;
63
+ } else {
64
+ nodeOk = false;
65
+ nodeDetail = `Installed (${nodeRes.stdout}) — requires Node.js >= 22.12.0`;
66
+ nodeSuggestion = 'Upgrade Node.js to >= 22.12.0 (LTS) via nvm, fnm, or brew';
67
+ }
68
+ }
69
+ checks.push({
70
+ name: 'Node.js runtime',
71
+ category: 'Environment',
72
+ ok: nodeOk,
73
+ version: nodeRes.stdout,
74
+ detail: nodeDetail,
75
+ suggestion: nodeSuggestion
76
+ });
77
+
78
+ // 2. Active Package Manager check (pnpm, npm, yarn, or bun)
79
+ const pmRes = await runExecutable(activePm, ['-v'], { silent: true });
80
+ checks.push({
81
+ name: `${activePm} package manager`,
82
+ category: 'Environment',
83
+ ok: pmRes.ok,
84
+ version: pmRes.stdout,
85
+ detail: pmRes.ok ? `Installed (${pmRes.stdout})` : `${activePm} is not found in PATH`,
86
+ suggestion: pmRes.ok
87
+ ? undefined
88
+ : `Install ${activePm} (${activePm === 'pnpm' ? 'npm install -g pnpm' : activePm === 'bun' ? 'curl -fsSL https://bun.sh/install | bash' : 'install ' + activePm})`
89
+ });
90
+
91
+ // 3. Git check
92
+ const gitExists = await commandExists('git');
93
+ let isGitRepo = fileExists(path.join(targetDir, '.git'));
94
+ let inWorkTree = false;
95
+ if (gitExists && !isGitRepo) {
96
+ const revParse = await runExecutable('git', ['rev-parse', '--is-inside-work-tree'], {
97
+ cwd: targetDir,
98
+ silent: true
99
+ });
100
+ if (revParse.ok && revParse.stdout === 'true') {
101
+ isGitRepo = true;
102
+ inWorkTree = true;
103
+ }
104
+ }
105
+ checks.push({
106
+ name: 'Git VCS',
107
+ category: 'Environment',
108
+ ok: gitExists && isGitRepo,
109
+ detail: gitExists
110
+ ? isGitRepo
111
+ ? inWorkTree
112
+ ? 'Git installed and target directory is within an active git repository'
113
+ : 'Git installed and repository initialized'
114
+ : 'Git installed, but target directory is not a git repo'
115
+ : 'Git is not installed',
116
+ suggestion: !gitExists
117
+ ? 'Install git via brew or Xcode Command Line Tools'
118
+ : !isGitRepo
119
+ ? 'Run `git init` or `aginit init` to initialize repository'
120
+ : undefined
121
+ });
122
+
123
+ // 4. Graft CLI check
124
+ const graftExists = await commandExists('graft');
125
+ let graftVersion = '';
126
+ if (graftExists) {
127
+ const gv = await runExecutable('graft', ['--version'], { silent: true });
128
+ graftVersion = gv.stdout;
129
+ }
130
+ checks.push({
131
+ name: 'Graft codebase graph CLI',
132
+ category: 'AI Tooling',
133
+ ok: graftExists,
134
+ version: graftVersion,
135
+ detail: graftExists ? `Installed globally (${graftVersion})` : 'Graft CLI not found in PATH',
136
+ suggestion: graftExists ? undefined : 'Install Graft: `npm install -g @nanonets/graft`'
137
+ });
138
+
139
+ // 5. Codex CLI check
140
+ const codexExists = await commandExists('codex');
141
+ checks.push({
142
+ name: 'Codex CLI (secondary agent)',
143
+ category: 'AI Tooling',
144
+ ok: codexExists,
145
+ detail: codexExists ? 'Codex CLI available in PATH' : 'Codex CLI not found in PATH',
146
+ suggestion: codexExists ? undefined : 'Install Codex CLI if you want dual-agent orchestration'
147
+ });
148
+
149
+ // 6. Skills CLI check (skills.sh engine)
150
+ const skillsRes = await runExecutable('npx', ['-y', 'skills', '--version'], { silent: true });
151
+ checks.push({
152
+ name: 'Skills CLI (skills.sh engine)',
153
+ category: 'AI Tooling',
154
+ ok: skillsRes.ok,
155
+ version: skillsRes.stdout,
156
+ detail: skillsRes.ok ? `Functional (${skillsRes.stdout})` : 'Failed running npx skills',
157
+ suggestion: skillsRes.ok ? undefined : 'Check network connectivity or npm/npx configuration'
158
+ });
159
+
160
+ // 7. Project-level checks
161
+ checks.push({
162
+ name: `Project configuration (${path.basename(configPath)})`,
163
+ category: 'Project Config',
164
+ ok: !!projectConfig,
165
+ detail: projectConfig
166
+ ? `Preset: "${projectConfig.preset}" (v${projectConfig.schemaVersion || '1.0.0'}), pm: ${activePm}, agents: ${projectConfig.agents.primary}/${projectConfig.agents.secondary}`
167
+ : configError
168
+ ? `Invalid configuration: ${configError}`
169
+ : 'No aginit.config.json found in current directory',
170
+ suggestion: projectConfig
171
+ ? undefined
172
+ : configError
173
+ ? 'Correct the configuration fields before running initialization'
174
+ : 'Run `aginit init` to generate declarative configuration'
175
+ });
176
+
177
+ // 8. AGENTS.md check
178
+ const agentsPath = path.join(targetDir, 'AGENTS.md');
179
+ const agentsExists = fileExists(agentsPath);
180
+ const agentsContent = agentsExists ? readTextFile(agentsPath) || '' : '';
181
+ const hasIntakeSection = agentsContent.includes('Bootstrap & First Session');
182
+
183
+ checks.push({
184
+ name: 'AGENTS.md Context',
185
+ category: 'Project Config',
186
+ ok: agentsExists && hasIntakeSection,
187
+ detail: agentsExists
188
+ ? hasIntakeSection
189
+ ? 'AGENTS.md present with intake/bootstrap guidance'
190
+ : 'AGENTS.md present but missing intake guidance'
191
+ : 'AGENTS.md not found in directory',
192
+ suggestion: !agentsExists
193
+ ? 'Run `aginit init` to generate standard AGENTS.md'
194
+ : !hasIntakeSection
195
+ ? 'Run `aginit init` to add intake section to AGENTS.md'
196
+ : undefined
197
+ });
198
+
199
+ // 9. Installed skills check
200
+ const installedSkills = getInstalledSkills(targetDir);
201
+ checks.push({
202
+ name: 'Local Agent Skills (.agents/skills/)',
203
+ category: 'Project Config',
204
+ ok: installedSkills.length > 0,
205
+ detail:
206
+ installedSkills.length > 0
207
+ ? `${installedSkills.length} skills installed: ${installedSkills.join(', ')}`
208
+ : 'No skills found in .agents/skills/',
209
+ suggestion:
210
+ installedSkills.length === 0
211
+ ? 'Run `aginit init` or `npx skills add ...` to install skills'
212
+ : undefined
213
+ });
214
+
215
+ // Print grouped results
216
+ const categories: Array<'Environment' | 'AI Tooling' | 'Project Config'> = [
217
+ 'Environment',
218
+ 'AI Tooling',
219
+ 'Project Config'
220
+ ];
221
+
222
+ for (const cat of categories) {
223
+ console.log(pc.bold(pc.underline(cat)));
224
+ const catChecks = checks.filter((c) => c.category === cat);
225
+ for (const c of catChecks) {
226
+ const icon = c.ok ? pc.green('✓') : pc.yellow('⚠');
227
+ const name = pc.bold(c.name);
228
+ console.log(` ${icon} ${name}: ${c.detail}`);
229
+ if (c.suggestion) {
230
+ console.log(` ${pc.cyan('→ Suggestion:')} ${pc.dim(c.suggestion)}`);
231
+ }
232
+ }
233
+ console.log();
234
+ }
235
+
236
+ const passed = checks.filter((c) => c.ok).length;
237
+ const total = checks.length;
238
+
239
+ if (passed === total) {
240
+ logger.success(pc.bold(`All ${total} health checks passed! System and repo are fully ready.`));
241
+ } else {
242
+ logger.info(`Doctor finished: ${passed}/${total} checks healthy.`);
243
+ }
244
+ }
@@ -0,0 +1,34 @@
1
+ import path from 'node:path';
2
+ import { readPackageManifest, readProjectConfig } from '../config/validation.js';
3
+ import { createProject, NewProjectOptions } from './new.js';
4
+ import { detectPackageManager } from '../adapters/package-manager.js';
5
+
6
+ function sanitizeProjectName(rawName: string): string {
7
+ const sanitized = rawName
8
+ .toLowerCase()
9
+ .trim()
10
+ .replace(/\s+/g, '-')
11
+ .replace(/[^a-z0-9._-]/g, '')
12
+ .replace(/^[^a-z0-9]+/, '');
13
+ return sanitized || 'ai-project';
14
+ }
15
+
16
+ export async function initCurrentDirectory(options: NewProjectOptions = {}): Promise<boolean> {
17
+ const currentDir = process.cwd();
18
+ let projectName = path.basename(currentDir);
19
+
20
+ const saved = readProjectConfig(currentDir);
21
+ const pkg = readPackageManifest(currentDir);
22
+ if (pkg?.name) projectName = pkg.name;
23
+ else if (saved?.name) projectName = saved.name;
24
+ else projectName = sanitizeProjectName(projectName);
25
+
26
+ const packageManager =
27
+ options.packageManager ?? saved?.packageManager ?? detectPackageManager(currentDir) ?? 'pnpm';
28
+
29
+ return createProject(projectName, {
30
+ ...options,
31
+ packageManager,
32
+ targetDir: currentDir
33
+ });
34
+ }
@@ -0,0 +1,191 @@
1
+ import { Command } from 'commander';
2
+ import { createProject } from './new.js';
3
+ import { initCurrentDirectory } from './init.js';
4
+ import { runDoctor } from './doctor.js';
5
+ import { updateSkills } from '../adapters/skills.js';
6
+ import { buildGraft } from '../adapters/graft.js';
7
+ import { readProjectConfig } from '../config/validation.js';
8
+ import { logger } from '../utils/logger.js';
9
+ import { PresetType, WebFramework, PackageManager } from '../config/schema.js';
10
+ import {
11
+ isInteractive,
12
+ promptNewProject,
13
+ promptInitProject,
14
+ runInteractiveWizard
15
+ } from './wizard.js';
16
+
17
+ export function createCliProgram(): Command {
18
+ const program = new Command();
19
+
20
+ program
21
+ .name('aginit')
22
+ .description(
23
+ 'Minimal, modular AI-first project bootstrapper for T3 Code, Antigravity, and Codex'
24
+ )
25
+ .version('0.1.0');
26
+
27
+ // Root action: interactive wizard when in TTY, else display help
28
+ program.action(async () => {
29
+ if (isInteractive()) {
30
+ await runInteractiveWizard();
31
+ } else {
32
+ program.outputHelp();
33
+ }
34
+ });
35
+
36
+ // Command: new
37
+ program
38
+ .command('new [projectName]')
39
+ .description('Create a brand new AI-first project')
40
+ .option('-p, --preset <preset>', 'Project preset: web | cli | generic', 'generic')
41
+ .option('-f, --framework <framework>', 'Web framework: none | vite | next | existing', 'none')
42
+ .option('-m, --package-manager <pm>', 'Package manager: pnpm | npm | yarn | bun', 'pnpm')
43
+ .option(
44
+ '--spec-workflow',
45
+ 'Include full spec and tickets skills (to-spec, to-tickets, implement-spec)'
46
+ )
47
+ .option('--no-skills', 'Skip installing AI skills')
48
+ .option('--no-graft', 'Skip codebase graph (Graft) setup')
49
+ .option('--no-git', 'Skip git repository initialization')
50
+ .option('--dry-run', 'Simulate creation without writing changes to disk')
51
+ .action(async (projectName: string | undefined, options: any, command: Command) => {
52
+ let finalName = projectName;
53
+ let finalPreset = options.preset;
54
+ let finalFramework = options.framework;
55
+ let finalPackageManager = options.packageManager;
56
+ let finalSpecWorkflow = options.specWorkflow;
57
+
58
+ if (!finalName) {
59
+ if (!isInteractive()) {
60
+ program.error("error: missing required argument 'projectName'");
61
+ }
62
+ const answers = await promptNewProject({
63
+ preset:
64
+ command.getOptionValueSource('preset') === 'cli'
65
+ ? (options.preset as PresetType)
66
+ : undefined,
67
+ framework:
68
+ command.getOptionValueSource('framework') === 'cli'
69
+ ? (options.framework as WebFramework)
70
+ : undefined,
71
+ packageManager:
72
+ command.getOptionValueSource('packageManager') === 'cli'
73
+ ? (options.packageManager as PackageManager)
74
+ : undefined,
75
+ specWorkflow:
76
+ command.getOptionValueSource('specWorkflow') === 'cli'
77
+ ? options.specWorkflow
78
+ : undefined
79
+ });
80
+ finalName = answers.projectName;
81
+ finalPreset = answers.preset;
82
+ finalFramework = answers.framework;
83
+ finalPackageManager = answers.packageManager;
84
+ finalSpecWorkflow = answers.specWorkflow;
85
+ }
86
+
87
+ const complete = await createProject(finalName, {
88
+ preset: finalPreset as PresetType,
89
+ framework: finalFramework as WebFramework,
90
+ packageManager: finalPackageManager as PackageManager,
91
+ specWorkflow: finalSpecWorkflow,
92
+ skills: options.skills,
93
+ graft: options.graft,
94
+ git: options.git,
95
+ dryRun: options.dryRun
96
+ });
97
+ if (!complete && !options.dryRun) process.exitCode = 1;
98
+ });
99
+
100
+ // Command: init
101
+ program
102
+ .command('init')
103
+ .description('Initialize or update AI-first capabilities in the current directory')
104
+ .option('-p, --preset <preset>', 'Project preset: web | cli | generic')
105
+ .option('-f, --framework <framework>', 'Web framework: none | vite | next | existing')
106
+ .option('-m, --package-manager <pm>', 'Package manager: pnpm | npm | yarn | bun')
107
+ .option(
108
+ '--spec-workflow',
109
+ 'Include full spec and tickets skills (to-spec, to-tickets, implement-spec)'
110
+ )
111
+ .option('--no-skills', 'Skip installing AI skills')
112
+ .option('--no-graft', 'Skip codebase graph (Graft) setup')
113
+ .option('--no-git', 'Skip git repository initialization')
114
+ .option('--dry-run', 'Simulate setup without writing changes to disk')
115
+ .action(async (options: any, command: Command) => {
116
+ let finalPreset = options.preset;
117
+ let finalFramework = options.framework;
118
+ let finalPackageManager = options.packageManager;
119
+ let finalSpecWorkflow = options.specWorkflow;
120
+
121
+ const isPresetExplicit = command.getOptionValueSource('preset') === 'cli';
122
+ const isFrameworkExplicit = command.getOptionValueSource('framework') === 'cli';
123
+ const isPmExplicit = command.getOptionValueSource('packageManager') === 'cli';
124
+ const isSpecExplicit = command.getOptionValueSource('specWorkflow') === 'cli';
125
+
126
+ const savedConfig = readProjectConfig(process.cwd());
127
+ if (isInteractive() && !isPresetExplicit && !options.dryRun && !savedConfig) {
128
+ const answers = await promptInitProject({
129
+ preset: isPresetExplicit ? (options.preset as PresetType) : undefined,
130
+ framework: isFrameworkExplicit ? (options.framework as WebFramework) : undefined,
131
+ packageManager: isPmExplicit ? (options.packageManager as PackageManager) : undefined,
132
+ specWorkflow: isSpecExplicit ? options.specWorkflow : undefined
133
+ });
134
+ finalPreset = answers.preset;
135
+ finalFramework = answers.framework;
136
+ finalPackageManager = answers.packageManager;
137
+ finalSpecWorkflow = answers.specWorkflow;
138
+ }
139
+
140
+ const complete = await initCurrentDirectory({
141
+ preset: finalPreset as PresetType,
142
+ framework: finalFramework as WebFramework,
143
+ packageManager: finalPackageManager as PackageManager,
144
+ specWorkflow: finalSpecWorkflow,
145
+ skills: options.skills,
146
+ graft: options.graft,
147
+ git: options.git,
148
+ dryRun: options.dryRun
149
+ });
150
+ if (!complete && !options.dryRun) process.exitCode = 1;
151
+ });
152
+
153
+ // Command: doctor
154
+ program
155
+ .command('doctor')
156
+ .description('Run system and project diagnostics for AI agent readiness')
157
+ .option('-d, --dir <path>', 'Target directory to check', process.cwd())
158
+ .action(async (options: any) => {
159
+ await runDoctor(options.dir);
160
+ });
161
+
162
+ // Command: update
163
+ program
164
+ .command('update')
165
+ .description('Update installed skills and refresh codebase graph')
166
+ .option('--dry-run', 'Simulate update')
167
+ .action(async (options: any) => {
168
+ logger.banner('Aginit — Update');
169
+ const targetDir = process.cwd();
170
+ const config = readProjectConfig(targetDir);
171
+ const skillsOk =
172
+ config?.skills.sources.length === 0
173
+ ? true
174
+ : await updateSkills(targetDir, { dryRun: options.dryRun });
175
+ const graftOk =
176
+ config?.codebase.graft === false
177
+ ? true
178
+ : await buildGraft(targetDir, { dryRun: options.dryRun });
179
+ if (skillsOk && graftOk) logger.success('Update finished.');
180
+ else {
181
+ if (!skillsOk) logger.warn('Skills update failed. Check connectivity and the skills CLI.');
182
+ if (!graftOk)
183
+ logger.warn(
184
+ 'Graft refresh failed or Graft is unavailable. Check installation and rerun graft build.'
185
+ );
186
+ process.exitCode = 1;
187
+ }
188
+ });
189
+
190
+ return program;
191
+ }