@ebowwa/daemons 0.5.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 (42) hide show
  1. package/README.md +264 -0
  2. package/dist/bin/discord-cli.js +124118 -0
  3. package/dist/bin/manager.js +143 -0
  4. package/dist/bin/telegram-cli.js +124114 -0
  5. package/dist/index.js +125340 -0
  6. package/package.json +94 -0
  7. package/src/agent.ts +111 -0
  8. package/src/channels/base.ts +573 -0
  9. package/src/channels/discord.ts +306 -0
  10. package/src/channels/index.ts +169 -0
  11. package/src/channels/telegram.ts +315 -0
  12. package/src/daemon.ts +534 -0
  13. package/src/hooks.ts +97 -0
  14. package/src/index.ts +111 -0
  15. package/src/memory.ts +369 -0
  16. package/src/skills/coding/commit.ts +202 -0
  17. package/src/skills/coding/execute-subtask.ts +136 -0
  18. package/src/skills/coding/fix-issues.ts +126 -0
  19. package/src/skills/coding/index.ts +26 -0
  20. package/src/skills/coding/plan-task.ts +158 -0
  21. package/src/skills/coding/quality-check.ts +155 -0
  22. package/src/skills/index.ts +65 -0
  23. package/src/skills/registry.ts +380 -0
  24. package/src/skills/shared/index.ts +21 -0
  25. package/src/skills/shared/reflect.ts +156 -0
  26. package/src/skills/shared/review.ts +201 -0
  27. package/src/skills/shared/trajectory.ts +319 -0
  28. package/src/skills/trading/analyze-market.ts +144 -0
  29. package/src/skills/trading/check-risk.ts +176 -0
  30. package/src/skills/trading/execute-trade.ts +185 -0
  31. package/src/skills/trading/generate-signal.ts +160 -0
  32. package/src/skills/trading/index.ts +26 -0
  33. package/src/skills/trading/monitor-position.ts +179 -0
  34. package/src/skills/types.ts +235 -0
  35. package/src/skills/workflows.ts +340 -0
  36. package/src/state.ts +77 -0
  37. package/src/tools.ts +134 -0
  38. package/src/types.ts +314 -0
  39. package/src/workflow.ts +341 -0
  40. package/src/workflows/coding.ts +580 -0
  41. package/src/workflows/index.ts +61 -0
  42. package/src/workflows/trading.ts +608 -0
