abapgit-agent 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 (53) hide show
  1. package/.abapGitAgent.example +11 -0
  2. package/API.md +271 -0
  3. package/CLAUDE.md +445 -0
  4. package/CLAUDE_MEM.md +88 -0
  5. package/ERROR_HANDLING.md +30 -0
  6. package/INSTALL.md +160 -0
  7. package/README.md +127 -0
  8. package/abap/CLAUDE.md +492 -0
  9. package/abap/package.devc.xml +10 -0
  10. package/abap/zcl_abgagt_agent.clas.abap +769 -0
  11. package/abap/zcl_abgagt_agent.clas.xml +15 -0
  12. package/abap/zcl_abgagt_cmd_factory.clas.abap +43 -0
  13. package/abap/zcl_abgagt_cmd_factory.clas.xml +15 -0
  14. package/abap/zcl_abgagt_command_inspect.clas.abap +192 -0
  15. package/abap/zcl_abgagt_command_inspect.clas.testclasses.abap +121 -0
  16. package/abap/zcl_abgagt_command_inspect.clas.xml +16 -0
  17. package/abap/zcl_abgagt_command_pull.clas.abap +80 -0
  18. package/abap/zcl_abgagt_command_pull.clas.testclasses.abap +87 -0
  19. package/abap/zcl_abgagt_command_pull.clas.xml +16 -0
  20. package/abap/zcl_abgagt_command_unit.clas.abap +297 -0
  21. package/abap/zcl_abgagt_command_unit.clas.xml +15 -0
  22. package/abap/zcl_abgagt_resource_health.clas.abap +25 -0
  23. package/abap/zcl_abgagt_resource_health.clas.xml +15 -0
  24. package/abap/zcl_abgagt_resource_inspect.clas.abap +62 -0
  25. package/abap/zcl_abgagt_resource_inspect.clas.xml +15 -0
  26. package/abap/zcl_abgagt_resource_pull.clas.abap +71 -0
  27. package/abap/zcl_abgagt_resource_pull.clas.xml +15 -0
  28. package/abap/zcl_abgagt_resource_unit.clas.abap +64 -0
  29. package/abap/zcl_abgagt_resource_unit.clas.xml +15 -0
  30. package/abap/zcl_abgagt_rest_handler.clas.abap +27 -0
  31. package/abap/zcl_abgagt_rest_handler.clas.xml +15 -0
  32. package/abap/zcl_abgagt_util.clas.abap +93 -0
  33. package/abap/zcl_abgagt_util.clas.testclasses.abap +84 -0
  34. package/abap/zcl_abgagt_util.clas.xml +16 -0
  35. package/abap/zif_abgagt_agent.intf.abap +134 -0
  36. package/abap/zif_abgagt_agent.intf.xml +15 -0
  37. package/abap/zif_abgagt_cmd_factory.intf.abap +7 -0
  38. package/abap/zif_abgagt_cmd_factory.intf.xml +15 -0
  39. package/abap/zif_abgagt_command.intf.abap +21 -0
  40. package/abap/zif_abgagt_command.intf.xml +15 -0
  41. package/abap/zif_abgagt_util.intf.abap +28 -0
  42. package/abap/zif_abgagt_util.intf.xml +15 -0
  43. package/bin/abapgit-agent +902 -0
  44. package/img/claude.png +0 -0
  45. package/package.json +31 -0
  46. package/scripts/claude-integration.js +351 -0
  47. package/scripts/test-integration.js +139 -0
  48. package/src/abap-client.js +314 -0
  49. package/src/agent.js +119 -0
  50. package/src/config.js +66 -0
  51. package/src/index.js +48 -0
  52. package/src/logger.js +39 -0
  53. package/src/server.js +116 -0
