@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,87 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import { logger } from './logger.js';
6
+ const execFileAsync = promisify(execFile);
7
+ function resolveExecutable(binName) {
8
+ if (process.platform !== 'win32') {
9
+ return { executable: binName, isBatch: false };
10
+ }
11
+ const isBatch = (file) => /\.(cmd|bat)$/i.test(file);
12
+ if (path.extname(binName)) {
13
+ return { executable: binName, isBatch: isBatch(binName) };
14
+ }
15
+ const pathExts = (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean);
16
+ const dirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
17
+ for (const dir of dirs) {
18
+ const cleanDir = dir.replace(/^"|"$/g, '');
19
+ for (const ext of pathExts) {
20
+ const candidate = path.join(cleanDir, `${binName}${ext}`);
21
+ try {
22
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
23
+ return { executable: candidate, isBatch: isBatch(candidate) };
24
+ }
25
+ }
26
+ catch {
27
+ // continue
28
+ }
29
+ }
30
+ }
31
+ return { executable: binName, isBatch: false };
32
+ }
33
+ /** Execute a program with literal arguments. No shell expansion or interpolation. */
34
+ export async function runExecutable(executable, args = [], options = {}) {
35
+ const { cwd = process.cwd(), env = process.env, dryRun = false, silent = false, timeout = 300_000 } = options;
36
+ if (dryRun) {
37
+ logger.dim(`[dry-run] Would execute: ${[executable, ...args].map((a) => JSON.stringify(a)).join(' ')} (in ${cwd})`);
38
+ return { stdout: '', stderr: '', exitCode: 0, ok: true };
39
+ }
40
+ try {
41
+ const { executable: targetExe, isBatch } = resolveExecutable(executable);
42
+ const { stdout, stderr } = await execFileAsync(targetExe, args, {
43
+ cwd,
44
+ env: { ...process.env, ...env },
45
+ maxBuffer: 10 * 1024 * 1024,
46
+ timeout,
47
+ shell: isBatch
48
+ });
49
+ if (!silent && stdout.trim())
50
+ logger.dim(stdout.trim());
51
+ return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0, ok: true };
52
+ }
53
+ catch (error) {
54
+ return {
55
+ stdout: String(error.stdout || '').trim(),
56
+ stderr: String(error.stderr || error.message || '').trim(),
57
+ exitCode: typeof error.code === 'number' ? error.code : 1,
58
+ ok: false
59
+ };
60
+ }
61
+ }
62
+ export async function commandExists(binName) {
63
+ if (!/^[a-zA-Z0-9._-]+$/.test(binName))
64
+ return false;
65
+ const isWindows = process.platform === 'win32';
66
+ const pathExts = isWindows
67
+ ? (process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
68
+ : [''];
69
+ return (process.env.PATH || '').split(path.delimiter).some((dir) => {
70
+ if (!dir)
71
+ return false;
72
+ const cleanDir = dir.replace(/^"|"$/g, '');
73
+ for (const ext of pathExts) {
74
+ try {
75
+ const file = path.join(cleanDir, isWindows && !path.extname(binName) ? `${binName}${ext}` : binName);
76
+ fs.accessSync(file, isWindows ? fs.constants.F_OK : fs.constants.X_OK);
77
+ if (fs.statSync(file).isFile())
78
+ return true;
79
+ }
80
+ catch {
81
+ // continue
82
+ }
83
+ }
84
+ return false;
85
+ });
86
+ }
87
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/utils/shell.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@antomanc/aginit",
3
+ "version": "0.1.0",
4
+ "description": "Minimal, modular AI-first project bootstrapper for T3 Code, Antigravity, and Codex",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "aginit": "./bin/aginit.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "bin",
21
+ "README.md",
22
+ "LICENSE",
23
+ "THIRD_PARTY_NOTICES.md"
24
+ ],
25
+ "author": "Antonio Mancuso <an.manc25@gmail.com>",
26
+ "homepage": "https://github.com/antomanc/aginit#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/antomanc/aginit/issues"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/antomanc/aginit.git"
36
+ },
37
+ "keywords": [
38
+ "ai",
39
+ "agents",
40
+ "antigravity",
41
+ "codex",
42
+ "t3-code",
43
+ "skills",
44
+ "graft",
45
+ "bootstrap",
46
+ "scaffolder"
47
+ ],
48
+ "dependencies": {
49
+ "@clack/prompts": "^1.8.1",
50
+ "commander": "^12.1.0",
51
+ "picocolors": "^1.1.1"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^22.8.0",
55
+ "tsx": "^4.19.2",
56
+ "typescript": "^5.6.3",
57
+ "vitest": "^4.1.11"
58
+ },
59
+ "license": "MIT",
60
+ "engines": {
61
+ "node": ">=22.12.0"
62
+ },
63
+ "scripts": {
64
+ "build": "tsc",
65
+ "watch": "tsc --watch",
66
+ "test": "vitest run",
67
+ "typecheck": "tsc --noEmit",
68
+ "check": "pnpm typecheck && pnpm test && pnpm build && pnpm audit",
69
+ "smoke": "node scripts/smoke-test.mjs"
70
+ }
71
+ }
@@ -0,0 +1,143 @@
1
+ import path from 'node:path';
2
+ import { fileExists, readTextFile, safeWriteFile } from '../utils/fs.js';
3
+ import { AginitConfig } from '../config/schema.js';
4
+ import { readPackageManifest } from '../config/validation.js';
5
+ import { logger } from '../utils/logger.js';
6
+ import { getPackageManagerAdapter } from './package-manager.js';
7
+
8
+ export function generateAgentsMarkdown(
9
+ config: AginitConfig,
10
+ scripts?: Record<string, string>
11
+ ): string {
12
+ const commands =
13
+ scripts ??
14
+ (config.preset === 'generic'
15
+ ? { test: 'vitest run' }
16
+ : { build: '', typecheck: '', test: '', dev: '', 'test:e2e': '' });
17
+ const hasCommand = (name: string) => Object.hasOwn(commands, name);
18
+ const isWeb = config.preset === 'web';
19
+ const hasBrowser = isWeb && config.browser.playwright;
20
+ const hasVisualAgent = isWeb && config.browser.visualAgent;
21
+ const pm = config.packageManager || 'pnpm';
22
+ const pmAdapter = getPackageManagerAdapter(pm);
23
+
24
+ let devCommand = '';
25
+ if (hasCommand('dev') && isWeb && config.framework && config.framework !== 'none') {
26
+ devCommand = `- Dev Server: \`${pmAdapter.runCmd('dev')}\``;
27
+ }
28
+
29
+ let testE2E = '';
30
+ if (hasBrowser && hasCommand('test:e2e')) {
31
+ testE2E = `- Test (E2E / Browser): \`${pmAdapter.runCmd('test:e2e')}\``;
32
+ }
33
+
34
+ const selectedSkills = config.skills.sources.flatMap((source) => source.skills);
35
+ const engineering = selectedSkills.filter((skill) =>
36
+ ['tdd', 'code-review', 'diagnosing-bugs'].includes(skill)
37
+ );
38
+ let uiCapability = '';
39
+ if (isWeb && selectedSkills.includes('impeccable')) {
40
+ uiCapability =
41
+ '- **UI & Design**: Use `impeccable` skill for frontend polish, design critique, and token craft.';
42
+ }
43
+
44
+ let browserCapability = '';
45
+ if (hasVisualAgent && selectedSkills.includes('agent-browser')) {
46
+ browserCapability =
47
+ '- **Browser Verification**: Use `agent-browser` skill for interactive inspection and visual QA.';
48
+ }
49
+
50
+ let graftCapability = '';
51
+ if (config.codebase.graft) {
52
+ graftCapability =
53
+ '- **Codebase Graph**: Use `graft ask "<query>" --source` or `graft map` for orientation before raw grep.';
54
+ }
55
+
56
+ let specWorkflow = '';
57
+ if (config.skills.workflow === 'spec' && selectedSkills.includes('to-spec')) {
58
+ specWorkflow = '- **Spec & Tickets Workflow**: `to-spec`, `to-tickets`, `implement-spec`';
59
+ }
60
+
61
+ const sections = [
62
+ `# ${config.name}`,
63
+ '',
64
+ `AI-first ${config.preset} project configured with \`aginit\`.`,
65
+ '',
66
+ '## Core Invariants',
67
+ '- Keep changes small, focused, and test-verified.',
68
+ '- Prefer native language & platform capabilities over heavy external frameworks.',
69
+ '- Never commit broken builds, failed typechecks, or unverified changes.',
70
+ '',
71
+ '## Essential Commands',
72
+ `- Package Manager: \`${pm}\``,
73
+ devCommand,
74
+ hasCommand('build') ? `- Build: \`${pmAdapter.runCmd('build')}\`` : '',
75
+ hasCommand('typecheck') ? `- Typecheck: \`${pmAdapter.runCmd('typecheck')}\`` : '',
76
+ hasCommand('test') ? `- Test: \`${pmAdapter.runCmd('test')}\`` : '',
77
+ hasCommand('test:unit') ? `- Unit Test: \`${pmAdapter.runCmd('test:unit')}\`` : '',
78
+ testE2E,
79
+ '',
80
+ '## AI Capabilities & Skills',
81
+ selectedSkills.length
82
+ ? 'Configured skills install into `.agents/skills/` (shared by Antigravity and Codex):'
83
+ : 'Skills installation is disabled in this configuration.',
84
+ engineering.length
85
+ ? `- **Engineering**: ${engineering.map((skill) => '\`' + skill + '\`').join(', ')}`
86
+ : '',
87
+ specWorkflow,
88
+ uiCapability,
89
+ browserCapability,
90
+ graftCapability,
91
+ '',
92
+ '## Bootstrap & First Session',
93
+ 'When the user asks to "bootstrap this project" or kicks off a new initiative:',
94
+ '1. Understand product intent and ask 2-3 essential questions to resolve ambiguity.',
95
+ '2. Draft or refine canonical context in `PRODUCT.md` and `DESIGN.md` (or `docs/adr/`).',
96
+ '3. Plan in small verifiable increments and implement test-first using installed skills.',
97
+ ''
98
+ ];
99
+
100
+ return (
101
+ sections
102
+ .filter((s) => s !== '')
103
+ .join('\n')
104
+ .trim() + '\n'
105
+ );
106
+ }
107
+
108
+ export function setupAgentsMarkdown(
109
+ targetDir: string,
110
+ config: AginitConfig,
111
+ options: { overwrite?: boolean; dryRun?: boolean; silent?: boolean } = {}
112
+ ): boolean {
113
+ const { overwrite = false, dryRun = false, silent = false } = options;
114
+ const filePath = path.join(targetDir, 'AGENTS.md');
115
+
116
+ if (fileExists(filePath) && !overwrite) {
117
+ // File exists; check if it has the bootstrap guidance. If not, append it conservatively.
118
+ const existing = readTextFile(filePath) || '';
119
+ if (!existing.includes('## Bootstrap & First Session')) {
120
+ const addition = [
121
+ '',
122
+ '## Bootstrap & First Session',
123
+ 'When the user asks to "bootstrap this project" or kicks off a new initiative:',
124
+ '1. Understand product intent and ask 2-3 essential questions to resolve ambiguity.',
125
+ '2. Draft or refine canonical context in `PRODUCT.md` and `DESIGN.md` (or `docs/adr/`).',
126
+ '3. Plan in small verifiable increments and implement test-first using installed skills.',
127
+ ''
128
+ ].join('\n');
129
+ safeWriteFile(filePath, existing.trimEnd() + '\n' + addition, {
130
+ overwrite: true,
131
+ dryRun,
132
+ silent
133
+ });
134
+ if (!silent) logger.dim('Appended Bootstrap section to existing AGENTS.md');
135
+ return true;
136
+ }
137
+ if (!silent) logger.dim('AGENTS.md already exists, preserving content.');
138
+ return true;
139
+ }
140
+
141
+ const content = generateAgentsMarkdown(config, readPackageManifest(targetDir)?.scripts);
142
+ return safeWriteFile(filePath, content, { overwrite: true, dryRun, silent });
143
+ }
@@ -0,0 +1,49 @@
1
+ import path from 'node:path';
2
+ import { safeWriteFile } from '../utils/fs.js';
3
+
4
+ const PLAYWRIGHT_CONFIG = `import { defineConfig, devices } from '@playwright/test';
5
+
6
+ export default defineConfig({
7
+ testDir: './e2e',
8
+ fullyParallel: true,
9
+ forbidOnly: !!process.env.CI,
10
+ retries: process.env.CI ? 2 : 0,
11
+ workers: process.env.CI ? 1 : undefined,
12
+ reporter: 'html',
13
+ use: {
14
+ trace: 'on-first-retry',
15
+ screenshot: 'only-on-failure'
16
+ },
17
+ projects: [
18
+ {
19
+ name: 'chromium',
20
+ use: { ...devices['Desktop Chrome'] }
21
+ }
22
+ ]
23
+ });
24
+ `;
25
+
26
+ const SMOKE_TEST = `import { test, expect } from '@playwright/test';
27
+
28
+ test.skip('TODO: verify the application in a browser', async ({ page }) => {
29
+ // Replace with local dev server or preview URL during development
30
+ // Example: await page.goto('/'); await expect(page).toHaveTitle(/Your app/);
31
+ });
32
+ `;
33
+
34
+ export function setupBrowserTesting(
35
+ targetDir: string,
36
+ options: { dryRun?: boolean; silent?: boolean } = {}
37
+ ): void {
38
+ const { dryRun = false, silent = false } = options;
39
+
40
+ safeWriteFile(path.join(targetDir, 'playwright.config.ts'), PLAYWRIGHT_CONFIG, {
41
+ dryRun,
42
+ silent
43
+ });
44
+
45
+ safeWriteFile(path.join(targetDir, 'e2e', 'smoke.spec.ts'), SMOKE_TEST, {
46
+ dryRun,
47
+ silent
48
+ });
49
+ }
@@ -0,0 +1,100 @@
1
+ import path from 'node:path';
2
+ import { fileExists, readTextFile, safeWriteFile } from '../utils/fs.js';
3
+ import { runExecutable } from '../utils/shell.js';
4
+ import { logger } from '../utils/logger.js';
5
+
6
+ const DEFAULT_GITIGNORE = `# Dependencies
7
+ node_modules/
8
+ .pnpm-store/
9
+
10
+ # Build artifacts
11
+ dist/
12
+ build/
13
+ .next/
14
+ out/
15
+
16
+ # Environment
17
+ .env
18
+ .env.*
19
+ !.env.example
20
+ !.env.*.example
21
+ *.local
22
+
23
+ # Logs & temp
24
+ *.log
25
+ .DS_Store
26
+ *.tsbuildinfo
27
+
28
+ # Testing & Coverage
29
+ coverage/
30
+ test-results/
31
+ playwright-report/
32
+ blob-report/
33
+
34
+ # Codebase intelligence cache (graft builds locally, git-ignored)
35
+ /graft/
36
+ `;
37
+
38
+ export async function setupGit(
39
+ targetDir: string,
40
+ options: { dryRun?: boolean; silent?: boolean } = {}
41
+ ): Promise<boolean> {
42
+ const { dryRun = false, silent = false } = options;
43
+ const gitDir = path.join(targetDir, '.git');
44
+ const gitignorePath = path.join(targetDir, '.gitignore');
45
+
46
+ let gitOk = true;
47
+
48
+ // Check if directory is already a git repo or inside an existing git work tree (e.g. monorepo package)
49
+ let isRepo = fileExists(gitDir);
50
+ if (!isRepo && !dryRun) {
51
+ const check = await runExecutable('git', ['rev-parse', '--is-inside-work-tree'], {
52
+ cwd: targetDir,
53
+ silent: true
54
+ });
55
+ if (check.ok && check.stdout === 'true') {
56
+ isRepo = true;
57
+ }
58
+ }
59
+
60
+ if (!isRepo) {
61
+ if (!silent) logger.dim('Initializing git repository...');
62
+ const res = await runExecutable('git', ['init'], { cwd: targetDir, dryRun, silent: true });
63
+ if (!res.ok) {
64
+ logger.warn(`Failed to initialize git repository: ${res.stderr}`);
65
+ gitOk = false;
66
+ }
67
+ }
68
+
69
+ // Ensure .gitignore exists or append essential rules regardless of git init status
70
+ if (!fileExists(gitignorePath)) {
71
+ safeWriteFile(gitignorePath, DEFAULT_GITIGNORE, { dryRun, silent });
72
+ } else {
73
+ const existing = readTextFile(gitignorePath) || '';
74
+ const toAppend: string[] = [];
75
+
76
+ const essentialRules = [
77
+ 'node_modules/',
78
+ '/graft/',
79
+ '.DS_Store',
80
+ 'dist/',
81
+ '.env',
82
+ '.env.*',
83
+ '!.env.example',
84
+ '!.env.*.example'
85
+ ];
86
+ for (const rule of essentialRules) {
87
+ if (!existing.split(/\r?\n/).includes(rule)) {
88
+ toAppend.push(rule);
89
+ }
90
+ }
91
+
92
+ if (toAppend.length > 0) {
93
+ const updated =
94
+ existing.trimEnd() + '\n\n# AI & Project rules\n' + toAppend.join('\n') + '\n';
95
+ safeWriteFile(gitignorePath, updated, { overwrite: true, dryRun, silent });
96
+ }
97
+ }
98
+
99
+ return gitOk;
100
+ }
@@ -0,0 +1,80 @@
1
+ import path from 'node:path';
2
+ import { fileExists, readTextFile, safeWriteFile } from '../utils/fs.js';
3
+ import { commandExists, runExecutable } from '../utils/shell.js';
4
+ import { logger } from '../utils/logger.js';
5
+
6
+ export async function isGraftInstalled(): Promise<boolean> {
7
+ return await commandExists('graft');
8
+ }
9
+
10
+ export async function initGraft(
11
+ targetDir: string,
12
+ options: { dryRun?: boolean; silent?: boolean } = {}
13
+ ): Promise<boolean> {
14
+ const { dryRun = false, silent = false } = options;
15
+
16
+ const installed = dryRun || (await isGraftInstalled());
17
+ if (!installed) {
18
+ if (!silent) {
19
+ logger.warn(
20
+ 'Graft CLI is not installed. Install it with `npm install -g @nanonets/graft`, then rerun aginit init.'
21
+ );
22
+ }
23
+ return false;
24
+ }
25
+
26
+ if (!silent) logger.dim('Initializing Graft context graph...');
27
+
28
+ // Run graft init with antigravity and agents, without touching global system files (--no-global)
29
+ const res = await runExecutable(
30
+ 'graft',
31
+ ['init', '--no-global', '--agents', 'antigravity', 'agents', '-y'],
32
+ {
33
+ cwd: targetDir,
34
+ dryRun,
35
+ silent: true
36
+ }
37
+ );
38
+
39
+ if (!res.ok) {
40
+ logger.warn(`Graft initialization warning: ${res.stderr || res.stdout}`);
41
+ return false;
42
+ }
43
+
44
+ return true;
45
+ }
46
+
47
+ export async function buildGraft(
48
+ targetDir: string,
49
+ options: { dryRun?: boolean; silent?: boolean } = {}
50
+ ): Promise<boolean> {
51
+ const { dryRun = false } = options;
52
+ const installed = dryRun || (await isGraftInstalled());
53
+ if (!installed) return false;
54
+
55
+ const res = await runExecutable('graft', ['build'], { cwd: targetDir, dryRun, silent: true });
56
+ return res.ok;
57
+ }
58
+
59
+ export async function uninstallGraft(
60
+ targetDir: string,
61
+ options: { dryRun?: boolean; silent?: boolean } = {}
62
+ ): Promise<boolean> {
63
+ const { dryRun = false } = options;
64
+ const installed = dryRun || (await isGraftInstalled());
65
+ if (installed) {
66
+ await runExecutable('graft', ['uninstall'], { cwd: targetDir, dryRun, silent: true });
67
+ }
68
+
69
+ // Also clean up AGENTS.md fenced section if any remain
70
+ const agentsPath = path.join(targetDir, 'AGENTS.md');
71
+ if (fileExists(agentsPath)) {
72
+ const content = readTextFile(agentsPath) || '';
73
+ const cleaned = content
74
+ .replace(/<!-- graft:start -->[\s\S]*?<!-- graft:end -->\n?/g, '')
75
+ .trim();
76
+ safeWriteFile(agentsPath, cleaned + '\n', { overwrite: true, dryRun });
77
+ }
78
+
79
+ return true;
80
+ }
@@ -0,0 +1,116 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { PackageManager } from '../config/schema.js';
4
+
5
+ export interface PackageManagerAdapter {
6
+ name: PackageManager;
7
+ installCmd: string;
8
+ runCmd: (script: string) => string;
9
+ execCmd: (pkg: string, args?: string[]) => string;
10
+ lockfile: string;
11
+ scaffoldViteCmd: (template?: string) => string;
12
+ scaffoldNextCmd: () => string;
13
+ }
14
+
15
+ export const PACKAGE_MANAGERS: Record<PackageManager, PackageManagerAdapter> = {
16
+ pnpm: {
17
+ name: 'pnpm',
18
+ installCmd: 'pnpm install',
19
+ runCmd: (script: string) => `pnpm ${script}`,
20
+ execCmd: (pkg: string, args: string[] = []) =>
21
+ `pnpm dlx ${pkg}${args.length ? ' ' + args.join(' ') : ''}`,
22
+ lockfile: 'pnpm-lock.yaml',
23
+ scaffoldViteCmd: (template = 'react-ts') => `pnpm create vite . --template ${template}`,
24
+ scaffoldNextCmd: () =>
25
+ `pnpm create next-app . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-pnpm`
26
+ },
27
+ npm: {
28
+ name: 'npm',
29
+ installCmd: 'npm install',
30
+ runCmd: (script: string) => `npm run ${script}`,
31
+ execCmd: (pkg: string, args: string[] = []) =>
32
+ `npx -y ${pkg}${args.length ? ' ' + args.join(' ') : ''}`,
33
+ lockfile: 'package-lock.json',
34
+ scaffoldViteCmd: (template = 'react-ts') =>
35
+ `npm create vite@latest . -- --template ${template}`,
36
+ scaffoldNextCmd: () =>
37
+ `npx -y create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm`
38
+ },
39
+ yarn: {
40
+ name: 'yarn',
41
+ installCmd: 'yarn install',
42
+ runCmd: (script: string) => `yarn ${script}`,
43
+ execCmd: (pkg: string, args: string[] = []) =>
44
+ `yarn dlx ${pkg}${args.length ? ' ' + args.join(' ') : ''}`,
45
+ lockfile: 'yarn.lock',
46
+ scaffoldViteCmd: (template = 'react-ts') => `yarn create vite . --template ${template}`,
47
+ scaffoldNextCmd: () =>
48
+ `yarn create next-app . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-yarn`
49
+ },
50
+ bun: {
51
+ name: 'bun',
52
+ installCmd: 'bun install',
53
+ runCmd: (script: string) => `bun run ${script}`,
54
+ execCmd: (pkg: string, args: string[] = []) =>
55
+ `bunx ${pkg}${args.length ? ' ' + args.join(' ') : ''}`,
56
+ lockfile: 'bun.lockb',
57
+ scaffoldViteCmd: (template = 'react-ts') => `bun create vite . --template ${template}`,
58
+ scaffoldNextCmd: () =>
59
+ `bun create next-app . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" --use-bun`
60
+ }
61
+ };
62
+
63
+ export function getPackageManagerAdapter(pm: PackageManager = 'pnpm'): PackageManagerAdapter {
64
+ return PACKAGE_MANAGERS[pm] || PACKAGE_MANAGERS.pnpm;
65
+ }
66
+
67
+ export function detectPackageManager(dir: string): PackageManager | null {
68
+ if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) {
69
+ return 'pnpm';
70
+ }
71
+ if (fs.existsSync(path.join(dir, 'bun.lockb')) || fs.existsSync(path.join(dir, 'bun.lock'))) {
72
+ return 'bun';
73
+ }
74
+ if (fs.existsSync(path.join(dir, 'yarn.lock'))) {
75
+ return 'yarn';
76
+ }
77
+ if (fs.existsSync(path.join(dir, 'package-lock.json'))) {
78
+ return 'npm';
79
+ }
80
+ return null;
81
+ }
82
+
83
+ /** Yarn Classic lacks dlx; npx fetches tools while --use-yarn selects the app manager.
84
+ * Commands used for execution; string helpers above are for displaying commands only. */
85
+ export function scaffoldCommand(
86
+ pm: PackageManager,
87
+ framework: 'vite' | 'next'
88
+ ): { executable: string; args: string[] } {
89
+ const runners: Record<PackageManager, [string, ...string[]]> = {
90
+ pnpm: ['pnpm', 'dlx'],
91
+ npm: ['npx', '-y'],
92
+ yarn: ['npx', '-y'],
93
+ bun: ['bunx']
94
+ };
95
+ const [executable, ...prefix] = runners[pm];
96
+ const args =
97
+ framework === 'vite'
98
+ ? ['create-vite@latest', '.', '--template', 'react-ts', '--no-interactive', '--no-immediate']
99
+ : [
100
+ 'create-next-app@latest',
101
+ '.',
102
+ '--typescript',
103
+ '--tailwind',
104
+ '--eslint',
105
+ '--app',
106
+ '--src-dir',
107
+ '--import-alias',
108
+ '@/*',
109
+ `--use-${pm}`,
110
+ '--yes',
111
+ '--skip-install',
112
+ '--disable-git',
113
+ '--no-agents-md'
114
+ ];
115
+ return { executable, args: [...prefix, ...args] };
116
+ }