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/.claude/commands/git-commit.md +61 -0
- package/.claude/commands/init-project.md +83 -0
- package/.claude/commands/jarvis.md +6 -3
- package/README.md +404 -621
- package/bin/aigroup.mjs +11 -8
- package/cli/commands/init.mjs +3 -3
- package/cli/commands/menu.mjs +65 -0
- package/cli/utils/prompts.mjs +135 -48
- package/package.json +4 -5
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
|
-
用法:
|
|
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
|
|
104
|
-
npx
|
|
105
|
-
npx
|
|
106
|
-
npx
|
|
106
|
+
npx aig init # 交互式初始化
|
|
107
|
+
npx aig init --yes # 使用默认配置初始化
|
|
108
|
+
npx aig update # 增量更新
|
|
109
|
+
npx aig check # 健康检查
|
|
107
110
|
`)
|
|
108
111
|
}
|
|
109
112
|
|
package/cli/commands/init.mjs
CHANGED
|
@@ -43,9 +43,9 @@ export async function init(ctx) {
|
|
|
43
43
|
log.info('使用默认配置:全部角色')
|
|
44
44
|
} else {
|
|
45
45
|
selectedAgents = await multiSelect('选择要安装的团队成员', [
|
|
46
|
-
{ name:
|
|
47
|
-
{ name:
|
|
48
|
-
{ name:
|
|
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
|
+
}
|
package/cli/utils/prompts.mjs
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 交互式提示工具 —
|
|
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(`
|
|
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(`
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
94
|
-
|
|
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
|
-
//
|
|
105
|
-
if (key === '
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
130
|
-
console.log('')
|
|
175
|
+
let cursor = 0
|
|
131
176
|
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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.
|
|
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
|
}
|