zero-ai-cli 1.0.0

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.
Files changed (44) hide show
  1. package/MERGE_REPORT.md +265 -0
  2. package/README.md +127 -0
  3. package/package.json +55 -0
  4. package/packages/cli/src/index.ts +271 -0
  5. package/packages/cli/src/interactive.ts +190 -0
  6. package/packages/cli/src/once.ts +50 -0
  7. package/packages/core/src/agent/config-loader.ts +174 -0
  8. package/packages/core/src/agent/engine.ts +266 -0
  9. package/packages/core/src/agent/registry-tools.ts +58 -0
  10. package/packages/core/src/agent/registry.ts +213 -0
  11. package/packages/core/src/commands/index.ts +116 -0
  12. package/packages/core/src/config/index.ts +139 -0
  13. package/packages/core/src/confirmation/message-bus.ts +206 -0
  14. package/packages/core/src/diff/index.ts +232 -0
  15. package/packages/core/src/diff-detect/index.ts +207 -0
  16. package/packages/core/src/edit-coder/index.ts +194 -0
  17. package/packages/core/src/events/index.ts +151 -0
  18. package/packages/core/src/file-tracker/index.ts +183 -0
  19. package/packages/core/src/git/index.ts +305 -0
  20. package/packages/core/src/history/index.ts +236 -0
  21. package/packages/core/src/image/index.ts +131 -0
  22. package/packages/core/src/index.ts +131 -0
  23. package/packages/core/src/lsp/index.ts +251 -0
  24. package/packages/core/src/mcp/index.ts +186 -0
  25. package/packages/core/src/memory/index.ts +141 -0
  26. package/packages/core/src/memory/prompts.ts +52 -0
  27. package/packages/core/src/orchestrator/protocol.ts +140 -0
  28. package/packages/core/src/orchestrator/server.ts +319 -0
  29. package/packages/core/src/output/index.ts +164 -0
  30. package/packages/core/src/permissions/index.ts +126 -0
  31. package/packages/core/src/prompts/engine.ts +188 -0
  32. package/packages/core/src/providers/registry.ts +687 -0
  33. package/packages/core/src/routing/model-router.ts +160 -0
  34. package/packages/core/src/server/index.ts +232 -0
  35. package/packages/core/src/session/index.ts +174 -0
  36. package/packages/core/src/session/service.ts +322 -0
  37. package/packages/core/src/skills/index.ts +205 -0
  38. package/packages/core/src/streaming/index.ts +166 -0
  39. package/packages/core/src/sub-agent/index.ts +179 -0
  40. package/packages/core/src/tools/builtin.ts +568 -0
  41. package/packages/core/src/types.ts +356 -0
  42. package/packages/sdk/src/index.ts +247 -0
  43. package/packages/tui/src/index.ts +141 -0
  44. package/tsconfig.json +26 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * ZERO Orchestrator IPC Protocol
