@eldrforge/ai-service 0.1.12 → 0.1.14

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.
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Example 1: Simple Commit Message Generation
4
+ *
5
+ * This example shows the most basic usage of the library to generate
6
+ * a commit message from staged git changes.
7
+ *
8
+ * Usage:
9
+ * 1. Stage some changes: git add .
10
+ * 2. Run: npx tsx examples/01-simple-commit.ts
11
+ */
12
+
13
+ /* eslint-disable no-console */
14
+
15
+ import { runAgenticCommit } from '@eldrforge/ai-service';
16
+ import { execSync } from 'child_process';
17
+
18
+ async function main() {
19
+ try {
20
+ console.log('šŸ” Checking for staged changes...\n');
21
+
22
+ // Get the diff of staged changes
23
+ const diffContent = execSync('git diff --staged', { encoding: 'utf8' });
24
+
25
+ if (!diffContent.trim()) {
26
+ console.log('āŒ No staged changes found.');
27
+ console.log(' Stage some changes first with: git add <files>');
28
+ process.exit(1);
29
+ }
30
+
31
+ // Get list of changed files
32
+ const statusOutput = execSync('git status --porcelain', { encoding: 'utf8' });
33
+ const changedFiles = statusOutput
34
+ .split('\n')
35
+ .filter(line => line.trim())
36
+ .map(line => line.substring(3));
37
+
38
+ console.log('šŸ“ Files changed:', changedFiles.length);
39
+ changedFiles.forEach(file => console.log(` - ${file}`));
40
+ console.log('');
41
+
42
+ console.log('šŸ¤– Generating commit message...\n');
43
+
44
+ // Generate commit message using agentic mode
45
+ const result = await runAgenticCommit({
46
+ changedFiles,
47
+ diffContent,
48
+ model: 'gpt-4o-mini', // Using mini for faster/cheaper generation
49
+ maxIterations: 10,
50
+ });
51
+
52
+ console.log('✨ Generated Commit Message:\n');
53
+ console.log('─'.repeat(60));
54
+ console.log(result.commitMessage);
55
+ console.log('─'.repeat(60));
56
+ console.log('');
57
+ console.log(`šŸ“Š Stats: ${result.toolCallsExecuted} tool calls, ${result.iterations} iterations`);
58
+
59
+ // Show suggested splits if any
60
+ if (result.suggestedSplits.length > 0) {
61
+ console.log('\nšŸ’” Suggested Commit Splits:');
62
+ console.log(' The AI recommends splitting this into multiple commits:\n');
63
+
64
+ result.suggestedSplits.forEach((split, idx) => {
65
+ console.log(` Split ${idx + 1}:`);
66
+ console.log(` Files: ${split.files.join(', ')}`);
67
+ console.log(` Rationale: ${split.rationale}`);
68
+ console.log(` Message: ${split.message}`);
69
+ console.log('');
70
+ });
71
+ }
72
+
73
+ } catch (error) {
74
+ console.error('āŒ Error:', error instanceof Error ? error.message : String(error));
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ main();
80
+
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Example 2: Generate Release Notes
4
+ *
5
+ * This example generates comprehensive release notes between two git refs.
6
+ * The AI will use multiple tools to analyze the changes and create
7
+ * detailed, context-aware release notes.
8
+ *
9
+ * Usage:
10
+ * npx tsx examples/02-release-notes.ts v1.0.0 v1.1.0
11
+ * npx tsx examples/02-release-notes.ts v1.0.0 HEAD
12
+ */
13
+
14
+ /* eslint-disable no-console */
15
+
16
+ import { runAgenticRelease } from '@eldrforge/ai-service';
17
+ import { execSync } from 'child_process';
18
+ import * as fs from 'fs/promises';
19
+ import * as path from 'path';
20
+
21
+ async function main() {
22
+ const args = process.argv.slice(2);
23
+
24
+ if (args.length < 2) {
25
+ console.log('Usage: npx tsx 02-release-notes.ts <from-ref> <to-ref>');
26
+ console.log('Example: npx tsx 02-release-notes.ts v1.0.0 v1.1.0');
27
+ process.exit(1);
28
+ }
29
+
30
+ const [fromRef, toRef] = args;
31
+
32
+ try {
33
+ console.log(`šŸ“‹ Generating release notes from ${fromRef} to ${toRef}...\n`);
34
+
35
+ // Verify refs exist
36
+ try {
37
+ execSync(`git rev-parse ${fromRef}`, { stdio: 'ignore' });
38
+ execSync(`git rev-parse ${toRef}`, { stdio: 'ignore' });
39
+ } catch {
40
+ console.error(`āŒ Invalid git reference: ${fromRef} or ${toRef}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ // Get commit log
45
+ console.log('šŸ“– Fetching commit history...');
46
+ const logContent = execSync(
47
+ `git log ${fromRef}..${toRef} --pretty=format:"%h %s (%an)" --abbrev-commit`,
48
+ { encoding: 'utf8' }
49
+ );
50
+
51
+ // Get diff stats
52
+ console.log('šŸ“Š Analyzing changes...');
53
+ const diffContent = execSync(
54
+ `git diff ${fromRef}..${toRef} --stat`,
55
+ { encoding: 'utf8' }
56
+ );
57
+
58
+ // Count commits
59
+ const commitCount = logContent.split('\n').filter(line => line.trim()).length;
60
+ console.log(` Found ${commitCount} commits\n`);
61
+
62
+ // Run agentic release notes generation
63
+ console.log('šŸ¤– AI is analyzing the release...');
64
+ console.log(' (This may take 1-3 minutes for thorough analysis)\n');
65
+
66
+ const result = await runAgenticRelease({
67
+ fromRef,
68
+ toRef,
69
+ logContent,
70
+ diffContent,
71
+ model: 'gpt-4o',
72
+ maxIterations: 30,
73
+ });
74
+
75
+ console.log('āœ… Release notes generated!\n');
76
+ console.log('═'.repeat(70));
77
+ console.log(`TITLE: ${result.releaseNotes.title}`);
78
+ console.log('═'.repeat(70));
79
+ console.log('');
80
+ console.log(result.releaseNotes.body);
81
+ console.log('');
82
+ console.log('═'.repeat(70));
83
+ console.log('');
84
+ console.log(`šŸ“Š Metrics:`);
85
+ console.log(` - Iterations: ${result.iterations}`);
86
+ console.log(` - Tool calls: ${result.toolCallsExecuted}`);
87
+ console.log('');
88
+
89
+ // Show which tools were used
90
+ const toolUsage: Record<string, number> = {};
91
+ result.toolMetrics.forEach(metric => {
92
+ toolUsage[metric.name] = (toolUsage[metric.name] || 0) + 1;
93
+ });
94
+
95
+ console.log('šŸ”§ Tools used:');
96
+ Object.entries(toolUsage)
97
+ .sort((a, b) => b[1] - a[1])
98
+ .forEach(([tool, count]) => {
99
+ console.log(` - ${tool}: ${count}x`);
100
+ });
101
+ console.log('');
102
+
103
+ // Save to file
104
+ const outputDir = path.join(process.cwd(), 'output');
105
+ await fs.mkdir(outputDir, { recursive: true });
106
+
107
+ const releaseFile = path.join(outputDir, 'RELEASE_NOTES.md');
108
+ await fs.writeFile(releaseFile, result.releaseNotes.body, 'utf8');
109
+
110
+ const titleFile = path.join(outputDir, 'RELEASE_TITLE.txt');
111
+ await fs.writeFile(titleFile, result.releaseNotes.title, 'utf8');
112
+
113
+ console.log('šŸ’¾ Saved to:');
114
+ console.log(` - ${releaseFile}`);
115
+ console.log(` - ${titleFile}`);
116
+
117
+ } catch (error) {
118
+ console.error('āŒ Error:', error instanceof Error ? error.message : String(error));
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ main();
124
+
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Example 3: Interactive Commit Workflow
4
+ *
5
+ * This example demonstrates an interactive workflow where the user can:
6
+ * - Generate an initial commit message
7
+ * - Review and choose to confirm, edit, or regenerate
8
+ * - Optionally add user feedback for improvement
9
+ * - Automatically create the commit
10
+ *
11
+ * Usage:
12
+ * 1. Stage changes: git add .
13
+ * 2. Run: npx tsx examples/03-interactive-commit.ts
14
+ */
15
+
16
+ /* eslint-disable no-console */
17
+
18
+ import {
19
+ runAgenticCommit,
20
+ getUserChoice,
21
+ editContentInEditor,
22
+ STANDARD_CHOICES,
23
+ } from '@eldrforge/ai-service';
24
+ import { execSync } from 'child_process';
25
+
26
+ async function main() {
27
+ try {
28
+ // Check for staged changes
29
+ const diffContent = execSync('git diff --staged', { encoding: 'utf8' });
30
+
31
+ if (!diffContent.trim()) {
32
+ console.log('āŒ No staged changes found.');
33
+ console.log(' Stage some changes first with: git add <files>');
34
+ process.exit(1);
35
+ }
36
+
37
+ // Get changed files
38
+ const statusOutput = execSync('git status --porcelain', { encoding: 'utf8' });
39
+ const changedFiles = statusOutput
40
+ .split('\n')
41
+ .filter(line => line.trim())
42
+ .map(line => line.substring(3));
43
+
44
+ console.log('šŸ“ Changed files:', changedFiles.length);
45
+ changedFiles.forEach(file => console.log(` - ${file}`));
46
+ console.log('');
47
+
48
+ // Generate initial commit message
49
+ console.log('šŸ¤– Generating commit message...\n');
50
+
51
+ const result = await runAgenticCommit({
52
+ changedFiles,
53
+ diffContent,
54
+ model: 'gpt-4o-mini',
55
+ maxIterations: 10,
56
+ });
57
+
58
+ let currentMessage = result.commitMessage;
59
+ let shouldContinue = true;
60
+
61
+ while (shouldContinue) {
62
+ // Display current message
63
+ console.log('\n✨ Commit Message:\n');
64
+ console.log('─'.repeat(60));
65
+ console.log(currentMessage);
66
+ console.log('─'.repeat(60));
67
+ console.log('');
68
+
69
+ // Get user choice
70
+ const choice = await getUserChoice(
71
+ 'What would you like to do?',
72
+ [
73
+ STANDARD_CHOICES.CONFIRM,
74
+ STANDARD_CHOICES.EDIT,
75
+ STANDARD_CHOICES.SKIP,
76
+ ],
77
+ {
78
+ nonTtyErrorSuggestions: [
79
+ 'Run in a terminal environment',
80
+ 'Use examples/01-simple-commit.ts for non-interactive mode',
81
+ ],
82
+ }
83
+ );
84
+
85
+ switch (choice) {
86
+ case 'c': { // Confirm
87
+ console.log('\nšŸ“ Creating commit...');
88
+
89
+ try {
90
+ execSync('git', ['commit', '-m', currentMessage], {
91
+ stdio: 'inherit',
92
+ });
93
+ console.log('āœ… Commit created successfully!');
94
+ } catch (commitError) {
95
+ console.error('āŒ Failed to create commit:', commitError);
96
+ }
97
+
98
+ shouldContinue = false;
99
+ break;
100
+ }
101
+
102
+ case 'e': { // Edit
103
+ console.log('\nšŸ“ Opening editor...');
104
+
105
+ const edited = await editContentInEditor(
106
+ currentMessage,
107
+ [
108
+ '# Edit your commit message below',
109
+ '# Lines starting with # will be removed',
110
+ '# Save and close the editor when done',
111
+ ],
112
+ '.txt'
113
+ );
114
+
115
+ if (edited.wasEdited) {
116
+ currentMessage = edited.content;
117
+ console.log('\nāœ… Commit message updated!');
118
+ } else {
119
+ console.log('\nāš ļø No changes made');
120
+ }
121
+ break;
122
+ }
123
+
124
+ case 's': // Skip
125
+ console.log('\nā­ļø Commit cancelled. Your changes are still staged.');
126
+ shouldContinue = false;
127
+ break;
128
+
129
+ default:
130
+ console.log('\nāŒ Invalid choice');
131
+ break;
132
+ }
133
+ }
134
+
135
+ } catch (error) {
136
+ console.error('āŒ Error:', error instanceof Error ? error.message : String(error));
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ // Check if running in TTY
142
+ if (!process.stdin.isTTY) {
143
+ console.error('āŒ This example requires a terminal (TTY).');
144
+ console.error(' Run it directly in your terminal, not piped or in CI.');
145
+ console.error(' For non-interactive mode, use: examples/01-simple-commit.ts');
146
+ process.exit(1);
147
+ }
148
+
149
+ main();
150
+
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Example 4: Custom Storage Adapter
4
+ *
5
+ * This example demonstrates how to implement a custom storage adapter
6
+ * to save AI-generated content to a specific location or cloud storage.
7
+ *
8
+ * Usage:
9
+ * npx tsx examples/04-custom-storage.ts
10
+ */
11
+
12
+ /* eslint-disable no-console */
13
+
14
+ import { runAgenticCommit, type StorageAdapter } from '@eldrforge/ai-service';
15
+ import { execSync } from 'child_process';
16
+ import * as fs from 'fs/promises';
17
+ import * as path from 'path';
18
+
19
+ /**
20
+ * Custom storage adapter that saves files to a structured output directory
21
+ */
22
+ class CustomStorageAdapter implements StorageAdapter {
23
+ private outputDir: string;
24
+ private tempDir: string;
25
+
26
+ constructor(outputDir: string, tempDir: string = '/tmp') {
27
+ this.outputDir = outputDir;
28
+ this.tempDir = tempDir;
29
+ }
30
+
31
+ async writeOutput(fileName: string, content: string): Promise<void> {
32
+ // Ensure output directory exists
33
+ await fs.mkdir(this.outputDir, { recursive: true });
34
+
35
+ const filePath = path.join(this.outputDir, fileName);
36
+ await fs.writeFile(filePath, content, 'utf8');
37
+
38
+ console.log(` šŸ“ Saved to: ${filePath}`);
39
+ }
40
+
41
+ async readTemp(fileName: string): Promise<string> {
42
+ const filePath = path.join(this.tempDir, fileName);
43
+ return fs.readFile(filePath, 'utf8');
44
+ }
45
+
46
+ async writeTemp(fileName: string, content: string): Promise<void> {
47
+ await fs.mkdir(this.tempDir, { recursive: true });
48
+ const filePath = path.join(this.tempDir, fileName);
49
+ await fs.writeFile(filePath, content, 'utf8');
50
+ }
51
+
52
+ async readFile(fileName: string): Promise<string> {
53
+ return fs.readFile(fileName, 'utf8');
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Alternative: Cloud storage adapter (example structure)
59
+ * In a real implementation, you would integrate with AWS S3, Google Cloud Storage, etc.
60
+ */
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
+ class CloudStorageAdapter implements StorageAdapter {
63
+ async writeOutput(fileName: string, _content: string): Promise<void> {
64
+ // Example: Upload to S3
65
+ // const s3 = new S3Client({ region: 'us-east-1' });
66
+ // await s3.send(new PutObjectCommand({
67
+ // Bucket: 'my-bucket',
68
+ // Key: `ai-output/${fileName}`,
69
+ // Body: content,
70
+ // }));
71
+
72
+ console.log(` ā˜ļø Would upload to cloud: ${fileName}`);
73
+ }
74
+
75
+ async readTemp(fileName: string): Promise<string> {
76
+ // Local temp files
77
+ return fs.readFile(path.join('/tmp', fileName), 'utf8');
78
+ }
79
+
80
+ async writeTemp(fileName: string, content: string): Promise<void> {
81
+ await fs.writeFile(path.join('/tmp', fileName), content, 'utf8');
82
+ }
83
+
84
+ async readFile(fileName: string): Promise<string> {
85
+ return fs.readFile(fileName, 'utf8');
86
+ }
87
+ }
88
+
89
+ async function main() {
90
+ try {
91
+ console.log('šŸ” Checking for staged changes...\n');
92
+
93
+ const diffContent = execSync('git diff --staged', { encoding: 'utf8' });
94
+
95
+ if (!diffContent.trim()) {
96
+ console.log('āŒ No staged changes found.');
97
+ console.log(' Stage some changes first with: git add <files>');
98
+ process.exit(1);
99
+ }
100
+
101
+ const statusOutput = execSync('git status --porcelain', { encoding: 'utf8' });
102
+ const changedFiles = statusOutput
103
+ .split('\n')
104
+ .filter(line => line.trim())
105
+ .map(line => line.substring(3));
106
+
107
+ console.log('šŸ“ Files changed:', changedFiles.length);
108
+ console.log('');
109
+
110
+ // Create custom storage adapter
111
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
112
+ const outputDir = path.join(process.cwd(), 'output', 'ai-service', timestamp);
113
+ const storage = new CustomStorageAdapter(outputDir);
114
+
115
+ console.log('šŸ¤– Generating commit message with custom storage...\n');
116
+
117
+ const result = await runAgenticCommit({
118
+ changedFiles,
119
+ diffContent,
120
+ model: 'gpt-4o-mini',
121
+ maxIterations: 10,
122
+ storage,
123
+ });
124
+
125
+ console.log('\n✨ Generated Commit Message:\n');
126
+ console.log('─'.repeat(60));
127
+ console.log(result.commitMessage);
128
+ console.log('─'.repeat(60));
129
+ console.log('');
130
+
131
+ // Save additional metadata
132
+ console.log('šŸ’¾ Saving files...\n');
133
+
134
+ await storage.writeOutput('commit-message.txt', result.commitMessage);
135
+
136
+ await storage.writeOutput(
137
+ 'metadata.json',
138
+ JSON.stringify({
139
+ timestamp: new Date().toISOString(),
140
+ iterations: result.iterations,
141
+ toolCallsExecuted: result.toolCallsExecuted,
142
+ model: 'gpt-4o-mini',
143
+ changedFiles,
144
+ }, null, 2)
145
+ );
146
+
147
+ // Save tool metrics
148
+ await storage.writeOutput(
149
+ 'tool-metrics.json',
150
+ JSON.stringify(result.toolMetrics, null, 2)
151
+ );
152
+
153
+ console.log('\nāœ… Complete! All files saved to:', outputDir);
154
+
155
+ } catch (error) {
156
+ console.error('āŒ Error:', error instanceof Error ? error.message : String(error));
157
+ process.exit(1);
158
+ }
159
+ }
160
+
161
+ main();
162
+