s9n-devops-agent 2.0.14 → 2.0.18-dev.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s9n-devops-agent",
3
- "version": "2.0.14",
3
+ "version": "2.0.18-dev.1",
4
4
  "description": "CS_DevOpsAgent - Intelligent Git Automation System with multi-agent support and session management",
5
5
  "type": "module",
6
6
  "main": "src/cs-devops-agent-worker.js",
@@ -12,8 +12,11 @@
12
12
  "src/",
13
13
  "scripts/",
14
14
  "docs/",
15
+ "House_Rules_Contracts/",
15
16
  "start-devops-session.sh",
16
17
  "cleanup-sessions.sh",
18
+ "houserules.md",
19
+ "houserules_structured.md",
17
20
  "LICENSE",
18
21
  "README.md"
19
22
  ],
@@ -25,6 +28,7 @@
25
28
  "devops:start": "node src/session-coordinator.js start",
26
29
  "devops:close": "node src/close-session.js",
27
30
  "devops:cleanup": "./cleanup-sessions.sh",
31
+ "chat": "node src/agent-chat.js",
28
32
  "setup": "node src/setup-cs-devops-agent.js",
29
33
  "house-rules:status": "node src/house-rules-manager.js status",
30
34
  "house-rules:update": "node src/house-rules-manager.js update",
@@ -33,7 +37,7 @@
33
37
  "test:watch": "jest --watch",
34
38
  "test:coverage": "jest --coverage",
35
39
  "test:worktree": "jest test_cases/worktree/",
36
- "test:e2e": "./test_scripts/test-multi-agent-e2e.sh",
40
+ "test:e2e": "./test_scripts/test-e2e-multi-session.sh",
37
41
  "test:contracts": "jest tests/integration/contract-workflow.test.js",
38
42
  "test:push-behind": "jest test_scripts/push_behind_spec.js",
