@c4ccz/zero 1.0.0 → 1.0.1

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 (47) hide show
  1. package/package.json +10 -7
  2. package/packages/core/src/agent/config-loader.ts +174 -0
  3. package/packages/core/src/agent/engine.ts +266 -0
  4. package/packages/core/src/agent/registry-tools.ts +58 -0
  5. package/packages/core/src/agent/registry.ts +213 -0
  6. package/packages/core/src/commands/index.ts +116 -0
  7. package/packages/core/src/config/index.ts +139 -0
  8. package/packages/core/src/confirmation/message-bus.ts +206 -0
  9. package/packages/core/src/diff/index.ts +232 -0
  10. package/packages/core/src/diff-detect/index.ts +207 -0
  11. package/packages/core/src/edit-coder/index.ts +194 -0
  12. package/packages/core/src/events/index.ts +151 -0
  13. package/packages/core/src/file-tracker/index.ts +183 -0
  14. package/packages/core/src/git/index.ts +305 -0
  15. package/packages/core/src/history/index.ts +236 -0
  16. package/packages/core/src/image/index.ts +131 -0
  17. package/packages/core/src/index.ts +131 -0
  18. package/packages/core/src/lsp/index.ts +251 -0
  19. package/packages/core/src/mcp/index.ts +186 -0
  20. package/packages/core/src/memory/index.ts +141 -0
  21. package/packages/core/src/memory/prompts.ts +52 -0
  22. package/packages/core/src/orchestrator/protocol.ts +140 -0
  23. package/packages/core/src/orchestrator/server.ts +319 -0
  24. package/packages/core/src/output/index.ts +164 -0
  25. package/packages/core/src/permissions/index.ts +126 -0
  26. package/packages/core/src/prompts/engine.ts +188 -0
  27. package/packages/core/src/providers/extended.ts +8 -0
  28. package/packages/core/src/providers/registry.ts +687 -0
  29. package/packages/core/src/routing/model-router.ts +160 -0
  30. package/packages/core/src/server/index.ts +232 -0
  31. package/packages/core/src/session/index.ts +174 -0
  32. package/packages/core/src/session/service.ts +322 -0
  33. package/packages/core/src/skills/index.ts +205 -0
  34. package/packages/core/src/streaming/index.ts +166 -0
  35. package/packages/core/src/sub-agent/index.ts +179 -0
  36. package/packages/core/src/tools/builtin.ts +568 -0
  37. package/packages/core/src/tools/linter.ts +26 -0
  38. package/packages/core/src/tools/repo-map.ts +11 -0
  39. package/packages/core/src/types.ts +356 -0
  40. package/packages/sdk/src/index.ts +247 -0
  41. package/packages/tui/src/index.ts +141 -0
  42. package/bun.lock +0 -35
  43. package/bunfig.toml +0 -6
  44. package/packages/cli/package.json +0 -23
  45. package/packages/core/package.json +0 -41
  46. package/packages/sdk/package.json +0 -16
  47. package/packages/tui/package.json +0 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c4ccz/zero",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "ZERO - Unified AI Coding Assistant. Merges 9 open-source AI coding projects into one powerful tool.",
5
5
  "type": "module",
6
6
  "main": "./packages/core/src/index.ts",
@@ -15,6 +15,12 @@
15
15
  "./sdk": "./packages/sdk/src/index.ts",
16
16
  "./tui": "./packages/tui/src/index.ts"
17
17
  },
18
+ "files": [
19
+ "packages/",
20
+ "README.md",
21
+ "MERGE_REPORT.md",
22
+ "tsconfig.json"
23
+ ],
18
24
  "scripts": {
19
25
  "build": "echo 'ZERO uses Bun runtime - no build step required'",
20
26
  "dev": "bun run packages/cli/src/index.ts",
@@ -27,19 +33,16 @@
27
33
  "gemini", "openai", "anthropic", "claude", "aider",
28
34
  "code-generation", "developer-tools", "zero"
29
35
  ],
30
- "author": "ZERO Project",
36
+ "author": "c4ccz",
31
37
  "license": "MIT",
32
38
  "engines": {
33
39
  "node": ">=20.0.0"
34
40
  },
35
41
  "repository": {
36
42
  "type": "git",
37
- "url": "https://github.com/zero-ai/zero"
38
- },
39
- "bugs": {
40
- "url": "https://github.com/zero-ai/zero/issues"
43
+ "url": "git+https://github.com/c4ccz/zero.git"
41
44
  },
