awesome-agv 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.
Files changed (2) hide show
  1. package/bin/awesome-agv.js +352 -0
  2. package/package.json +35 -0
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const https = require('https');
6
+ const http = require('http');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { createInterface } = require('readline');
10
+ const { execSync } = require('child_process');
11
+ const os = require('os');
12
+ const { createGunzip } = require('zlib');
13
+
14
+ // ── ANSI Colors (zero-dependency) ──────────────────────────────────────────────
15
+ const color = {
16
+ reset: '\x1b[0m',
17
+ bold: '\x1b[1m',
18
+ dim: '\x1b[2m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ red: '\x1b[31m',
22
+ cyan: '\x1b[36m',
23
+ magenta: '\x1b[35m',
24
+ };
25
+
26
+ const icons = {
27
+ check: `${color.green}✓${color.reset}`,
28
+ warn: `${color.yellow}⚠${color.reset}`,
29
+ error: `${color.red}✗${color.reset}`,
30
+ arrow: `${color.cyan}→${color.reset}`,
31
+ rocket: '🚀',
32
+ shield: '🛡️',
33
+ gear: '⚙️',
34
+ book: '📏',
35
+ tool: '🛠️',
36
+ cycle: '🔄',
37
+ };
38
+
39
+ // ── Constants ──────────────────────────────────────────────────────────────────
40
+ const REPO_OWNER = 'irahardianto';
41
+ const REPO_NAME = 'awesome-agv';
42
+ const BRANCH = 'main';
43
+ const TARBALL_URL = `https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/heads/${BRANCH}.tar.gz`;
44
+ const AGENT_DIR = '.agent';
45
+
46
+ // ── CLI Argument Parsing ───────────────────────────────────────────────────────
47
+ function parseArgs(argv) {
48
+ const args = argv.slice(2);
49
+ const options = {
50
+ force: false,
51
+ targetDir: process.cwd(),
52
+ help: false,
53
+ };
54
+
55
+ for (const arg of args) {
56
+ if (arg === '--force' || arg === '-f') {
57
+ options.force = true;
58
+ } else if (arg === '--help' || arg === '-h') {
59
+ options.help = true;
60
+ } else if (!arg.startsWith('-')) {
61
+ options.targetDir = path.resolve(arg);
62
+ }
63
+ }
64
+
65
+ return options;
66
+ }
67
+
68
+ // ── Help Text ──────────────────────────────────────────────────────────────────
69
+ function printHelp() {
70
+ console.log(`
71
+ ${color.bold}awesome-agv${color.reset} — Install the Awesome AGV AI Agent configuration suite
72
+
73
+ ${color.bold}USAGE${color.reset}
74
+ npx awesome-agv [target-dir] [options]
75
+
76
+ ${color.bold}ARGUMENTS${color.reset}
77
+ target-dir Directory to install into (default: current directory)
78
+
79
+ ${color.bold}OPTIONS${color.reset}
80
+ -f, --force Overwrite existing .agent directory without prompting
81
+ -h, --help Show this help message
82
+
83
+ ${color.bold}EXAMPLES${color.reset}
84
+ ${color.dim}# Install into current directory${color.reset}
85
+ npx awesome-agv
86
+
87
+ ${color.dim}# Install into a specific project${color.reset}
88
+ npx awesome-agv ./my-project
89
+
90
+ ${color.dim}# Overwrite existing installation${color.reset}
91
+ npx awesome-agv --force
92
+
93
+ ${color.bold}WHAT GETS INSTALLED${color.reset}
94
+ ${icons.book} 30 Rules — Security, architecture, testing, DevOps standards
95
+ ${icons.tool} 7 Skills — Debugging, design, code review, and more
96
+ ${icons.cycle} 10 Workflows — End-to-end development processes
97
+
98
+ ${color.dim}https://github.com/${REPO_OWNER}/${REPO_NAME}${color.reset}
99
+ `);
100
+ }
101
+
102
+ // ── User Prompt ────────────────────────────────────────────────────────────────
103
+ function promptUser(question) {
104
+ return new Promise((resolve) => {
105
+ const rl = createInterface({
106
+ input: process.stdin,
107
+ output: process.stdout,
108
+ });
109
+ rl.question(question, (answer) => {
110
+ rl.close();
111
+ resolve(answer.trim().toLowerCase());
112
+ });
113
+ });
114
+ }
115
+
116
+ // ── HTTP Download with Redirect Following ──────────────────────────────────────
117
+ function downloadToFile(url, destPath, maxRedirects = 5) {
118
+ return new Promise((resolve, reject) => {
119
+ if (maxRedirects <= 0) {
120
+ reject(new Error('Too many redirects'));
121
+ return;
122
+ }
123
+
124
+ const client = url.startsWith('https') ? https : http;
125
+ client
126
+ .get(url, (res) => {
127
+ // Follow redirects (GitHub returns 302)
128
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
129
+ res.resume(); // Consume response to free up memory
130
+ downloadToFile(res.headers.location, destPath, maxRedirects - 1)
131
+ .then(resolve)
132
+ .catch(reject);
133
+ return;
134
+ }
135
+
136
+ if (res.statusCode !== 200) {
137
+ res.resume();
138
+ reject(new Error(`Failed to download: HTTP ${res.statusCode}`));
139
+ return;
140
+ }
141
+
142
+ const fileStream = fs.createWriteStream(destPath);
143
+ res.pipe(fileStream);
144
+ fileStream.on('finish', () => {
145
+ fileStream.close(resolve);
146
+ });
147
+ fileStream.on('error', (err) => {
148
+ fs.unlinkSync(destPath);
149
+ reject(err);
150
+ });
151
+ })
152
+ .on('error', reject);
153
+ });
154
+ }
155
+
156
+ // ── Extract .agent directory from tarball ──────────────────────────────────────
157
+ function extractAgentDir(tarballPath, targetDir) {
158
+ // The tarball from GitHub has a root directory like: awesome-agv-main/
159
+ // We need to extract awesome-agv-main/.agent/ → targetDir/.agent/
160
+ const stripPrefix = `${REPO_NAME}-${BRANCH}/${AGENT_DIR}`;
161
+
162
+ const agentTargetDir = path.join(targetDir, AGENT_DIR);
163
+
164
+ // Ensure target exists
165
+ fs.mkdirSync(agentTargetDir, { recursive: true });
166
+
167
+ try {
168
+ // Use system tar (available on macOS and Linux)
169
+ execSync(
170
+ `tar -xzf "${tarballPath}" --strip-components=2 -C "${agentTargetDir}" "${stripPrefix}"`,
171
+ { stdio: 'pipe' }
172
+ );
173
+ } catch {
174
+ // Fallback: manual extraction using Node.js streams
175
+ extractManually(tarballPath, targetDir, stripPrefix);
176
+ }
177
+ }
178
+
179
+ // ── Manual tar extraction fallback (for Windows or missing tar) ────────────────
180
+ function extractManually(tarballPath, targetDir, stripPrefix) {
181
+ // Read the gzipped tarball
182
+ const gzipData = fs.readFileSync(tarballPath);
183
+
184
+ // Decompress using zlib
185
+ const { execFileSync } = require('child_process');
186
+ const tmpExtractDir = path.join(os.tmpdir(), `awesome-agv-extract-${Date.now()}`);
187
+ fs.mkdirSync(tmpExtractDir, { recursive: true });
188
+
189
+ try {
190
+ // Try using tar with different flags (Windows Git Bash compatibility)
191
+ execFileSync('tar', ['-xzf', tarballPath, '-C', tmpExtractDir], { stdio: 'pipe' });
192
+
193
+ // Find the extracted .agent directory
194
+ const extractedRoot = path.join(tmpExtractDir, `${REPO_NAME}-${BRANCH}`, AGENT_DIR);
195
+
196
+ if (fs.existsSync(extractedRoot)) {
197
+ copyDirRecursive(extractedRoot, path.join(targetDir, AGENT_DIR));
198
+ } else {
199
+ throw new Error(`Could not find ${AGENT_DIR} in downloaded archive`);
200
+ }
201
+ } finally {
202
+ // Clean up temp extraction dir
203
+ fs.rmSync(tmpExtractDir, { recursive: true, force: true });
204
+ }
205
+ }
206
+
207
+ // ── Recursive directory copy ───────────────────────────────────────────────────
208
+ function copyDirRecursive(src, dest) {
209
+ fs.mkdirSync(dest, { recursive: true });
210
+
211
+ const entries = fs.readdirSync(src, { withFileTypes: true });
212
+ for (const entry of entries) {
213
+ const srcPath = path.join(src, entry.name);
214
+ const destPath = path.join(dest, entry.name);
215
+
216
+ if (entry.isDirectory()) {
217
+ copyDirRecursive(srcPath, destPath);
218
+ } else {
219
+ fs.copyFileSync(srcPath, destPath);
220
+ }
221
+ }
222
+ }
223
+
224
+ // ── Count files recursively ────────────────────────────────────────────────────
225
+ function countFiles(dirPath) {
226
+ let count = 0;
227
+ if (!fs.existsSync(dirPath)) return count;
228
+
229
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
230
+ for (const entry of entries) {
231
+ const fullPath = path.join(dirPath, entry.name);
232
+ if (entry.isDirectory()) {
233
+ count += countFiles(fullPath);
234
+ } else {
235
+ count++;
236
+ }
237
+ }
238
+ return count;
239
+ }
240
+
241
+ // ── Print Banner ───────────────────────────────────────────────────────────────
242
+ function printBanner() {
243
+ // Load version from package.json
244
+ const { version } = require('../package.json');
245
+ console.log(`
246
+ ${color.bold}${color.cyan} ╔══════════════════════════════════════════╗
247
+ ║ ${color.magenta}awesome-agv${color.cyan} ${color.dim}v${version}${color.cyan}${color.bold} ║
248
+ ║ ${color.reset}${color.dim}Awesome AGV AI Agent Configuration${color.cyan}${color.bold} ║
249
+ ╚══════════════════════════════════════════╝${color.reset}
250
+ `);
251
+ }
252
+
253
+ // ── Print Success Summary ──────────────────────────────────────────────────────
254
+ function printSuccess(targetDir) {
255
+ const agentDir = path.join(targetDir, AGENT_DIR);
256
+ const rulesDir = path.join(agentDir, 'rules');
257
+ const skillsDir = path.join(agentDir, 'skills');
258
+ const workflowsDir = path.join(agentDir, 'workflows');
259
+
260
+ const rulesCount = fs.existsSync(rulesDir)
261
+ ? fs.readdirSync(rulesDir).filter((f) => f.endsWith('.md')).length
262
+ : 0;
263
+ const skillsCount = fs.existsSync(skillsDir)
264
+ ? fs.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).length
265
+ : 0;
266
+ const workflowsCount = fs.existsSync(workflowsDir)
267
+ ? fs.readdirSync(workflowsDir).filter((f) => f.endsWith('.md')).length
268
+ : 0;
269
+ const totalFiles = countFiles(agentDir);
270
+
271
+ console.log(`
272
+ ${color.green}${color.bold} Installation complete! ${icons.rocket}${color.reset}
273
+
274
+ ${icons.check} ${color.bold}${totalFiles} files${color.reset} installed to ${color.cyan}${path.relative(process.cwd(), agentDir) || AGENT_DIR}/${color.reset}
275
+
276
+ ${icons.book} ${color.bold}${rulesCount}${color.reset} Rules ${color.dim}Security, architecture, testing standards${color.reset}
277
+ ${icons.tool} ${color.bold}${skillsCount}${color.reset} Skills ${color.dim}Debugging, design, code review${color.reset}
278
+ ${icons.cycle} ${color.bold}${workflowsCount}${color.reset} Workflows ${color.dim}End-to-end dev processes${color.reset}
279
+
280
+ ${icons.arrow} ${color.dim}Your AI agent will automatically pick up the${color.reset}
281
+ ${color.dim}${AGENT_DIR}/ directory. No additional configuration needed.${color.reset}
282
+
283
+ ${icons.shield} ${color.dim}Learn more: ${color.cyan}https://github.com/${REPO_OWNER}/${REPO_NAME}${color.reset}
284
+ `);
285
+ }
286
+
287
+ // ── Main ───────────────────────────────────────────────────────────────────────
288
+ async function main() {
289
+ const options = parseArgs(process.argv);
290
+
291
+ if (options.help) {
292
+ printHelp();
293
+ process.exit(0);
294
+ }
295
+
296
+ printBanner();
297
+
298
+ const targetAgentDir = path.join(options.targetDir, AGENT_DIR);
299
+
300
+ // Check if .agent already exists
301
+ if (fs.existsSync(targetAgentDir)) {
302
+ if (!options.force) {
303
+ console.log(
304
+ ` ${icons.warn} ${color.yellow}An existing ${AGENT_DIR}/ directory was found at:${color.reset}`
305
+ );
306
+ console.log(` ${color.dim}${targetAgentDir}${color.reset}\n`);
307
+
308
+ const answer = await promptUser(
309
+ ` ${color.bold}Overwrite? ${color.reset}${color.dim}(y/N)${color.reset} `
310
+ );
311
+
312
+ if (answer !== 'y' && answer !== 'yes') {
313
+ console.log(`\n ${icons.arrow} ${color.dim}Installation cancelled.${color.reset}\n`);
314
+ process.exit(0);
315
+ }
316
+ }
317
+
318
+ // Remove existing .agent directory
319
+ console.log(` ${icons.arrow} Removing existing ${AGENT_DIR}/ directory...`);
320
+ fs.rmSync(targetAgentDir, { recursive: true, force: true });
321
+ }
322
+
323
+ // Download tarball to a temp file
324
+ const tmpDir = os.tmpdir();
325
+ const tarballPath = path.join(tmpDir, `awesome-agv-${Date.now()}.tar.gz`);
326
+
327
+ try {
328
+ console.log(` ${icons.arrow} Fetching latest configuration from GitHub...`);
329
+ await downloadToFile(TARBALL_URL, tarballPath);
330
+
331
+ console.log(` ${icons.arrow} Extracting ${AGENT_DIR}/ directory...`);
332
+ extractAgentDir(tarballPath, options.targetDir);
333
+
334
+ printSuccess(options.targetDir);
335
+ } catch (err) {
336
+ console.error(`\n ${icons.error} ${color.red}Installation failed:${color.reset} ${err.message}\n`);
337
+
338
+ // Clean up partial installation
339
+ if (fs.existsSync(targetAgentDir)) {
340
+ fs.rmSync(targetAgentDir, { recursive: true, force: true });
341
+ }
342
+
343
+ process.exit(1);
344
+ } finally {
345
+ // Clean up tarball
346
+ if (fs.existsSync(tarballPath)) {
347
+ fs.unlinkSync(tarballPath);
348
+ }
349
+ }
350
+ }
351
+
352
+ main();
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "awesome-agv",
3
+ "version": "1.0.0",
4
+ "description": "1-click installer for Awesome AGV — a rugged, high-quality configuration suite for AI Agents.",
5
+ "bin": {
6
+ "awesome-agv": "./bin/awesome-agv.js"
7
+ },
8
+ "files": [
9
+ "bin/"
10
+ ],
11
+ "keywords": [
12
+ "ai",
13
+ "agent",
14
+ "coding-assistant",
15
+ "configuration",
16
+ "rules",
17
+ "skills",
18
+ "workflows",
19
+ "awesome-agv",
20
+ "cli"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/irahardianto/awesome-agv.git"
25
+ },
26
+ "homepage": "https://github.com/irahardianto/awesome-agv",
27
+ "bugs": {
28
+ "url": "https://github.com/irahardianto/awesome-agv/issues"
29
+ },
30
+ "license": "MIT",
31
+ "author": "irahardianto",
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ }
35
+ }