package/src/index.ts ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Daemons - Autonomous Agent Daemon Framework
3
+ *
4
+ * A daemon framework for autonomous AI agents.
5
+ * Implements SLAM pattern (State → Loop → Action → Memory).
6
+ * Supports hooks, tools via MCP, multi-agent coordination, and communication channels.
7
+ *
8
+ * @module @ebowwa/daemons
9
+ */
10
+
11
+ // Main daemon class
12
+ export { GLMDaemon, type GLMDaemonConfigWithWorkflow } from "./daemon.js";
13
+
14
+ // Agent class
15
+ export { GLMAgent } from "./agent.js";
16
+
17
+ // Supporting systems
18
+ export { HookSystem } from "./hooks.js";
19
+ export { ToolBridge } from "./tools.js";
20
+ export { StateManager } from "./state.js";
21
+
22
+ // Workflow system
23
+ export {
24
+ // Classes
25
+ BaseWorkflow,
26
+ WorkflowRegistry,
27
+ workflowRegistry,
28
+ // Functions
29
+ initializeWorkflows,
30
+ getWorkflow,
31
+ getDefaultWorkflow,
32
+ // Built-in workflows
33
+ CodingWorkflow,
34
+ codingWorkflow,
35
+ CODING_PHASES,
36
+ CODING_TRANSITIONS,
37
+ CODING_WORKFLOW_CONFIG,
38
+ TradingWorkflow,
39
+ tradingWorkflow,
40
+ TRADING_PHASES,
41
+ TRADING_TRANSITIONS,
42
+ TRADING_WORKFLOW_CONFIG,
43
+ // Types
44
+ type GLMWorkflowPhase,
45
+ type GLMWorkflowContext,
46
+ type GLMWorkflowPhaseResult,
47
+ type GLMWorkflowTransition,
48
+ type GLMWorkflowConfig,
49
+ type GLMWorkflowExecutor,
50
+ type CodingPhase,
51
+ type TradingPhase,
52
+ } from "./workflows/index.js";
53
+
54
+ // Built-in tools - re-exported from @ebowwa/ai/tools for convenience
55
+ export {
56
+ BUILTIN_TOOLS,
57
+ getBuiltinTool,
58
+ getBuiltinToolNames,
59
+ toGLMFormat,
60
+ executeBuiltinTool,
61
+ ToolExecutor,
62
+ type ToolDefinition,
63
+ type ToolCall,
64
+ type ToolExecutorOptions,
65
+ type ToolExecutionResult,
66
+ } from "@ebowwa/ai/tools";
67
+
68
+ // Memory systems
69
+ export {
70
+ ConversationMemory,
71
+ NumericConversationMemory,
72
+ StringConversationMemory,
73
+ type ConversationMessage,
74
+ type ConversationMemoryConfig,
75
+ } from "./memory.js";
76
+
77
+ // Communication channels
78
+ export {
79
+ BaseChannel,
80
+ TelegramChannel,
81
+ DiscordChannel,
82
+ ChannelRegistry,
83
+ type BaseChannelConfig,
84
+ type TelegramChannelConfig,
85
+ type DiscordChannelConfig,
86
+ type MessageContext,
87
+ type RouteResult,
88
+ type MessageClassification,
89
+ } from "./channels/index.js";
90
+
91
+ // Types
92
+ export * from "./types.js";
93
+
94
+ // Convenience function to create and start a daemon
95
+ import { GLMDaemon } from "./daemon.js";
96
+ import type { GLMDaemonConfig } from "./types.js";
97
+
98
+ export async function createGLMDaemon(
99
+ config: GLMDaemonConfig
100
+ ): Promise<GLMDaemon> {
101
+ const daemon = new GLMDaemon(config);
102
+ return daemon;
103
+ }
104
+
105
+ export async function startGLMDaemon(
106
+ config: GLMDaemonConfig,
107
+ prompt: string
108
+ ): Promise<string> {
109
+ const daemon = new GLMDaemon(config);
110
+ return await daemon.start(prompt);
111
+ }
package/src/memory.ts ADDED
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Conversation Memory - Generic conversation tracking for any channel
3
+ *
4
+ * Tracks chat history per user/conversation with persistence.
5
+ * Can be used by Telegram, Discord, or any other channel.
6
+ *
7
+ * Storage format: JSONL (JSON Lines)
8
+ * Each line is a JSON object: {"id": "...", "messages": [...], "lastUpdated": timestamp}
9
+ */
10
+
11
+ import {
12
+ readFileSync,
13
+ writeFileSync,
14
+ existsSync,
15
+ mkdirSync,
16
+ renameSync,
17
+ } from "fs";
18
+ import { dirname } from "path";
19
+
20
+ /**
21
+ * A single message in a conversation
22
+ */
23
+ export interface ConversationMessage {
24
+ role: "user" | "assistant" | "system";
25
+ content: string;
26
+ timestamp?: number;
27
+ }
28
+
29
+ /**
30
+ * Internal storage format for JSONL
31
+ */
32
+ interface ConversationEntry {
33
+ id: string;
34
+ messages: ConversationMessage[];
35
+ lastUpdated: number;
36
+ }
37
+
38
+ /**
39
+ * Configuration for ConversationMemory
40
+ */
41
+ export interface ConversationMemoryConfig {
42
+ /** Path to persistence file (default: ./conversations.jsonl) */
43
+ file?: string;
44
+ /** Maximum messages to keep per conversation (default: 10) */
45
+ maxMessages?: number;
46
+ /** Include timestamps in messages (default: true) */
47
+ includeTimestamps?: boolean;
48
+ /** Auto-migrate old .json files to .jsonl (default: true) */
49
+ autoMigrate?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Generic conversation memory that tracks chat history per conversation ID.
54
+ *
55
+ * Uses JSONL format for efficient append-only writes and streaming reads.
56
+ * Automatically migrates from old JSON format on first load.
57
+ *
58
+ * The ID type is generic (string | number) to support different channel types:
59
+ * - Telegram: number (chatId)
60
+ * - Discord: string (channelId/userId)
61
+ * - Slack: string (channelId)
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const memory = new ConversationMemory({ maxMessages: 20 });
66
+ * memory.add(12345, 'user', 'Hello!');
67
+ * memory.add(12345, 'assistant', 'Hi there!');
68
+ * const history = memory.get(12345);
69
+ * ```
70
+ */
71
+ export class ConversationMemory<IdType = string | number> {
72
+ private conversations = new Map<IdType, ConversationMessage[]>();
73
+ private maxMessages: number;
74
+ private includeTimestamps: boolean;
75
+ private file: string;
76
+ private needsFullSave = false;
77
+
78
+ constructor(config: ConversationMemoryConfig = {}) {
79
+ // Default to .jsonl extension
80
+ const defaultFile = "./conversations.jsonl";
81
+ this.file = config.file || defaultFile;
82
+
83
+ // Auto-migrate from .json to .jsonl if needed
84
+ if (config.autoMigrate !== false && !existsSync(this.file)) {
85
+ this.migrateFromJson(this.file);
86
+ }
87
+
88
+ this.maxMessages = config.maxMessages ?? 10;
89
+ this.includeTimestamps = config.includeTimestamps ?? true;
90
+ this.load();
91
+ }
92
+
93
+ /**
94
+ * Migrate from old JSON format to JSONL
95
+ */
96
+ private migrateFromJson(jsonlFile: string): void {
97
+ // Check for old .json file
98
+ const jsonFile = jsonlFile.replace(/\.jsonl$/, ".json");
99
+ if (!existsSync(jsonFile)) return;
100
+
101
+ try {
102
+ const data = JSON.parse(readFileSync(jsonFile, "utf-8"));
103
+ const entries: ConversationEntry[] = [];
104
+
105
+ for (const [id, messages] of Object.entries(data)) {
106
+ entries.push({
107
+ id,
108
+ messages: messages as ConversationMessage[],
109
+ lastUpdated: Date.now(),
110
+ });
111
+ }
112
+
113
+ // Write as JSONL
114
+ const dir = dirname(jsonlFile);
115
+ if (!existsSync(dir)) {
116
+ mkdirSync(dir, { recursive: true });
117
+ }
118
+
119
+ const lines = entries.map((e) => JSON.stringify(e)).join("\n");
120
+ writeFileSync(jsonlFile, lines + (lines ? "\n" : ""));
121
+
122
+ // Backup old file instead of deleting
123
+ const backupFile = jsonFile + ".backup";
124
+ renameSync(jsonFile, backupFile);
125
+
126
+ console.log(`Migrated ${jsonFile} to ${jsonlFile} (${entries.length} conversations)`);
127
+ } catch (error) {
128
+ console.error(`Migration failed: ${error}`);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Load conversations from JSONL file
134
+ * Uses synchronous read for constructor compatibility
135
+ */
136
+ private load(): void {
137
+ if (!existsSync(this.file)) return;
138
+
139
+ try {
140
+ const content = readFileSync(this.file, "utf-8");
141
+ const lines = content.split("\n");
142
+
143
+ for (const line of lines) {
144
+ if (!line.trim()) continue;
145
+ try {
146
+ const entry: ConversationEntry = JSON.parse(line);
147
+ const key = this.parseKey(entry.id);
148
+ this.conversations.set(key, entry.messages);
149
+ } catch {
150
+ // Skip malformed lines
151
+ }
152
+ }
153
+ } catch {
154
+ // Start fresh on error
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Parse string key back to original type
160
+ */
161
+ private parseKey(id: string): IdType {
162
+ // Try to parse as number if it looks like one
163
+ if (/^-?\d+$/.test(id)) {
164
+ return Number(id) as IdType;
165
+ }
166
+ return id as IdType;
167
+ }
168
+
169
+ /**
170
+ * Convert IdType to string for storage
171
+ */
172
+ private stringifyKey(id: IdType): string {
173
+ return String(id);
174
+ }
175
+
176
+ /**
177
+ * Save all conversations to disk (full rewrite)
178
+ * Used when entries are deleted or modified
179
+ */
180
+ private saveFull(): void {
181
+ try {
182
+ const dir = dirname(this.file);
183
+ if (!existsSync(dir)) {
184
+ mkdirSync(dir, { recursive: true });
185
+ }
186
+
187
+ const lines: string[] = [];
188
+ for (const [id, messages] of this.conversations) {
189
+ const entry: ConversationEntry = {
190
+ id: this.stringifyKey(id),
191
+ messages,
192
+ lastUpdated: Date.now(),
193
+ };
194
+ lines.push(JSON.stringify(entry));
195
+ }
196
+
197
+ writeFileSync(this.file, lines.join("\n") + (lines.length ? "\n" : ""));
198
+ this.needsFullSave = false;
199
+ } catch (error) {
200
+ console.error(`Save failed: ${error}`);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Append a single entry to the file (efficient for adds)
206
+ */
207
+ private appendEntry(conversationId: IdType, messages: ConversationMessage[]): void {
208
+ try {
209
+ const dir = dirname(this.file);
210
+ if (!existsSync(dir)) {
211
+ mkdirSync(dir, { recursive: true });
212
+ }
213
+
214
+ const entry: ConversationEntry = {
215
+ id: this.stringifyKey(conversationId),
216
+ messages,
217
+ lastUpdated: Date.now(),
218
+ };
219
+
220
+ // Check if file exists and has content to determine if we need newline
221
+ const hasContent = existsSync(this.file) && readFileSync(this.file, "utf-8").length > 0;
222
+ const prefix = hasContent ? "" : "";
223
+ const line = JSON.stringify(entry) + "\n";
224
+
225
+ // We need to do a full save to replace the old entry for this conversation
226
+ // JSONL doesn't support in-place updates, so we mark for full save
227
+ this.needsFullSave = true;
228
+
229
+ // For now, do a full save since we need to replace the entry
230
+ this.saveFull();
231
+ } catch (error) {
232
+ console.error(`Append failed: ${error}`);
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Add a message to a conversation
238
+ */
239
+ add(
240
+ conversationId: IdType,
241
+ role: "user" | "assistant" | "system",
242
+ content: string
243
+ ): void {
244
+ if (!this.conversations.has(conversationId)) {
245
+ this.conversations.set(conversationId, []);
246
+ }
247
+
248
+ const history = this.conversations.get(conversationId)!;
249
+ const message: ConversationMessage = { role, content };
250
+
251
+ if (this.includeTimestamps) {
252
+ message.timestamp = Date.now();
253
+ }
254
+
255
+ history.push(message);
256
+
257
+ // Keep only last N messages
258
+ if (history.length > this.maxMessages) {
259
+ history.splice(0, history.length - this.maxMessages);
260
+ }
261
+
262
+ // Save the updated conversation
263
+ this.appendEntry(conversationId, history);
264
+ }
265
+
266
+ /**
267
+ * Get conversation history for a specific conversation
268
+ */
269
+ get(conversationId: IdType): ConversationMessage[] {
270
+ return this.conversations.get(conversationId) || [];
271
+ }
272
+
273
+ /**
274
+ * Get all conversations
275
+ */
276
+ getAll(): Map<IdType, ConversationMessage[]> {
277
+ return new Map(this.conversations);
278
+ }
279
+
280
+ /**
281
+ * Clear a specific conversation
282
+ */
283
+ clear(conversationId: IdType): void {
284
+ this.conversations.delete(conversationId);
285
+ this.saveFull();
286
+ }
287
+
288
+ /**
289
+ * Clear all conversations
290
+ */
291
+ clearAll(): void {
292
+ this.conversations.clear();
293
+ this.saveFull();
294
+ }
295
+
296
+ /**
297
+ * Check if a conversation exists
298
+ */
299
+ has(conversationId: IdType): boolean {
300
+ return this.conversations.has(conversationId);
301
+ }
302
+
303
+ /**
304
+ * Get the number of conversations
305
+ */
306
+ size(): number {
307
+ return this.conversations.size;
308
+ }
309
+
310
+ /**
311
+ * Get the messages count for a specific conversation
312
+ */
313
+ messageCount(conversationId: IdType): number {
314
+ return this.conversations.get(conversationId)?.length || 0;
315
+ }
316
+
317
+ /**
318
+ * Get formatted history for API calls (OpenAI/Anthropic format)
319
+ */
320
+ getForAPI(
321
+ conversationId: IdType
322
+ ): Array<{ role: string; content: string }> {
323
+ return this.get(conversationId).map(({ role, content }) => ({
324
+ role,
325
+ content,
326
+ }));
327
+ }
328
+
329
+ /**
330
+ * Force a full save (useful before shutdown)
331
+ */
332
+ flush(): void {
333
+ if (this.needsFullSave || this.conversations.size > 0) {
334
+ this.saveFull();
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Get the file path being used
340
+ */
341
+ getFilePath(): string {
342
+ return this.file;
343
+ }
344
+
345
+ /**
346
+ * Check if the storage uses JSONL format
347
+ */
348
+ isJsonl(): boolean {
349
+ return this.file.endsWith(".jsonl");
350
+ }
351
+ }
352
+
353
+ /**
354
+ * Pre-configured memory for numeric IDs (like Telegram chat IDs)
355
+ */
356
+ export class NumericConversationMemory extends ConversationMemory<number> {
357
+ constructor(config: Omit<ConversationMemoryConfig, "file"> & { file?: string } = {}) {
358
+ super(config);
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Pre-configured memory for string IDs (like Discord/Slack channel IDs)
364
+ */
365
+ export class StringConversationMemory extends ConversationMemory<string> {
366
+ constructor(config: Omit<ConversationMemoryConfig, "file"> & { file?: string } = {}) {
367
+ super(config);
368
+ }
369
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * GLM Daemon - Commit Skill
3
+ *
4
+ * Commit changes to git with auto-generated message.
5
+ */
6
+
7
+ import { z } from "zod";
8
+ import { exec } from "child_process";
9
+ import { promisify } from "util";
10
+ import type { Skill, SkillContext, SkillResult } from "../types.js";
11
+ import { skillRegistry } from "../registry.js";
12
+
13
+ const execAsync = promisify(exec);
14
+
15
+ /**
16
+ * Commit parameters
17
+ */
18
+ const CommitParams = z.object({
19
+ /** Custom commit message */
20
+ message: z.string().optional(),
21
+ /** Whether to push after commit */
22
+ push: z.boolean().optional(),
23
+ /** Skip hooks */
24
+ noVerify: z.boolean().optional(),
25
+ });
26
+
27
+ /**
28
+ * Commit result data
29
+ */
30
+ interface CommitData {
31
+ commitHash: string;
32
+ message: string;
33
+ filesCount: number;
34
+ pushed: boolean;
35
+ }
36
+
37
+ /**
38
+ * Commit skill
39
+ */
40
+ export const commitSkill: Skill<z.infer<typeof CommitParams>, CommitData> = {
41
+ id: "/commit",
42
+ name: "Commit",
43
+ description: "Commit changes to git with an auto-generated or custom message.",
44
+ paramsSchema: CommitParams,
45
+ tags: ["coding", "git"],
46
+ parallelizable: false,
47
+
48
+ async execute(params, context): Promise<SkillResult<CommitData>> {
49
+ const filesCount = context.state.filesChanged.length;
50
+
51
+ if (filesCount === 0) {
52
+ console.log("[/commit] No files to commit");
53
+ return {
54
+ success: true,
55
+ data: {
56
+ commitHash: "",
57
+ message: "No changes to commit",
58
+ filesCount: 0,
59
+ pushed: false,
60
+ },
61
+ nextSkill: "/complete",
62
+ };
63
+ }
64
+
65
+ console.log(`[/commit] Committing ${filesCount} files`);
66
+
67
+ // Generate commit message if not provided
68
+ let message = params?.message;
69
+ if (!message) {
70
+ const prompt = `Generate a concise git commit message for these changes:
71
+
72
+ Task: ${context.state.prompt}
73
+ Files: ${context.state.filesChanged.join(", ")}
74
+
75
+ Format: <type>: <description>
76
+
77
+ Types: feat, fix, refactor, docs, test, chore
78
+
79
+ Just output the commit message, nothing else.`;
80
+
81
+ const response = await context.agent.execute(prompt);
82
+ message = response.split("\n")[0].trim();
83
+ }
84
+
85
+ console.log(`[/commit] Message: ${message}`);
86
+
87
+ // Get the working directory from workflow config or use current directory
88
+ const cwd = (context.workflowConfig?.cwd as string) || process.cwd();
89
+ const pushed = params?.push ?? false;
90
+
91
+ try {
92
+ // Step 1: Stage all changed files
93
+ const filesToStage = context.state.filesChanged.length > 0
94
+ ? context.state.filesChanged.join(" ")
95
+ : "."; // Stage all if no specific files tracked
96
+
97
+ console.log(`[/commit] Staging files: ${filesToStage}`);
98
+ await execAsync(`git add ${filesToStage}`, { cwd });
99
+
100
+ // Step 2: Commit with message
101
+ const noVerifyFlag = params?.noVerify ? " --no-verify" : "";
102
+ const escapedMessage = message.replace(/"/g, '\\"');
103
+ const commitCommand = `git commit -m "${escapedMessage}"${noVerifyFlag}`;
104
+
105
+ console.log(`[/commit] Executing commit...`);
106
+ const { stdout: commitOutput } = await execAsync(commitCommand, { cwd });
107
+
108
+ // Extract commit hash from output (format: [main abc1234] message)
109
+ const hashMatch = commitOutput.match(/\[[\w-]+\s+([a-f0-9]+)\]/);
110
+ const commitHash = hashMatch ? hashMatch[1] : "";
111
+
112
+ if (!commitHash) {
113
+ console.warn("[/commit] Could not extract commit hash from output");
114
+ }
115
+
116
+ console.log(`[/commit] Committed: ${commitHash}`);
117
+
118
+ // Step 3: Push if requested
119
+ if (pushed) {
120
+ console.log(`[/commit] Pushing to remote...`);
121
+ try {
122
+ await execAsync("git push", { cwd });
123
+ console.log(`[/commit] Pushed successfully`);
124
+ } catch (pushError) {
125
+ console.error("[/commit] Push failed:", pushError);
126
+ // Don't fail the whole commit if push fails
127
+ return {
128
+ success: true,
129
+ data: {
130
+ commitHash,
131
+ message: message || "Changes committed",
132
+ filesCount,
133
+ pushed: false,
134
+ },
135
+ contextUpdates: {
136
+ lastCommit: commitHash,
137
+ lastCommitMessage: message,
138
+ },
139
+ stateUpdates: {
140
+ git: {
141
+ ...context.state.git,
142
+ currentCommit: commitHash,
143
+ },
144
+ },
145
+ nextSkill: "/complete",
146
+ error: `Commit succeeded but push failed: ${pushError instanceof Error ? pushError.message : String(pushError)}`,
147
+ };
148
+ }
149
+ }
150
+
151
+ return {
152
+ success: true,
153
+ data: {
154
+ commitHash,
155
+ message: message || "Changes committed",
156
+ filesCount,
157
+ pushed,
158
+ },
159
+ contextUpdates: {
160
+ lastCommit: commitHash,
161
+ lastCommitMessage: message,
162
+ },
163
+ stateUpdates: {
164
+ git: {
165
+ ...context.state.git,
166
+ currentCommit: commitHash,
167
+ },
168
+ },
169
+ nextSkill: "/complete",
170
+ };
171
+ } catch (error) {
172
+ const errorMessage = error instanceof Error ? error.message : String(error);
173
+ console.error("[/commit] Git operation failed:", errorMessage);
174
+
175
+ // Check if it's just "nothing to commit"
176
+ if (errorMessage.includes("nothing to commit") || errorMessage.includes("no changes added to commit")) {
177
+ console.log("[/commit] No changes to commit (already clean)");
178
+ return {
179
+ success: true,
180
+ data: {
181
+ commitHash: context.state.git.currentCommit || "",
182
+ message: "No changes to commit",
183
+ filesCount: 0,
184
+ pushed: false,
185
+ },
186
+ nextSkill: "/complete",
187
+ };
188
+ }
189
+
190
+ return {
191
+ success: false,
192
+ error: `Git operation failed: ${errorMessage}`,
193
+ nextSkill: "/fix-issues",
194
+ };
195
+ }
196
+ },
197
+ };
198
+
199
+ skillRegistry.register(commitSkill);
200
+
201
+ export { CommitParams };
202
+ export type { CommitData };