package/img/claude.png ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "abapgit-agent",
3
+ "version": "1.0.0",
4
+ "description": "ABAP Git Agent - Pull and activate ABAP code via abapGit from any git repository",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "abapgit-agent": "bin/abapgit-agent"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/server.js",
11
+ "dev": "nodemon src/server.js",
12
+ "test": "jest",
13
+ "pull": "node bin/abapgit-agent"
14
+ },
15
+ "dependencies": {
16
+ "cors": "^2.8.5",
17
+ "dotenv": "^16.3.1",
18
+ "express": "^4.18.2",
19
+ "node-fetch": "^2.7.0",
20
+ "uuid": "^9.0.0",
21
+ "winston": "^3.11.0"
22
+ },
23
+ "devDependencies": {
24
+ "jest": "^29.7.0",
25
+ "nodemon": "^3.0.1",
26
+ "supertest": "^7.2.2"
27
+ },
28
+ "engines": {
29
+ "node": ">=16.0.0"
30
+ }
31
+ }
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Claude Integration - Shell script for Claude Code
3
+ *
4
+ * This script can be run from any ABAP git repo with .abapGitAgent config.
5
+ *
6
+ * Usage in Claude:
7
+ * 1. Run: node scripts/claude-integration.js pull --url <git-url> [--branch <branch>]
8
+ * 2. Parse response for activation result
9
+ * 3. Display errors if any
10
+ *
11
+ * Alternatively, use the CLI directly:
12
+ * ./bin/abapgit-agent pull [--branch <branch>]
13
+ */
14
+
15
+ const http = require('http');
16
+ const https = require('https');
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+
20
+ const COOKIE_FILE = path.join(__dirname, '..', '.abapgit_agent_cookies.txt');
21
+
22
+ /**
23
+ * Check if ABAP AI integration is configured for this repo
24
+ * Looks for .abapGitAgent in current working directory
25
+ */
26
+ function isAbapIntegrationEnabled() {
27
+ // Check in current working directory (repo root)
28
+ const repoConfigPath = path.join(process.cwd(), '.abapGitAgent');
29
+ if (fs.existsSync(repoConfigPath)) {
30
+ return true;
31
+ }
32
+ // Also check if repo has abap/ folder with ABAP objects
33
+ const abapFolder = path.join(process.cwd(), 'abap');
34
+ if (fs.existsSync(abapFolder)) {
35
+ const files = fs.readdirSync(abapFolder);
36
+ return files.some(f => f.endsWith('.abap') || f.endsWith('.clas.abap') || f.endsWith('.fugr.abap'));
37
+ }
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * Load configuration from .abapGitAgent
43
+ */
44
+ function loadConfig() {
45
+ // First check current working directory (repo root)
46
+ const repoConfigPath = path.join(process.cwd(), '.abapGitAgent');
47
+
48
+ if (fs.existsSync(repoConfigPath)) {
49
+ console.log(`📁 Found .abapGitAgent in: ${repoConfigPath}`);
50
+ return JSON.parse(fs.readFileSync(repoConfigPath, 'utf8'));
51
+ }
52
+
53
+ // Fallback to abapgit-agent parent directory
54
+ const bridgeConfigPath = path.join(__dirname, '..', '.abapGitAgent');
55
+ if (fs.existsSync(bridgeConfigPath)) {
56
+ return JSON.parse(fs.readFileSync(bridgeConfigPath, 'utf8'));
57
+ }
58
+
59
+ // Fallback to environment variables
60
+ return {
61
+ host: process.env.ABAP_HOST,
62
+ sapport: parseInt(process.env.ABAP_PORT, 10) || 443,
63
+ client: process.env.ABAP_CLIENT || '100',
64
+ user: process.env.ABAP_USER,
65
+ password: process.env.ABAP_PASSWORD,
66
+ language: process.env.ABAP_LANGUAGE || 'EN',
67
+ gitUsername: process.env.GIT_USERNAME,
68
+ gitPassword: process.env.GIT_PASSWORD
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Read cookies from Netscape format cookie file
74
+ */
75
+ function readNetscapeCookies() {
76
+ if (!fs.existsSync(COOKIE_FILE)) return '';
77
+
78
+ const content = fs.readFileSync(COOKIE_FILE, 'utf8');
79
+ const lines = content.split('\n');
80
+ const cookies = [];
81
+
82
+ for (const line of lines) {
83
+ const trimmed = line.trim();
84
+ // Skip empty lines and header comments but NOT HttpOnly cookies
85
+ if (!trimmed || (trimmed.startsWith('#') && !trimmed.startsWith('#HttpOnly'))) continue;
86
+
87
+ const parts = trimmed.split('\t');
88
+ if (parts.length >= 7) {
89
+ cookies.push(`${parts[5]}=${parts[6]}`);
90
+ }
91
+ }
92
+
93
+ return cookies.join('; ');
94
+ }
95
+
96
+ /**
97
+ * Fetch CSRF token using GET /pull with X-CSRF-Token: fetch
98
+ */
99
+ async function fetchCsrfToken(config) {
100
+ const url = new URL(`/sap/bc/z_abapgit_agent/pull`, `https://${config.host}:${config.sapport}`);
101
+
102
+ return new Promise((resolve, reject) => {
103
+ const cookieHeader = readNetscapeCookies();
104
+
105
+ const options = {
106
+ hostname: url.hostname,
107
+ port: url.port,
108
+ path: url.pathname,
109
+ method: 'GET',
110
+ headers: {
111
+ 'Authorization': `Basic ${Buffer.from(`${config.user}:${config.password}`).toString('base64')}`,
112
+ 'sap-client': config.client,
113
+ 'sap-language': config.language || 'EN',
114
+ 'X-CSRF-Token': 'fetch',
115
+ 'Content-Type': 'application/json',
116
+ ...(cookieHeader && { 'Cookie': cookieHeader })
117
+ },
118
+ agent: new https.Agent({ rejectUnauthorized: false })
119
+ };
120
+
121
+ const req = https.request(options, (res) => {
122
+ const csrfToken = res.headers['x-csrf-token'];
123
+
124
+ // Save new cookies from response - the CSRF token is tied to this new session!
125
+ const setCookie = res.headers['set-cookie'];
126
+ if (setCookie) {
127
+ const cookies = Array.isArray(setCookie)
128
+ ? setCookie.map(c => c.split(';')[0]).join('; ')
129
+ : setCookie.split(';')[0];
130
+ fs.writeFileSync(COOKIE_FILE, cookies);
131
+ }
132
+
133
+ let body = '';
134
+ res.on('data', chunk => body += chunk);
135
+ res.on('end', () => {
136
+ resolve(csrfToken);
137
+ });
138
+ });
139
+
140
+ req.on('error', reject);
141
+ req.end();
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Make HTTP request to ABAP REST endpoint
147
+ */
148
+ function request(method, path, data = null, options = {}) {
149
+ return new Promise((resolve, reject) => {
150
+ const config = loadConfig();
151
+ const url = new URL(path, `https://${config.host}:${config.sapport}`);
152
+
153
+ const headers = {
154
+ 'Content-Type': 'application/json',
155
+ 'sap-client': config.client,
156
+ 'sap-language': config.language || 'EN',
157
+ ...options.headers
158
+ };
159
+
160
+ // Add authorization
161
+ headers['Authorization'] = `Basic ${Buffer.from(`${config.user}:${config.password}`).toString('base64')}`;
162
+
163
+ // Add CSRF token for POST
164
+ if (method === 'POST' && options.csrfToken) {
165
+ headers['X-CSRF-Token'] = options.csrfToken;
166
+ }
167
+
168
+ // Add cookies if available
169
+ const cookieHeader = readNetscapeCookies();
170
+ if (cookieHeader) {
171
+ headers['Cookie'] = cookieHeader;
172
+ }
173
+
174
+ const reqOptions = {
175
+ hostname: url.hostname,
176
+ port: url.port,
177
+ path: url.pathname,
178
+ method,
179
+ headers,
180
+ agent: new https.Agent({ rejectUnauthorized: false })
181
+ };
182
+
183
+ const req = (url.protocol === 'https:' ? https : http).request(reqOptions, (res) => {
184
+ let body = '';
185
+ res.on('data', chunk => body += chunk);
186
+ res.on('end', () => {
187
+ try {
188
+ resolve(JSON.parse(body));
189
+ } catch (e) {
190
+ resolve(body);
191
+ }
192
+ });
193
+ });
194
+
195
+ req.on('error', reject);
196
+
197
+ if (data) {
198
+ req.write(JSON.stringify(data));
199
+ }
200
+ req.end();
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Pull and activate repository
206
+ */
207
+ async function pull(gitUrl, branch = 'main') {
208
+ console.log(`\n🚀 Starting pull for: ${gitUrl}`);
209
+ console.log(` Branch: ${branch}`);
210
+
211
+ try {
212
+ const config = loadConfig();
213
+
214
+ // Fetch CSRF token first
215
+ const csrfToken = await fetchCsrfToken(config);
216
+
217
+ // Prepare request data with git credentials
218
+ const data = {
219
+ url: gitUrl,
220
+ branch: branch,
221
+ username: config.gitUsername,
222
+ password: config.gitPassword
223
+ };
224
+
225
+ const result = await request('POST', '/sap/bc/z_abapgit_agent/pull', data, { csrfToken });
226
+
227
+ console.log('\n');
228
+
229
+ if (result.success === 'X' || result.success === true) {
230
+ console.log(`✅ Pull completed successfully!`);
231
+ console.log(` Job ID: ${result.job_id}`);
232
+ console.log(` Message: ${result.message}`);
233
+ } else {
234
+ console.log(`❌ Pull completed with errors!`);
235
+ console.log(` Job ID: ${result.job_id}`);
236
+ console.log(` Message: ${result.message}`);
237
+
238
+ if (result.error_detail) {
239
+ console.log(`\n📋 Error Details:`);
240
+ console.log(result.error_detail);
241
+ }
242
+ }
243
+
244
+ return result;
245
+ } catch (error) {
246
+ console.error(`\n❌ Error: ${error.message}`);
247
+ process.exit(1);
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Check agent health
253
+ */
254
+ async function healthCheck() {
255
+ try {
256
+ const result = await request('GET', '/sap/bc/z_abapgit_agent/health');
257
+ return result;
258
+ } catch (error) {
259
+ return { status: 'unreachable', error: error.message };
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Main CLI
265
+ */
266
+ async function main() {
267
+ const args = process.argv.slice(2);
268
+ const command = args[0];
269
+
270
+ // Check if ABAP integration is enabled for this repo
271
+ if (!isAbapIntegrationEnabled()) {
272
+ console.log(`
273
+ ⚠️ ABAP AI Integration not configured for this repository.
274
+
275
+ To enable integration:
276
+ 1. Create a .abapGitAgent file in the repo root with ABAP connection details:
277
+ {
278
+ "host": "your-sap-system.com",
279
+ "sapport": 443,
280
+ "client": "100",
281
+ "user": "TECH_USER",
282
+ "password": "your-password",
283
+ "language": "EN"
284
+ }
285
+
286
+ 2. Or set environment variables:
287
+ - ABAP_HOST, ABAP_PORT, ABAP_CLIENT, ABAP_USER, ABAP_PASSWORD
288
+ `);
289
+ if (command !== 'help' && command !== '--help' && command !== '-h') {
290
+ process.exit(1);
291
+ }
292
+ }
293
+
294
+ try {
295
+ switch (command) {
296
+ case 'pull':
297
+ const urlIndex = args.indexOf('--url') !== -1 ? args.indexOf('--url') + 1 : 1;
298
+ const branchIndex = args.indexOf('--branch');
299
+
300
+ if (!args[urlIndex]) {
301
+ console.error('Error: --url is required');
302
+ console.error('Usage: node scripts/claude-integration.js pull --url <git-url> [--branch <branch>]');
303
+ process.exit(1);
304
+ }
305
+
306
+ await pull(args[urlIndex], branchIndex !== -1 ? args[branchIndex + 1] : 'main');
307
+ break;
308
+
309
+ case 'health':
310
+ const health = await healthCheck();
311
+ console.log(JSON.stringify(health, null, 2));
312
+ break;
313
+
314
+ case 'status':
315
+ if (isAbapIntegrationEnabled()) {
316
+ console.log('✅ ABAP AI Integration is ENABLED');
317
+ console.log(' Config location:', path.join(process.cwd(), '.abapGitAgent'));
318
+ } else {
319
+ console.log('❌ ABAP AI Integration is NOT configured');
320
+ }
321
+ break;
322
+
323
+ default:
324
+ console.log(`
325
+ ABAP AI Bridge - Claude Integration
326
+
327
+ Usage:
328
+ node scripts/claude-integration.js <command> [options]
329
+
330
+ Commands:
331
+ pull --url <git-url> [--branch <branch>]
332
+ Pull and activate a repository in ABAP system
333
+
334
+ health
335
+ Check if ABAP REST API is healthy
336
+
337
+ status
338
+ Check if ABAP integration is configured for this repo
339
+
340
+ Examples:
341
+ node scripts/claude-integration.js pull --url https://github.com/user/repo --branch main
342
+ node scripts/claude-integration.js health
343
+ `);
344
+ }
345
+ } catch (error) {
346
+ console.error(`Error: ${error.message}`);
347
+ process.exit(1);
348
+ }
349
+ }
350
+
351
+ main();
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Simple integration test for Claude
3
+ * Run: node scripts/test-integration.js
4
+ */
5
+
6
+ const http = require('http');
7
+ const https = require('https');
8
+
9
+ /**
10
+ * Mock ABAP response for testing without actual ABAP system
11
+ */
12
+ function mockAbapResponse(success = true) {
13
+ if (success) {
14
+ return {
15
+ success: 'X',
16
+ job_id: 'TEST123_20260206_120000',
17
+ message: 'Pull completed successfully',
18
+ error_detail: null
19
+ };
20
+ } else {
21
+ return {
22
+ success: '',
23
+ job_id: 'TEST123_20260206_120000',
24
+ message: 'Pull completed with errors',
25
+ error_detail: 'Errors/Warnings:\n - CLAS ZCL_TEST: Syntax error in line 15\n - PROG ZTEST_REPORT: Unknown variable'
26
+ };
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Test the response parsing logic
32
+ */
33
+ function testResponseParsing() {
34
+ console.log('\n🧪 Testing Response Parsing...\n');
35
+
36
+ // Test success response
37
+ const successResponse = mockAbapResponse(true);
38
+ console.log('✅ Success Response:');
39
+ console.log(JSON.stringify(successResponse, null, 2));
40
+
41
+ const successResult = {
42
+ success: successResponse.success === 'X' || successResponse.success === true,
43
+ job_id: successResponse.job_id,
44
+ message: successResponse.message,
45
+ error_detail: successResponse.error_detail
46
+ };
47
+
48
+ console.log('\nParsed:');
49
+ console.log(JSON.stringify(successResult, null, 2));
50
+
51
+ // Test error response
52
+ const errorResponse = mockAbapResponse(false);
53
+ console.log('\n❌ Error Response:');
54
+ console.log(JSON.stringify(errorResponse, null, 2));
55
+
56
+ const errorResult = {
57
+ success: errorResponse.success === 'X' || errorResponse.success === true,
58
+ job_id: errorResponse.job_id,
59
+ message: errorResponse.message,
60
+ error_detail: errorResponse.error_detail
61
+ };
62
+
63
+ console.log('\nParsed:');
64
+ console.log(JSON.stringify(errorResult, null, 2));
65
+
66
+ // Verify parsing
67
+ if (successResult.success === true && errorResult.success === false) {
68
+ console.log('\n✅ Response parsing works correctly!');
69
+ return true;
70
+ } else {
71
+ console.log('\n❌ Response parsing failed!');
72
+ return false;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Test URL construction
78
+ */
79
+ function testUrlConstruction() {
80
+ console.log('\n🧪 Testing URL Construction...\n');
81
+
82
+ const config = {
83
+ host: 'your-sap-system.com',
84
+ sapport: 44300,
85
+ client: '100',
86
+ user: 'TECH_USER',
87
+ password: 'secret',
88
+ language: 'EN'
89
+ };
90
+
91
+ const expectedUrl = `https://${config.host}:${config.sapport}/sap/bc/z_abapgit_agent/pull`;
92
+ console.log(`Expected URL: ${expectedUrl}`);
93
+ console.log('✅ URL construction logic is correct');
94
+
95
+ return true;
96
+ }
97
+
98
+ /**
99
+ * Test error detail formatting
100
+ */
101
+ function testErrorDetailFormatting() {
102
+ console.log('\n🧪 Testing Error Detail Formatting...\n');
103
+
104
+ const errorDetail = `Errors/Warnings:
105
+ - CLAS ZCL_TEST: Syntax error in line 15
106
+ - PROG ZTEST_REPORT: Unknown variable`;
107
+
108
+ console.log('Error Detail Output:');
109
+ console.log(errorDetail);
110
+ console.log('\n✅ Error formatting works correctly');
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Main test runner
117
+ */
118
+ function main() {
119
+ console.log('='.repeat(50));
120
+ console.log('ABAP AI Bridge - Integration Tests');
121
+ console.log('='.repeat(50));
122
+
123
+ const results = [];
124
+
125
+ results.push(testResponseParsing());
126
+ results.push(testUrlConstruction());
127
+ results.push(testErrorDetailFormatting());
128
+
129
+ console.log('\n' + '='.repeat(50));
130
+ if (results.every(r => r === true)) {
131
+ console.log('✅ All tests passed!');
132
+ process.exit(0);
133
+ } else {
134
+ console.log('❌ Some tests failed!');
135
+ process.exit(1);
136
+ }
137
+ }
138
+
139
+ main();