jinzd-ai-cli 0.3.0 → 0.3.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.
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HubAgent,
4
+ assignRoleColors,
5
+ renderHubBanner,
6
+ renderHubEvent
7
+ } from "./chunk-4ZAZF4CF.js";
8
+
9
+ // src/hub/discuss.ts
10
+ var DiscussionOrchestrator = class {
11
+ constructor(config, providers) {
12
+ this.config = config;
13
+ this.providers = providers;
14
+ this.agents = config.roles.map(
15
+ (role) => new HubAgent(role, providers, config.defaultProvider, config.defaultModel)
16
+ );
17
+ this.state = {
18
+ topic: "",
19
+ messages: [],
20
+ round: 0,
21
+ maxRounds: config.maxRounds ?? 10,
22
+ finished: false
23
+ };
24
+ }
25
+ agents = [];
26
+ state;
27
+ aborted = false;
28
+ /** Callback for rendering events */
29
+ onEvent;
30
+ /** Get all agents */
31
+ getAgents() {
32
+ return this.agents;
33
+ }
34
+ /** Signal the orchestrator to stop after current turn */
35
+ abort() {
36
+ this.aborted = true;
37
+ }
38
+ /**
39
+ * Run the full discussion on a given topic.
40
+ * Returns the final DiscussionState including summary.
41
+ */
42
+ async run(topic) {
43
+ this.state.topic = topic;
44
+ this.state.round = 0;
45
+ this.state.finished = false;
46
+ this.aborted = false;
47
+ try {
48
+ for (let round = 1; round <= this.state.maxRounds; round++) {
49
+ if (this.aborted) {
50
+ this.emit({ type: "discussion_end", reason: "user_interrupt" });
51
+ break;
52
+ }
53
+ this.state.round = round;
54
+ this.emit({ type: "round_start", round, maxRounds: this.state.maxRounds });
55
+ let allPassed = true;
56
+ for (const agent of this.agents) {
57
+ if (this.aborted) break;
58
+ this.emit({ type: "agent_speaking", roleId: agent.role.id, roleName: agent.role.name });
59
+ try {
60
+ const message = await agent.speak(topic, this.state.messages, round, this.state.maxRounds);
61
+ this.state.messages.push(message);
62
+ if (message.passed) {
63
+ this.emit({ type: "agent_passed", roleId: agent.role.id });
64
+ this.emit({ type: "agent_spoke", roleId: agent.role.id, message });
65
+ } else {
66
+ allPassed = false;
67
+ this.emit({ type: "agent_spoke", roleId: agent.role.id, message });
68
+ }
69
+ } catch (err) {
70
+ const errMsg = err instanceof Error ? err.message : String(err);
71
+ this.emit({ type: "error", roleId: agent.role.id, message: errMsg });
72
+ this.state.messages.push({
73
+ speaker: "system",
74
+ speakerName: "System",
75
+ content: `[${agent.role.name} encountered an error: ${errMsg}]`,
76
+ timestamp: /* @__PURE__ */ new Date()
77
+ });
78
+ }
79
+ }
80
+ this.emit({ type: "round_end", round });
81
+ if (allPassed && round > 1) {
82
+ this.emit({ type: "discussion_end", reason: "consensus" });
83
+ break;
84
+ }
85
+ if (round === this.state.maxRounds) {
86
+ this.emit({ type: "discussion_end", reason: "max_rounds" });
87
+ }
88
+ }
89
+ } catch (err) {
90
+ const errMsg = err instanceof Error ? err.message : String(err);
91
+ this.emit({ type: "error", message: errMsg });
92
+ }
93
+ await this.generateSummary();
94
+ this.state.finished = true;
95
+ return this.state;
96
+ }
97
+ // ── Private ──────────────────────────────────────────────────────
98
+ async generateSummary() {
99
+ if (this.state.messages.filter((m) => !m.passed && m.speaker !== "system").length === 0) {
100
+ this.state.summary = "(No substantive discussion occurred.)";
101
+ this.emit({ type: "summary", content: this.state.summary });
102
+ return;
103
+ }
104
+ const summarizer = this.agents[0];
105
+ if (!summarizer) {
106
+ this.state.summary = "(No agents available to summarize.)";
107
+ this.emit({ type: "summary", content: this.state.summary });
108
+ return;
109
+ }
110
+ try {
111
+ this.state.summary = await summarizer.summarize(this.state.topic, this.state.messages);
112
+ this.emit({ type: "summary", content: this.state.summary });
113
+ } catch (err) {
114
+ const errMsg = err instanceof Error ? err.message : String(err);
115
+ this.state.summary = `(Summary generation failed: ${errMsg})`;
116
+ this.emit({ type: "summary", content: this.state.summary });
117
+ }
118
+ }
119
+ emit(event) {
120
+ this.onEvent?.(event);
121
+ }
122
+ };
123
+
124
+ // src/hub/presets.ts
125
+ var PRESETS = [
126
+ {
127
+ id: "tech-review",
128
+ name: "Tech Review Panel",
129
+ description: "\u67B6\u6784\u5E08 + \u5F00\u53D1\u8005 + \u5B89\u5168\u4E13\u5BB6 \u8BA8\u8BBA\u6280\u672F\u65B9\u6848",
130
+ roles: [
131
+ {
132
+ id: "architect",
133
+ name: "\u67B6\u6784\u5E08",
134
+ persona: `\u4F60\u662F\u4E00\u4F4D\u8D44\u6DF1\u8F6F\u4EF6\u67B6\u6784\u5E08\uFF0C\u6709 15 \u5E74\u4EE5\u4E0A\u7684\u7CFB\u7EDF\u8BBE\u8BA1\u7ECF\u9A8C\u3002
135
+ \u4F60\u7684\u4E13\u957F\uFF1A\u7CFB\u7EDF\u67B6\u6784\u3001\u53EF\u6269\u5C55\u6027\u3001\u6280\u672F\u9009\u578B\u3001\u8BBE\u8BA1\u6A21\u5F0F\u3002
136
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u7CFB\u7EDF\u6574\u4F53\u7ED3\u6784\u3001\u6A21\u5757\u89E3\u8026\u3001\u957F\u671F\u53EF\u7EF4\u62A4\u6027\u3001\u6280\u672F\u503A\u52A1\u3002
137
+ \u98CE\u683C\uFF1A\u5168\u5C40\u89C6\u89D2\uFF0C\u5584\u4E8E\u6743\u8861 trade-off\uFF0C\u7528\u56FE\u8868\u548C\u7C7B\u6BD4\u89E3\u91CA\u590D\u6742\u6982\u5FF5\u3002`,
138
+ color: "cyan"
139
+ },
140
+ {
141
+ id: "developer",
142
+ name: "\u5168\u6808\u5F00\u53D1\u8005",
143
+ persona: `\u4F60\u662F\u4E00\u4F4D\u7ECF\u9A8C\u4E30\u5BCC\u7684\u5168\u6808\u5F00\u53D1\u8005\uFF0C\u7CBE\u901A\u524D\u7AEF\u548C\u540E\u7AEF\u6280\u672F\u6808\u3002
144
+ \u4F60\u7684\u4E13\u957F\uFF1A\u4EE3\u7801\u5B9E\u73B0\u3001\u6027\u80FD\u4F18\u5316\u3001API \u8BBE\u8BA1\u3001\u5F00\u53D1\u6548\u7387\u3002
145
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u4EE3\u7801\u53EF\u8BFB\u6027\u3001\u5B9E\u73B0\u590D\u6742\u5EA6\u3001DX(\u5F00\u53D1\u8005\u4F53\u9A8C)\u3001\u5177\u4F53\u6280\u672F\u7EC6\u8282\u3002
146
+ \u98CE\u683C\uFF1A\u52A1\u5B9E\u3001\u6CE8\u91CD\u7EC6\u8282\uFF0C\u559C\u6B22\u7ED9\u51FA\u5177\u4F53\u7684\u4EE3\u7801\u793A\u4F8B\u548C\u5B9E\u73B0\u5EFA\u8BAE\u3002`,
147
+ color: "green"
148
+ },
149
+ {
150
+ id: "security",
151
+ name: "\u5B89\u5168\u4E13\u5BB6",
152
+ persona: `\u4F60\u662F\u4E00\u4F4D\u7F51\u7EDC\u5B89\u5168\u4E13\u5BB6\uFF0C\u4E13\u6CE8\u4E8E\u5E94\u7528\u5B89\u5168\u548C\u5B89\u5168\u67B6\u6784\u8BBE\u8BA1\u3002
153
+ \u4F60\u7684\u4E13\u957F\uFF1A\u5A01\u80C1\u5EFA\u6A21\u3001\u6F0F\u6D1E\u5206\u6790\u3001\u5B89\u5168\u6700\u4F73\u5B9E\u8DF5\u3001\u5408\u89C4\u8981\u6C42\u3002
154
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u653B\u51FB\u9762\u3001\u6570\u636E\u4FDD\u62A4\u3001\u8BA4\u8BC1\u6388\u6743\u3001\u5B89\u5168\u7F16\u7801\u5B9E\u8DF5\u3002
155
+ \u98CE\u683C\uFF1A\u8C28\u614E\u3001\u4E25\u8C28\uFF0C\u5584\u4E8E\u53D1\u73B0\u6F5C\u5728\u98CE\u9669\uFF0C\u540C\u65F6\u4E5F\u4F1A\u63D0\u51FA\u5B9E\u7528\u7684\u5B89\u5168\u65B9\u6848\u800C\u4E0D\u662F\u4E00\u5473\u5426\u5B9A\u3002`,
156
+ color: "red"
157
+ }
158
+ ]
159
+ },
160
+ {
161
+ id: "brainstorm",
162
+ name: "Brainstorm Team",
163
+ description: "\u521B\u610F\u8005 + \u5206\u6790\u5E08 + \u6267\u884C\u8005 \u5934\u8111\u98CE\u66B4",
164
+ roles: [
165
+ {
166
+ id: "creative",
167
+ name: "\u521B\u610F\u8005",
168
+ persona: `\u4F60\u662F\u4E00\u4F4D\u5145\u6EE1\u521B\u610F\u7684\u4EA7\u54C1\u601D\u8003\u8005\uFF0C\u5584\u4E8E\u8DF3\u51FA\u6846\u67B6\u601D\u8003\u3002
169
+ \u4F60\u7684\u4E13\u957F\uFF1A\u521B\u65B0\u601D\u7EF4\u3001\u7528\u6237\u4F53\u9A8C\u3001\u4EA7\u54C1\u613F\u666F\u3001\u8BBE\u8BA1\u601D\u7EF4\u3002
170
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u7528\u6237\u9700\u6C42\u3001\u521B\u65B0\u6027\u3001\u5DEE\u5F02\u5316\u3001\u60C5\u611F\u4EF7\u503C\u3002
171
+ \u98CE\u683C\uFF1A\u53D1\u6563\u6027\u601D\u7EF4\uFF0C\u5927\u80C6\u63D0\u51FA\u65B0\u60F3\u6CD5\uFF0C\u4E0D\u6015\u5929\u9A6C\u884C\u7A7A\u3002\u5584\u4E8E\u7528\u6545\u4E8B\u548C\u573A\u666F\u6765\u63CF\u8FF0\u6784\u60F3\u3002`,
172
+ color: "magenta"
173
+ },
174
+ {
175
+ id: "analyst",
176
+ name: "\u5206\u6790\u5E08",
177
+ persona: `\u4F60\u662F\u4E00\u4F4D\u7406\u6027\u7684\u6570\u636E\u5206\u6790\u5E08\uFF0C\u5584\u4E8E\u7528\u6570\u636E\u548C\u903B\u8F91\u8BC4\u4F30\u65B9\u6848\u3002
178
+ \u4F60\u7684\u4E13\u957F\uFF1A\u6570\u636E\u5206\u6790\u3001\u5E02\u573A\u7814\u7A76\u3001\u53EF\u884C\u6027\u8BC4\u4F30\u3001ROI \u5206\u6790\u3002
179
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u6570\u636E\u652F\u6491\u3001\u6210\u672C\u6536\u76CA\u3001\u98CE\u9669\u8BC4\u4F30\u3001\u5E02\u573A\u53EF\u884C\u6027\u3002
180
+ \u98CE\u683C\uFF1A\u4E25\u8C28\u5BA2\u89C2\uFF0C\u5584\u4E8E\u63D0\u51FA\u5173\u952E\u95EE\u9898\uFF0C\u7528\u6570\u636E\u8BF4\u8BDD\u3002\u4E0D\u8F7B\u6613\u5426\u5B9A\u4F46\u4F1A\u6307\u51FA\u903B\u8F91\u6F0F\u6D1E\u3002`,
181
+ color: "yellow"
182
+ },
183
+ {
184
+ id: "executor",
185
+ name: "\u6267\u884C\u8005",
186
+ persona: `\u4F60\u662F\u4E00\u4F4D\u9AD8\u6548\u7684\u9879\u76EE\u6267\u884C\u8005\uFF0C\u5584\u4E8E\u5C06\u60F3\u6CD5\u8F6C\u5316\u4E3A\u53EF\u6267\u884C\u7684\u8BA1\u5212\u3002
187
+ \u4F60\u7684\u4E13\u957F\uFF1A\u9879\u76EE\u7BA1\u7406\u3001\u8D44\u6E90\u89C4\u5212\u3001\u98CE\u9669\u7BA1\u7406\u3001\u654F\u6377\u65B9\u6CD5\u3002
188
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u53EF\u6267\u884C\u6027\u3001\u65F6\u95F4\u7EBF\u3001\u8D44\u6E90\u9700\u6C42\u3001MVP \u7B56\u7565\u3002
189
+ \u98CE\u683C\uFF1A\u5B9E\u9645\u3001\u9AD8\u6548\uFF0C\u5584\u4E8E\u62C6\u89E3\u4EFB\u52A1\uFF0C\u63D0\u51FA\u5177\u4F53\u7684\u5B9E\u65BD\u6B65\u9AA4\u548C\u91CC\u7A0B\u7891\u3002`,
190
+ color: "green"
191
+ }
192
+ ]
193
+ },
194
+ {
195
+ id: "code-review",
196
+ name: "Code Review Panel",
197
+ description: "\u4EE3\u7801\u8D28\u91CF + \u6027\u80FD + \u53EF\u6D4B\u8BD5\u6027 \u591A\u89D2\u5EA6\u5BA1\u67E5",
198
+ roles: [
199
+ {
200
+ id: "quality",
201
+ name: "\u4EE3\u7801\u8D28\u91CF\u5BA1\u67E5\u5458",
202
+ persona: `\u4F60\u662F\u4E00\u4F4D\u4EE3\u7801\u8D28\u91CF\u4E13\u5BB6\uFF0C\u4E13\u6CE8\u4E8E\u53EF\u8BFB\u6027\u3001\u53EF\u7EF4\u62A4\u6027\u548C\u6700\u4F73\u5B9E\u8DF5\u3002
203
+ \u4F60\u7684\u4E13\u957F\uFF1AClean Code\u3001\u8BBE\u8BA1\u6A21\u5F0F\u3001SOLID \u539F\u5219\u3001\u91CD\u6784\u6280\u672F\u3002
204
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u547D\u540D\u89C4\u8303\u3001\u51FD\u6570\u957F\u5EA6\u3001\u8026\u5408\u5EA6\u3001\u4EE3\u7801\u590D\u6742\u5EA6\u3001\u6CE8\u91CA\u8D28\u91CF\u3002
205
+ \u98CE\u683C\uFF1A\u5EFA\u8BBE\u6027\u6279\u8BC4\uFF0C\u603B\u662F\u7ED9\u51FA\u6539\u8FDB\u5EFA\u8BAE\u800C\u4E0D\u4EC5\u662F\u6307\u51FA\u95EE\u9898\u3002`,
206
+ color: "cyan"
207
+ },
208
+ {
209
+ id: "perf",
210
+ name: "\u6027\u80FD\u5DE5\u7A0B\u5E08",
211
+ persona: `\u4F60\u662F\u4E00\u4F4D\u6027\u80FD\u4F18\u5316\u4E13\u5BB6\uFF0C\u5584\u4E8E\u53D1\u73B0\u548C\u89E3\u51B3\u6027\u80FD\u74F6\u9888\u3002
212
+ \u4F60\u7684\u4E13\u957F\uFF1A\u7B97\u6CD5\u590D\u6742\u5EA6\u3001\u5185\u5B58\u7BA1\u7406\u3001\u5E76\u53D1\u4F18\u5316\u3001\u7F13\u5B58\u7B56\u7565\u3002
213
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u65F6\u95F4\u590D\u6742\u5EA6\u3001\u7A7A\u95F4\u590D\u6742\u5EA6\u3001IO \u74F6\u9888\u3001\u70ED\u70B9\u8DEF\u5F84\u3002
214
+ \u98CE\u683C\uFF1A\u6570\u636E\u9A71\u52A8\uFF0C\u559C\u6B22\u7528\u57FA\u51C6\u6D4B\u8BD5\u548C\u5927 O \u5206\u6790\u6765\u8BBA\u8BC1\u89C2\u70B9\u3002`,
215
+ color: "yellow"
216
+ },
217
+ {
218
+ id: "testing",
219
+ name: "\u6D4B\u8BD5\u67B6\u6784\u5E08",
220
+ persona: `\u4F60\u662F\u4E00\u4F4D\u6D4B\u8BD5\u67B6\u6784\u5E08\uFF0C\u4E13\u6CE8\u4E8E\u4EE3\u7801\u7684\u53EF\u6D4B\u8BD5\u6027\u548C\u6D4B\u8BD5\u7B56\u7565\u3002
221
+ \u4F60\u7684\u4E13\u957F\uFF1A\u5355\u5143\u6D4B\u8BD5\u3001\u96C6\u6210\u6D4B\u8BD5\u3001Mock \u7B56\u7565\u3001TDD\u3001\u6D4B\u8BD5\u8986\u76D6\u7387\u3002
222
+ \u4F60\u5173\u6CE8\u7684\u7EF4\u5EA6\uFF1A\u53EF\u6D4B\u8BD5\u6027\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u9519\u8BEF\u5904\u7406\u8DEF\u5F84\u3001\u6D4B\u8BD5\u91D1\u5B57\u5854\u3002
223
+ \u98CE\u683C\uFF1A\u5173\u6CE8\u8FB9\u754C\u60C5\u51B5\u548C\u5F02\u5E38\u8DEF\u5F84\uFF0C\u5584\u4E8E\u63D0\u51FA"\u5982\u679C...\u600E\u4E48\u529E"\u7684\u95EE\u9898\u3002`,
224
+ color: "green"
225
+ }
226
+ ]
227
+ },
228
+ {
229
+ id: "debate",
230
+ name: "Debate (Pro vs Con)",
231
+ description: "\u6B63\u65B9 + \u53CD\u65B9 + \u4E3B\u6301\u4EBA \u8FA9\u8BBA\u6A21\u5F0F",
232
+ roles: [
233
+ {
234
+ id: "pro",
235
+ name: "\u6B63\u65B9",
236
+ persona: `\u4F60\u662F\u8FA9\u8BBA\u4E2D\u7684\u6B63\u65B9\uFF0C\u4F60\u7684\u4EFB\u52A1\u662F\u8BBA\u8BC1\u8BA8\u8BBA\u4E3B\u9898\u7684\u6B63\u9762\u4EF7\u503C\u3002
237
+ \u4F60\u5FC5\u987B\uFF1A\u4E3A\u4E3B\u9898\u8FA9\u62A4\uFF0C\u627E\u5230\u652F\u6301\u8BBA\u636E\uFF0C\u56DE\u5E94\u53CD\u65B9\u7684\u8D28\u7591\u3002
238
+ \u98CE\u683C\uFF1A\u903B\u8F91\u4E25\u5BC6\uFF0C\u5584\u4E8E\u5F15\u7528\u6848\u4F8B\u548C\u6570\u636E\u3002\u5373\u4F7F\u9762\u5BF9\u5F3A\u6709\u529B\u7684\u53CD\u9A73\u4E5F\u8981\u627E\u5230\u65B0\u7684\u8BBA\u8BC1\u89D2\u5EA6\u3002
239
+ \u6CE8\u610F\uFF1A\u4F60\u53EF\u4EE5\u627F\u8BA4\u5BF9\u65B9\u7684\u90E8\u5206\u89C2\u70B9\uFF0C\u4F46\u8981\u6307\u51FA\u8FD9\u4E0D\u5F71\u54CD\u4F60\u7684\u6838\u5FC3\u8BBA\u70B9\u3002`,
240
+ color: "green"
241
+ },
242
+ {
243
+ id: "con",
244
+ name: "\u53CD\u65B9",
245
+ persona: `\u4F60\u662F\u8FA9\u8BBA\u4E2D\u7684\u53CD\u65B9\uFF0C\u4F60\u7684\u4EFB\u52A1\u662F\u627E\u51FA\u8BA8\u8BBA\u4E3B\u9898\u7684\u95EE\u9898\u548C\u98CE\u9669\u3002
246
+ \u4F60\u5FC5\u987B\uFF1A\u63D0\u51FA\u8D28\u7591\uFF0C\u53D1\u73B0\u6F0F\u6D1E\uFF0C\u6307\u51FA\u6F5C\u5728\u98CE\u9669\u548C\u66FF\u4EE3\u65B9\u6848\u3002
247
+ \u98CE\u683C\uFF1A\u7280\u5229\u3001\u5584\u4E8E\u53CD\u95EE\uFF0C\u7528\u53CD\u9762\u6848\u4F8B\u548C\u903B\u8F91\u63A8\u7406\u6765\u8BBA\u8BC1\u3002
248
+ \u6CE8\u610F\uFF1A\u4F60\u4E0D\u662F\u4E3A\u4E86\u5426\u5B9A\u800C\u5426\u5B9A\uFF0C\u800C\u662F\u901A\u8FC7\u8D28\u7591\u6765\u5E2E\u52A9\u5168\u9762\u7406\u89E3\u95EE\u9898\u3002`,
249
+ color: "red"
250
+ },
251
+ {
252
+ id: "moderator",
253
+ name: "\u4E3B\u6301\u4EBA",
254
+ persona: `\u4F60\u662F\u8FA9\u8BBA\u7684\u4E3B\u6301\u4EBA\uFF0C\u4F60\u7684\u4EFB\u52A1\u662F\u5F15\u5BFC\u8BA8\u8BBA\u3001\u603B\u7ED3\u89C2\u70B9\u3001\u63D0\u51FA\u65B0\u7684\u8BA8\u8BBA\u65B9\u5411\u3002
255
+ \u4F60\u5FC5\u987B\uFF1A\u4FDD\u6301\u4E2D\u7ACB\uFF0C\u603B\u7ED3\u53CC\u65B9\u8981\u70B9\uFF0C\u5728\u8BA8\u8BBA\u9677\u5165\u50F5\u5C40\u65F6\u63D0\u51FA\u65B0\u89D2\u5EA6\u3002
256
+ \u98CE\u683C\uFF1A\u516C\u6B63\u3001\u5584\u4E8E\u5F52\u7EB3\uFF0C\u6BCF\u6B21\u53D1\u8A00\u5148\u7B80\u77ED\u603B\u7ED3\u53CC\u65B9\u89C2\u70B9\uFF0C\u518D\u5F15\u5BFC\u4E0B\u4E00\u4E2A\u8BA8\u8BBA\u65B9\u5411\u3002
257
+ \u6CE8\u610F\uFF1A\u4E0D\u8981\u8FC7\u591A\u53D1\u8868\u4E2A\u4EBA\u89C2\u70B9\uFF0C\u91CD\u70B9\u662F\u63A8\u8FDB\u8BA8\u8BBA\u8D28\u91CF\u3002`,
258
+ color: "cyan"
259
+ }
260
+ ]
261
+ }
262
+ ];
263
+ function getPreset(id) {
264
+ return PRESETS.find((p) => p.id === id);
265
+ }
266
+ function listPresets() {
267
+ return PRESETS;
268
+ }
269
+
270
+ // src/hub/index.ts
271
+ import { readFileSync, existsSync } from "fs";
272
+ async function startHub(options, configManager, providers) {
273
+ if (options.listPresets) {
274
+ console.log("\n Available discussion presets:\n");
275
+ for (const p of listPresets()) {
276
+ console.log(` ${p.id.padEnd(16)} ${p.name}`);
277
+ console.log(` ${"".padEnd(16)} ${p.description}`);
278
+ for (const r of p.roles) {
279
+ console.log(` ${"".padEnd(18)} \u2022 ${r.name} (${r.id})`);
280
+ }
281
+ console.log();
282
+ }
283
+ return;
284
+ }
285
+ let roles;
286
+ let presetUsed;
287
+ if (options.rolesFile) {
288
+ if (!existsSync(options.rolesFile)) {
289
+ console.error(` \u2717 Roles file not found: ${options.rolesFile}`);
290
+ process.exit(1);
291
+ }
292
+ try {
293
+ const raw = readFileSync(options.rolesFile, "utf-8");
294
+ const parsed = JSON.parse(raw);
295
+ roles = Array.isArray(parsed) ? parsed : parsed.roles;
296
+ if (!Array.isArray(roles) || roles.length === 0) {
297
+ throw new Error("roles must be a non-empty array");
298
+ }
299
+ } catch (err) {
300
+ console.error(` \u2717 Invalid roles file: ${err.message}`);
301
+ process.exit(1);
302
+ }
303
+ } else {
304
+ const presetId = options.preset ?? "tech-review";
305
+ presetUsed = getPreset(presetId);
306
+ if (!presetUsed) {
307
+ console.error(` \u2717 Preset "${presetId}" not found. Use --presets to list available presets.`);
308
+ process.exit(1);
309
+ }
310
+ roles = presetUsed.roles;
311
+ }
312
+ const defaultProvider = options.provider ?? configManager.get("defaultProvider");
313
+ const allDefaultModels = configManager.get("defaultModels");
314
+ let defaultModel = options.model ?? allDefaultModels[defaultProvider] ?? "";
315
+ if (!defaultModel) {
316
+ try {
317
+ const p = providers.get(defaultProvider);
318
+ defaultModel = p.info.defaultModel;
319
+ } catch {
320
+ console.error(` \u2717 Provider "${defaultProvider}" not configured. Run \`aicli config\` first.`);
321
+ process.exit(1);
322
+ }
323
+ }
324
+ if (!providers.has(defaultProvider)) {
325
+ console.error(` \u2717 Provider "${defaultProvider}" not available. Check API key configuration.`);
326
+ process.exit(1);
327
+ }
328
+ if (!options.topic?.trim()) {
329
+ console.error(" \u2717 Please provide a discussion topic.");
330
+ console.error(' Usage: aicli hub "your topic here"');
331
+ console.error(' aicli hub --preset brainstorm "your topic here"');
332
+ process.exit(1);
333
+ }
334
+ const config = {
335
+ mode: options.mode ?? "discuss",
336
+ roles,
337
+ defaultProvider,
338
+ defaultModel,
339
+ maxRounds: options.maxRounds ?? 10,
340
+ enableTools: false
341
+ };
342
+ if (config.mode === "discuss") {
343
+ if (options.distributed) {
344
+ await runDistributedDiscussion(config, providers, options.topic, options.port ?? 9527);
345
+ } else {
346
+ await runDiscussion(config, providers, options.topic);
347
+ }
348
+ } else {
349
+ console.error(" \u2717 Task mode is not yet implemented. Use --discuss (default).");
350
+ process.exit(1);
351
+ }
352
+ }
353
+ async function runDistributedDiscussion(config, providers, topic, port) {
354
+ const { HubServer } = await import("./hub-server-P547YWUN.js");
355
+ const hub = new HubServer(config, providers, port);
356
+ let interrupted = false;
357
+ const onSigint = () => {
358
+ if (interrupted) {
359
+ console.log("\n Force exit.");
360
+ hub.shutdown();
361
+ process.exit(0);
362
+ }
363
+ interrupted = true;
364
+ hub.abort();
365
+ };
366
+ process.on("SIGINT", onSigint);
367
+ try {
368
+ await hub.start(topic);
369
+ } catch (err) {
370
+ console.error(`
371
+ \u2717 Hub error: ${err.message}`);
372
+ } finally {
373
+ process.removeListener("SIGINT", onSigint);
374
+ }
375
+ }
376
+ async function runDiscussion(config, providers, topic) {
377
+ assignRoleColors(config.roles);
378
+ const orchestrator = new DiscussionOrchestrator(config, providers);
379
+ orchestrator.onEvent = renderHubEvent;
380
+ let interrupted = false;
381
+ const onSigint = () => {
382
+ if (interrupted) {
383
+ console.log("\n Force exit.");
384
+ process.exit(0);
385
+ }
386
+ interrupted = true;
387
+ orchestrator.abort();
388
+ };
389
+ process.on("SIGINT", onSigint);
390
+ renderHubBanner(topic, config.roles, config.maxRounds ?? 10);
391
+ try {
392
+ await orchestrator.run(topic);
393
+ } catch (err) {
394
+ console.error(`
395
+ \u2717 Hub error: ${err.message}`);
396
+ } finally {
397
+ process.removeListener("SIGINT", onSigint);
398
+ }
399
+ }
400
+ export {
401
+ startHub
402
+ };