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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-termo",
3
- "version": "1.0.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.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
- // Apply CLI overrides to env before main initializes
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(' Create a .env file with your NIM_API_KEY to get started.');
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
- // Check for unparsed arguments — if none, run repl mode
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 runRepl(process.cwd());
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
+ });
@@ -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
+ }