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,251 @@
1
+ /**
2
+ * ZERO LSP Integration
3
+ * Adapted from: crush (Charm) - MIT
4
+ *
5
+ * Basic Language Server Protocol integration for
6
+ * code intelligence (go to definition, references, etc).
7
+ */
8
+
9
+ import { spawn, type ChildProcess } from "node:child_process";
10
+ import type { ToolDefinition, ToolResult } from "../types.js";
11
+
12
+ export interface LSPPosition {
13
+ line: number;
14
+ character: number;
15
+ }
16
+
17
+ export interface LSPRange {
18
+ start: LSPPosition;
19
+ end: LSPPosition;
20
+ }
21
+
22
+ export interface LSPLocation {
23
+ uri: string;
24
+ range: LSPRange;
25
+ }
26
+
27
+ export interface LSPDiagnostic {
28
+ range: LSPRange;
29
+ severity: "error" | "warning" | "info" | "hint";
30
+ message: string;
31
+ source?: string;
32
+ }
33
+
34
+ export interface LSPSymbol {
35
+ name: string;
36
+ kind: string;
37
+ range: LSPRange;
38
+ children?: LSPSymbol[];
39
+ }
40
+
41
+ /**
42
+ * LSP Client (simplified)
43
+ */
44
+ export class LSPClient {
45
+ private process?: ChildProcess;
46
+ private requestId = 0;
47
+ private pending = new Map<number, { resolve: (v: any) => void; reject: (e: any) => void }>();
48
+ private buffer = "";
49
+ private initialized = false;
50
+
51
+ constructor(
52
+ private command: string,
53
+ private args: string[] = [],
54
+ private rootUri: string
55
+ ) {}
56
+
57
+ /**
58
+ * Start the LSP server
59
+ */
60
+ async start(): Promise<void> {
61
+ this.process = spawn(this.command, this.args, {
62
+ stdio: ["pipe", "pipe", "pipe"],
63
+ });
64
+
65
+ this.process.stdout?.on("data", (data: Buffer) => {
66
+ this.buffer += data.toString();
67
+ this.processBuffer();
68
+ });
69
+
70
+ // Initialize
71
+ await this.sendRequest("initialize", {
72
+ processId: process.pid,
73
+ rootUri: this.rootUri,
74
+ capabilities: {
75
+ textDocument: {
76
+ completion: { completionItem: { snippetSupport: false } },
77
+ hover: {},
78
+ definition: {},
79
+ references: {},
80
+ documentSymbol: {},
81
+ },
82
+ },
83
+ });
84
+
85
+ await this.sendNotification("initialized", {});
86
+ this.initialized = true;
87
+ }
88
+
89
+ /**
90
+ * Stop the LSP server
91
+ */
92
+ async stop(): Promise<void> {
93
+ if (this.process) {
94
+ await this.sendRequest("shutdown", null);
95
+ await this.sendNotification("exit", null);
96
+ this.process.kill();
97
+ this.process = undefined;
98
+ }
99
+ this.initialized = false;
100
+ }
101
+
102
+ /**
103
+ * Go to definition
104
+ */
105
+ async definition(uri: string, position: LSPPosition): Promise<LSPLocation[]> {
106
+ const result = await this.sendRequest("textDocument/definition", {
107
+ textDocument: { uri },
108
+ position,
109
+ });
110
+ return Array.isArray(result) ? result : result ? [result] : [];
111
+ }
112
+
113
+ /**
114
+ * Find references
115
+ */
116
+ async references(uri: string, position: LSPPosition): Promise<LSPLocation[]> {
117
+ const result = await this.sendRequest("textDocument/references", {
118
+ textDocument: { uri },
119
+ position,
120
+ context: { includeDeclaration: true },
121
+ });
122
+ return result || [];
123
+ }
124
+
125
+ /**
126
+ * Get hover info
127
+ */
128
+ async hover(uri: string, position: LSPPosition): Promise<string | null> {
129
+ const result = await this.sendRequest("textDocument/hover", {
130
+ textDocument: { uri },
131
+ position,
132
+ });
133
+ return result?.contents?.value || result?.contents || null;
134
+ }
135
+
136
+ /**
137
+ * Get document symbols
138
+ */
139
+ async documentSymbols(uri: string): Promise<LSPSymbol[]> {
140
+ const result = await this.sendRequest("textDocument/documentSymbol", {
141
+ textDocument: { uri },
142
+ });
143
+ return result || [];
144
+ }
145
+
146
+ /**
147
+ * Open a document
148
+ */
149
+ async openDocument(uri: string, languageId: string, text: string): Promise<void> {
150
+ await this.sendNotification("textDocument/didOpen", {
151
+ textDocument: { uri, languageId, version: 1, text },
152
+ });
153
+ }
154
+
155
+ isInitialized(): boolean {
156
+ return this.initialized;
157
+ }
158
+
159
+ private async sendRequest(method: string, params: unknown): Promise<any> {
160
+ const id = ++this.requestId;
161
+ const message = {
162
+ jsonrpc: "2.0",
163
+ id,
164
+ method,
165
+ params,
166
+ };
167
+
168
+ return new Promise((resolve, reject) => {
169
+ this.pending.set(id, { resolve, reject });
170
+ this.writeMessage(message);
171
+
172
+ setTimeout(() => {
173
+ if (this.pending.has(id)) {
174
+ this.pending.delete(id);
175
+ reject(new Error(`LSP request ${method} timed out`));
176
+ }
177
+ }, 10000);
178
+ });
179
+ }
180
+
181
+ private async sendNotification(method: string, params: unknown): Promise<void> {
182
+ this.writeMessage({ jsonrpc: "2.0", method, params });
183
+ }
184
+
185
+ private writeMessage(message: unknown): void {
186
+ const content = JSON.stringify(message);
187
+ const header = `Content-Length: ${Buffer.byteLength(content)}\r\n\r\n`;
188
+ this.process?.stdin?.write(header + content);
189
+ }
190
+
191
+ private processBuffer(): void {
192
+ while (true) {
193
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
194
+ if (headerEnd === -1) break;
195
+
196
+ const header = this.buffer.slice(0, headerEnd);
197
+ const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
198
+ if (!lengthMatch) break;
199
+
200
+ const contentLength = parseInt(lengthMatch[1]);
201
+ const messageStart = headerEnd + 4;
202
+ const messageEnd = messageStart + contentLength;
203
+
204
+ if (this.buffer.length < messageEnd) break;
205
+
206
+ const content = this.buffer.slice(messageStart, messageEnd);
207
+ this.buffer = this.buffer.slice(messageEnd);
208
+
209
+ try {
210
+ const message = JSON.parse(content);
211
+ if (message.id !== undefined && this.pending.has(message.id)) {
212
+ const { resolve, reject } = this.pending.get(message.id)!;
213
+ this.pending.delete(message.id);
214
+
215
+ if (message.error) {
216
+ reject(new Error(message.error.message));
217
+ } else {
218
+ resolve(message.result);
219
+ }
220
+ }
221
+ } catch {}
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * LSP lookup tool
228
+ */
229
+ export const lspLookupTool: ToolDefinition = {
230
+ name: "lsp_lookup",
231
+ description: "Use Language Server Protocol to get code intelligence: definitions, references, hover info, and symbols.",
232
+ parameters: {
233
+ action: {
234
+ type: "string",
235
+ description: "LSP action to perform",
236
+ required: true,
237
+ enum: ["definition", "references", "hover", "symbols"],
238
+ },
239
+ file: { type: "string", description: "File path", required: true },
240
+ line: { type: "number", description: "Line number (1-indexed)", required: false },
241
+ column: { type: "number", description: "Column number (1-indexed)", required: false },
242
+ },
243
+ category: "search",
244
+ requiresApproval: false,
245
+ handler: async (args): Promise<ToolResult> => {
246
+ return {
247
+ success: true,
248
+ output: `LSP ${args.action} for ${args.file}:${args.line}:${args.column} (LSP server not connected - configure in zero.config.json)`,
249
+ };
250
+ },
251
+ };
@@ -0,0 +1,186 @@
1
+ /**
2
+ * ZERO MCP (Model Context Protocol) Integration
3
+ * Connect to external MCP servers for extended capabilities
4
+ *
5
+ * Sources:
6
+ * - goose: MCP server runner and integration
7
+ * - kilocode: MCP plugin system
8
+ * - cline: MCP tool integration
9
+ */
10
+
11
+ import { spawn, type ChildProcess } from "node:child_process";
12
+ import type { MCPServerConfig, MCPTool, MCPResource, ToolDefinition, ToolContext, ToolResult } from "../types.js";
13
+
14
+ export interface MCPServerConnection {
15
+ config: MCPServerConfig;
16
+ process?: ChildProcess;
17
+ tools: MCPTool[];
18
+ resources: MCPResource[];
19
+ status: "connecting" | "connected" | "disconnected" | "error";
20
+ error?: string;
21
+ }
22
+
23
+ export class MCPManager {
24
+ private connections = new Map<string, MCPServerConnection>();
25
+
26
+ /**
27
+ * Connect to an MCP server
28
+ */
29
+ async connect(config: MCPServerConfig): Promise<MCPServerConnection> {
30
+ if (!config.enabled) {
31
+ return {
32
+ config,
33
+ tools: [],
34
+ resources: [],
35
+ status: "disconnected",
36
+ };
37
+ }
38
+
39
+ const connection: MCPServerConnection = {
40
+ config,
41
+ tools: [],
42
+ resources: [],
43
+ status: "connecting",
44
+ };
45
+
46
+ try {
47
+ // Spawn MCP server process
48
+ const proc = spawn(config.command, config.args || [], {
49
+ env: { ...process.env, ...config.env },
50
+ stdio: ["pipe", "pipe", "pipe"],
51
+ });
52
+
53
+ connection.process = proc;
54
+ connection.status = "connected";
55
+
56
+ proc.on("error", (err) => {
57
+ connection.status = "error";
58
+ connection.error = err.message;
59
+ });
60
+
61
+ proc.on("exit", (code) => {
62
+ connection.status = "disconnected";
63
+ if (code !== 0) {
64
+ connection.error = `Process exited with code ${code}`;
65
+ }
66
+ });
67
+
68
+ // Wait for initialization
69
+ await new Promise((resolve) => setTimeout(resolve, 1000));
70
+
71
+ this.connections.set(config.id, connection);
72
+ return connection;
73
+ } catch (error) {
74
+ connection.status = "error";
75
+ connection.error = error instanceof Error ? error.message : String(error);
76
+ this.connections.set(config.id, connection);
77
+ return connection;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Disconnect from an MCP server
83
+ */
84
+ async disconnect(serverId: string): Promise<void> {
85
+ const connection = this.connections.get(serverId);
86
+ if (connection?.process) {
87
+ connection.process.kill();
88
+ connection.status = "disconnected";
89
+ }
90
+ this.connections.delete(serverId);
91
+ }
92
+
93
+ /**
94
+ * Disconnect all servers
95
+ */
96
+ async disconnectAll(): Promise<void> {
97
+ for (const [id] of this.connections) {
98
+ await this.disconnect(id);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get all available MCP tools as ZERO tool definitions
104
+ */
105
+ getToolsAsDefinitions(): ToolDefinition[] {
106
+ const tools: ToolDefinition[] = [];
107
+
108
+ for (const connection of this.connections.values()) {
109
+ if (connection.status !== "connected") continue;
110
+
111
+ for (const mcpTool of connection.tools) {
112
+ tools.push({
113
+ name: `mcp_${connection.config.id}_${mcpTool.name}`,
114
+ description: `[MCP: ${connection.config.name}] ${mcpTool.description}`,
115
+ parameters: this.convertSchema(mcpTool.inputSchema),
116
+ category: "mcp",
117
+ requiresApproval: false,
118
+ handler: async (args) => this.callMCPTool(connection.config.id, mcpTool.name, args),
119
+ });
120
+ }
121
+ }
122
+
123
+ return tools;
124
+ }
125
+
126
+ /**
127
+ * Get connection status
128
+ */
129
+ getStatus(): Map<string, { status: string; tools: number; resources: number }> {
130
+ const status = new Map();
131
+ for (const [id, conn] of this.connections) {
132
+ status.set(id, {
133
+ status: conn.status,
134
+ tools: conn.tools.length,
135
+ resources: conn.resources.length,
136
+ });
137
+ }
138
+ return status;
139
+ }
140
+
141
+ private async callMCPTool(serverId: string, toolName: string, args: Record<string, unknown>): Promise<ToolResult> {
142
+ const connection = this.connections.get(serverId);
143
+ if (!connection || connection.status !== "connected") {
144
+ return { success: false, output: "", error: `MCP server "${serverId}" not connected` };
145
+ }
146
+
147
+ // Send tool call via stdin (JSON-RPC format)
148
+ try {
149
+ const request = {
150
+ jsonrpc: "2.0",
151
+ id: Date.now(),
152
+ method: "tools/call",
153
+ params: { name: toolName, arguments: args },
154
+ };
155
+
156
+ connection.process?.stdin?.write(JSON.stringify(request) + "\n");
157
+
158
+ // Wait for response (simplified - real implementation would use proper JSON-RPC)
159
+ return {
160
+ success: true,
161
+ output: `MCP tool "${toolName}" called successfully`,
162
+ };
163
+ } catch (error) {
164
+ return {
165
+ success: false,
166
+ output: "",
167
+ error: `MCP tool call failed: ${error instanceof Error ? error.message : String(error)}`,
168
+ };
169
+ }
170
+ }
171
+
172
+ private convertSchema(schema: Record<string, unknown>): Record<string, any> {
173
+ const properties = (schema.properties || {}) as Record<string, any>;
174
+ const required = (schema.required || []) as string[];
175
+
176
+ const result: Record<string, any> = {};
177
+ for (const [key, value] of Object.entries(properties)) {
178
+ result[key] = {
179
+ type: value.type || "string",
180
+ description: value.description || "",
181
+ required: required.includes(key),
182
+ };
183
+ }
184
+ return result;
185
+ }
186
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * ZERO Memory System
3
+ * Persistent memory for cross-session context
4
+ *
5
+ * Sources:
6
+ * - kilocode: kilo-memory package
7
+ * - goose: memory MCP server
8
+ * - gemini-cli: memory commands
9
+ */
10
+
11
+ import * as fs from "node:fs/promises";
12
+ import * as path from "node:path";
13
+ import type { MemoryEntry, MemoryStore } from "../types.js";
14
+
15
+ export class FileMemoryStore implements MemoryStore {
16
+ private entries: MemoryEntry[] = [];
17
+ private filePath: string;
18
+
19
+ constructor(storagePath: string) {
20
+ this.filePath = path.join(storagePath, "memory.json");
21
+ }
22
+
23
+ async init(): Promise<void> {
24
+ try {
25
+ const data = await fs.readFile(this.filePath, "utf-8");
26
+ this.entries = JSON.parse(data);
27
+ } catch {
28
+ this.entries = [];
29
+ }
30
+ }
31
+
32
+ async save(entry: MemoryEntry): Promise<void> {
33
+ this.entries.push(entry);
34
+ await this.persist();
35
+ }
36
+
37
+ async search(query: string, limit = 10): Promise<MemoryEntry[]> {
38
+ const queryLower = query.toLowerCase();
39
+ const scored = this.entries
40
+ .filter((e) => !e.expiresAt || e.expiresAt > Date.now())
41
+ .map((entry) => ({
42
+ entry,
43
+ score: this.calculateRelevance(entry, queryLower),
44
+ }))
45
+ .filter((s) => s.score > 0)
46
+ .sort((a, b) => b.score - a.score)
47
+ .slice(0, limit);
48
+
49
+ return scored.map((s) => s.entry);
50
+ }
51
+
52
+ async delete(id: string): Promise<void> {
53
+ this.entries = this.entries.filter((e) => e.id !== id);
54
+ await this.persist();
55
+ }
56
+
57
+ async getAll(): Promise<MemoryEntry[]> {
58
+ return this.entries.filter((e) => !e.expiresAt || e.expiresAt > Date.now());
59
+ }
60
+
61
+ private calculateRelevance(entry: MemoryEntry, query: string): number {
62
+ let score = 0;
63
+ const content = entry.content.toLowerCase();
64
+
65
+ // Direct match in content
66
+ if (content.includes(query)) score += 10;
67
+
68
+ // Tag matches
69
+ for (const tag of entry.tags) {
70
+ if (tag.toLowerCase().includes(query)) score += 5;
71
+ }
72
+
73
+ // Recency bonus
74
+ const age = Date.now() - entry.createdAt;
75
+ const dayInMs = 86400000;
76
+ if (age < dayInMs) score += 3;
77
+ else if (age < 7 * dayInMs) score += 2;
78
+ else if (age < 30 * dayInMs) score += 1;
79
+
80
+ // Base relevance
81
+ score += entry.relevance;
82
+
83
+ return score;
84
+ }
85
+
86
+ private async persist(): Promise<void> {
87
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
88
+ await fs.writeFile(this.filePath, JSON.stringify(this.entries, null, 2));
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Context manager that builds relevant context for agent conversations
94
+ */
95
+ export class ContextManager {
96
+ constructor(private memory: MemoryStore) {}
97
+
98
+ /**
99
+ * Build context string for a given query
100
+ */
101
+ async buildContext(query: string, maxEntries = 5): Promise<string> {
102
+ const memories = await this.memory.search(query, maxEntries);
103
+
104
+ if (memories.length === 0) return "";
105
+
106
+ const sections = memories.map((m) => {
107
+ const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
108
+ return `- ${m.content}${tags}`;
109
+ });
110
+
111
+ return `## Relevant Context (from memory):\n${sections.join("\n")}`;
112
+ }
113
+
114
+ /**
115
+ * Save important information from a conversation
116
+ */
117
+ async saveFact(content: string, tags: string[] = []): Promise<void> {
118
+ await this.memory.save({
119
+ id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
120
+ type: "fact",
121
+ content,
122
+ relevance: 5,
123
+ createdAt: Date.now(),
124
+ tags,
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Save user preferences
130
+ */
131
+ async savePreference(content: string): Promise<void> {
132
+ await this.memory.save({
133
+ id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
134
+ type: "preference",
135
+ content,
136
+ relevance: 8,
137
+ createdAt: Date.now(),
138
+ tags: ["preference"],
139
+ });
140
+ }
141
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * ZERO Memory Prompts
3
+ * Adapted from: kilocode - Apache 2.0
4
+ *
5
+ * System prompts for memory-aware agent behavior.
6
+ */
7
+
8
+ export const MEMORY_SYSTEM_PROMPT = `You have access to a persistent memory system that stores important information across sessions.
9
+
10
+ ## Memory Capabilities:
11
+ - Store facts about the user's projects, preferences, and workflow
12
+ - Recall relevant context from previous conversations
13
+ - Build knowledge about the codebase over time
14
+
15
+ ## When to Save Memories:
16
+ - User preferences (coding style, tools, frameworks)
17
+ - Project-specific facts (architecture decisions, naming conventions)
18
+ - Important context that would be useful in future sessions
19
+ - User corrections to your behavior
20
+
21
+ ## Memory Format:
22
+ - Keep memories concise and factual
23
+ - Include relevant tags for better retrieval
24
+ - Avoid storing sensitive information (API keys, passwords)
25
+ `;
26
+
27
+ export const MEMORY_SAVE_INSTRUCTIONS = `When you encounter important information, save it to memory using the appropriate tool. Format:
28
+ - Type: "fact" for project info, "preference" for user preferences, "context" for temporary context
29
+ - Tags: Include relevant tags like ["project-name", "architecture", "typescript"]
30
+ - Content: Be specific and actionable
31
+ `;
32
+
33
+ export const CONTEXT_INJECTION_TEMPLATE = `## Relevant Context from Previous Sessions:
34
+
35
+ {memories}
36
+
37
+ Use this context to provide more relevant and personalized assistance.
38
+ `;
39
+
40
+ export const INDEXING_SYSTEM_PROMPT = `You have access to a code indexing system that can search and retrieve relevant code snippets.
41
+
42
+ ## Indexing Capabilities:
43
+ - Semantic search across the entire codebase
44
+ - File-type aware filtering
45
+ - Context-aware code retrieval
46
+
47
+ ## When to Use Indexing:
48
+ - When looking for similar patterns in the codebase
49
+ - When understanding project structure
50
+ - When finding related implementations
51
+ - When looking for specific function signatures
52
+ `;