@meraki-digital/agent-init 1.0.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.
- package/README.md +79 -0
- package/package.json +27 -0
- package/src/cli.js +372 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# agent-init
|
|
2
|
+
|
|
3
|
+
Bootstrap repositories with Meraki agent infrastructure.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @meraki-digital/agent-init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use directly without installing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @meraki-digital/agent-init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Run in any repository you want to bootstrap:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd your-project
|
|
23
|
+
agent-init
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The tool will:
|
|
27
|
+
|
|
28
|
+
1. Check for `meraki-standards` as a sibling directory
|
|
29
|
+
2. Clone it if missing (with your permission)
|
|
30
|
+
3. Ask a few questions about your setup preferences
|
|
31
|
+
4. Create the agent infrastructure:
|
|
32
|
+
- `AGENTS.md` (symlinked from meraki-standards)
|
|
33
|
+
- `PROJECT.md` (project-specific, you fill this in)
|
|
34
|
+
- `.amp/skills/` or `.claude/skills/` (symlinked)
|
|
35
|
+
- `agentic/prd/` structure (optional)
|
|
36
|
+
|
|
37
|
+
## Options
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
--copy Copy files instead of symlinking (Windows fallback)
|
|
41
|
+
--update Pull latest meraki-standards and verify symlinks
|
|
42
|
+
--help Show help message
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Updating
|
|
46
|
+
|
|
47
|
+
After initial setup, run `--update` to pull the latest standards:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
agent-init --update
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This will:
|
|
54
|
+
1. Pull latest changes to meraki-standards
|
|
55
|
+
2. Verify all symlinks are intact
|
|
56
|
+
3. Report any issues
|
|
57
|
+
|
|
58
|
+
## Windows
|
|
59
|
+
|
|
60
|
+
On Windows, symlinks require Developer Mode enabled. The tool will detect this and provide instructions, or you can use `--copy` to copy files instead.
|
|
61
|
+
|
|
62
|
+
## Project Structure After Init
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
your-project/
|
|
66
|
+
AGENTS.md # Symlink → ../meraki-standards/AGENTS.md
|
|
67
|
+
PROJECT.md # Project-specific (edit this!)
|
|
68
|
+
.amp/skills/ # Symlink → ../meraki-standards/skills/
|
|
69
|
+
agentic/
|
|
70
|
+
prd/
|
|
71
|
+
proposed/{name}/ # PRDs in development
|
|
72
|
+
working/ # PRDs being implemented
|
|
73
|
+
completed/ # Archived PRDs
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Requirements
|
|
77
|
+
|
|
78
|
+
- Node.js 18+
|
|
79
|
+
- Git (for cloning meraki-standards)
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meraki-digital/agent-init",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Bootstrap repositories with Meraki agent infrastructure",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-init": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"bootstrap",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"author": "Meraki Digital",
|
|
19
|
+
"license": "UNLICENSED",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/meraki-digital/agent-init.git"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, symlinkSync, copyFileSync, cpSync, writeFileSync, lstatSync, readlinkSync } from 'fs';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import * as readline from 'readline';
|
|
7
|
+
|
|
8
|
+
const STANDARDS_REPO = 'https://github.com/meraki-digital/meraki-standards.git';
|
|
9
|
+
const STANDARDS_DIR = '../meraki-standards';
|
|
10
|
+
|
|
11
|
+
// Colors
|
|
12
|
+
const colors = {
|
|
13
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
14
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
15
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
16
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Readline interface
|
|
20
|
+
function createPrompt() {
|
|
21
|
+
return readline.createInterface({
|
|
22
|
+
input: process.stdin,
|
|
23
|
+
output: process.stdout,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function ask(rl, question) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
rl.question(question, (answer) => {
|
|
30
|
+
resolve(answer.trim());
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if symlinks work
|
|
36
|
+
function canSymlink() {
|
|
37
|
+
const testTarget = join(process.env.TMPDIR || '/tmp', `agent-init-target-${process.pid}`);
|
|
38
|
+
const testLink = join(process.env.TMPDIR || '/tmp', `agent-init-link-${process.pid}`);
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
writeFileSync(testTarget, '');
|
|
42
|
+
symlinkSync(testTarget, testLink);
|
|
43
|
+
execSync(`rm -f "${testLink}" "${testTarget}"`, { stdio: 'ignore' });
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
try {
|
|
47
|
+
execSync(`rm -f "${testTarget}"`, { stdio: 'ignore' });
|
|
48
|
+
} catch {}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check if Windows
|
|
54
|
+
function isWindows() {
|
|
55
|
+
return process.platform === 'win32';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if path is a symlink pointing to expected target
|
|
59
|
+
function isValidSymlink(path, expectedTarget) {
|
|
60
|
+
try {
|
|
61
|
+
const stat = lstatSync(path);
|
|
62
|
+
if (!stat.isSymbolicLink()) return false;
|
|
63
|
+
const target = readlinkSync(path);
|
|
64
|
+
return target === expectedTarget;
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Update mode
|
|
71
|
+
async function runUpdate() {
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(colors.bold('agent-init --update'));
|
|
74
|
+
console.log('===================');
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
// Check meraki-standards exists
|
|
78
|
+
if (!existsSync(STANDARDS_DIR)) {
|
|
79
|
+
console.log(`${colors.red('✗')} meraki-standards not found at ${STANDARDS_DIR}/`);
|
|
80
|
+
console.log(' Run agent-init first to set up this repo.');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Pull latest
|
|
85
|
+
console.log('Updating meraki-standards...');
|
|
86
|
+
try {
|
|
87
|
+
const result = execSync('git pull', {
|
|
88
|
+
cwd: STANDARDS_DIR,
|
|
89
|
+
encoding: 'utf-8',
|
|
90
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
91
|
+
});
|
|
92
|
+
if (result.includes('Already up to date')) {
|
|
93
|
+
console.log(` ${colors.green('✓')} Already up to date`);
|
|
94
|
+
} else {
|
|
95
|
+
const commits = result.match(/(\d+) files? changed/);
|
|
96
|
+
console.log(` ${colors.green('✓')} Pulled latest${commits ? ` (${commits[0]})` : ''}`);
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.log(` ${colors.yellow('!')} Could not pull: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check symlinks
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log('Checking symlinks...');
|
|
105
|
+
|
|
106
|
+
const expectedLinks = [
|
|
107
|
+
{ path: 'AGENTS.md', target: `${STANDARDS_DIR}/AGENTS.md` },
|
|
108
|
+
{ path: 'instructions', target: `${STANDARDS_DIR}/instructions` },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
// Detect skills folder
|
|
112
|
+
if (existsSync('.amp/skills')) {
|
|
113
|
+
expectedLinks.push({ path: '.amp/skills', target: `${STANDARDS_DIR}/skills` });
|
|
114
|
+
} else if (existsSync('.claude/skills')) {
|
|
115
|
+
expectedLinks.push({ path: '.claude/skills', target: `${STANDARDS_DIR}/skills` });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let allGood = true;
|
|
119
|
+
|
|
120
|
+
for (const { path, target } of expectedLinks) {
|
|
121
|
+
if (!existsSync(path)) {
|
|
122
|
+
console.log(` ${colors.red('✗')} ${path} (missing)`);
|
|
123
|
+
allGood = false;
|
|
124
|
+
} else if (isValidSymlink(path, target)) {
|
|
125
|
+
console.log(` ${colors.green('✓')} ${path}`);
|
|
126
|
+
} else {
|
|
127
|
+
console.log(` ${colors.yellow('!')} ${path} (not a symlink or wrong target)`);
|
|
128
|
+
allGood = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check PROJECT.md exists
|
|
133
|
+
if (existsSync('PROJECT.md')) {
|
|
134
|
+
console.log(` · PROJECT.md (local file)`);
|
|
135
|
+
} else {
|
|
136
|
+
console.log(` ${colors.yellow('!')} PROJECT.md (missing - run agent-init to create)`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log('');
|
|
140
|
+
if (allGood) {
|
|
141
|
+
console.log(colors.green('All up to date.'));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(colors.yellow('Some issues found. Run agent-init to fix.'));
|
|
144
|
+
}
|
|
145
|
+
console.log('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Clone meraki-standards
|
|
149
|
+
function cloneStandards(targetDir) {
|
|
150
|
+
console.log('\nCloning meraki-standards...');
|
|
151
|
+
try {
|
|
152
|
+
execSync(`git clone ${STANDARDS_REPO} "${targetDir}"`, { stdio: 'inherit' });
|
|
153
|
+
console.log(` ${colors.green('✓')} Cloned to ${targetDir}/`);
|
|
154
|
+
return true;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.log(` ${colors.red('✗')} Failed to clone: ${err.message}`);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create symlink or copy
|
|
162
|
+
function linkOrCopy(src, dest, name, useCopy) {
|
|
163
|
+
if (existsSync(dest)) {
|
|
164
|
+
console.log(` · ${name} (exists, skipped)`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
if (useCopy) {
|
|
170
|
+
try {
|
|
171
|
+
// Try directory copy first
|
|
172
|
+
cpSync(src, dest, { recursive: true });
|
|
173
|
+
} catch {
|
|
174
|
+
// Fall back to file copy
|
|
175
|
+
copyFileSync(src, dest);
|
|
176
|
+
}
|
|
177
|
+
console.log(` ${colors.green('✓')} ${name} (copied)`);
|
|
178
|
+
} else {
|
|
179
|
+
symlinkSync(src, dest);
|
|
180
|
+
console.log(` ${colors.green('✓')} ${name} (symlinked)`);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.log(` ${colors.red('✗')} ${name} failed: ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Create PROJECT.md
|
|
188
|
+
function createProjectMd() {
|
|
189
|
+
if (existsSync('PROJECT.md')) {
|
|
190
|
+
console.log(' · PROJECT.md (exists, skipped)');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const content = `# PROJECT.md
|
|
195
|
+
|
|
196
|
+
<!--
|
|
197
|
+
Project-specific instructions for AI agents.
|
|
198
|
+
AGENTS.md (symlinked from meraki-standards) provides the generic foundation.
|
|
199
|
+
This file adds context unique to this repository.
|
|
200
|
+
|
|
201
|
+
Suggested sections:
|
|
202
|
+
- Overview: What does this project do?
|
|
203
|
+
- Tech Stack: Languages, frameworks, key dependencies
|
|
204
|
+
- Commands: Build, test, lint, deploy
|
|
205
|
+
- Structure: Key directories and their purpose
|
|
206
|
+
- Conventions: Project-specific patterns
|
|
207
|
+
-->
|
|
208
|
+
|
|
209
|
+
## Overview
|
|
210
|
+
|
|
211
|
+
## Tech Stack
|
|
212
|
+
|
|
213
|
+
## Commands
|
|
214
|
+
|
|
215
|
+
## Structure
|
|
216
|
+
|
|
217
|
+
## Conventions
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
writeFileSync('PROJECT.md', content);
|
|
221
|
+
console.log(` ${colors.green('✓')} PROJECT.md (created)`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Create directory if it doesn't exist
|
|
225
|
+
function createDir(dir) {
|
|
226
|
+
if (existsSync(dir)) {
|
|
227
|
+
console.log(` · ${dir}/ (exists, skipped)`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
mkdirSync(dir, { recursive: true });
|
|
231
|
+
console.log(` ${colors.green('✓')} ${dir}/`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function main() {
|
|
235
|
+
const args = process.argv.slice(2);
|
|
236
|
+
let useCopy = args.includes('--copy');
|
|
237
|
+
const showHelp = args.includes('--help') || args.includes('-h');
|
|
238
|
+
const runUpdateMode = args.includes('--update');
|
|
239
|
+
|
|
240
|
+
if (showHelp) {
|
|
241
|
+
console.log('Usage: agent-init [--copy] [--update] [--help]');
|
|
242
|
+
console.log('');
|
|
243
|
+
console.log('Bootstrap a repository with Meraki agent infrastructure.');
|
|
244
|
+
console.log('');
|
|
245
|
+
console.log('Options:');
|
|
246
|
+
console.log(' --copy Copy files instead of symlinking (Windows fallback)');
|
|
247
|
+
console.log(' --update Pull latest meraki-standards and check symlinks');
|
|
248
|
+
console.log(' --help Show this help message');
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (runUpdateMode) {
|
|
253
|
+
await runUpdate();
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log('');
|
|
258
|
+
console.log(colors.bold('agent-init - Meraki Standards Bootstrap'));
|
|
259
|
+
console.log('========================================');
|
|
260
|
+
console.log('');
|
|
261
|
+
|
|
262
|
+
// Check for meraki-standards
|
|
263
|
+
console.log('Checking for meraki-standards repo...');
|
|
264
|
+
|
|
265
|
+
if (!existsSync(STANDARDS_DIR)) {
|
|
266
|
+
console.log(` ${colors.yellow('!')} Not found at ${STANDARDS_DIR}/`);
|
|
267
|
+
|
|
268
|
+
const rl = createPrompt();
|
|
269
|
+
const answer = await ask(rl, '\nClone meraki-standards now? [Y/n]: ');
|
|
270
|
+
|
|
271
|
+
if (answer.toLowerCase() === 'n') {
|
|
272
|
+
console.log('\nmeraki-standards is required. Clone it manually:');
|
|
273
|
+
console.log(` cd ${dirname(process.cwd())}`);
|
|
274
|
+
console.log(` git clone ${STANDARDS_REPO}`);
|
|
275
|
+
console.log('');
|
|
276
|
+
rl.close();
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
rl.close();
|
|
281
|
+
|
|
282
|
+
if (!cloneStandards(STANDARDS_DIR)) {
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
console.log(` ${colors.green('✓')} Found at ${STANDARDS_DIR}/`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Windows symlink check
|
|
290
|
+
if (isWindows() && !useCopy) {
|
|
291
|
+
if (!canSymlink()) {
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log(colors.yellow('Windows detected. Symlinks not enabled.'));
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log('To enable symlinks (recommended):');
|
|
296
|
+
console.log(' 1. Enable Developer Mode: Settings > System > For Developers');
|
|
297
|
+
console.log(' 2. Run: git config --global core.symlinks true');
|
|
298
|
+
console.log(' 3. Re-run this script');
|
|
299
|
+
console.log('');
|
|
300
|
+
console.log('Or run with --copy to copy files instead of symlinking:');
|
|
301
|
+
console.log(' agent-init --copy');
|
|
302
|
+
console.log('');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const rl = createPrompt();
|
|
308
|
+
|
|
309
|
+
// Interview
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log('Skills folder');
|
|
312
|
+
console.log(' 1) .amp/skills (default)');
|
|
313
|
+
console.log(' 2) .claude/skills');
|
|
314
|
+
const skillsChoice = await ask(rl, 'Choice [1]: ');
|
|
315
|
+
const skillsFolder = skillsChoice === '2' ? '.claude/skills' : '.amp/skills';
|
|
316
|
+
|
|
317
|
+
console.log('');
|
|
318
|
+
const projectInput = await ask(rl, 'Project name for initial PRD folder [initial]: ');
|
|
319
|
+
const projectName = projectInput || 'initial';
|
|
320
|
+
|
|
321
|
+
console.log('');
|
|
322
|
+
const prdInput = await ask(rl, 'Create agentic/prd structure? [Y/n]: ');
|
|
323
|
+
const createPrd = prdInput.toLowerCase() !== 'n';
|
|
324
|
+
|
|
325
|
+
rl.close();
|
|
326
|
+
|
|
327
|
+
console.log('');
|
|
328
|
+
console.log('Creating structure...');
|
|
329
|
+
|
|
330
|
+
// AGENTS.md
|
|
331
|
+
linkOrCopy(`${STANDARDS_DIR}/AGENTS.md`, 'AGENTS.md', 'AGENTS.md', useCopy);
|
|
332
|
+
|
|
333
|
+
// PROJECT.md
|
|
334
|
+
createProjectMd();
|
|
335
|
+
|
|
336
|
+
// Skills folder
|
|
337
|
+
const skillsParent = dirname(skillsFolder);
|
|
338
|
+
if (skillsParent !== '.') {
|
|
339
|
+
mkdirSync(skillsParent, { recursive: true });
|
|
340
|
+
}
|
|
341
|
+
// Extra ../ because we're inside .amp/ or .claude/
|
|
342
|
+
linkOrCopy(`../${STANDARDS_DIR}/skills`, skillsFolder, `${skillsFolder}/`, useCopy);
|
|
343
|
+
|
|
344
|
+
// Instructions folder
|
|
345
|
+
linkOrCopy(`${STANDARDS_DIR}/instructions`, 'instructions', 'instructions/', useCopy);
|
|
346
|
+
|
|
347
|
+
// PRD structure
|
|
348
|
+
if (createPrd) {
|
|
349
|
+
createDir(`agentic/prd/proposed/${projectName}`);
|
|
350
|
+
createDir('agentic/prd/working');
|
|
351
|
+
createDir('agentic/prd/completed');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log('Done. Next steps:');
|
|
356
|
+
console.log(' - Edit PROJECT.md with project-specific details');
|
|
357
|
+
if (createPrd) {
|
|
358
|
+
console.log(` - Add seed.md to agentic/prd/proposed/${projectName}/`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (useCopy) {
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log(colors.yellow('Note: Files were copied, not symlinked. They may drift from meraki-standards.'));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log('');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
main().catch((err) => {
|
|
370
|
+
console.error(colors.red(`Error: ${err.message}`));
|
|
371
|
+
process.exit(1);
|
|
372
|
+
});
|