3
+ * Adapted from: pi (earendil-works) - AGPL-3.0
4
+ *
5
+ * Inter-process communication protocol for multi-agent orchestration.
6
+ * Enables spawning, managing, and communicating with agent instances.
7
+ */
8
+
9
+ export interface SpawnRequest {
10
+ type: "spawn";
11
+ cwd: string;
12
+ label?: string;
13
+ provider?: string;
14
+ model?: string;
15
+ agentId?: string;
16
+ }
17
+
18
+ export interface ListRequest {
19
+ type: "list";
20
+ }
21
+
22
+ export interface StopRequest {
23
+ type: "stop";
24
+ instanceId: string;
25
+ }
26
+
27
+ export interface StatusRequest {
28
+ type: "status";
29
+ instanceId: string;
30
+ }
31
+
32
+ export interface RpcRequest {
33
+ type: "rpc";
34
+ instanceId: string;
35
+ command: RpcCommand;
36
+ }
37
+
38
+ export interface RpcStreamRequest {
39
+ type: "rpc_stream";
40
+ instanceId: string;
41
+ }
42
+
43
+ export interface RequestMap {
44
+ spawn: SpawnRequest;
45
+ list: ListRequest;
46
+ stop: StopRequest;
47
+ status: StatusRequest;
48
+ rpc: RpcRequest;
49
+ rpc_stream: RpcStreamRequest;
50
+ }
51
+
52
+ export type OrchestratorRequest = RequestMap[keyof RequestMap];
53
+
54
+ export type InstanceStatus = "starting" | "running" | "stopped" | "error";
55
+
56
+ export interface InstanceSummary {
57
+ id: string;
58
+ status: InstanceStatus;
59
+ cwd: string;
60
+ label?: string;
61
+ sessionId?: string;
62
+ agentId?: string;
63
+ model?: string;
64
+ createdAt: number;
65
+ }
66
+
67
+ export interface ResponseBase {
68
+ ok: boolean;
69
+ error?: string;
70
+ }
71
+
72
+ export interface SpawnResponse extends ResponseBase {
73
+ type: "spawn_result";
74
+ instance?: InstanceSummary;
75
+ }
76
+
77
+ export interface ListResponse extends ResponseBase {
78
+ type: "list_result";
79
+ instances?: InstanceSummary[];
80
+ }
81
+
82
+ export interface StopResponse extends ResponseBase {
83
+ type: "stop_result";
84
+ instanceId?: string;
85
+ }
86
+
87
+ export interface StatusResponse extends ResponseBase {
88
+ type: "status_result";
89
+ instance?: InstanceSummary;
90
+ }
91
+
92
+ export interface RpcResponse {
93
+ type: "rpc_result";
94
+ data: unknown;
95
+ }
96
+
97
+ export interface RpcBridgeResponse extends ResponseBase {
98
+ type: "rpc_result";
99
+ response: RpcResponse;
100
+ }
101
+
102
+ export interface RpcReadyResponse extends ResponseBase {
103
+ type: "rpc_ready";
104
+ instance?: InstanceSummary;
105
+ }
106
+
107
+ export interface ErrorResponse extends ResponseBase {
108
+ type: "error";
109
+ ok: false;
110
+ error: string;
111
+ }
112
+
113
+ export interface ResponseMap {
114
+ spawn: SpawnResponse;
115
+ list: ListResponse;
116
+ stop: StopResponse;
117
+ status: StatusResponse;
118
+ rpc: RpcBridgeResponse;
119
+ rpc_stream: RpcReadyResponse;
120
+ }
121
+
122
+ export type OrchestratorResponse = ResponseMap[keyof ResponseMap] | ErrorResponse;
123
+
124
+ export type RpcCommand =
125
+ | { type: "chat"; message: string }
126
+ | { type: "abort" }
127
+ | { type: "get_messages" }
128
+ | { type: "get_state" };
129
+
130
+ export function encodeMessage(message: OrchestratorRequest | OrchestratorResponse): string {
131
+ return `${JSON.stringify(message)}\n`;
132
+ }
133
+
134
+ export function parseRequestLine(line: string): OrchestratorRequest {
135
+ return JSON.parse(line) as OrchestratorRequest;
136
+ }
137
+
138
+ export function parseResponseLine(line: string): OrchestratorResponse {
139
+ return JSON.parse(line) as OrchestratorResponse;
140
+ }
@@ -0,0 +1,319 @@
1
+ /**
2
+ * ZERO Orchestrator Server
3
+ * Adapted from: pi (earendil-works) - AGPL-3.0
4
+ *
5
+ * IPC server for managing multiple agent instances.
6
+ */
7
+
8
+ import { createConnection, createServer, type Server } from "node:net";
9
+ import * as path from "node:path";
10
+ import * as fs from "node:fs";
11
+ import * as os from "node:os";
12
+ import type {
13
+ OrchestratorRequest,
14
+ OrchestratorResponse,
15
+ InstanceSummary,
16
+ InstanceStatus,
17
+ SpawnRequest,
18
+ ListRequest,
19
+ StopRequest,
20
+ StatusRequest,
21
+ RpcRequest,
22
+ ErrorResponse,
23
+ SpawnResponse,
24
+ ListResponse,
25
+ StopResponse,
26
+ StatusResponse,
27
+ RpcBridgeResponse,
28
+ RpcReadyResponse,
29
+ RpcCommand,
30
+ } from "./protocol.js";
31
+ import { encodeMessage, parseRequestLine } from "./protocol.js";
32
+
33
+ interface AgentInstance {
34
+ id: string;
35
+ status: InstanceStatus;
36
+ cwd: string;
37
+ label?: string;
38
+ sessionId?: string;
39
+ agentId?: string;
40
+ model?: string;
41
+ createdAt: number;
42
+ messages: Array<{ role: string; content: string; timestamp: number }>;
43
+ }
44
+
45
+ export class OrchestratorServer {
46
+ private server?: Server;
47
+ private instances = new Map<string, AgentInstance>();
48
+ private socketPath: string;
49
+
50
+ constructor(socketPath?: string) {
51
+ this.socketPath = socketPath || path.join(os.tmpdir(), `zero-orchestrator-${process.pid}.sock`);
52
+ }
53
+
54
+ async start(): Promise<void> {
55
+ // Remove stale socket
56
+ try {
57
+ if (fs.existsSync(this.socketPath)) {
58
+ fs.unlinkSync(this.socketPath);
59
+ }
60
+ } catch {}
61
+
62
+ return new Promise((resolve, reject) => {
63
+ this.server = createServer((socket) => {
64
+ let buffer = "";
65
+
66
+ socket.on("data", async (chunk: Buffer | string) => {
67
+ buffer += chunk.toString();
68
+ const newlineIndex = buffer.indexOf("\n");
69
+ if (newlineIndex === -1) return;
70
+
71
+ const line = buffer.slice(0, newlineIndex).trim();
72
+ buffer = buffer.slice(newlineIndex + 1);
73
+ if (!line) return;
74
+
75
+ try {
76
+ const request = parseRequestLine(line);
77
+ const response = await this.handleRequest(request);
78
+ socket.write(encodeMessage(response));
79
+ } catch (error) {
80
+ const errorResponse: ErrorResponse = {
81
+ type: "error",
82
+ ok: false,
83
+ error: error instanceof Error ? error.message : String(error),
84
+ };
85
+ socket.write(encodeMessage(errorResponse));
86
+ }
87
+ });
88
+
89
+ socket.on("error", () => {});
90
+ });
91
+
92
+ this.server.listen(this.socketPath, () => resolve());
93
+ this.server.on("error", reject);
94
+ });
95
+ }
96
+
97
+ async stop(): Promise<void> {
98
+ return new Promise((resolve) => {
99
+ if (this.server) {
100
+ this.server.close(() => {
101
+ try { fs.unlinkSync(this.socketPath); } catch {}
102
+ resolve();
103
+ });
104
+ } else {
105
+ resolve();
106
+ }
107
+ });
108
+ }
109
+
110
+ private async handleRequest(request: OrchestratorRequest): Promise<OrchestratorResponse> {
111
+ switch (request.type) {
112
+ case "spawn": return this.handleSpawn(request);
113
+ case "list": return this.handleList();
114
+ case "stop": return this.handleStop(request);
115
+ case "status": return this.handleStatus(request);
116
+ case "rpc": return this.handleRpc(request);
117
+ default:
118
+ return { type: "error", ok: false, error: `Unknown request type: ${(request as any).type}` };
119
+ }
120
+ }
121
+
122
+ private async handleSpawn(request: SpawnRequest): Promise<SpawnResponse> {
123
+ const id = `instance_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
124
+ const instance: AgentInstance = {
125
+ id,
126
+ status: "running",
127
+ cwd: request.cwd || process.cwd(),
128
+ label: request.label,
129
+ agentId: request.agentId || "coder",
130
+ model: request.model || "default",
131
+ createdAt: Date.now(),
132
+ messages: [],
133
+ };
134
+
135
+ this.instances.set(id, instance);
136
+
137
+ return {
138
+ type: "spawn_result",
139
+ ok: true,
140
+ instance: {
141
+ id: instance.id,
142
+ status: instance.status,
143
+ cwd: instance.cwd,
144
+ label: instance.label,
145
+ agentId: instance.agentId,
146
+ model: instance.model,
147
+ createdAt: instance.createdAt,
148
+ },
149
+ };
150
+ }
151
+
152
+ private async handleList(): Promise<ListResponse> {
153
+ const instances: InstanceSummary[] = Array.from(this.instances.values()).map((i) => ({
154
+ id: i.id,
155
+ status: i.status,
156
+ cwd: i.cwd,
157
+ label: i.label,
158
+ agentId: i.agentId,
159
+ model: i.model,
160
+ createdAt: i.createdAt,
161
+ }));
162
+
163
+ return { type: "list_result", ok: true, instances };
164
+ }
165
+
166
+ private async handleStop(request: StopRequest): Promise<StopResponse> {
167
+ const instance = this.instances.get(request.instanceId);
168
+ if (!instance) {
169
+ return { type: "stop_result", ok: false, error: `Instance ${request.instanceId} not found` };
170
+ }
171
+
172
+ instance.status = "stopped";
173
+ this.instances.delete(request.instanceId);
174
+
175
+ return { type: "stop_result", ok: true, instanceId: request.instanceId };
176
+ }
177
+
178
+ private async handleStatus(request: StatusRequest): Promise<StatusResponse> {
179
+ const instance = this.instances.get(request.instanceId);
180
+ if (!instance) {
181
+ return { type: "status_result", ok: false, error: `Instance ${request.instanceId} not found` };
182
+ }
183
+
184
+ return {
185
+ type: "status_result",
186
+ ok: true,
187
+ instance: {
188
+ id: instance.id,
189
+ status: instance.status,
190
+ cwd: instance.cwd,
191
+ label: instance.label,
192
+ agentId: instance.agentId,
193
+ model: instance.model,
194
+ createdAt: instance.createdAt,
195
+ },
196
+ };
197
+ }
198
+
199
+ private async handleRpc(request: RpcRequest): Promise<RpcBridgeResponse> {
200
+ const instance = this.instances.get(request.instanceId);
201
+ if (!instance) {
202
+ return { type: "rpc_result", ok: false, error: `Instance ${request.instanceId} not found`, response: { type: "rpc_result", data: null } };
203
+ }
204
+
205
+ // Handle RPC commands
206
+ switch (request.command.type) {
207
+ case "chat":
208
+ instance.messages.push({
209
+ role: "user",
210
+ content: request.command.message,
211
+ timestamp: Date.now(),
212
+ });
213
+ return {
214
+ type: "rpc_result",
215
+ ok: true,
216
+ response: { type: "rpc_result", data: { queued: true } },
217
+ };
218
+
219
+ case "get_messages":
220
+ return {
221
+ type: "rpc_result",
222
+ ok: true,
223
+ response: { type: "rpc_result", data: instance.messages },
224
+ };
225
+
226
+ case "get_state":
227
+ return {
228
+ type: "rpc_result",
229
+ ok: true,
230
+ response: { type: "rpc_result", data: { status: instance.status, messageCount: instance.messages.length } },
231
+ };
232
+
233
+ default:
234
+ return { type: "rpc_result", ok: false, error: "Unknown RPC command", response: { type: "rpc_result", data: null } };
235
+ }
236
+ }
237
+
238
+ getSocketPath(): string {
239
+ return this.socketPath;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * IPC Client for connecting to orchestrator
245
+ */
246
+ export class OrchestratorClient {
247
+ private socket?: ReturnType<typeof createConnection>;
248
+ private buffer = "";
249
+ private responseHandlers = new Map<string, (response: OrchestratorResponse) => void>();
250
+
251
+ constructor(private socketPath: string) {}
252
+
253
+ async connect(): Promise<void> {
254
+ return new Promise((resolve, reject) => {
255
+ this.socket = createConnection(this.socketPath, () => resolve());
256
+ this.socket.on("error", reject);
257
+ this.socket.on("data", (chunk: Buffer) => {
258
+ this.buffer += chunk.toString();
259
+ const lines = this.buffer.split("\n");
260
+ this.buffer = lines.pop() || "";
261
+
262
+ for (const line of lines) {
263
+ if (!line.trim()) continue;
264
+ try {
265
+ const response = JSON.parse(line) as OrchestratorResponse;
266
+ // Broadcast to all handlers
267
+ for (const handler of this.responseHandlers.values()) {
268
+ handler(response);
269
+ }
270
+ } catch {}
271
+ }
272
+ });
273
+ });
274
+ }
275
+
276
+ async send(request: OrchestratorRequest): Promise<OrchestratorResponse> {
277
+ return new Promise((resolve) => {
278
+ const id = Math.random().toString(36).slice(2);
279
+ this.responseHandlers.set(id, (response) => {
280
+ this.responseHandlers.delete(id);
281
+ resolve(response);
282
+ });
283
+
284
+ this.socket?.write(encodeMessage(request));
285
+
286
+ // Timeout fallback
287
+ setTimeout(() => {
288
+ if (this.responseHandlers.has(id)) {
289
+ this.responseHandlers.delete(id);
290
+ resolve({ type: "error", ok: false, error: "Timeout" });
291
+ }
292
+ }, 30000);
293
+ });
294
+ }
295
+
296
+ async spawn(request: Omit<SpawnRequest, "type">): Promise<SpawnResponse> {
297
+ return (await this.send({ ...request, type: "spawn" })) as SpawnResponse;
298
+ }
299
+
300
+ async list(): Promise<ListResponse> {
301
+ return (await this.send({ type: "list" })) as ListResponse;
302
+ }
303
+
304
+ async stop(instanceId: string): Promise<StopResponse> {
305
+ return (await this.send({ type: "stop", instanceId })) as StopResponse;
306
+ }
307
+
308
+ async status(instanceId: string): Promise<StatusResponse> {
309
+ return (await this.send({ type: "status", instanceId })) as StatusResponse;
310
+ }
311
+
312
+ async rpc(instanceId: string, command: RpcCommand): Promise<RpcBridgeResponse> {
313
+ return (await this.send({ type: "rpc", instanceId, command })) as RpcBridgeResponse;
314
+ }
315
+
316
+ disconnect(): void {
317
+ this.socket?.end();
318
+ }
319
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * ZERO Output Formatting
3
+ * Adapted from: qwen-code (Alibaba) - MIT
4
+ *
5
+ * Output formatting for JSON, streaming, and markdown responses.
6
+ */
7
+
8
+ import type { Message, TokenUsage, AgentResult } from "../types.js";
9
+
10
+ // ============================================================================
11
+ // JSON Output Formatter
12
+ // ============================================================================
13
+
14
+ export interface JSONOutput {
15
+ version: string;
16
+ timestamp: string;
17
+ result: {
18
+ success: boolean;
19
+ messages: Array<{
20
+ role: string;
21
+ content: string;
22
+ timestamp: number;
23
+ }>;
24
+ usage: TokenUsage;
25
+ duration: number;
26
+ error?: string;
27
+ };
28
+ }
29
+
30
+ export function formatJSON(result: AgentResult): JSONOutput {
31
+ return {
32
+ version: "1.0",
33
+ timestamp: new Date().toISOString(),
34
+ result: {
35
+ success: result.success,
36
+ messages: result.messages.map((m) => ({
37
+ role: m.role,
38
+ content: m.content
39
+ .filter((c) => c.type === "text")
40
+ .map((c) => (c as any).text)
41
+ .join(""),
42
+ timestamp: m.timestamp,
43
+ })),
44
+ usage: result.tokenUsage,
45
+ duration: result.duration,
46
+ error: result.error,
47
+ },
48
+ };
49
+ }
50
+
51
+ // ============================================================================
52
+ // Streaming Output Formatter
53
+ // ============================================================================
54
+
55
+ export interface StreamEvent {
56
+ type: "text" | "tool_call" | "tool_result" | "error" | "done";
57
+ data: unknown;
58
+ timestamp: number;
59
+ }
60
+
61
+ export function createStreamFormatter(): {
62
+ format: (event: StreamEvent) => string;
63
+ } {
64
+ return {
65
+ format: (event: StreamEvent) => {
66
+ return JSON.stringify(event) + "\n";
67
+ },
68
+ };
69
+ }
70
+
71
+ // ============================================================================
72
+ // Markdown Output Formatter
73
+ // ============================================================================
74
+
75
+ export function formatMarkdown(result: AgentResult): string {
76
+ const lines: string[] = [];
77
+
78
+ lines.push("# ZERO Agent Result");
79
+ lines.push("");
80
+ lines.push(`**Status:** ${result.success ? "✅ Success" : "❌ Failed"}`);
81
+ lines.push(`**Duration:** ${(result.duration / 1000).toFixed(1)}s`);
82
+ lines.push(`**Tokens:** ${result.tokenUsage.inputTokens} input / ${result.tokenUsage.outputTokens} output`);
83
+ lines.push("");
84
+
85
+ if (result.error) {
86
+ lines.push(`**Error:** ${result.error}`);
87
+ lines.push("");
88
+ }
89
+
90
+ lines.push("## Messages");
91
+ lines.push("");
92
+
93
+ for (const msg of result.messages) {
94
+ if (msg.role === "user") {
95
+ lines.push(`### 👤 User`);
96
+ } else if (msg.role === "assistant") {
97
+ lines.push(`### 🤖 Assistant`);
98
+ } else if (msg.role === "tool") {
99
+ lines.push(`### 🔧 Tool Result`);
100
+ } else {
101
+ lines.push(`### ${msg.role}`);
102
+ }
103
+
104
+ for (const content of msg.content) {
105
+ if (content.type === "text") {
106
+ lines.push(content.text);
107
+ } else if (content.type === "tool_call") {
108
+ lines.push(`\`\`\`tool\n${content.name}(${JSON.stringify(content.arguments, null, 2)})\n\`\`\``);
109
+ } else if (content.type === "tool_result") {
110
+ lines.push(`\`\`\`result\n${typeof content.result === "string" ? content.result : JSON.stringify(content.result, null, 2)}\n\`\`\``);
111
+ }
112
+ }
113
+
114
+ lines.push("");
115
+ }
116
+
117
+ return lines.join("\n");
118
+ }
119
+
120
+ // ============================================================================
121
+ // Plain Text Formatter
122
+ // ============================================================================
123
+
124
+ export function formatPlainText(result: AgentResult): string {
125
+ const lines: string[] = [];
126
+
127
+ for (const msg of result.messages) {
128
+ if (msg.role === "assistant") {
129
+ for (const content of msg.content) {
130
+ if (content.type === "text") {
131
+ lines.push(content.text);
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ return lines.join("\n");
138
+ }
139
+
140
+ // ============================================================================
141
+ // Cost Calculator
142
+ // ============================================================================
143
+
144
+ const MODEL_COSTS: Record<string, { input: number; output: number }> = {
145
+ "gpt-4o": { input: 0.000005, output: 0.000015 },
146
+ "gpt-4o-mini": { input: 0.00000015, output: 0.0000006 },
147
+ "claude-sonnet-4-20250514": { input: 0.000003, output: 0.000015 },
148
+ "claude-3-5-haiku-20241022": { input: 0.000001, output: 0.000005 },
149
+ "gemini-2.5-pro": { input: 0.00000125, output: 0.00001 },
150
+ "gemini-2.5-flash": { input: 0.00000015, output: 0.0000006 },
151
+ "deepseek-v4-pro": { input: 0.00000027, output: 0.0000011 },
152
+ "grok-4.5": { input: 0.000003, output: 0.000015 },
153
+ };
154
+
155
+ export function calculateCost(model: string, usage: TokenUsage): number {
156
+ const costs = MODEL_COSTS[model] || { input: 0.000005, output: 0.000015 };
157
+ return (usage.inputTokens * costs.input) + (usage.outputTokens * costs.output);
158
+ }
159
+
160
+ export function formatCost(cost: number): string {
161
+ if (cost < 0.001) return `$${cost.toFixed(6)}`;
162
+ if (cost < 1) return `$${cost.toFixed(4)}`;
163
+ return `$${cost.toFixed(2)}`;
164
+ }