aigroup-workflow 1.1.0 → 1.1.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/bin/aigroup.mjs CHANGED
@@ -67,11 +67,15 @@ async function main() {
67
67
  }
68
68
  case 'help':
69
69
  case '--help':
70
- case '-h':
71
- case '': {
70
+ case '-h': {
72
71
  printHelp()
73
72
  break
74
73
  }
74
+ case '': {
75
+ const { showMenu } = await import('../cli/commands/menu.mjs')
76
+ await showMenu(ctx)
77
+ break
78
+ }
75
79
  default: {
76
80
  console.error(`\n 未知命令: ${command}\n`)
77
81
  printHelp()
@@ -86,7 +90,7 @@ function printHelp() {
86
90
  ║ aiGroup — AI 团队协作框架 CLI ║
87
91
  ╚══════════════════════════════════════════╝
88
92
 
89
- 用法: aigroup <命令> [选项]
93
+ 用法: aig <命令> [选项] (也可用 aigroup)
90
94
 
91
95
  命令:
92
96
  init, i 完整初始化(交互式选择角色、技能、配置)
@@ -97,13 +101,12 @@ function printHelp() {
97
101
 
98
102
  选项:
99
103
  --yes, -y 跳过确认,全部使用默认值
100
- --lang <语言> 设置语言(zh-CN / en)
101
104
 
102
105
  示例:
103
- npx aigroup-workflow init # 交互式初始化
104
- npx aigroup-workflow init --yes # 使用默认配置初始化
105
- npx aigroup-workflow update # 增量更新
106
- npx aigroup-workflow check # 健康检查
106
+ npx aig init # 交互式初始化
107
+ npx aig init --yes # 使用默认配置初始化
108
+ npx aig update # 增量更新
109
+ npx aig check # 健康检查
107
110
  `)
108
111
  }
109
112
 
@@ -43,9 +43,9 @@ export async function init(ctx) {
43
43
  log.info('使用默认配置:全部角色')
44
44
  } else {
45
45
  selectedAgents = await multiSelect('选择要安装的团队成员', [
46
- { name: AGENT_ASSETS.jarvis.label, value: 'jarvis', checked: true },
47
- { name: AGENT_ASSETS.ella.label, value: 'ella', checked: true },
48
- { name: AGENT_ASSETS.kyle.label, value: 'kyle', checked: true },
46
+ { name: '贾维斯 (Jarvis)', value: 'jarvis', description: '全栈开发工程师 · 45 Skills', checked: true },
47
+ { name: '艾拉 (Ella)', value: 'ella', description: 'UI/UX 设计师 · 10 Skills', checked: true },
48
+ { name: '凯尔 (Kyle)', value: 'kyle', description: '质量保证工程师 · 7 Skills', checked: true },
49
49
  ])
50
50
 
51
51
  if (selectedAgents.length === 0) {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * menu 命令 — 交互式主菜单(类似 zcf 风格)
3
+ */
4
+
5
+ import { select } from '../utils/prompts.mjs'
6
+ import * as log from '../utils/logger.mjs'
7
+ import { isInitialized, readConfig } from '../utils/scaffold.mjs'
8
+
9
+ export async function showMenu(ctx) {
10
+ log.banner()
11
+
12
+ // 检测项目状态
13
+ const initialized = isInitialized(ctx.PROJECT_ROOT)
14
+ if (initialized) {
15
+ const config = readConfig(ctx.PROJECT_ROOT)
16
+ if (config) {
17
+ log.dim(`已初始化 · 角色: ${config.agents?.join(', ') || '未知'} · 版本: ${config.version || '?'}`)
18
+ }
19
+ } else {
20
+ log.dim('当前项目尚未初始化 aiGroup 框架')
21
+ }
22
+
23
+ // 主菜单循环
24
+ while (true) {
25
+ const action = await select('选择操作', [
26
+ { name: '初始化项目', value: 'init', description: '交互式选择角色和技能,安装框架文件' },
27
+ { name: '增量更新', value: 'update', description: '更新技能和传感器,保留自定义配置' },
28
+ { name: '健康检查', value: 'check', description: '运行 Harness 传感器全量检查' },
29
+ { name: '工作流状态', value: 'status', description: '查看当前工作流管道状态' },
30
+ { name: '退出', value: 'quit', description: '' },
31
+ ])
32
+
33
+ if (action === 'quit') {
34
+ console.log('')
35
+ break
36
+ }
37
+
38
+ console.log('')
39
+
40
+ switch (action) {
41
+ case 'init': {
42
+ const { init } = await import('./init.mjs')
43
+ await init(ctx)
44
+ break
45
+ }
46
+ case 'update': {
47
+ const { update } = await import('./update.mjs')
48
+ await update(ctx)
49
+ break
50
+ }
51
+ case 'check': {
52
+ const { check } = await import('./check.mjs')
53
+ await check(ctx)
54
+ break
55
+ }
56
+ case 'status': {
57
+ const { status } = await import('./status.mjs')
58
+ await status(ctx)
59
+ break
60
+ }
61
+ }
62
+
63
+ console.log('')
64
+ }
65
+ }
@@ -1,9 +1,30 @@
1
1
  /**
2
- * 交互式提示工具 — 零依赖,基于 Node.js readline
2
+ * 交互式提示工具 — 零依赖,inquirer 风格
3
+ *
4
+ * 样式参考 zcf (https://github.com/UfoMiao/zcf)
5
+ * - 方向键移动光标
6
+ * - 空格切换选中(多选)/ 回车选择(单选)
7
+ * - 彩色描述文字
3
8
  */
4
9
 
5
10
  import { createInterface } from 'node:readline'
6
11
 
12
+ // ── ANSI 颜色 ──
13
+
14
+ const C = {
15
+ reset: '\x1b[0m',
16
+ bold: '\x1b[1m',
17
+ dim: '\x1b[2m',
18
+ cyan: '\x1b[36m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ gray: '\x1b[90m',
22
+ blue: '\x1b[34m',
23
+ white: '\x1b[37m',
24
+ hideCursor: '\x1b[?25l',
25
+ showCursor: '\x1b[?25h',
26
+ }
27
+
7
28
  function createRL() {
8
29
  return createInterface({
9
30
  input: process.stdin,
@@ -18,7 +39,7 @@ export async function confirm(message, defaultValue = true) {
18
39
  const rl = createRL()
19
40
  const hint = defaultValue ? 'Y/n' : 'y/N'
20
41
  return new Promise(resolve => {
21
- rl.question(` ? ${message} (${hint}) `, answer => {
42
+ rl.question(` ${C.cyan}?${C.reset} ${C.bold}${message}${C.reset} ${C.dim}(${hint})${C.reset} `, answer => {
22
43
  rl.close()
23
44
  if (!answer.trim()) return resolve(defaultValue)
24
45
  resolve(answer.trim().toLowerCase().startsWith('y'))
@@ -31,9 +52,9 @@ export async function confirm(message, defaultValue = true) {
31
52
  */
32
53
  export async function input(message, defaultValue = '') {
33
54
  const rl = createRL()
34
- const hint = defaultValue ? ` (${defaultValue})` : ''
55
+ const hint = defaultValue ? ` ${C.dim}(${defaultValue})${C.reset}` : ''
35
56
  return new Promise(resolve => {
36
- rl.question(` ? ${message}${hint}: `, answer => {
57
+ rl.question(` ${C.cyan}?${C.reset} ${C.bold}${message}${C.reset}${hint} `, answer => {
37
58
  rl.close()
38
59
  resolve(answer.trim() || defaultValue)
39
60
  })
@@ -41,9 +62,10 @@ export async function input(message, defaultValue = '') {
41
62
  }
42
63
 
43
64
  /**
44
- * 多选菜单 — 方向键移动,空格切换选中,回车确认
65
+ * 多选菜单 — inquirer checkbox 风格
66
+ *
45
67
  * @param {string} message
46
- * @param {{ name: string, value: string, checked?: boolean }[]} choices
68
+ * @param {{ name: string, value: string, description?: string, checked?: boolean }[]} choices
47
69
  * @returns {Promise<string[]>}
48
70
  */
49
71
  export async function multiSelect(message, choices) {
@@ -52,26 +74,35 @@ export async function multiSelect(message, choices) {
52
74
  )
53
75
  let cursor = 0
54
76
 
55
- function render() {
56
- // 移动光标到列表起始位置并清除下方内容
57
- process.stdout.write(`\x1B[${choices.length + 1}A`)
58
- process.stdout.write('\x1B[0J')
59
- choices.forEach((c, i) => {
60
- const pointer = i === cursor ? '❯' : ' '
61
- const mark = selected.has(c.value) ? '◉' : ''
62
- console.log(` ${pointer} ${mark} ${c.name}`)
63
- })
64
- process.stdout.write(' ↑↓ 移动 空格 切换 回车 确认')
77
+ function renderLine(c, i) {
78
+ const pointer = i === cursor ? `${C.cyan}❯${C.reset}` : ' '
79
+ const check = selected.has(c.value)
80
+ ? `${C.green}◉${C.reset}`
81
+ : `${C.dim}◯${C.reset}`
82
+ const name = i === cursor ? `${C.white}${C.bold}${c.name}${C.reset}` : c.name
83
+ const desc = c.description ? ` ${C.gray}— ${c.description}${C.reset}` : ''
84
+ return ` ${pointer} ${check} ${name}${desc}`
85
+ }
86
+
87
+ function renderHint() {
88
+ return ` ${C.dim}↑↓ 移动${C.reset} ${C.dim}空格 选择${C.reset} ${C.dim}a 全选/取消${C.reset} ${C.dim}回车 确认${C.reset}`
89
+ }
90
+
91
+ function render(isFirst) {
92
+ if (!isFirst) {
93
+ // 回到列表顶部重绘
94
+ process.stdout.write(`\x1b[${choices.length + 1}A\x1b[0J`)
95
+ }
96
+ for (const [i, c] of choices.entries()) {
97
+ console.log(renderLine(c, i))
98
+ }
99
+ process.stdout.write(renderHint())
65
100
  }
66
101
 
67
102
  // 首次绘制
68
- console.log(`\n ? ${message}`)
69
- choices.forEach((c, i) => {
70
- const pointer = i === cursor ? '❯' : ' '
71
- const mark = selected.has(c.value) ? '◉' : '◯'
72
- console.log(` ${pointer} ${mark} ${c.name}`)
73
- })
74
- process.stdout.write(' ↑↓ 移动 空格 切换 回车 确认')
103
+ console.log(`\n ${C.cyan}?${C.reset} ${C.bold}${message}${C.reset} ${C.dim}(空格选择, 回车确认)${C.reset}`)
104
+ process.stdout.write(C.hideCursor)
105
+ render(true)
75
106
 
76
107
  return new Promise(resolve => {
77
108
  const { stdin } = process
@@ -84,29 +115,43 @@ export async function multiSelect(message, choices) {
84
115
  // Ctrl+C
85
116
  if (key === '\x03') {
86
117
  cleanup()
118
+ process.stdout.write(C.showCursor)
87
119
  process.exit(0)
88
120
  }
89
121
  // 回车 — 确认
90
122
  if (key === '\r' || key === '\n') {
91
123
  cleanup()
92
- // 清除提示行并显示最终选择
93
- process.stdout.write('\r\x1B[K\n')
94
- return resolve([...selected])
124
+ process.stdout.write(`\r\x1b[K\n`)
125
+ process.stdout.write(C.showCursor)
126
+ // 打印选择结果
127
+ const result = [...selected]
128
+ const names = choices.filter(c => result.includes(c.value)).map(c => c.name)
129
+ if (names.length) {
130
+ console.log(` ${C.green}✔${C.reset} 已选择: ${C.cyan}${names.join(', ')}${C.reset}`)
131
+ }
132
+ return resolve(result)
95
133
  }
96
134
  // 空格 — 切换选中
97
135
  if (key === ' ') {
98
136
  const val = choices[cursor].value
99
137
  if (selected.has(val)) selected.delete(val)
100
138
  else selected.add(val)
101
- render()
139
+ render(false)
102
140
  return
103
141
  }
104
- // 方向键(上: \x1B[A, 下: \x1B[B)
105
- if (key === '\x1B[A' || key === '\x1B[B') {
106
- if (key === '\x1B[A') cursor = (cursor - 1 + choices.length) % choices.length
107
- else cursor = (cursor + 1) % choices.length
108
- render()
142
+ // a 全选/全不选
143
+ if (key === 'a' || key === 'A') {
144
+ if (selected.size === choices.length) {
145
+ selected.clear()
146
+ } else {
147
+ for (const c of choices) selected.add(c.value)
148
+ }
149
+ render(false)
150
+ return
109
151
  }
152
+ // 方向键
153
+ if (key === '\x1b[A') { cursor = (cursor - 1 + choices.length) % choices.length; render(false) }
154
+ if (key === '\x1b[B') { cursor = (cursor + 1) % choices.length; render(false) }
110
155
  }
111
156
 
112
157
  function cleanup() {
@@ -120,29 +165,71 @@ export async function multiSelect(message, choices) {
120
165
  }
121
166
 
122
167
  /**
123
- * 单选菜单
168
+ * 单选菜单 — inquirer list 风格
169
+ *
124
170
  * @param {string} message
125
- * @param {{ name: string, value: string }[]} choices
171
+ * @param {{ name: string, value: string, description?: string }[]} choices
126
172
  * @returns {Promise<string>}
127
173
  */
128
174
  export async function select(message, choices) {
129
- console.log(`\n ? ${message}`)
130
- console.log('')
175
+ let cursor = 0
131
176
 
132
- choices.forEach((c, i) => {
133
- console.log(` ${i + 1}. ${c.name}`)
134
- })
177
+ function renderLine(c, i) {
178
+ const pointer = i === cursor ? `${C.cyan}❯${C.reset}` : ' '
179
+ const name = i === cursor ? `${C.cyan}${C.bold}${c.name}${C.reset}` : c.name
180
+ const desc = c.description ? ` ${C.gray}— ${c.description}${C.reset}` : ''
181
+ return ` ${pointer} ${name}${desc}`
182
+ }
183
+
184
+ function renderHint() {
185
+ return ` ${C.dim}↑↓ 移动${C.reset} ${C.dim}回车 选择${C.reset}`
186
+ }
187
+
188
+ function render(isFirst) {
189
+ if (!isFirst) {
190
+ process.stdout.write(`\x1b[${choices.length + 1}A\x1b[0J`)
191
+ }
192
+ for (const [i, c] of choices.entries()) {
193
+ console.log(renderLine(c, i))
194
+ }
195
+ process.stdout.write(renderHint())
196
+ }
197
+
198
+ // 首次绘制
199
+ console.log(`\n ${C.cyan}?${C.reset} ${C.bold}${message}${C.reset}`)
200
+ process.stdout.write(C.hideCursor)
201
+ render(true)
135
202
 
136
- const rl = createRL()
137
203
  return new Promise(resolve => {
138
- rl.question(`\n 输入编号选择 (1-${choices.length}): `, answer => {
139
- rl.close()
140
- const idx = parseInt(answer.trim()) - 1
141
- if (idx >= 0 && idx < choices.length) {
142
- resolve(choices[idx].value)
143
- } else {
144
- resolve(choices[0].value)
204
+ const { stdin } = process
205
+ const wasRaw = stdin.isRaw
206
+ stdin.setRawMode(true)
207
+ stdin.resume()
208
+ stdin.setEncoding('utf-8')
209
+
210
+ function onData(key) {
211
+ if (key === '\x03') {
212
+ cleanup()
213
+ process.stdout.write(C.showCursor)
214
+ process.exit(0)
145
215
  }
146
- })
216
+ if (key === '\r' || key === '\n') {
217
+ cleanup()
218
+ process.stdout.write(`\r\x1b[K\n`)
219
+ process.stdout.write(C.showCursor)
220
+ console.log(` ${C.green}✔${C.reset} 已选择: ${C.cyan}${choices[cursor].name}${C.reset}`)
221
+ return resolve(choices[cursor].value)
222
+ }
223
+ if (key === '\x1b[A') { cursor = (cursor - 1 + choices.length) % choices.length; render(false) }
224
+ if (key === '\x1b[B') { cursor = (cursor + 1) % choices.length; render(false) }
225
+ }
226
+
227
+ function cleanup() {
228
+ stdin.removeListener('data', onData)
229
+ stdin.setRawMode(wasRaw ?? false)
230
+ stdin.pause()
231
+ }
232
+
233
+ stdin.on('data', onData)
147
234
  })
148
235
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "aigroup-workflow",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "AI 团队协作框架 — 通过角色派遣、工作流管道和 Harness 传感器驱动 AI 协作开发",
5
5
  "type": "module",
6
6
  "bin": {
7
- "aigroup": "bin/aigroup.mjs"
7
+ "aigroup": "bin/aigroup.mjs",
8
+ "aig": "bin/aigroup.mjs"
8
9
  },
9
10
  "files": [
10
11
  "bin/",
@@ -29,7 +30,5 @@
29
30
  "engines": {
30
31
  "node": ">=18.0.0"
31
32
  },
32
- "dependencies": {
33
- "aigroup-workflow": "^1.0.0"
34
- }
33
+ "dependencies": {}
35
34
  }