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,299 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HubAgent,
4
+ assignRoleColors,
5
+ clearSpeakingLine,
6
+ renderAgentMessage,
7
+ renderAgentSpeaking,
8
+ renderConsensus,
9
+ renderHubBanner,
10
+ renderMaxRounds,
11
+ renderRoundHeader,
12
+ renderSummary,
13
+ renderUserInterrupt
14
+ } from "./chunk-4ZAZF4CF.js";
15
+
16
+ // src/hub/hub-server.ts
17
+ import { WebSocketServer } from "ws";
18
+ import { createServer } from "http";
19
+ import chalk from "chalk";
20
+ var HubServer = class {
21
+ constructor(config, providers, port) {
22
+ this.config = config;
23
+ this.providers = providers;
24
+ this.port = port;
25
+ this.unassignedRoles = [...config.roles];
26
+ }
27
+ wss = null;
28
+ httpServer = null;
29
+ agents = /* @__PURE__ */ new Map();
30
+ unassignedRoles = [];
31
+ history = [];
32
+ aborted = false;
33
+ currentTurnResolve = null;
34
+ /**
35
+ * Start the hub server and run the discussion.
36
+ */
37
+ async start(topic) {
38
+ assignRoleColors(this.config.roles);
39
+ this.httpServer = createServer();
40
+ this.wss = new WebSocketServer({ server: this.httpServer });
41
+ this.wss.on("connection", (ws) => this.handleConnection(ws));
42
+ await new Promise((resolve) => {
43
+ this.httpServer.listen(this.port, () => resolve());
44
+ });
45
+ const maxRounds = this.config.maxRounds ?? 10;
46
+ renderHubBanner(topic, this.config.roles, maxRounds);
47
+ console.log(chalk.bold.cyan(` \u{1F310} Hub server listening on ws://localhost:${this.port}`));
48
+ console.log(chalk.dim(` Waiting for ${this.config.roles.length} agent(s) to connect...`));
49
+ console.log(chalk.dim(` Each agent should run: aicli join --port ${this.port}`));
50
+ console.log();
51
+ await this.waitForAgents(topic);
52
+ await this.runDiscussion(topic, maxRounds);
53
+ this.shutdown();
54
+ }
55
+ abort() {
56
+ this.aborted = true;
57
+ if (this.currentTurnResolve) {
58
+ this.currentTurnResolve({ content: "", passed: true });
59
+ }
60
+ }
61
+ shutdown() {
62
+ for (const agent of this.agents.values()) {
63
+ try {
64
+ agent.ws.close();
65
+ } catch {
66
+ }
67
+ }
68
+ this.wss?.close();
69
+ this.httpServer?.close();
70
+ }
71
+ // ── Connection Handling ──────────────────────────────────────────
72
+ handleConnection(ws) {
73
+ ws.on("message", (raw) => {
74
+ try {
75
+ const msg = JSON.parse(raw.toString());
76
+ this.handleAgentMessage(ws, msg);
77
+ } catch {
78
+ this.sendTo(ws, { type: "error", message: "Invalid message format" });
79
+ }
80
+ });
81
+ ws.on("close", () => {
82
+ for (const [roleId, agent] of this.agents) {
83
+ if (agent.ws === ws) {
84
+ console.log(chalk.yellow(` \u26A0 Agent "${agent.roleName}" (${roleId}) disconnected`));
85
+ this.agents.delete(roleId);
86
+ break;
87
+ }
88
+ }
89
+ });
90
+ }
91
+ handleAgentMessage(ws, msg) {
92
+ switch (msg.type) {
93
+ case "register": {
94
+ const requestedRole = msg.roleId;
95
+ const roleIndex = this.unassignedRoles.findIndex((r) => r.id === requestedRole);
96
+ if (roleIndex === -1) {
97
+ if (this.unassignedRoles.length === 0) {
98
+ this.sendTo(ws, { type: "error", message: "All roles are already assigned." });
99
+ ws.close();
100
+ return;
101
+ }
102
+ const role = requestedRole ? void 0 : this.unassignedRoles[0];
103
+ if (!role && requestedRole) {
104
+ const exists = this.config.roles.some((r) => r.id === requestedRole);
105
+ if (!exists) {
106
+ this.sendTo(ws, { type: "error", message: `Role "${requestedRole}" not found. Available: ${this.unassignedRoles.map((r) => r.id).join(", ")}` });
107
+ ws.close();
108
+ return;
109
+ }
110
+ this.sendTo(ws, { type: "error", message: `Role "${requestedRole}" is already taken. Available: ${this.unassignedRoles.map((r) => r.id).join(", ")}` });
111
+ ws.close();
112
+ return;
113
+ }
114
+ if (role) {
115
+ this.assignRole(ws, role);
116
+ this.unassignedRoles.splice(0, 1);
117
+ }
118
+ } else {
119
+ const role = this.unassignedRoles[roleIndex];
120
+ this.assignRole(ws, role);
121
+ this.unassignedRoles.splice(roleIndex, 1);
122
+ }
123
+ break;
124
+ }
125
+ case "response": {
126
+ if (this.currentTurnResolve) {
127
+ this.currentTurnResolve({ content: msg.content, passed: msg.passed });
128
+ this.currentTurnResolve = null;
129
+ }
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ assignRole(ws, role) {
135
+ this.agents.set(role.id, {
136
+ ws,
137
+ roleId: role.id,
138
+ roleName: role.name,
139
+ ready: true
140
+ });
141
+ console.log(chalk.green(` \u2713 Agent connected: ${role.name} (${role.id})`));
142
+ this.sendTo(ws, {
143
+ type: "welcome",
144
+ roleId: role.id,
145
+ roleName: role.name,
146
+ topic: "",
147
+ // Will be sent when discussion starts
148
+ roles: this.config.roles.map((r) => ({ id: r.id, name: r.name }))
149
+ });
150
+ }
151
+ // ── Wait for Agents ──────────────────────────────────────────────
152
+ async waitForAgents(topic) {
153
+ const totalRoles = this.config.roles.length;
154
+ const deadline = Date.now() + 12e4;
155
+ const checkInterval = 1e3;
156
+ while (this.unassignedRoles.length > 0 && Date.now() < deadline && !this.aborted) {
157
+ const connected = totalRoles - this.unassignedRoles.length;
158
+ process.stdout.write(`\r Agents: ${connected}/${totalRoles} connected. Waiting... (${Math.ceil((deadline - Date.now()) / 1e3)}s remaining) `);
159
+ await sleep(checkInterval);
160
+ }
161
+ process.stdout.write("\r\x1B[2K");
162
+ if (this.unassignedRoles.length > 0) {
163
+ console.log(chalk.dim(` ${this.unassignedRoles.length} role(s) unfilled \u2014 using built-in AI agents:`));
164
+ for (const role of this.unassignedRoles) {
165
+ console.log(chalk.dim(` \u2022 ${role.name} (${role.id}) \u2192 built-in`));
166
+ }
167
+ console.log();
168
+ }
169
+ for (const agent of this.agents.values()) {
170
+ this.sendTo(agent.ws, {
171
+ type: "welcome",
172
+ roleId: agent.roleId,
173
+ roleName: agent.roleName,
174
+ topic,
175
+ roles: this.config.roles.map((r) => ({ id: r.id, name: r.name }))
176
+ });
177
+ }
178
+ }
179
+ // ── Discussion Loop ──────────────────────────────────────────────
180
+ async runDiscussion(topic, maxRounds) {
181
+ for (let round = 1; round <= maxRounds; round++) {
182
+ if (this.aborted) {
183
+ renderUserInterrupt();
184
+ break;
185
+ }
186
+ renderRoundHeader(round, maxRounds);
187
+ this.broadcastAll({ type: "round_start", round, maxRounds });
188
+ let allPassed = true;
189
+ for (const role of this.config.roles) {
190
+ if (this.aborted) break;
191
+ renderAgentSpeaking(role.name, role.id);
192
+ let message;
193
+ const connectedAgent = this.agents.get(role.id);
194
+ if (connectedAgent) {
195
+ message = await this.getRemoteResponse(connectedAgent, role, topic, round, maxRounds);
196
+ } else {
197
+ message = await this.getBuiltinResponse(role, topic, round, maxRounds);
198
+ }
199
+ clearSpeakingLine();
200
+ this.history.push(message);
201
+ if (!message.passed) {
202
+ allPassed = false;
203
+ }
204
+ renderAgentMessage(message);
205
+ this.broadcastAll({ type: "agent_spoke", message: serializeMessage(message) });
206
+ }
207
+ if (allPassed && round > 1) {
208
+ renderConsensus();
209
+ this.broadcastAll({ type: "discussion_end", reason: "consensus" });
210
+ break;
211
+ }
212
+ if (round === maxRounds) {
213
+ renderMaxRounds(maxRounds);
214
+ this.broadcastAll({ type: "discussion_end", reason: "max_rounds" });
215
+ }
216
+ }
217
+ await this.generateSummary(topic);
218
+ }
219
+ async getRemoteResponse(agent, role, _topic, round, maxRounds) {
220
+ this.sendTo(agent.ws, {
221
+ type: "your_turn",
222
+ round,
223
+ maxRounds,
224
+ history: this.history.map(serializeMessage)
225
+ });
226
+ try {
227
+ const response = await Promise.race([
228
+ new Promise((resolve) => {
229
+ this.currentTurnResolve = resolve;
230
+ }),
231
+ sleep(12e4).then(() => ({ content: `[${role.name} timed out]`, passed: true }))
232
+ ]);
233
+ return {
234
+ speaker: role.id,
235
+ speakerName: role.name,
236
+ content: response.passed ? "" : response.content,
237
+ timestamp: /* @__PURE__ */ new Date(),
238
+ passed: response.passed
239
+ };
240
+ } catch {
241
+ return {
242
+ speaker: role.id,
243
+ speakerName: role.name,
244
+ content: "",
245
+ timestamp: /* @__PURE__ */ new Date(),
246
+ passed: true
247
+ };
248
+ }
249
+ }
250
+ async getBuiltinResponse(role, topic, round, maxRounds) {
251
+ const agent = new HubAgent(role, this.providers, this.config.defaultProvider, this.config.defaultModel);
252
+ return agent.speak(topic, this.history, round, maxRounds);
253
+ }
254
+ async generateSummary(topic) {
255
+ if (this.history.filter((m) => !m.passed && m.speaker !== "system").length === 0) {
256
+ return;
257
+ }
258
+ const summarizer = new HubAgent(
259
+ this.config.roles[0],
260
+ this.providers,
261
+ this.config.defaultProvider,
262
+ this.config.defaultModel
263
+ );
264
+ try {
265
+ console.log(chalk.dim(" Generating summary..."));
266
+ const summary = await summarizer.summarize(topic, this.history);
267
+ renderSummary(summary);
268
+ this.broadcastAll({ type: "discussion_end", reason: "max_rounds", summary });
269
+ } catch (err) {
270
+ console.log(chalk.red(` Summary generation failed: ${err.message}`));
271
+ }
272
+ }
273
+ // ── Messaging ────────────────────────────────────────────────────
274
+ sendTo(ws, msg) {
275
+ if (ws.readyState === ws.OPEN) {
276
+ ws.send(JSON.stringify(msg));
277
+ }
278
+ }
279
+ broadcastAll(msg) {
280
+ const data = JSON.stringify(msg);
281
+ for (const agent of this.agents.values()) {
282
+ if (agent.ws.readyState === agent.ws.OPEN) {
283
+ agent.ws.send(data);
284
+ }
285
+ }
286
+ }
287
+ };
288
+ function sleep(ms) {
289
+ return new Promise((resolve) => setTimeout(resolve, ms));
290
+ }
291
+ function serializeMessage(msg) {
292
+ return {
293
+ ...msg,
294
+ timestamp: msg.timestamp instanceof Date ? msg.timestamp : new Date(msg.timestamp)
295
+ };
296
+ }
297
+ export {
298
+ HubServer
299
+ };
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  theme,
37
37
  truncateOutput,
38
38
  undoStack
39
- } from "./chunk-PTXAUPO2.js";
39
+ } from "./chunk-GBP4DNBU.js";
40
40
  import {
41
41
  AGENTIC_BEHAVIOR_GUIDELINE,
42
42
  AUTHOR,
@@ -56,7 +56,7 @@ import {
56
56
  REPO_URL,
57
57
  SKILLS_DIR_NAME,
58
58
  VERSION
59
- } from "./chunk-TIGK5MRC.js";
59
+ } from "./chunk-UEOKNL7V.js";
60
60
 
61
61
  // src/index.ts
62
62
  import { program } from "commander";
@@ -1907,7 +1907,7 @@ ${hint}` : "")
1907
1907
  description: "Run project tests and show structured report",
1908
1908
  usage: "/test [command|filter]",
1909
1909
  async execute(args, _ctx) {
1910
- const { executeTests } = await import("./run-tests-DI7E4ARF.js");
1910
+ const { executeTests } = await import("./run-tests-6VOBLE77.js");
1911
1911
  const argStr = args.join(" ").trim();
1912
1912
  let testArgs = {};
1913
1913
  if (argStr) {
@@ -5517,7 +5517,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5517
5517
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5518
5518
  process.exit(1);
5519
5519
  }
5520
- const { startWebServer } = await import("./server-HBM2BCSF.js");
5520
+ const { startWebServer } = await import("./server-YFR3DO2P.js");
5521
5521
  await startWebServer({ port, host: options.host });
5522
5522
  });
5523
5523
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5638,7 +5638,7 @@ program.command("sessions").description("List recent conversation sessions").act
5638
5638
  }
5639
5639
  console.log();
5640
5640
  });
5641
- program.command("hub [topic]").description("Start multi-agent hub (discuss / brainstorm with multiple AI roles)").option("--preset <name>", "Use a built-in role preset (default: tech-review)").option("--roles <file>", "Load roles from a JSON file").option("--provider <name>", "Override default AI provider").option("-m, --model <name>", "Override default model").option("--max-rounds <n>", "Max discussion rounds (default: 10)").option("--presets", "List available role presets").action(async (topic, options) => {
5641
+ program.command("hub [topic]").description("Start multi-agent hub (discuss / brainstorm with multiple AI roles)").option("--preset <name>", "Use a built-in role preset (default: tech-review)").option("--roles <file>", "Load roles from a JSON file").option("--provider <name>", "Override default AI provider").option("-m, --model <name>", "Override default model").option("--max-rounds <n>", "Max discussion rounds (default: 10)").option("--presets", "List available role presets").option("--distributed", "Start WebSocket server so remote aicli instances can join as agents").option("--port <n>", "WebSocket port for distributed mode (default: 9527)", "9527").action(async (topic, options) => {
5642
5642
  const config = new ConfigManager();
5643
5643
  const registry = new ProviderRegistry();
5644
5644
  await registry.initialize(
@@ -5649,7 +5649,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5649
5649
  }),
5650
5650
  config.get("customProviders")
5651
5651
  );
5652
- const { startHub } = await import("./hub-72MJ7DED.js");
5652
+ const { startHub } = await import("./hub-ZTEJJ4WD.js");
5653
5653
  await startHub(
5654
5654
  {
5655
5655
  topic: topic ?? "",
@@ -5658,7 +5658,33 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5658
5658
  provider: options.provider,
5659
5659
  model: options.model,
5660
5660
  maxRounds: options.maxRounds ? parseInt(options.maxRounds, 10) : void 0,
5661
- listPresets: options.presets === true
5661
+ listPresets: options.presets === true,
5662
+ distributed: options.distributed === true,
5663
+ port: options.port ? parseInt(options.port, 10) : void 0
5664
+ },
5665
+ config,
5666
+ registry
5667
+ );
5668
+ });
5669
+ program.command("join").description("Join a running hub as a remote AI agent").option("--port <n>", "Hub WebSocket port (default: 9527)", "9527").option("--host <name>", "Hub host (default: localhost)", "localhost").option("--role <id>", "Request a specific role (e.g. architect, developer)").option("--provider <name>", "Override AI provider for this agent").option("-m, --model <name>", "Override model for this agent").action(async (options) => {
5670
+ const config = new ConfigManager();
5671
+ const registry = new ProviderRegistry();
5672
+ await registry.initialize(
5673
+ (id) => config.getApiKey(id),
5674
+ (id) => ({
5675
+ baseUrl: config.get("customBaseUrls")[id],
5676
+ timeout: config.get("timeouts")[id]
5677
+ }),
5678
+ config.get("customProviders")
5679
+ );
5680
+ const { joinHub } = await import("./agent-client-OGXLZR5O.js");
5681
+ await joinHub(
5682
+ {
5683
+ port: parseInt(options.port ?? "9527", 10),
5684
+ host: options.host,
5685
+ role: options.role,
5686
+ provider: options.provider,
5687
+ model: options.model
5662
5688
  },
5663
5689
  config,
5664
5690
  registry
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-TIGK5MRC.js";
5
+ } from "./chunk-UEOKNL7V.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-TVODRYPD.js";
4
+ } from "./chunk-AJ26XC7E.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -27,7 +27,7 @@ import {
27
27
  spawnAgentContext,
28
28
  truncateOutput,
29
29
  undoStack
30
- } from "./chunk-PTXAUPO2.js";
30
+ } from "./chunk-GBP4DNBU.js";
31
31
  import {
32
32
  AGENTIC_BEHAVIOR_GUIDELINE,
33
33
  CONTEXT_FILE_CANDIDATES,
@@ -39,7 +39,7 @@ import {
39
39
  PLAN_MODE_SYSTEM_ADDON,
40
40
  SKILLS_DIR_NAME,
41
41
  VERSION
42
- } from "./chunk-TIGK5MRC.js";
42
+ } from "./chunk-UEOKNL7V.js";
43
43
  import {
44
44
  AuthManager
45
45
  } from "./chunk-CPLT6CD3.js";
@@ -1438,7 +1438,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1438
1438
  case "test": {
1439
1439
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1440
1440
  try {
1441
- const { executeTests } = await import("./run-tests-DI7E4ARF.js");
1441
+ const { executeTests } = await import("./run-tests-6VOBLE77.js");
1442
1442
  const argStr = args.join(" ").trim();
1443
1443
  let testArgs = {};
1444
1444
  if (argStr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,6 +56,9 @@
56
56
  "dist/server-*.js",
57
57
  "dist/auth-*.js",
58
58
  "dist/run-tests-*.js",
59
+ "dist/hub-*.js",
60
+ "dist/hub-server-*.js",
61
+ "dist/agent-client-*.js",
59
62
  "dist/web/",
60
63
  "README.md"
61
64
  ],