agent-termo 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.
- package/README.md +3 -9
- package/dist/cli.js +71 -67
- package/dist/cli.js.map +3 -3
- package/dist/main.js +54 -54
- package/dist/main.js.map +3 -3
- package/package.json +2 -2
- package/src/cli.ts +91 -12
- package/src/utils/config.ts +45 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-termo",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Terminal-based AI coding agent that replicates Claude Code experience",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -87,4 +87,4 @@
|
|
|
87
87
|
],
|
|
88
88
|
"outputPath": "dist"
|
|
89
89
|
}
|
|
90
|
-
}
|
|
90
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -5,25 +5,104 @@
|
|
|
5
5
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { runRepl } from './main.js';
|
|
8
|
+
import { saveNimApiKey } from './utils/config.js';
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
10
11
|
|
|
12
|
+
async function promptForApiKey(): Promise<string> {
|
|
13
|
+
const input = process.stdin;
|
|
14
|
+
const output = process.stdout;
|
|
15
|
+
|
|
16
|
+
if (!input.isTTY || typeof input.setRawMode !== 'function') {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'NVIDIA NIM API key is missing. Run Agent-Termo in an interactive terminal or set NIM_API_KEY before starting.',
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log('An NVIDIA NIM API key is required to start Agent-Termo.');
|
|
23
|
+
console.log('Get your key at https://build.nvidia.com/');
|
|
24
|
+
output.write('Enter your API key (input hidden): ');
|
|
25
|
+
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
let apiKey = '';
|
|
28
|
+
let previousWasCarriageReturn = false;
|
|
29
|
+
|
|
30
|
+
const restoreTerminal = () => {
|
|
31
|
+
input.off('data', onData);
|
|
32
|
+
input.setRawMode(false);
|
|
33
|
+
input.pause();
|
|
34
|
+
output.write('\n');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const onData = (data: Buffer) => {
|
|
38
|
+
for (const char of data.toString('utf-8')) {
|
|
39
|
+
if (char === '\n' && previousWasCarriageReturn) {
|
|
40
|
+
previousWasCarriageReturn = false;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
previousWasCarriageReturn = char === '\r';
|
|
44
|
+
|
|
45
|
+
if (char === '\u0003' || char === '\u0004') {
|
|
46
|
+
restoreTerminal();
|
|
47
|
+
reject(new Error('API key setup cancelled.'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (char === '\r' || char === '\n') {
|
|
52
|
+
if (!apiKey.trim()) {
|
|
53
|
+
output.write('\nAn API key is required. Enter your API key: ');
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
restoreTerminal();
|
|
58
|
+
resolve(apiKey.trim());
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (char === '\u007f' || char === '\b') {
|
|
63
|
+
apiKey = apiKey.slice(0, -1);
|
|
64
|
+
} else if (char >= ' ') {
|
|
65
|
+
apiKey += char;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
input.setRawMode(true);
|
|
71
|
+
input.resume();
|
|
72
|
+
input.on('data', onData);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function startAgent(
|
|
77
|
+
project: string,
|
|
78
|
+
options: { apiKey?: string; model?: string; debug?: boolean },
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
// Apply CLI overrides to env before main initializes.
|
|
81
|
+
if (options.apiKey) process.env.NIM_API_KEY = options.apiKey;
|
|
82
|
+
if (options.model) process.env.NIM_MODEL = options.model;
|
|
83
|
+
if (options.debug) process.env.LOG_LEVEL = 'debug';
|
|
84
|
+
|
|
85
|
+
if (!process.env.NIM_API_KEY?.trim()) {
|
|
86
|
+
const apiKey = await promptForApiKey();
|
|
87
|
+
saveNimApiKey(apiKey);
|
|
88
|
+
process.env.NIM_API_KEY = apiKey;
|
|
89
|
+
console.log('API key saved. Agent-Termo will remember it for future sessions.');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`Starting Agent-Termo in: ${project}`);
|
|
93
|
+
await runRepl(project);
|
|
94
|
+
}
|
|
95
|
+
|
|
11
96
|
program
|
|
12
97
|
.name('agent-termo')
|
|
13
98
|
.description('Terminal AI coding agent with Nvidia NIM APIs')
|
|
14
|
-
.version('1.0.
|
|
99
|
+
.version('1.0.1')
|
|
15
100
|
.argument('[project]', 'Project directory to operate in', process.cwd())
|
|
16
101
|
.option('-k, --api-key <key>', 'Nvidia NIM API key')
|
|
17
102
|
.option('-m, --model <model>', 'Model name to use')
|
|
18
103
|
.option('--debug', 'Enable debug logging')
|
|
19
104
|
.action(async (project: string, options: { apiKey?: string; model?: string; debug?: boolean }) => {
|
|
20
|
-
|
|
21
|
-
if (options.apiKey) process.env.NIM_API_KEY = options.apiKey;
|
|
22
|
-
if (options.model) process.env.NIM_MODEL = options.model;
|
|
23
|
-
if (options.debug) process.env.LOG_LEVEL = 'debug';
|
|
24
|
-
|
|
25
|
-
console.log(`Starting Agent-Termo in: ${project}`);
|
|
26
|
-
await runRepl(project);
|
|
105
|
+
await startAgent(project, options);
|
|
27
106
|
});
|
|
28
107
|
|
|
29
108
|
program
|
|
@@ -56,14 +135,14 @@ program
|
|
|
56
135
|
|
|
57
136
|
console.log(`\n✓ Initialized Agent-Termo in ${resolve(dir)}`);
|
|
58
137
|
console.log(' Created: docs/MEMORY.md, docs/TIMELINE.md');
|
|
59
|
-
console.log('
|
|
138
|
+
console.log(' Agent-Termo will ask for your NVIDIA NIM API key the first time you launch it.');
|
|
60
139
|
});
|
|
61
140
|
|
|
62
141
|
async function main() {
|
|
63
|
-
//
|
|
142
|
+
// Run REPL mode directly when no command or options are supplied.
|
|
64
143
|
const args = process.argv.slice(2);
|
|
65
144
|
if (args.length === 0) {
|
|
66
|
-
await
|
|
145
|
+
await startAgent(process.cwd(), {});
|
|
67
146
|
return;
|
|
68
147
|
}
|
|
69
148
|
|
|
@@ -73,4 +152,4 @@ async function main() {
|
|
|
73
152
|
main().catch((err) => {
|
|
74
153
|
console.error(`\nFatal error: ${err instanceof Error ? err.message : err}`);
|
|
75
154
|
process.exit(1);
|
|
76
|
-
});
|
|
155
|
+
});
|
package/src/utils/config.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Config Management
|
|
3
3
|
// =============================================================================
|
|
4
4
|
|
|
5
|
-
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { resolve, dirname } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import type { AppConfig } from '../types/index.js';
|
|
@@ -52,6 +52,49 @@ export function getAgentTermoDir(): string {
|
|
|
52
52
|
return resolve(process.env.HOME || process.env.USERPROFILE || '.', '.agent-termo');
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function loadStoredNimApiKey(): void {
|
|
56
|
+
if (process.env.NIM_API_KEY) return;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const stored = JSON.parse(
|
|
60
|
+
readFileSync(resolve(getAgentTermoDir(), 'config.json'), 'utf-8'),
|
|
61
|
+
) as { nimApiKey?: unknown };
|
|
62
|
+
|
|
63
|
+
if (typeof stored.nimApiKey === 'string' && stored.nimApiKey.trim()) {
|
|
64
|
+
process.env.NIM_API_KEY = stored.nimApiKey;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// No saved user configuration yet.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function saveNimApiKey(apiKey: string): void {
|
|
72
|
+
const configDir = getAgentTermoDir();
|
|
73
|
+
const configPath = resolve(configDir, 'config.json');
|
|
74
|
+
let storedConfig: Record<string, unknown> = {};
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const existing: unknown = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
78
|
+
if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
|
|
79
|
+
storedConfig = existing as Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Create a new configuration file when one does not exist.
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
storedConfig.nimApiKey = apiKey;
|
|
86
|
+
mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
87
|
+
writeFileSync(configPath, `${JSON.stringify(storedConfig, null, 2)}\n`, {
|
|
88
|
+
encoding: 'utf-8',
|
|
89
|
+
mode: 0o600,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Restrict permissions even when updating an existing file on Unix.
|
|
93
|
+
if (process.platform !== 'win32') chmodSync(configPath, 0o600);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
loadStoredNimApiKey();
|
|
97
|
+
|
|
55
98
|
export function getConfig(projectRoot: string): AppConfig {
|
|
56
99
|
loadEnvFile(resolve(projectRoot, '.env'));
|
|
57
100
|
|
|
@@ -72,4 +115,4 @@ export function getConfig(projectRoot: string): AppConfig {
|
|
|
72
115
|
|
|
73
116
|
export function resolveProjectRoot(cwd?: string): string {
|
|
74
117
|
return cwd || PROJECT_ROOT;
|
|
75
|
-
}
|
|
118
|
+
}
|