jarvis-agent-factory 2.0.1 → 2.0.2

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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # Jarvis Agent Factory · 贾维斯智能体工厂
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
4
- [![Version](https://img.shields.io/badge/version-v2.0.0-green)](https://gitee.com/wujl1124/JarvisAgentFactory/releases)
4
+ [![Version](https://img.shields.io/badge/version-v2.0.1-green)](https://gitee.com/wujl1124/JarvisAgentFactory/releases)
5
5
  <br>**简体中文** | [English](./README_EN.md)
6
6
 
7
7
  一套跨平台的多智能体(Multi-Agent)AI 编程助手配置集,定义了一条**从想法到交付的完整软件开发流水线**。支持 Claude Code、OpenCode、Codex 三平台,共享同一套工作流规范与技能体系。
8
8
 
9
- > **v2.0.0** — Claude Code 47 agents + 15 commands / OpenCode 55 agents(纯智能体切换) / Codex 45 agents + 42 skills(Skill 触发)
9
+ > **v2.0.1** — Claude Code 47 agents + 15 commands / OpenCode 55 agents(纯智能体切换) / Codex 45 agents + 42 skills(Skill 触发)
10
10
 
11
11
  ## 核心概念
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jarvis-agent-factory",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Jarvis Agent Factory CLI — 跨平台多智能体 AI 编程助手配置安装器 | Multi-agent AI coding assistant config installer for Claude Code / OpenCode / Codex",
5
5
  "keywords": [
6
6
  "jarvis",
package/src/install.js CHANGED
@@ -1,7 +1,10 @@
1
- import { resolve, join } from 'node:path';
1
+ import { resolve, join, basename } from 'node:path';
2
2
  import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'node:fs';
3
3
  import { createInterface } from 'node:readline';
4
4
 
5
+ // Only these subdirectories are installed — everything else in the platform dir is left untouched
6
+ const INSTALL_BUCKETS = ['agents', 'commands', 'skills'];
7
+
5
8
  const SKIP_FILES = new Set([
6
9
  'settings.json',
7
10
  'settings.local.json',
@@ -10,39 +13,66 @@ const SKIP_FILES = new Set([
10
13
  ]);
11
14
 
12
15
  /**
13
- * Copy a platform config directory from the package to the target.
14
- * Strategy: new files added, existing files overwritten (after confirmation).
15
- * Skips sensitive files.
16
+ * Install platform config: only agents/ commands/ skills/ subdirectories.
17
+ * File-level merge: new files added, same-name overwritten, extra files in target preserved.
16
18
  */
17
19
  export async function install({ platform, target, pkgRoot, platforms, force }) {
18
20
  const info = platforms[platform];
19
- const srcDir = resolve(pkgRoot, info.dir);
20
- const destDir = resolve(target, info.dir);
21
+ const srcRoot = resolve(pkgRoot, info.dir);
22
+ const destRoot = resolve(target, info.dir);
21
23
 
22
- if (!existsSync(srcDir)) {
23
- console.error(` ⚠ Source not found: ${srcDir}`);
24
+ if (!existsSync(srcRoot)) {
25
+ console.error(` ⚠ Source not found: ${srcRoot}`);
24
26
  return;
25
27
  }
26
28
 
27
- // Check if target already has this platform
28
- const destExists = existsSync(destDir);
29
+ const destExists = existsSync(destRoot);
30
+
31
+ // Only confirm if target already has this platform
29
32
  if (destExists && !force) {
30
- const ok = await confirm(` 📁 ${info.dir}/ already exists. Overwrite? [y/N] `);
33
+ const ok = await confirm(` 📁 ${info.dir}/ exists, merge agents/skills/commands? [y/N] `);
31
34
  if (!ok) {
32
- console.log(` ⏭ Skipped ${platform} (directory exists)`);
35
+ console.log(` ⏭ Skipped ${platform}`);
33
36
  return;
34
37
  }
35
38
  }
36
39
 
37
- const stats = copyDir(srcDir, destDir);
40
+ // Ensure platform root exists
41
+ if (!destExists) {
42
+ mkdirSync(destRoot, { recursive: true });
43
+ }
44
+
45
+ let totalFiles = 0;
46
+ let totalDirs = 0;
47
+
48
+ for (const bucket of INSTALL_BUCKETS) {
49
+ const srcDir = join(srcRoot, bucket);
50
+ const destDir = join(destRoot, bucket);
51
+
52
+ if (!existsSync(srcDir)) continue;
53
+
54
+ const stats = mergeDir(srcDir, destDir);
55
+ totalFiles += stats.files;
56
+ totalDirs += stats.dirs;
57
+
58
+ const existed = existsSync(destDir) && stats.files > 0;
59
+ const tag = existed ? '~' : '+';
60
+ console.log(` ${tag} ${info.dir}/${bucket.padEnd(8)} → ${stats.files} files`);
61
+ }
62
+
38
63
  const status = destExists ? 'updated' : 'installed';
39
- console.log(` ✅ ${platform.padEnd(10)} ${status} → ${destDir} (${stats.files} files, ${stats.dirs} dirs${stats.skipped > 0 ? `, ${stats.skipped} skipped` : ''})`);
64
+ console.log(` ✅ ${platform.padEnd(10)} ${status} (${totalFiles} files total)`);
40
65
  }
41
66
 
42
- function copyDir(src, dest) {
67
+ /**
68
+ * Merge files from src into dest.
69
+ * - New files: added
70
+ * - Same-name files: overwritten
71
+ * - Extra files in dest: PRESERVED
72
+ */
73
+ function mergeDir(src, dest) {
43
74
  let files = 0;
44
75
  let dirs = 0;
45
- let skipped = 0;
46
76
 
47
77
  if (!existsSync(dest)) {
48
78
  mkdirSync(dest, { recursive: true });
@@ -50,29 +80,22 @@ function copyDir(src, dest) {
50
80
 
51
81
  for (const entry of readdirSync(src)) {
52
82
  if (SKIP_FILES.has(entry)) continue;
53
- // Skip hidden files except platform dirs and .mcp.json
54
- if (entry.startsWith('.')) {
55
- if (entry !== '.mcp.json' &&
56
- !entry.startsWith('.claude') &&
57
- !entry.startsWith('.opencode') &&
58
- !entry.startsWith('.codex')) continue;
59
- }
83
+ if (entry.startsWith('.') || entry === 'node_modules') continue;
60
84
 
61
85
  const srcPath = join(src, entry);
62
86
  const destPath = join(dest, entry);
63
87
 
64
88
  if (statSync(srcPath).isDirectory()) {
65
- const d = copyDir(srcPath, destPath);
89
+ const d = mergeDir(srcPath, destPath);
66
90
  files += d.files;
67
91
  dirs += d.dirs + 1;
68
- skipped += d.skipped;
69
92
  } else {
70
93
  copyFileSync(srcPath, destPath);
71
94
  files++;
72
95
  }
73
96
  }
74
97
 
75
- return { files, dirs, skipped };
98
+ return { files, dirs };
76
99
  }
77
100
 
78
101
  async function confirm(question) {