42
- "homepage": "https://github.com/zero-ai/zero#readme",
45
+ "homepage": "https://github.com/c4ccz/zero#readme",
43
46
  "dependencies": {
44
47
  "zod": "^3.23.0",
45
48
  "chalk": "^5.3.0",
@@ -0,0 +1,174 @@
1
+ /**
2
+ * ZERO Agent Config Loader
3
+ * Adapted from: cline - Apache 2.0
4
+ *
5
+ * Loads agent configurations from YAML/Markdown frontmatter files.
6
+ * Supports file watching for dynamic agent reloading.
7
+ */
8
+
9
+ import * as fs from "node:fs/promises";
10
+ import * as path from "node:path";
11
+ import type { AgentConfig, ToolCategory } from "../types.js";
12
+
13
+ /**
14
+ * Built-in tool names available for agents
15
+ */
16
+ export const BUILTIN_TOOLS = [
17
+ "read_file",
18
+ "write_file",
19
+ "edit_file",
20
+ "list_directory",
21
+ "search_files",
22
+ "search_code",
23
+ "execute_shell",
24
+ "git_status",
25
+ "git_diff",
26
+ "git_commit",
27
+ "web_fetch",
28
+ "web_search",
29
+ "delegate_to_agent",
30
+ ] as const;
31
+
32
+ export type BuiltinTool = (typeof BUILTIN_TOOLS)[number];
33
+
34
+ /**
35
+ * Schema for agent configuration in YAML frontmatter
36
+ */
37
+ export interface AgentFrontmatterConfig {
38
+ name: string;
39
+ description: string;
40
+ modelId?: string;
41
+ tools?: string | string[];
42
+ skills?: string | string[];
43
+ maxTurns?: number;
44
+ }
45
+
46
+ /**
47
+ * Parse YAML frontmatter from a markdown file
48
+ */
49
+ export function parseFrontmatter(content: string): { frontmatter: Record<string, unknown>; body: string } {
50
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
51
+ if (!match) {
52
+ return { frontmatter: {}, body: content };
53
+ }
54
+
55
+ const yamlStr = match[1];
56
+ const body = match[2];
57
+
58
+ // Simple YAML parser for key: value pairs
59
+ const frontmatter: Record<string, unknown> = {};
60
+ const lines = yamlStr.split("\n");
61
+ let currentKey = "";
62
+ let currentArray: string[] = [];
63
+ let inArray = false;
64
+
65
+ for (const line of lines) {
66
+ const trimmed = line.trim();
67
+ if (!trimmed || trimmed.startsWith("#")) continue;
68
+
69
+ if (trimmed.startsWith("- ") && inArray) {
70
+ currentArray.push(trimmed.slice(2).trim());
71
+ continue;
72
+ }
73
+
74
+ if (inArray && currentKey) {
75
+ frontmatter[currentKey] = currentArray;
76
+ inArray = false;
77
+ currentArray = [];
78
+ }
79
+
80
+ const colonIdx = trimmed.indexOf(":");
81
+ if (colonIdx > 0) {
82
+ const key = trimmed.slice(0, colonIdx).trim();
83
+ const value = trimmed.slice(colonIdx + 1).trim();
84
+
85
+ if (value === "" || value === "[]") {
86
+ currentKey = key;
87
+ inArray = true;
88
+ currentArray = [];
89
+ } else {
90
+ frontmatter[key] = value.replace(/^["']|["']$/g, "");
91
+ }
92
+ }
93
+ }
94
+
95
+ if (inArray && currentKey) {
96
+ frontmatter[currentKey] = currentArray;
97
+ }
98
+
99
+ return { frontmatter, body };
100
+ }
101
+
102
+ /**
103
+ * Parse tools from config (string or array)
104
+ */
105
+ function parseTools(tools: string | string[] | undefined): string[] {
106
+ if (!tools) return [...BUILTIN_TOOLS].filter((t) => t !== "delegate_to_agent");
107
+
108
+ const rawTools = Array.isArray(tools) ? tools : tools.split(",").map((t) => t.trim());
109
+ return Array.from(new Set(rawTools.filter((t) => BUILTIN_TOOLS.includes(t as BuiltinTool))));
110
+ }
111
+
112
+ /**
113
+ * Parse skills from config
114
+ */
115
+ function parseSkills(skills: string | string[] | undefined): string[] {
116
+ if (!skills) return [];
117
+ return Array.isArray(skills) ? skills : skills.split(",").map((s) => s.trim());
118
+ }
119
+
120
+ /**
121
+ * Load an agent configuration from a markdown file
122
+ */
123
+ export async function loadAgentFromFile(filePath: string): Promise<AgentConfig> {
124
+ const content = await fs.readFile(filePath, "utf-8");
125
+ const { frontmatter, body } = parseFrontmatter(content);
126
+
127
+ const config = frontmatter as unknown as AgentFrontmatterConfig;
128
+
129
+ if (!config.name) {
130
+ throw new Error(`Agent config in ${filePath} missing required field: name`);
131
+ }
132
+
133
+ if (!config.description) {
134
+ throw new Error(`Agent config in ${filePath} missing required field: description`);
135
+ }
136
+
137
+ const id = path.basename(filePath, path.extname(filePath)).toLowerCase().replace(/[^a-z0-9]/g, "-");
138
+
139
+ return {
140
+ id,
141
+ name: config.name,
142
+ description: config.description,
143
+ systemPrompt: body.trim() || `You are ${config.name}, a specialized agent.`,
144
+ tools: parseTools(config.tools),
145
+ maxTurns: config.maxTurns || 50,
146
+ model: config.modelId || "default",
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Load all agent configurations from a directory
152
+ */
153
+ export async function loadAgentsFromDirectory(dirPath: string): Promise<AgentConfig[]> {
154
+ const agents: AgentConfig[] = [];
155
+
156
+ try {
157
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
158
+
159
+ for (const entry of entries) {
160
+ if (entry.isFile() && (entry.name.endsWith(".md") || entry.name.endsWith(".yaml"))) {
161
+ try {
162
+ const agent = await loadAgentFromFile(path.join(dirPath, entry.name));
163
+ agents.push(agent);
164
+ } catch (error) {
165
+ // Skip invalid files
166
+ }
167
+ }
168
+ }
169
+ } catch {
170
+ // Directory doesn't exist
171
+ }
172
+
173
+ return agents;
174
+ }
@@ -0,0 +1,266 @@
1
+ /**
2
+ * ZERO Agent Engine
3
+ * Core agent loop implementation combining best patterns from:
4
+ * - goose: sub-agent orchestration
5
+ * - gemini-cli: ReAct loop with tool execution
6
+ * - pi: multi-agent orchestrator
7
+ * - cline: tool approval flow
8
+ */
9
+
10
+ import type {
11
+ AgentConfig,
12
+ AgentResult,
13
+ ChatOptions,
14
+ LLMClient,
15
+ Message,
16
+ MessageContent,
17
+ ToolCallContent,
18
+ ToolDefinition,
19
+ ToolResult,
20
+ TokenUsage,
21
+ PermissionHandler,
22
+ SessionState,
23
+ } from "../types.js";
24
+
25
+ export interface AgentEngineOptions {
26
+ config: AgentConfig;
27
+ client: LLMClient;
28
+ tools: Map<string, ToolDefinition>;
29
+ permissionHandler: PermissionHandler;
30
+ workingDirectory: string;
31
+ sessionId: string;
32
+ onStateChange?: (state: SessionState) => void;
33
+ onMessage?: (message: Message) => void;
34
+ }
35
+
36
+ export class AgentEngine {
37
+ private config: AgentConfig;
38
+ private client: LLMClient;
39
+ private tools: Map<string, ToolDefinition>;
40
+ private permissionHandler: PermissionHandler;
41
+ private workingDirectory: string;
42
+ private sessionId: string;
43
+ private messages: Message[] = [];
44
+ private totalUsage: TokenUsage = { inputTokens: 0, outputTokens: 0 };
45
+ private turnCount = 0;
46
+ private aborted = false;
47
+ private abortController = new AbortController();
48
+ private onStateChange?: (state: SessionState) => void;
49
+ private onMessage?: (message: Message) => void;
50
+
51
+ constructor(options: AgentEngineOptions) {
52
+ this.config = options.config;
53
+ this.client = options.client;
54
+ this.tools = options.tools;
55
+ this.permissionHandler = options.permissionHandler;
56
+ this.workingDirectory = options.workingDirectory;
57
+ this.sessionId = options.sessionId;
58
+ this.onStateChange = options.onStateChange;
59
+ this.onMessage = options.onMessage;
60
+ }
61
+
62
+ /**
63
+ * Run the agent loop with a user message
64
+ */
65
+ async run(userMessage: string): Promise<AgentResult> {
66
+ const startTime = Date.now();
67
+ this.aborted = false;
68
+ this.turnCount = 0;
69
+
70
+ // Add user message
71
+ const userMsg: Message = {
72
+ id: this.generateId(),
73
+ role: "user",
74
+ content: [{ type: "text", text: userMessage }],
75
+ timestamp: Date.now(),
76
+ };
77
+ this.messages.push(userMsg);
78
+ this.onMessage?.(userMsg);
79
+
80
+ try {
81
+ // Agent loop (ReAct pattern)
82
+ while (this.turnCount < this.config.maxTurns && !this.aborted) {
83
+ this.turnCount++;
84
+ this.updateState({ status: "running", currentAgent: this.config.name, tokenUsage: this.totalUsage });
85
+
86
+ // Get available tools for this agent
87
+ const availableTools = this.getAvailableTools();
88
+
89
+ // Call LLM
90
+ const chatOptions: ChatOptions = {
91
+ tools: availableTools.length > 0 ? availableTools : undefined,
92
+ systemPrompt: this.config.systemPrompt,
93
+ };
94
+
95
+ const response = await this.client.chat(this.messages, chatOptions);
96
+
97
+ // Track usage
98
+ this.totalUsage.inputTokens += response.usage.inputTokens;
99
+ this.totalUsage.outputTokens += response.usage.outputTokens;
100
+
101
+ // Add assistant message
102
+ const assistantMsg: Message = {
103
+ id: this.generateId(),
104
+ role: "assistant",
105
+ content: response.content,
106
+ timestamp: Date.now(),
107
+ metadata: { model: response.model, usage: response.usage },
108
+ };
109
+ this.messages.push(assistantMsg);
110
+ this.onMessage?.(assistantMsg);
111
+
112
+ // Check if we should stop
113
+ if (response.stopReason === "end_turn" || response.stopReason === "max_tokens") {
114
+ break;
115
+ }
116
+
117
+ // Process tool calls
118
+ if (response.stopReason === "tool_use") {
119
+ const toolCalls = response.content.filter(
120
+ (c): c is ToolCallContent => c.type === "tool_call"
121
+ );
122
+
123
+ for (const toolCall of toolCalls) {
124
+ const result = await this.executeTool(toolCall);
125
+ const toolResultMsg: Message = {
126
+ id: this.generateId(),
127
+ role: "tool",
128
+ content: [{
129
+ type: "tool_result",
130
+ id: toolCall.id,
131
+ name: toolCall.name,
132
+ result: result.output,
133
+ error: result.error,
134
+ }],
135
+ timestamp: Date.now(),
136
+ };
137
+ this.messages.push(toolResultMsg);
138
+ this.onMessage?.(toolResultMsg);
139
+ }
140
+ }
141
+ }
142
+
143
+ const duration = Date.now() - startTime;
144
+ const result: AgentResult = {
145
+ success: !this.aborted,
146
+ messages: this.messages,
147
+ tokenUsage: this.totalUsage,
148
+ duration,
149
+ };
150
+
151
+ this.updateState({ status: "completed", tokenUsage: this.totalUsage });
152
+ return result;
153
+ } catch (error) {
154
+ const duration = Date.now() - startTime;
155
+ const errorMessage = error instanceof Error ? error.message : String(error);
156
+ this.updateState({ status: "error", tokenUsage: this.totalUsage, error: errorMessage });
157
+ return {
158
+ success: false,
159
+ messages: this.messages,
160
+ tokenUsage: this.totalUsage,
161
+ duration,
162
+ error: errorMessage,
163
+ };
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Execute a single tool call with permission checking
169
+ */
170
+ private async executeTool(toolCall: ToolCallContent): Promise<ToolResult> {
171
+ const tool = this.tools.get(toolCall.name);
172
+
173
+ if (!tool) {
174
+ return {
175
+ success: false,
176
+ output: "",
177
+ error: `Tool "${toolCall.name}" not found`,
178
+ };
179
+ }
180
+
181
+ // Check permissions if tool requires approval
182
+ if (tool.requiresApproval) {
183
+ this.updateState({ status: "waiting_approval", tokenUsage: this.totalUsage });
184
+
185
+ const decision = await this.permissionHandler({
186
+ type: this.getPermissionType(tool.category),
187
+ resource: toolCall.name,
188
+ details: JSON.stringify(toolCall.arguments),
189
+ });
190
+
191
+ if (decision === "deny") {
192
+ return {
193
+ success: false,
194
+ output: "",
195
+ error: "Permission denied by user",
196
+ };
197
+ }
198
+
199
+ this.updateState({ status: "running", tokenUsage: this.totalUsage });
200
+ }
201
+
202
+ // Execute the tool
203
+ try {
204
+ const result = await tool.handler(toolCall.arguments, {
205
+ sessionId: this.sessionId,
206
+ workingDirectory: this.workingDirectory,
207
+ permissions: {
208
+ fileRead: "allow",
209
+ fileWrite: tool.requiresApproval ? "allow" : "ask",
210
+ shellExecution: tool.requiresApproval ? "allow" : "ask",
211
+ networkAccess: "allow",
212
+ mcpAccess: "allow",
213
+ },
214
+ abortSignal: this.abortController.signal,
215
+ });
216
+ return result;
217
+ } catch (error) {
218
+ return {
219
+ success: false,
220
+ output: "",
221
+ error: error instanceof Error ? error.message : String(error),
222
+ };
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Get tools available to this agent
228
+ */
229
+ private getAvailableTools(): ToolDefinition[] {
230
+ return this.config.tools
231
+ .map((name) => this.tools.get(name))
232
+ .filter((t): t is ToolDefinition => t !== undefined);
233
+ }
234
+
235
+ private getPermissionType(category: string): "file_read" | "file_write" | "shell" | "network" | "mcp" {
236
+ switch (category) {
237
+ case "file": return "file_write";
238
+ case "shell": return "shell";
239
+ case "web": return "network";
240
+ case "mcp": return "mcp";
241
+ default: return "file_read";
242
+ }
243
+ }
244
+
245
+ abort(): void {
246
+ this.aborted = true;
247
+ this.abortController.abort();
248
+ }
249
+
250
+ getMessages(): Message[] {
251
+ return [...this.messages];
252
+ }
253
+
254
+ private updateState(partial: Partial<SessionState>): void {
255
+ this.onStateChange?.({
256
+ status: partial.status ?? "idle",
257
+ currentAgent: partial.currentAgent ?? this.config.name,
258
+ tokenUsage: partial.tokenUsage ?? this.totalUsage,
259
+ error: partial.error,
260
+ });
261
+ }
262
+
263
+ private generateId(): string {
264
+ return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
265
+ }
266
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * ZERO Tool Registry
3
+ * Manages all available tools for agents
4
+ */
5
+
6
+ import type { ToolDefinition, ToolCategory } from "../types.js";
7
+
8
+ export class ToolRegistry {
9
+ private tools = new Map<string, ToolDefinition>();
10
+
11
+ register(tool: ToolDefinition): void {
12
+ this.tools.set(tool.name, tool);
13
+ }
14
+
15
+ unregister(name: string): void {
16
+ this.tools.delete(name);
17
+ }
18
+
19
+ get(name: string): ToolDefinition | undefined {
20
+ return this.tools.get(name);
21
+ }
22
+
23
+ getAll(): ToolDefinition[] {
24
+ return Array.from(this.tools.values());
25
+ }
26
+
27
+ getByCategory(category: ToolCategory): ToolDefinition[] {
28
+ return this.getAll().filter((t) => t.category === category);
29
+ }
30
+
31
+ getMap(): Map<string, ToolDefinition> {
32
+ return new Map(this.tools);
33
+ }
34
+
35
+ has(name: string): boolean {
36
+ return this.tools.has(name);
37
+ }
38
+
39
+ names(): string[] {
40
+ return Array.from(this.tools.keys());
41
+ }
42
+
43
+ /**
44
+ * Load all built-in tools
45
+ */
46
+ loadBuiltins(): void {
47
+ const builtins = getBuiltinTools();
48
+ for (const tool of builtins) {
49
+ this.register(tool);
50
+ }
51
+ }
52
+ }
53
+
54
+ // Lazy import to avoid circular deps - tools are loaded at runtime
55
+ function getBuiltinTools(): ToolDefinition[] {
56
+ // Will be populated by the tools/index.ts
57
+ return [];
58
+ }