39
43
  "test:debug": "./debug-failing-tests.sh"
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ============================================================================
5
+ * AI COMMIT MESSAGE GENERATOR (via Groq/Llama 3)
6
+ * ============================================================================
7
+ *
8
+ * Generates conventional commit messages by analyzing staged changes.
9
+ * Enforces project House Rules and commit conventions.
10
+ *
11
+ * Usage:
12
+ * node scripts/generate-ai-commit.js
13
+ * node scripts/generate-ai-commit.js --dry-run
14
+ *
15
+ * Configuration:
16
+ * Requires GROQ_API_KEY in .env or environment
17
+ * ============================================================================
18
+ */
19
+
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import { execSync } from 'child_process';
23
+ import Groq from 'groq-sdk';
24
+ import { credentialsManager } from '../src/credentials-manager.js';
25
+
26
+ // Inject credentials
27
+ credentialsManager.injectEnv();
28
+
29
+ const CONFIG = {
30
+ model: 'llama-3.1-70b-versatile',
31
+ maxDiffLength: 12000, // Characters
32
+ houseRulesPath: 'houserules.md'
33
+ };
34
+
35
+ async function main() {
36
+ try {
37
+ // 1. Check for API Key
38
+ if (!process.env.GROQ_API_KEY) {
39
+ console.error('❌ GROQ_API_KEY not found. Cannot generate AI commit message.');
40
+ process.exit(1);
41
+ }
42
+
43
+ const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
44
+
45
+ // 2. Get Staged Diff
46
+ let diff = '';
47
+ try {
48
+ diff = execSync('git diff --cached', { encoding: 'utf8', maxBuffer: 1024 * 1024 });
49
+ } catch (e) {
50
+ console.error('❌ Failed to get git diff. Are there staged changes?');
51
+ process.exit(1);
52
+ }
53
+
54
+ if (!diff.trim()) {
55
+ console.log('ℹ️ No staged changes to analyze.');
56
+ process.exit(0);
57
+ }
58
+
59
+ // Truncate diff if too large
60
+ if (diff.length > CONFIG.maxDiffLength) {
61
+ diff = diff.substring(0, CONFIG.maxDiffLength) + '\n... (truncated)';
62
+ }
63
+
64
+ // 3. Read House Rules (if available)
65
+ let houseRules = '';
66
+ if (fs.existsSync(CONFIG.houseRulesPath)) {
67
+ houseRules = fs.readFileSync(CONFIG.houseRulesPath, 'utf8');
68
+ // Truncate house rules to essential sections if needed to save context
69
+ houseRules = houseRules.substring(0, 5000);
70
+ }
71
+
72
+ // 4. Construct Prompt
73
+ const prompt = `
74
+ You are a senior DevOps engineer and code reviewer.
75
+ Generate a conventional commit message for the following git diff.
76
+
77
+ CONTEXT:
78
+ - Project uses Semantic Versioning.
79
+ - Strict House Rules are in effect.
80
+
81
+ HOUSE RULES SUMMARY:
82
+ ${houseRules}
83
+
84
+ INSTRUCTIONS:
85
+ 1. Format: <type>(<scope>): <subject>
86
+ 2. Followed by a blank line and a bulleted list of changes.
87
+ 3. Type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
88
+ 4. Scope is the affected module/component (optional).
89
+ 5. Subject must be imperative, lowercase, no period.
90
+ 6. Check the diff for any violations of House Rules (e.g. console.logs, hardcoded secrets) and mention them in the body if critical.
91
+ 7. Output ONLY the commit message. No markdown blocks, no conversational text.
92
+
93
+ GIT DIFF:
94
+ ${diff}
95
+ `;
96
+
97
+ // 5. Call AI
98
+ console.log('🤖 Analyzing changes with Llama 3...');
99
+ const completion = await groq.chat.completions.create({
100
+ messages: [
101
+ { role: 'system', content: 'You are a helpful coding assistant.' },
102
+ { role: 'user', content: prompt }
103
+ ],
104
+ model: CONFIG.model,
105
+ temperature: 0.2, // Low temperature for deterministic output
106
+ max_tokens: 500,
107
+ });
108
+
109
+ const commitMsg = completion.choices[0]?.message?.content?.trim();
110
+
111
+ if (!commitMsg) {
112
+ throw new Error('Received empty response from AI.');
113
+ }
114
+
115
+ // 6. Output or Save
116
+ // If --dry-run, just print
117
+ if (process.argv.includes('--dry-run')) {
118
+ console.log('\n--- Generated Message ---\n');
119
+ console.log(commitMsg);
120
+ console.log('\n-------------------------\n');
121
+ } else {
122
+ // Determine output file
123
+ const agentName = process.env.AGENT_NAME || 'claude';
124
+ const outputFile = `.${agentName.toLowerCase()}-commit-msg`;
125
+ fs.writeFileSync(outputFile, commitMsg);
126
+ console.log(`✅ Commit message written to ${outputFile}`);
127
+ }
128
+
129
+ } catch (error) {
130
+ console.error(`❌ Error generating commit message: ${error.message}`);
131
+ process.exit(1);
132
+ }
133
+ }
134
+
135
+ main();
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+
3
+ # ============================================================================
4
+ # Local Installation Helper
5
+ # ============================================================================
6
+ # Installs the current version of DevOps Agent into a target repository
7
+ # for testing purposes without publishing to npm.
8
+ # ============================================================================
9
+
10
+ if [ -z "$1" ]; then
11
+ echo "Usage: $0 /path/to/target/repo"
12
+ echo "Example: $0 ../my-other-project"
13
+ exit 1
14
+ fi
15
+
16
+ TARGET_REPO=$(realpath "$1")
17
+ CURRENT_DIR=$(pwd)
18
+
19
+ echo "📦 Packaging DevOps Agent..."
20
+ npm pack
21
+
22
+ PACKAGE_FILE=$(ls s9n-devops-agent-*.tgz)
23
+
24
+ if [ -z "$PACKAGE_FILE" ]; then
25
+ echo "❌ Error: Package file not found"
26
+ exit 1
27
+ fi
28
+
29
+ echo "🚀 Installing into $TARGET_REPO..."
30
+
31
+ cd "$TARGET_REPO" || exit 1
32
+
33
+ # Install the tarball
34
+ npm install -g "$CURRENT_DIR/$PACKAGE_FILE"
35
+
36
+ # Clean up
37
+ cd "$CURRENT_DIR"
38
+ rm "$PACKAGE_FILE"
39
+
40
+ echo "✅ Installation complete!"
41
+ echo "You can now run 's9n-devops-agent' or 'devops' in the target repository."
42
+ echo "To test Kora, run: s9n-devops-agent chat"
@@ -0,0 +1,457 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ============================================================================
5
+ * SMART ASSISTANT - Conversational UX for DevOps Agent
6
+ * ============================================================================
7
+ *
8
+ * This module provides a conversational interface (Chat UX) for the DevOps Agent.
9
+ * It uses Groq LLM to understand user intent and execute agent commands.
10
+ *
11
+ * CAPABILITIES:
12
+ * - Answer questions about House Rules and Contracts
13
+ * - Help start sessions with proper naming and context
14
+ * - Analyze current project status
15
+ * - Guide users through the development workflow
16
+ *
17
+ * ============================================================================
18
+ */
19
+
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import readline from 'readline';
23
+ import Groq from 'groq-sdk';
24
+ import { fileURLToPath } from 'url';
25
+ import { dirname } from 'path';
26
+ import { spawn } from 'child_process';
27
+ import { credentialsManager } from './credentials-manager.js';
28
+ import HouseRulesManager from './house-rules-manager.js';
29
+ // We'll import SessionCoordinator dynamically to avoid circular deps if any
30
+
31
+ const __filename = fileURLToPath(import.meta.url);
32
+ const __dirname = dirname(__filename);
33
+
34
+ // Initialize credentials
35
+ credentialsManager.injectEnv();
36
+
37
+ const CONFIG = {
38
+ model: 'llama-3.3-70b-versatile',
39
+ colors: {
40
+ reset: '\x1b[0m',
41
+ bright: '\x1b[1m',
42
+ dim: '\x1b[2m',
43
+ green: '\x1b[32m',
44
+ yellow: '\x1b[33m',
45
+ blue: '\x1b[36m',
46
+ magenta: '\x1b[35m',
47
+ cyan: '\x1b[36m',
48
+ red: '\x1b[31m'
49
+ }
50
+ };
51
+
52
+ class SmartAssistant {
53
+ constructor() {
54
+ this.groq = new Groq({
55
+ apiKey: process.env.GROQ_API_KEY || process.env.OPENAI_API_KEY
56
+ });
57
+
58
+ this.history = [];
59
+ this.repoRoot = process.cwd();
60
+ this.houseRulesManager = new HouseRulesManager(this.repoRoot);
61
+
62
+ // Tools definition for the LLM
63
+ this.tools = [
64
+ {
65
+ type: "function",
66
+ function: {
67
+ name: "get_house_rules_summary",
68
+ description: "Get a summary of the current project's House Rules and folder structure",
69
+ parameters: { type: "object", properties: {} }
70
+ }
71
+ },
72
+ {
73
+ type: "function",
74
+ function: {
75
+ name: "list_contracts",
76
+ description: "List all available contract files and their completion status",
77
+ parameters: { type: "object", properties: {} }
78
+ }
79
+ },
80
+ {
81
+ type: "function",
82
+ function: {
83
+ name: "start_session",
84
+ description: "Start a new development session with a specific task name",
85
+ parameters: {
86
+ type: "object",
87
+ properties: {
88
+ taskName: { type: "string", description: "The name of the task (kebab-case preferred)" },
89
+ description: { type: "string", description: "Brief description of the task" }
90
+ },
91
+ required: ["taskName"]
92
+ }
93
+ }
94
+ },
95
+ {
96
+ type: "function",
97
+ function: {
98
+ name: "check_session_status",
99
+ description: "Check the status of active sessions and locks",
100
+ parameters: { type: "object", properties: {} }
101
+ }
102
+ }
103
+ ];
104
+
105
+ this.systemPrompt = `You are Kora, the Smart DevOps Assistant.
106
+ Your goal is to help developers follow the House Rules and Contract System while being helpful and efficient.
107
+
108
+ CONTEXT:
109
+ - You are running inside a "DevOps Agent" environment.
110
+ - The project follows a strict "Contract System" (API, DB, Features, etc.).
111
+ - Users need to create "Sessions" to do work.
112
+ - You can execute tools to help the user.
113
+
114
+ STYLE:
115
+ - Be concise but helpful.
116
+ - Identify yourself as "Kora".
117
+ - If the user asks about starting a task, ask for a clear task name if not provided.
118
+ - If the user asks about rules, summarize them from the actual files.
119
+ - Always prefer "Structured" organization for new code.
120
+
121
+ When you want to perform an action, use the available tools.`;
122
+ }
123
+
124
+ /**
125
+ * Initialize the chat session
126
+ */
127
+ async start() {
128
+ // Check for Groq API Key
129
+ if (!credentialsManager.hasGroqApiKey()) {
130
+ console.log('\n' + '='.repeat(60));
131
+ console.log(`${CONFIG.colors.yellow}⚠️ GROQ API KEY MISSING${CONFIG.colors.reset}`);
132
+ console.log('='.repeat(60));
133
+ console.log('\nTo use Kora (Smart DevOps Assistant), you need a Groq API key.');
134
+ console.log('It allows Kora to understand your requests and help you manage sessions.\n');
135
+
136
+ const rl = readline.createInterface({
137
+ input: process.stdin,
138
+ output: process.stdout
139
+ });
140
+
141
+ console.log(`${CONFIG.colors.bright}How to get a key:${CONFIG.colors.reset}`);
142
+ console.log(`1. Go to: ${CONFIG.colors.cyan}https://console.groq.com/keys${CONFIG.colors.reset}`);
143
+ console.log('2. Log in or sign up');
144
+ console.log('3. Click "Create API Key"');
145
+ console.log('4. Copy the key and paste it below\n');
146
+
147
+ const apiKey = await new Promise((resolve) => {
148
+ rl.question(`${CONFIG.colors.green}Enter your Groq API Key: ${CONFIG.colors.reset}`, (answer) => {
149
+ resolve(answer.trim());
150
+ });
151
+ });
152
+
153
+ if (apiKey) {
154
+ credentialsManager.setGroqApiKey(apiKey);
155
+ // Re-initialize Groq client with new key
156
+ this.groq = new Groq({
157
+ apiKey: apiKey
158
+ });
159
+ console.log(`\n${CONFIG.colors.green}✅ API Key saved successfully!${CONFIG.colors.reset}\n`);
160
+ } else {
161
+ console.log(`\n${CONFIG.colors.red}❌ No key provided. Exiting.${CONFIG.colors.reset}`);
162
+ process.exit(1);
163
+ }
164
+ rl.close();
165
+ }
166
+
167
+ console.log('\n' + '='.repeat(60));
168
+ console.log(`${CONFIG.colors.magenta}🤖 Kora - Smart DevOps Assistant${CONFIG.colors.reset}`);
169
+ console.log(`${CONFIG.colors.dim}Powered by Groq (${CONFIG.model})${CONFIG.colors.reset}`);
170
+ console.log('='.repeat(60));
171
+ console.log(`\n${CONFIG.colors.cyan}Hi! I'm Kora. How can I help you today?${CONFIG.colors.reset}`);
172
+ console.log(`${CONFIG.colors.dim}(Try: "Start a new task for login", "Explain house rules", "Check contracts")${CONFIG.colors.reset}\n`);
173
+
174
+ this.startReadline();
175
+ }
176
+
177
+ startReadline() {
178
+ if (this.rl) {
179
+ this.rl.close();
180
+ }
181
+
182
+ this.rl = readline.createInterface({
183
+ input: process.stdin,
184
+ output: process.stdout,
185
+ prompt: `${CONFIG.colors.green}You > ${CONFIG.colors.reset}`
186
+ });
187
+
188
+ this.rl.prompt();
189
+
190
+ this.rl.on('line', async (line) => {
191
+ const input = line.trim();
192
+ if (!input) {
193
+ this.rl.prompt();
194
+ return;
195
+ }
196
+
197
+ if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
198
+ console.log(`${CONFIG.colors.magenta}Goodbye!${CONFIG.colors.reset}`);
199
+ process.exit(0);
200
+ }
201
+
202
+ await this.handleUserMessage(input);
203
+ // Only prompt if rl is still active (it might be closed if starting a session)
204
+ if (this.rl && !this.rl.closed) {
205
+ this.rl.prompt();
206
+ }
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Handle a user message through the LLM
212
+ */
213
+ async handleUserMessage(content) {
214
+ // Add user message to history
215
+ this.history.push({ role: 'user', content });
216
+
217
+ // Pause readline while thinking/executing
218
+ if (this.rl) {
219
+ this.rl.pause();
220
+ }
221
+
222
+ try {
223
+ process.stdout.write(`${CONFIG.colors.dim}Thinking...${CONFIG.colors.reset}`);
224
+
225
+ const response = await this.groq.chat.completions.create({
226
+ model: CONFIG.model,
227
+ messages: [
228
+ { role: 'system', content: this.systemPrompt },
229
+ ...this.history
230
+ ],
231
+ tools: this.tools,
232
+ tool_choice: "auto",
233
+ temperature: 0.5,
234
+ max_tokens: 1024
235
+ });
236
+
237
+ const message = response.choices[0].message;
238
+
239
+ // Clear "Thinking..."
240
+ readline.cursorTo(process.stdout, 0);
241
+ readline.clearLine(process.stdout, 0);
242
+
243
+ if (message.tool_calls) {
244
+ // Handle tool calls
245
+ await this.handleToolCalls(message.tool_calls);
246
+ } else {
247
+ // Just a text response
248
+ console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}${message.content}\n`);
249
+ this.history.push(message);
250
+ }
251
+ } catch (error) {
252
+ readline.cursorTo(process.stdout, 0);
253
+ readline.clearLine(process.stdout, 0);
254
+ console.error(`${CONFIG.colors.red}Error: ${error.message}${CONFIG.colors.reset}\n`);
255
+ } finally {
256
+ // Resume readline if it exists and we're not starting a session (which handles its own RL)
257
+ if (this.rl && !this.rl.closed) {
258
+ this.rl.resume();
259
+ }
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Execute tool calls from the LLM
265
+ */
266
+ async handleToolCalls(toolCalls) {
267
+ // Add the assistant's message with tool calls to history
268
+ this.history.push({
269
+ role: 'assistant',
270
+ content: null,
271
+ tool_calls: toolCalls
272
+ });
273
+
274
+ for (const toolCall of toolCalls) {
275
+ const functionName = toolCall.function.name;
276
+ const args = JSON.parse(toolCall.function.arguments);
277
+
278
+ console.log(`${CONFIG.colors.dim}Executing: ${functionName}...${CONFIG.colors.reset}`);
279
+
280
+ let result;
281
+ try {
282
+ switch (functionName) {
283
+ case 'get_house_rules_summary':
284
+ result = await this.getHouseRulesSummary();
285
+ break;
286
+ case 'list_contracts':
287
+ result = await this.listContracts();
288
+ break;
289
+ case 'check_session_status':
290
+ result = await this.checkSessionStatus();
291
+ break;
292
+ case 'start_session':
293
+ result = await this.startSession(args);
294
+ break;
295
+ default:
296
+ result = JSON.stringify({ error: "Unknown tool" });
297
+ }
298
+ } catch (err) {
299
+ result = JSON.stringify({ error: err.message });
300
+ }
301
+
302
+ // Add tool result to history
303
+ this.history.push({
304
+ tool_call_id: toolCall.id,
305
+ role: "tool",
306
+ name: functionName,
307
+ content: result
308
+ });
309
+ }
310
+
311
+ // Get final response from LLM after tool execution
312
+ try {
313
+ const response = await this.groq.chat.completions.create({
314
+ model: CONFIG.model,
315
+ messages: [
316
+ { role: 'system', content: this.systemPrompt },
317
+ ...this.history
318
+ ]
319
+ });
320
+
321
+ const finalMessage = response.choices[0].message;
322
+ console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}${finalMessage.content}\n`);
323
+ this.history.push(finalMessage);
324
+
325
+ } catch (error) {
326
+ console.error(`${CONFIG.colors.red}Error getting final response: ${error.message}${CONFIG.colors.reset}`);
327
+ }
328
+ }
329
+
330
+ // ==========================================================================
331
+ // TOOL IMPLEMENTATIONS
332
+ // ==========================================================================
333
+
334
+ async getHouseRulesSummary() {
335
+ const status = this.houseRulesManager.getStatus();
336
+ const rulesPath = this.houseRulesManager.houseRulesPath;
337
+
338
+ if (fs.existsSync(rulesPath)) {
339
+ const content = fs.readFileSync(rulesPath, 'utf8');
340
+ // Extract first 50 lines or so for context
341
+ const summary = content.split('\n').slice(0, 50).join('\n');
342
+ return JSON.stringify({
343
+ exists: true,
344
+ status: status,
345
+ preview: summary,
346
+ path: rulesPath
347
+ });
348
+ }
349
+ return JSON.stringify({ exists: false, message: "House Rules file not found." });
350
+ }
351
+
352
+ async listContracts() {
353
+ const contractsDir = path.join(this.repoRoot, 'House_Rules_Contracts');
354
+ if (!fs.existsSync(contractsDir)) {
355
+ return JSON.stringify({ exists: false, message: "Contracts folder not found." });
356
+ }
357
+
358
+ const files = fs.readdirSync(contractsDir).filter(f => f.endsWith('.md') || f.endsWith('.json'));
359
+ return JSON.stringify({
360
+ exists: true,
361
+ files: files,
362
+ location: contractsDir
363
+ });
364
+ }
365
+
366
+ async checkSessionStatus() {
367
+ // We need to import SessionCoordinator here to avoid top-level await/circular deps issues
368
+ // Note: In a real implementation we might want to refactor SessionCoordinator to be more modular
369
+ // For now, we'll check the file system directly which is safer/faster for this tool
370
+
371
+ const sessionsDir = path.join(this.repoRoot, 'local_deploy/sessions');
372
+ const locksDir = path.join(this.repoRoot, 'local_deploy/session-locks');
373
+
374
+ let activeSessions = [];
375
+ let activeLocks = [];
376
+
377
+ if (fs.existsSync(sessionsDir)) {
378
+ activeSessions = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.json'));
379
+ }
380
+
381
+ if (fs.existsSync(locksDir)) {
382
+ activeLocks = fs.readdirSync(locksDir);
383
+ }
384
+
385
+ return JSON.stringify({
386
+ activeSessionsCount: activeSessions.length,
387
+ activeLocksCount: activeLocks.length,
388
+ sessions: activeSessions,
389
+ locks: activeLocks
390
+ });
391
+ }
392
+
393
+ async startSession(args) {
394
+ const taskName = args.taskName;
395
+
396
+ console.log(`${CONFIG.colors.magenta}Kora > ${CONFIG.colors.reset}Starting new session for: ${taskName}...`);
397
+
398
+ // Close readline interface to release stdin for the child process
399
+ if (this.rl) {
400
+ this.rl.close();
401
+ this.rl = null;
402
+ }
403
+
404
+ // We need to run the session coordinator interactively
405
+ // We'll use the 'create-and-start' command to jump straight to the task
406
+ const scriptPath = path.join(__dirname, 'session-coordinator.js');
407
+
408
+ return new Promise((resolve, reject) => {
409
+ // Use 'inherit' for stdio to allow interactive input/output
410
+ const child = spawn('node', [scriptPath, 'create-and-start', '--task', taskName], {
411
+ stdio: 'inherit',
412
+ cwd: this.repoRoot
413
+ });
414
+
415
+ child.on('close', (code) => {
416
+ // Re-initialize readline interface after child process exits
417
+ this.startReadline();
418
+
419
+ if (code === 0) {
420
+ resolve(JSON.stringify({
421
+ success: true,
422
+ message: `Session for '${taskName}' completed successfully.`
423
+ }));
424
+ } else {
425
+ resolve(JSON.stringify({
426
+ success: false,
427
+ message: `Session process exited with code ${code}.`
428
+ }));
429
+ }
430
+
431
+ // Resume the chat interface after the child process exits
432
+ console.log(`\n${CONFIG.colors.cyan}Welcome back to Kora!${CONFIG.colors.reset}`);
433
+ });
434
+
435
+ child.on('error', (err) => {
436
+ // Re-initialize readline interface on error
437
+ this.startReadline();
438
+
439
+ resolve(JSON.stringify({
440
+ success: false,
441
+ error: err.message
442
+ }));
443
+ });
444
+ });
445
+ }
446
+ }
447
+
448
+ export { SmartAssistant };
449
+
450
+ // Run the assistant only if executed directly
451
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
452
+ const assistant = new SmartAssistant();
453
+ assistant.start().catch(err => {
454
+ console.error('Fatal Error:', err);
455
+ process.exit(1);
456
+ });
457
+ }
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
8
8
  const rootDir = path.join(__dirname, '..');
9
9
 
10
- const CREDENTIALS_PATH = path.join(rootDir, 'local_deploy', 'credentials.json');
10
+ const CREDENTIALS_PATH = process.env.DEVOPS_CREDENTIALS_PATH || path.join(rootDir, 'local_deploy', 'credentials.json');
11
11
 
12
12
  // Simple obfuscation to prevent casual shoulder surfing
13
13
  // NOTE: This is NOT strong encryption. In a production environment with sensitive keys,