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,236 @@
1
+ /**
2
+ * ZERO History Manager
3
+ * Adapted from: aider (Python) - Apache 2.0
4
+ *
5
+ * Manages conversation history with context window optimization,
6
+ * summarization, and persistent storage.
7
+ */
8
+
9
+ import * as fs from "node:fs/promises";
10
+ import * as path from "node:path";
11
+ import type { Message, TokenUsage } from "../types.js";
12
+
13
+ export interface HistoryEntry {
14
+ sessionId: string;
15
+ messages: Message[];
16
+ totalTokens: TokenUsage;
17
+ createdAt: number;
18
+ updatedAt: number;
19
+ summary?: string;
20
+ }
21
+
22
+ export interface HistoryManagerOptions {
23
+ storagePath: string;
24
+ maxHistoryEntries?: number;
25
+ maxMessagesPerSession?: number;
26
+ contextWindowTokens?: number;
27
+ }
28
+
29
+ export class HistoryManager {
30
+ private history = new Map<string, HistoryEntry>();
31
+ private options: Required<HistoryManagerOptions>;
32
+
33
+ constructor(options: HistoryManagerOptions) {
34
+ this.options = {
35
+ storagePath: options.storagePath,
36
+ maxHistoryEntries: options.maxHistoryEntries || 100,
37
+ maxMessagesPerSession: options.maxMessagesPerSession || 200,
38
+ contextWindowTokens: options.contextWindowTokens || 128000,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Initialize and load history from storage
44
+ */
45
+ async init(): Promise<void> {
46
+ await fs.mkdir(this.options.storagePath, { recursive: true });
47
+
48
+ try {
49
+ const files = await fs.readdir(this.options.storagePath);
50
+ for (const file of files) {
51
+ if (file.endsWith(".json")) {
52
+ try {
53
+ const data = await fs.readFile(path.join(this.options.storagePath, file), "utf-8");
54
+ const entry = JSON.parse(data) as HistoryEntry;
55
+ this.history.set(entry.sessionId, entry);
56
+ } catch {}
57
+ }
58
+ }
59
+ } catch {}
60
+ }
61
+
62
+ /**
63
+ * Get or create a history entry for a session
64
+ */
65
+ getOrCreate(sessionId: string): HistoryEntry {
66
+ let entry = this.history.get(sessionId);
67
+ if (!entry) {
68
+ entry = {
69
+ sessionId,
70
+ messages: [],
71
+ totalTokens: { inputTokens: 0, outputTokens: 0 },
72
+ createdAt: Date.now(),
73
+ updatedAt: Date.now(),
74
+ };
75
+ this.history.set(sessionId, entry);
76
+ }
77
+ return entry;
78
+ }
79
+
80
+ /**
81
+ * Add a message to session history
82
+ */
83
+ async addMessage(sessionId: string, message: Message): Promise<void> {
84
+ const entry = this.getOrCreate(sessionId);
85
+ entry.messages.push(message);
86
+ entry.updatedAt = Date.now();
87
+
88
+ // Trim if too many messages
89
+ if (entry.messages.length > this.options.maxMessagesPerSession) {
90
+ entry.messages = entry.messages.slice(-this.options.maxMessagesPerSession);
91
+ }
92
+
93
+ await this.persist(entry);
94
+ }
95
+
96
+ /**
97
+ * Get messages for a session, optimized for context window
98
+ */
99
+ getMessages(sessionId: string, maxTokens?: number): Message[] {
100
+ const entry = this.history.get(sessionId);
101
+ if (!entry) return [];
102
+
103
+ const limit = maxTokens || this.options.contextWindowTokens;
104
+ const messages = [...entry.messages];
105
+
106
+ // Simple token estimation (4 chars per token)
107
+ let totalChars = 0;
108
+ const selected: Message[] = [];
109
+
110
+ // Always include system messages first
111
+ for (let i = messages.length - 1; i >= 0; i--) {
112
+ const msg = messages[i];
113
+ const charCount = JSON.stringify(msg.content).length;
114
+
115
+ if (totalChars + charCount > limit * 4) break;
116
+
117
+ totalChars += charCount;
118
+ selected.unshift(msg);
119
+ }
120
+
121
+ return selected;
122
+ }
123
+
124
+ /**
125
+ * Get recent conversations
126
+ */
127
+ getRecentSessions(limit = 10): HistoryEntry[] {
128
+ return Array.from(this.history.values())
129
+ .sort((a, b) => b.updatedAt - a.updatedAt)
130
+ .slice(0, limit);
131
+ }
132
+
133
+ /**
134
+ * Search history across all sessions
135
+ */
136
+ search(query: string, limit = 20): Array<{ sessionId: string; message: Message; relevance: number }> {
137
+ const results: Array<{ sessionId: string; message: Message; relevance: number }> = [];
138
+ const queryLower = query.toLowerCase();
139
+
140
+ for (const entry of this.history.values()) {
141
+ for (const msg of entry.messages) {
142
+ const text = msg.content
143
+ .filter((c) => c.type === "text")
144
+ .map((c) => (c as any).text)
145
+ .join(" ")
146
+ .toLowerCase();
147
+
148
+ if (text.includes(queryLower)) {
149
+ // Simple relevance: count occurrences
150
+ const count = (text.match(new RegExp(queryLower, "g")) || []).length;
151
+ results.push({ sessionId: entry.sessionId, message: msg, relevance: count });
152
+ }
153
+ }
154
+ }
155
+
156
+ return results.sort((a, b) => b.relevance - a.relevance).slice(0, limit);
157
+ }
158
+
159
+ /**
160
+ * Set summary for a session (for context compression)
161
+ */
162
+ async setSummary(sessionId: string, summary: string): Promise<void> {
163
+ const entry = this.history.get(sessionId);
164
+ if (!entry) return;
165
+
166
+ entry.summary = summary;
167
+ entry.updatedAt = Date.now();
168
+ await this.persist(entry);
169
+ }
170
+
171
+ /**
172
+ * Compact old messages into a summary
173
+ */
174
+ async compact(sessionId: string, keepRecent = 10, summary?: string): Promise<void> {
175
+ const entry = this.history.get(sessionId);
176
+ if (!entry || entry.messages.length <= keepRecent) return;
177
+
178
+ const oldMessages = entry.messages.slice(0, -keepRecent);
179
+ const oldSummary = oldMessages
180
+ .filter((m) => m.role === "assistant")
181
+ .map((m) => m.content.filter((c) => c.type === "text").map((c) => (c as any).text).join(""))
182
+ .join("\n")
183
+ .slice(0, 500);
184
+
185
+ entry.summary = summary || `Previous conversation summary: ${oldSummary}`;
186
+ entry.messages = entry.messages.slice(-keepRecent);
187
+ entry.updatedAt = Date.now();
188
+ await this.persist(entry);
189
+ }
190
+
191
+ /**
192
+ * Delete a session's history
193
+ */
194
+ async delete(sessionId: string): Promise<void> {
195
+ this.history.delete(sessionId);
196
+ try {
197
+ await fs.unlink(path.join(this.options.storagePath, `${sessionId}.json`));
198
+ } catch {}
199
+ }
200
+
201
+ /**
202
+ * Get statistics
203
+ */
204
+ getStats(): { sessions: number; totalMessages: number; totalTokens: TokenUsage } {
205
+ let totalMessages = 0;
206
+ const totalTokens: TokenUsage = { inputTokens: 0, outputTokens: 0 };
207
+
208
+ for (const entry of this.history.values()) {
209
+ totalMessages += entry.messages.length;
210
+ totalTokens.inputTokens += entry.totalTokens.inputTokens;
211
+ totalTokens.outputTokens += entry.totalTokens.outputTokens;
212
+ }
213
+
214
+ return { sessions: this.history.size, totalMessages, totalTokens };
215
+ }
216
+
217
+ /**
218
+ * Update token usage for a session
219
+ */
220
+ async updateUsage(sessionId: string, usage: TokenUsage): Promise<void> {
221
+ const entry = this.history.get(sessionId);
222
+ if (!entry) return;
223
+
224
+ entry.totalTokens.inputTokens += usage.inputTokens;
225
+ entry.totalTokens.outputTokens += usage.outputTokens;
226
+ entry.updatedAt = Date.now();
227
+ await this.persist(entry);
228
+ }
229
+
230
+ private async persist(entry: HistoryEntry): Promise<void> {
231
+ await fs.writeFile(
232
+ path.join(this.options.storagePath, `${entry.sessionId}.json`),
233
+ JSON.stringify(entry, null, 2)
234
+ );
235
+ }
236
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * ZERO Image Processing
3
+ * Adapted from: opencode (SST) - MIT
4
+ *
5
+ * Image handling for vision-capable LLMs.
6
+ * Supports reading, encoding, and resizing images.
7
+ */
8
+
9
+ import * as fs from "node:fs/promises";
10
+ import * as path from "node:path";
11
+ import type { ToolDefinition, ToolResult } from "../types.js";
12
+
13
+ export interface ImageData {
14
+ data: string; // base64
15
+ mimeType: string;
16
+ width?: number;
17
+ height?: number;
18
+ size: number;
19
+ }
20
+
21
+ const IMAGE_EXTENSIONS: Record<string, string> = {
22
+ ".png": "image/png",
23
+ ".jpg": "image/jpeg",
24
+ ".jpeg": "image/jpeg",
25
+ ".gif": "image/gif",
26
+ ".webp": "image/webp",
27
+ ".bmp": "image/bmp",
28
+ ".svg": "image/svg+xml",
29
+ ".ico": "image/x-icon",
30
+ };
31
+
32
+ /**
33
+ * Read and encode an image file
34
+ */
35
+ export async function readImage(filePath: string, workingDir: string): Promise<ImageData> {
36
+ const fullPath = path.resolve(workingDir, filePath);
37
+ const ext = path.extname(filePath).toLowerCase();
38
+ const mimeType = IMAGE_EXTENSIONS[ext];
39
+
40
+ if (!mimeType) {
41
+ throw new Error(`Unsupported image format: ${ext}`);
42
+ }
43
+
44
+ const buffer = await fs.readFile(fullPath);
45
+ const stats = await fs.stat(fullPath);
46
+
47
+ return {
48
+ data: buffer.toString("base64"),
49
+ mimeType,
50
+ size: stats.size,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Check if a file is an image
56
+ */
57
+ export function isImage(filePath: string): boolean {
58
+ const ext = path.extname(filePath).toLowerCase();
59
+ return ext in IMAGE_EXTENSIONS;
60
+ }
61
+
62
+ /**
63
+ * Get MIME type for an image
64
+ */
65
+ export function getMimeType(filePath: string): string | null {
66
+ const ext = path.extname(filePath).toLowerCase();
67
+ return IMAGE_EXTENSIONS[ext] || null;
68
+ }
69
+
70
+ /**
71
+ * Estimate image dimensions from base64 PNG header
72
+ */
73
+ export function estimatePngDimensions(base64Data: string): { width: number; height: number } | null {
74
+ try {
75
+ const buffer = Buffer.from(base64Data, "base64");
76
+ // PNG header: 8 bytes signature, then IHDR chunk
77
+ if (buffer.length < 24) return null;
78
+ if (buffer[0] !== 0x89 || buffer[1] !== 0x50) return null; // Not PNG
79
+
80
+ const width = buffer.readUInt32BE(16);
81
+ const height = buffer.readUInt32BE(20);
82
+ return { width, height };
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Create a data URI from image data
90
+ */
91
+ export function toDataURI(image: ImageData): string {
92
+ return `data:${image.mimeType};base64,${image.data}`;
93
+ }
94
+
95
+ /**
96
+ * Format image info for display
97
+ */
98
+ export function formatImageInfo(image: ImageData, filePath: string): string {
99
+ const sizeKB = (image.size / 1024).toFixed(1);
100
+ const dims = image.width && image.height ? ` (${image.width}x${image.height})` : "";
101
+ return `📷 ${filePath} [${image.mimeType}, ${sizeKB}KB${dims}]`;
102
+ }
103
+
104
+ /**
105
+ * Image read tool
106
+ */
107
+ export const readImageTool: ToolDefinition = {
108
+ name: "read_image",
109
+ description: "Read an image file and return it as base64-encoded data. Supports PNG, JPG, GIF, WebP, BMP, SVG.",
110
+ parameters: {
111
+ path: { type: "string", description: "Path to the image file", required: true },
112
+ },
113
+ category: "file",
114
+ requiresApproval: false,
115
+ handler: async (args, context): Promise<ToolResult> => {
116
+ try {
117
+ const image = await readImage(args.path as string, context.workingDirectory);
118
+ return {
119
+ success: true,
120
+ output: formatImageInfo(image, args.path as string),
121
+ data: image,
122
+ };
123
+ } catch (error) {
124
+ return {
125
+ success: false,
126
+ output: "",
127
+ error: error instanceof Error ? error.message : String(error),
128
+ };
129
+ }
130
+ },
131
+ };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * ZERO Core - Main Entry Point
3
+ *
4
+ * ⚡ ZERO is a unified AI coding assistant that combines the best features from:
5
+ *
6
+ * ┌──────────────────────┬─────────────────────────────────────────────────────┐
7
+ * │ Gemini CLI (Google) │ Model Router, Policy Engine, ReAct Loop │
8
+ * │ Qwen Code (Alibaba) │ Confirmation Bus, Provider Presets, Output Format │
9
+ * │ Pi (earendil-works) │ Multi-Agent Orchestrator, IPC Protocol │
10
+ * │ OpenCode (SST) │ Git Service, Permissions, Server │
11
+ * │ Aider │ EditBlock Editing, Linter, RepoMap, History │
12
+ * │ Crush (Charm) │ Session Service, PubSub, Prompt Engine, LSP │
13
+ * │ Cline │ Agent Config Loader, Sub-Agents, Tool Approval │
14
+ * │ Goose (Block) │ MCP Integration, Skills, Provider Types │
15
+ * │ KiloCode │ Memory Prompts, Indexing, Plugin Architecture │
16
+ * └──────────────────────┴─────────────────────────────────────────────────────┘
17
+ */
18
+
19
+ // Types
20
+ export * from "./types.js";
21
+
22
+ // Agent System
23
+ export { AgentEngine } from "./agent/engine.js";
24
+ export { AgentRegistry, DEFAULT_AGENTS } from "./agent/registry.js";
25
+ export { ToolRegistry } from "./agent/registry-tools.js";
26
+ export { loadAgentFromFile, loadAgentsFromDirectory, parseFrontmatter, BUILTIN_TOOLS } from "./agent/config-loader.js";
27
+ export type { AgentFrontmatterConfig, BuiltinTool } from "./agent/config-loader.js";
28
+
29
+ // Tools
30
+ export { getAllBuiltinTools, getAllTools } from "./tools/builtin.js";
31
+ export { lintTool, lintFile, formatLintResult, detectLanguage } from "./tools/linter.js";
32
+ export { repoMapTool, generateRepoMap, formatRepoMap } from "./tools/repo-map.js";
33
+
34
+ // LLM Providers
35
+ export { ProviderRegistry, OpenAICompatibleClient } from "./providers/registry.js";
36
+ export { createDeepSeekProvider, createGrokProvider, createOpenRouterProvider, createTogetherProvider, createFireworksProvider, getExtendedProviders } from "./providers/extended.js";
37
+
38
+ // Model Router
39
+ export { ModelRouterService, DefaultStrategy, OverrideStrategy, CompositeStrategy } from "./routing/model-router.js";
40
+ export type { RoutingDecision, RoutingContext, RoutingStrategy, TerminalStrategy } from "./routing/model-router.js";
41
+
42
+ // Confirmation Bus
43
+ export { MessageBus, MessageBusType } from "./confirmation/message-bus.js";
44
+ export type { ToolConfirmationRequest, ToolConfirmationResponse, SerializableConfirmationDetails, ToolExecutionSuccess, ToolExecutionFailure, HookExecutionRequest, HookExecutionResponse, Message } from "./confirmation/message-bus.js";
45
+
46
+ // Orchestrator IPC
47
+ export { OrchestratorServer, OrchestratorClient } from "./orchestrator/server.js";
48
+ export { encodeMessage, parseRequestLine, parseResponseLine } from "./orchestrator/protocol.js";
49
+ export type { SpawnRequest, ListRequest, StopRequest, StatusRequest, RpcRequest, RpcStreamRequest, OrchestratorRequest, InstanceSummary, InstanceStatus, SpawnResponse, ListResponse, StopResponse, StatusResponse, RpcBridgeResponse, RpcReadyResponse, ErrorResponse, OrchestratorResponse, RpcCommand } from "./orchestrator/protocol.js";
50
+
51
+ // Permissions
52
+ export { createPermissionHandler, createAutoPermissionHandler, DEFAULT_PERMISSION_CONFIG } from "./permissions/index.js";
53
+ export type { PermissionConfig, PermissionRule } from "./permissions/index.js";
54
+
55
+ // Session Management
56
+ export { SessionManager } from "./session/index.js";
57
+ export { SessionService, hasIncompleteTodos } from "./session/service.js";
58
+ export type { TodoStatus, Todo, SessionRecord, SessionEvent, SessionSubscriber } from "./session/service.js";
59
+
60
+ // Memory & Context
61
+ export { FileMemoryStore, ContextManager } from "./memory/index.js";
62
+ export { MEMORY_SYSTEM_PROMPT, MEMORY_SAVE_INSTRUCTIONS, CONTEXT_INJECTION_TEMPLATE, INDEXING_SYSTEM_PROMPT } from "./memory/prompts.js";
63
+
64
+ // MCP Integration
65
+ export { MCPManager } from "./mcp/index.js";
66
+
67
+ // Git Service
68
+ export { GitService } from "./git/index.js";
69
+ export type { ChangeKind, GitItem, GitStat, GitPatch, GitBase, PatchOptions, GitResult } from "./git/index.js";
70
+
71
+ // Configuration
72
+ export { ConfigManager, DEFAULT_CONFIG } from "./config/index.js";
73
+
74
+ // Diff System
75
+ export { generateDiff, formatUnifiedDiff, applySearchReplace, applyMultiEdit, smartMerge } from "./diff/index.js";
76
+
77
+ // Diff Detect
78
+ export { detectDiff, detectFileDiff, formatDiff, summarizeDiff } from "./diff-detect/index.js";
79
+ export type { DiffStats, DiffHunk, DiffLine, FileDiff } from "./diff-detect/index.js";
80
+
81
+ // Prompt Engine
82
+ export { PromptEngine, createPromptEngine, ZERO_SYSTEM_PROMPT } from "./prompts/engine.js";
83
+ export type { PromptContext } from "./prompts/engine.js";
84
+
85
+ // Custom Commands
86
+ export { loadCommandsFromDir, loadAllCommands, executeCommand } from "./commands/index.js";
87
+ export type { CustomCommand, CommandArgument } from "./commands/index.js";
88
+
89
+ // Skills System
90
+ export { SkillRegistry } from "./skills/index.js";
91
+ export type { Skill, SkillCategory, SkillParameter, SkillContext, SkillResult } from "./skills/index.js";
92
+
93
+ // PubSub Events
94
+ export { EventBus, ZERO_EVENTS, globalEventBus } from "./events/index.js";
95
+ export type { EventHandler, EventDefinition } from "./events/index.js";
96
+
97
+ // File Tracker
98
+ export { FileTracker } from "./file-tracker/index.js";
99
+ export type { FileChange, FileTrackerOptions } from "./file-tracker/index.js";
100
+
101
+ // Output Formatting
102
+ export { formatJSON, formatMarkdown, formatPlainText, createStreamFormatter, calculateCost, formatCost } from "./output/index.js";
103
+ export type { JSONOutput, StreamEvent } from "./output/index.js";
104
+
105
+ // HTTP API Server
106
+ export { ZeroServer, createAPIServer } from "./server/index.js";
107
+ export type { ServerOptions, RouteHandler } from "./server/index.js";
108
+
109
+ // Streaming
110
+ export { StreamProcessor, createTextStream, mergeStreams } from "./streaming/index.js";
111
+ export type { StreamOptions, StreamState } from "./streaming/index.js";
112
+
113
+ // History Manager
114
+ export { HistoryManager } from "./history/index.js";
115
+ export type { HistoryEntry, HistoryManagerOptions } from "./history/index.js";
116
+
117
+ // EditBlock Coder
118
+ export { parseEditBlocks, applyEditBlocks, applyEditBlocksToFile, formatEditBlock, editBlockTool } from "./edit-coder/index.js";
119
+ export type { EditBlock, EditBlockResult } from "./edit-coder/index.js";
120
+
121
+ // Sub-Agent System
122
+ export { SubAgentManager, createDelegateTool } from "./sub-agent/index.js";
123
+ export type { SubAgentTask, SubAgentResult, SubAgentOptions } from "./sub-agent/index.js";
124
+
125
+ // LSP Integration
126
+ export { LSPClient, lspLookupTool } from "./lsp/index.js";
127
+ export type { LSPPosition, LSPRange, LSPLocation, LSPDiagnostic, LSPSymbol } from "./lsp/index.js";
128
+
129
+ // Image Processing
130
+ export { readImage, isImage, getMimeType, estimatePngDimensions, toDataURI, formatImageInfo, readImageTool } from "./image/index.js";
131
+ export type { ImageData } from "./image/index.js";