@codebehind/agent-workflow 1.0.0 → 1.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.
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from 'fs';
2
+ import {
3
+ existsSync, mkdirSync, copyFileSync, readdirSync, statSync,
4
+ readFileSync,
5
+ } from 'fs';
3
6
  import { join, dirname, relative } from 'path';
4
7
  import { fileURLToPath } from 'url';
8
+ import { execSync } from 'child_process';
9
+ import readline from 'readline';
5
10
 
6
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
12
  const TEMPLATES_DIR = join(__dirname, '..', 'templates');
8
- const VERSION = '1.0.0';
13
+ const VERSION = '1.1.0';
9
14
 
10
15
  const args = process.argv.slice(2);
11
16
  const command = args[0];
@@ -16,44 +21,59 @@ function printUsage() {
16
21
  @codebehind/agent-workflow v${VERSION}
17
22
 
18
23
  Usage:
19
- npx @codebehind/agent-workflow init [--force]
24
+ npx @codebehind/agent-workflow <command> [options]
20
25
 
21
26
  Commands:
22
27
  init Copy the agent-workflow framework files into the current directory.
23
- Skips files that already exist (use --force to overwrite).
28
+ Skips files that already exist (use --force to overwrite all).
29
+ upgrade Update all framework files to the latest version.
30
+ Prompts before overwriting any file that differs locally.
24
31
 
25
32
  Options:
26
- --force Overwrite existing files.
33
+ --force (init only) Overwrite existing files without prompting.
27
34
  --version Print version and exit.
28
35
  `);
29
36
  }
30
37
 
31
- function walkDir(dir, baseDir, callback) {
38
+ const SKIP_FILES = new Set(['.DS_Store']);
39
+
40
+ // Async walk — processes files sequentially so prompts work correctly
41
+ async function walkDir(dir, baseDir, callback) {
32
42
  for (const entry of readdirSync(dir)) {
43
+ if (SKIP_FILES.has(entry)) continue;
33
44
  const fullPath = join(dir, entry);
34
45
  const relPath = relative(baseDir, fullPath);
35
46
  if (statSync(fullPath).isDirectory()) {
36
- walkDir(fullPath, baseDir, callback);
47
+ await walkDir(fullPath, baseDir, callback);
37
48
  } else {
38
- callback(fullPath, relPath);
49
+ await callback(fullPath, relPath);
39
50
  }
40
51
  }
41
52
  }
42
53
 
43
- function init(targetDir) {
54
+ function getDiff(localPath, templatePath) {
55
+ try {
56
+ // diff exits with code 1 when files differ — that's expected, not an error
57
+ return execSync(`diff -u "${localPath}" "${templatePath}"`, { stdio: ['pipe', 'pipe', 'pipe'] }).toString();
58
+ } catch (e) {
59
+ return e.stdout ? e.stdout.toString() : '';
60
+ }
61
+ }
62
+
63
+ async function init(targetDir) {
44
64
  const copied = [];
45
65
  const skipped = [];
46
66
 
47
- walkDir(TEMPLATES_DIR, TEMPLATES_DIR, (srcPath, relPath) => {
67
+ await walkDir(TEMPLATES_DIR, TEMPLATES_DIR, async (srcPath, relPath) => {
48
68
  const destPath = join(targetDir, relPath);
49
- const destDir = dirname(destPath);
69
+ const destDirPath = dirname(destPath);
50
70
 
51
71
  if (!force && existsSync(destPath)) {
52
72
  skipped.push(relPath);
53
73
  return;
54
74
  }
55
75
 
56
- mkdirSync(destDir, { recursive: true });
76
+ mkdirSync(destDirPath, { recursive: true });
57
77
  copyFileSync(srcPath, destPath);
58
78
  copied.push(relPath);
59
79
  });
@@ -83,12 +103,66 @@ Next steps:
83
103
  `);
84
104
  }
85
105
 
106
+ async function upgrade(targetDir) {
107
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
108
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
109
+
110
+ let overridden = 0;
111
+ let skipped = 0;
112
+ let added = 0;
113
+
114
+ console.log(`\nUpgrading to @codebehind/agent-workflow v${VERSION}...\n`);
115
+
116
+ await walkDir(TEMPLATES_DIR, TEMPLATES_DIR, async (srcPath, relPath) => {
117
+ const destPath = join(targetDir, relPath);
118
+
119
+ if (!existsSync(destPath)) {
120
+ mkdirSync(dirname(destPath), { recursive: true });
121
+ copyFileSync(srcPath, destPath);
122
+ console.log(` + ${relPath} (new)`);
123
+ added++;
124
+ return;
125
+ }
126
+
127
+ const localContent = readFileSync(destPath);
128
+ const templateContent = readFileSync(srcPath);
129
+
130
+ if (localContent.equals(templateContent)) {
131
+ console.log(` ~ ${relPath} (up to date)`);
132
+ return;
133
+ }
134
+
135
+ // Files differ — show diff and prompt
136
+ const diff = getDiff(destPath, srcPath);
137
+ console.log(`\n--- Conflict: ${relPath} ---`);
138
+ console.log(diff);
139
+
140
+ const answer = await ask(`Override local file? [o=override, s=skip] (default: s): `);
141
+
142
+ if (answer.trim().toLowerCase() === 'o') {
143
+ copyFileSync(srcPath, destPath);
144
+ console.log(` ✓ ${relPath} (overridden)\n`);
145
+ overridden++;
146
+ } else {
147
+ console.log(` - ${relPath} (skipped)\n`);
148
+ skipped++;
149
+ }
150
+ });
151
+
152
+ rl.close();
153
+ console.log(`Done. ${added} new, ${overridden} overridden, ${skipped} skipped.`);
154
+ }
155
+
86
156
  // Entry point
87
157
  if (command === '--version' || command === '-v') {
88
158
  console.log(VERSION);
89
159
  process.exit(0);
90
- } else if (!command || command === 'init') {
91
- init(process.cwd());
160
+ } else if (command === 'init') {
161
+ await init(process.cwd());
162
+ } else if (command === 'upgrade') {
163
+ await upgrade(process.cwd());
164
+ } else if (!command) {
165
+ printUsage();
92
166
  } else {
93
167
  console.error(`Unknown command: ${command}`);
94
168
  printUsage();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebehind/agent-workflow",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Scaffold the agent-workflow spec-driven delivery framework into any repo",
5
5
  "type": "module",
6
6
  "bin": {