@terminusagents/agents 0.1.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 (55) hide show
  1. package/README.md +192 -0
  2. package/bin/terminus-agent.js +2 -0
  3. package/dist/agent/executor.d.ts +24 -0
  4. package/dist/agent/executor.js +96 -0
  5. package/dist/agents/budget-planner.d.ts +3 -0
  6. package/dist/agents/budget-planner.js +43 -0
  7. package/dist/agents/career-coach.d.ts +3 -0
  8. package/dist/agents/career-coach.js +43 -0
  9. package/dist/agents/crypto-advisor.d.ts +3 -0
  10. package/dist/agents/crypto-advisor.js +49 -0
  11. package/dist/agents/event-planner.d.ts +3 -0
  12. package/dist/agents/event-planner.js +41 -0
  13. package/dist/agents/fitness-coach.d.ts +3 -0
  14. package/dist/agents/fitness-coach.js +43 -0
  15. package/dist/agents/food-expert.d.ts +3 -0
  16. package/dist/agents/food-expert.js +43 -0
  17. package/dist/agents/fundamental-analyst.d.ts +3 -0
  18. package/dist/agents/fundamental-analyst.js +54 -0
  19. package/dist/agents/health-advisor.d.ts +3 -0
  20. package/dist/agents/health-advisor.js +52 -0
  21. package/dist/agents/index.d.ts +25 -0
  22. package/dist/agents/index.js +99 -0
  23. package/dist/agents/language-tutor.d.ts +3 -0
  24. package/dist/agents/language-tutor.js +41 -0
  25. package/dist/agents/legal-advisor.d.ts +3 -0
  26. package/dist/agents/legal-advisor.js +42 -0
  27. package/dist/agents/real-estate.d.ts +3 -0
  28. package/dist/agents/real-estate.js +41 -0
  29. package/dist/agents/shopping-assistant.d.ts +3 -0
  30. package/dist/agents/shopping-assistant.js +44 -0
  31. package/dist/agents/tech-support.d.ts +3 -0
  32. package/dist/agents/tech-support.js +46 -0
  33. package/dist/agents/technical-analyst.d.ts +3 -0
  34. package/dist/agents/technical-analyst.js +45 -0
  35. package/dist/agents/travel-planner.d.ts +3 -0
  36. package/dist/agents/travel-planner.js +91 -0
  37. package/dist/agents/types.d.ts +20 -0
  38. package/dist/agents/types.js +4 -0
  39. package/dist/cli/doctor.d.ts +4 -0
  40. package/dist/cli/doctor.js +184 -0
  41. package/dist/cli/init.d.ts +16 -0
  42. package/dist/cli/init.js +429 -0
  43. package/dist/cli/run.d.ts +1 -0
  44. package/dist/cli/run.js +98 -0
  45. package/dist/cli/status.d.ts +1 -0
  46. package/dist/cli/status.js +104 -0
  47. package/dist/config/store.d.ts +19 -0
  48. package/dist/config/store.js +148 -0
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +61 -0
  51. package/dist/llm/provider.d.ts +56 -0
  52. package/dist/llm/provider.js +181 -0
  53. package/dist/network/client.d.ts +33 -0
  54. package/dist/network/client.js +389 -0
  55. package/package.json +63 -0
@@ -0,0 +1,98 @@
1
+ // =============================================================================
2
+ // TERMINUS AGENT - Run Command (Enhanced UX)
3
+ // =============================================================================
4
+ import chalk from 'chalk';
5
+ import { loadConfig, configExists } from '../config/store.js';
6
+ import { AgentClient } from '../network/client.js';
7
+ const AGENT_EMOJIS = {
8
+ 'travel-planner': 'āœˆļø',
9
+ 'budget-planner': 'šŸ’°',
10
+ 'health-advisor': 'šŸ„',
11
+ 'fundamental-analyst': 'šŸ“Š',
12
+ 'technical-analyst': 'šŸ“ˆ',
13
+ 'crypto-advisor': 'šŸŖ™',
14
+ 'food-expert': 'šŸ³',
15
+ 'fitness-coach': 'šŸ’Ŗ',
16
+ 'legal-advisor': 'āš–ļø',
17
+ 'real-estate': 'šŸ ',
18
+ 'career-coach': 'šŸ’¼',
19
+ 'event-planner': 'šŸŽ‰',
20
+ 'tech-support': 'šŸ”§',
21
+ 'shopping-assistant': 'šŸ›’',
22
+ 'language-tutor': 'šŸŒ',
23
+ };
24
+ function printStartupBanner(config) {
25
+ if (!config)
26
+ return;
27
+ const emoji = AGENT_EMOJIS[config.agentType] || 'šŸ¤–';
28
+ const provider = config.llmProvider || 'grok';
29
+ const providerIcon = provider === 'grok' ? '🌐' : provider === 'ollama' ? 'šŸ¦™' : 'šŸ”§';
30
+ console.log();
31
+ console.log(chalk.cyan.bold('╔════════════════════════════════════════════════════════════╗'));
32
+ console.log(chalk.cyan.bold('ā•‘ šŸš€ TERMINUS AGENT STARTING ā•‘'));
33
+ console.log(chalk.cyan.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•'));
34
+ console.log();
35
+ console.log(` ${chalk.gray('Agent:')} ${emoji} ${chalk.white.bold(config.agentType)}`);
36
+ console.log(` ${chalk.gray('Node ID:')} ${chalk.cyan(config.nodeId)}`);
37
+ console.log(` ${chalk.gray('Wallet:')} ${config.wallet.slice(0, 10)}...${config.wallet.slice(-8)}`);
38
+ console.log(` ${chalk.gray('LLM Provider:')} ${providerIcon} ${provider}${config.llmModel ? ` (${config.llmModel})` : ''}`);
39
+ console.log(` ${chalk.gray('Profile:')} ${config.networkProfile || 'local'}`);
40
+ console.log(` ${chalk.gray('Control Plane:')} ${config.controlPlaneUrl}`);
41
+ console.log(chalk.gray('\n────────────────────────────────────────────────────────────\n'));
42
+ console.log(chalk.yellow(' ā³ Connecting to Control Plane...\n'));
43
+ }
44
+ export async function runCommand() {
45
+ if (!configExists()) {
46
+ console.log();
47
+ console.log(chalk.red.bold('╔════════════════════════════════════════╗'));
48
+ console.log(chalk.red.bold('ā•‘ āŒ NOT CONFIGURED ā•‘'));
49
+ console.log(chalk.red.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•'));
50
+ console.log();
51
+ console.log(chalk.gray(' No configuration found. Run setup first:\n'));
52
+ console.log(chalk.cyan(' npx terminus-agent init\n'));
53
+ process.exit(1);
54
+ }
55
+ const config = loadConfig();
56
+ if (!config) {
57
+ console.log(chalk.red('\n āŒ Failed to load configuration.\n'));
58
+ console.log(chalk.gray(' Try reconfiguring:'));
59
+ console.log(chalk.cyan(' npx terminus-agent init\n'));
60
+ console.log(chalk.gray(' Or run diagnostics:'));
61
+ console.log(chalk.cyan(' npx terminus-agent doctor\n'));
62
+ process.exit(1);
63
+ }
64
+ if (config.networkProfile === 'mainnet' && !config.controlPlaneUrl.startsWith('wss://')) {
65
+ console.log(chalk.red('\n āŒ Mainnet profile requires wss:// control-plane URL.\n'));
66
+ console.log(chalk.gray(' Fix with:'));
67
+ console.log(chalk.cyan(' npx terminus-agent init --profile mainnet\n'));
68
+ process.exit(1);
69
+ }
70
+ printStartupBanner(config);
71
+ if (!process.env.TERMINUS_WALLET_PRIVATE_KEY) {
72
+ console.log(chalk.yellow(' ⚠ TERMINUS_WALLET_PRIVATE_KEY is not set. Challenge-signature auth will fail in strict remote mode.\n'));
73
+ }
74
+ const client = new AgentClient(config);
75
+ // Handle graceful shutdown
76
+ process.on('SIGINT', () => {
77
+ console.log(chalk.yellow('\n\n šŸ›‘ Shutting down gracefully...\n'));
78
+ client.disconnect();
79
+ console.log(chalk.gray(' Agent disconnected. Goodbye! šŸ‘‹\n'));
80
+ process.exit(0);
81
+ });
82
+ process.on('SIGTERM', () => {
83
+ console.log(chalk.yellow('\n\n šŸ›‘ Received SIGTERM...\n'));
84
+ client.disconnect();
85
+ process.exit(0);
86
+ });
87
+ try {
88
+ await client.connect();
89
+ }
90
+ catch (error) {
91
+ console.log(chalk.red(`\n āŒ Connection failed: ${error.message}\n`));
92
+ console.log(chalk.gray(' Check if Control Plane is running:'));
93
+ console.log(chalk.gray(` ${config.controlPlaneUrl}\n`));
94
+ console.log(chalk.gray(' Run diagnostics:'));
95
+ console.log(chalk.cyan(' npx terminus-agent doctor\n'));
96
+ process.exit(1);
97
+ }
98
+ }
@@ -0,0 +1 @@
1
+ export declare function statusCommand(): Promise<void>;
@@ -0,0 +1,104 @@
1
+ // =============================================================================
2
+ // TERMINUS AGENT - Status Command (Enhanced UX)
3
+ // =============================================================================
4
+ import chalk from 'chalk';
5
+ import { loadConfig, configExists, getConfigPath } from '../config/store.js';
6
+ import { checkOllamaAvailable } from '../llm/provider.js';
7
+ const AGENT_EMOJIS = {
8
+ 'travel-planner': 'āœˆļø',
9
+ 'budget-planner': 'šŸ’°',
10
+ 'health-advisor': 'šŸ„',
11
+ 'fundamental-analyst': 'šŸ“Š',
12
+ 'technical-analyst': 'šŸ“ˆ',
13
+ 'crypto-advisor': 'šŸŖ™',
14
+ 'food-expert': 'šŸ³',
15
+ 'fitness-coach': 'šŸ’Ŗ',
16
+ 'legal-advisor': 'āš–ļø',
17
+ 'real-estate': 'šŸ ',
18
+ 'career-coach': 'šŸ’¼',
19
+ 'event-planner': 'šŸŽ‰',
20
+ 'tech-support': 'šŸ”§',
21
+ 'shopping-assistant': 'šŸ›’',
22
+ 'language-tutor': 'šŸŒ',
23
+ };
24
+ export async function statusCommand() {
25
+ console.log();
26
+ console.log(chalk.cyan.bold('╔════════════════════════════════════════╗'));
27
+ console.log(chalk.cyan.bold('ā•‘ šŸ“Š TERMINUS AGENT STATUS ā•‘'));
28
+ console.log(chalk.cyan.bold('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•'));
29
+ console.log();
30
+ if (!configExists()) {
31
+ console.log(chalk.red(' āŒ No configuration found.\n'));
32
+ console.log(chalk.gray(' Run this command to set up:'));
33
+ console.log(chalk.cyan(' npx terminus-agent init\n'));
34
+ return;
35
+ }
36
+ const config = loadConfig();
37
+ if (!config) {
38
+ console.log(chalk.red(' āŒ Failed to load configuration.\n'));
39
+ console.log(chalk.gray(' Try reconfiguring:'));
40
+ console.log(chalk.cyan(' npx terminus-agent init\n'));
41
+ return;
42
+ }
43
+ // Agent Info
44
+ console.log(chalk.yellow('ā”Œā”€ Agent Configuration ─────────────────┐\n'));
45
+ const emoji = AGENT_EMOJIS[config.agentType] || 'šŸ¤–';
46
+ console.log(` ${chalk.gray('Agent Type:')} ${emoji} ${chalk.white.bold(config.agentType)}`);
47
+ console.log(` ${chalk.gray('Node ID:')} ${chalk.cyan(config.nodeId)}`);
48
+ console.log(` ${chalk.gray('Config File:')} ${chalk.gray(getConfigPath())}`);
49
+ // Wallet Info
50
+ console.log(chalk.yellow('\nā”Œā”€ Wallet ───────────────────────────────┐\n'));
51
+ const walletShort = `${config.wallet.slice(0, 10)}...${config.wallet.slice(-8)}`;
52
+ const runtimePrivateKey = process.env.TERMINUS_WALLET_PRIVATE_KEY?.trim();
53
+ console.log(` ${chalk.gray('Address:')} ${chalk.green(walletShort)}`);
54
+ console.log(` ${chalk.gray('Private Key:')} ${runtimePrivateKey ? chalk.green('āœ“ Runtime env configured') : chalk.gray('ā—‹ Missing (set TERMINUS_WALLET_PRIVATE_KEY)')}`);
55
+ // LLM Provider Info
56
+ console.log(chalk.yellow('\nā”Œā”€ LLM Provider ─────────────────────────┐\n'));
57
+ const provider = config.llmProvider || 'grok';
58
+ const providerIcon = provider === 'grok' ? '🌐' : provider === 'ollama' ? 'šŸ¦™' : 'šŸ”§';
59
+ const providerName = provider === 'grok' ? 'Grok API (xAI Cloud)'
60
+ : provider === 'ollama' ? 'Ollama (Local)'
61
+ : 'OpenAI-Compatible';
62
+ console.log(` ${chalk.gray('Provider:')} ${providerIcon} ${chalk.white(providerName)}`);
63
+ if (config.llmModel) {
64
+ console.log(` ${chalk.gray('Model:')} ${chalk.white(config.llmModel)}`);
65
+ }
66
+ if (config.llmBaseUrl) {
67
+ console.log(` ${chalk.gray('Base URL:')} ${chalk.gray(config.llmBaseUrl)}`);
68
+ }
69
+ // Provider Status Check
70
+ if (provider === 'ollama') {
71
+ const ollamaOk = await checkOllamaAvailable(config.llmBaseUrl);
72
+ console.log(` ${chalk.gray('Status:')} ${ollamaOk ? chalk.green('āœ“ Connected') : chalk.red('āœ— Not reachable')}`);
73
+ }
74
+ else if (provider === 'grok') {
75
+ const hasRuntimeKey = Boolean(process.env.TERMINUS_GROK_API_KEY?.trim() || process.env.XAI_API_KEY?.trim());
76
+ const hasStoredKey = Boolean(config.apiKey?.trim() && config.apiKey !== '__ENV__');
77
+ const source = hasRuntimeKey ? 'Runtime env' : hasStoredKey ? 'Config file' : 'Missing';
78
+ console.log(` ${chalk.gray('API Key:')} ${hasRuntimeKey || hasStoredKey ? chalk.green(`āœ“ ${source}`) : chalk.red('āœ— Missing')}`);
79
+ }
80
+ // Network Info
81
+ console.log(chalk.yellow('\nā”Œā”€ Network ──────────────────────────────┐\n'));
82
+ console.log(` ${chalk.gray('Control Plane:')} ${chalk.cyan(config.controlPlaneUrl)}`);
83
+ // Test connection hint
84
+ const profile = config.networkProfile || (config.controlPlaneUrl.includes('localhost') ? 'local' : 'testnet');
85
+ if (profile === 'local') {
86
+ console.log(` ${chalk.gray('Profile:')} ${chalk.yellow('Local development')}`);
87
+ }
88
+ else if (profile === 'testnet') {
89
+ console.log(` ${chalk.gray('Profile:')} ${chalk.cyan('Base Sepolia testnet')}`);
90
+ }
91
+ else {
92
+ console.log(` ${chalk.gray('Profile:')} ${chalk.green('Base mainnet')}`);
93
+ }
94
+ if (profile === 'mainnet' && !config.controlPlaneUrl.startsWith('wss://')) {
95
+ console.log(` ${chalk.gray('Transport:')} ${chalk.red('Invalid for mainnet (requires wss://)')}`);
96
+ }
97
+ // Footer
98
+ console.log(chalk.gray('\n─────────────────────────────────────────\n'));
99
+ console.log(chalk.white.bold('šŸ“‹ Quick Commands:\n'));
100
+ console.log(` ${chalk.cyan('npx terminus-agent run')} ${chalk.gray('Start the agent')}`);
101
+ console.log(` ${chalk.cyan('npx terminus-agent init')} ${chalk.gray('Reconfigure')}`);
102
+ console.log(` ${chalk.cyan('npx terminus-agent doctor')} ${chalk.gray('Run readiness checks')}`);
103
+ console.log();
104
+ }
@@ -0,0 +1,19 @@
1
+ export type NetworkProfile = 'local' | 'testnet' | 'mainnet';
2
+ export interface AgentConfig {
3
+ agentType: string;
4
+ wallet: string;
5
+ privateKey?: string;
6
+ apiKey: string;
7
+ controlPlaneUrl: string;
8
+ nodeId: string;
9
+ llmProvider?: 'grok' | 'ollama' | 'openai-compatible';
10
+ llmBaseUrl?: string;
11
+ llmModel?: string;
12
+ networkProfile?: NetworkProfile;
13
+ }
14
+ export declare function getConfigPath(): string;
15
+ export declare function configExists(): boolean;
16
+ export declare function loadConfig(): AgentConfig | null;
17
+ export declare function validateConfig(config: AgentConfig): string[];
18
+ export declare function saveConfig(config: AgentConfig): void;
19
+ export declare function generateNodeId(agentType: string, wallet: string): string;
@@ -0,0 +1,148 @@
1
+ // =============================================================================
2
+ // TERMINUS AGENT - Config Store
3
+ // =============================================================================
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ import { randomUUID } from 'crypto';
8
+ const CONFIG_DIR = join(homedir(), '.terminus');
9
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
10
+ const FILE_MODE = 0o600;
11
+ const DIR_MODE = 0o700;
12
+ export function getConfigPath() {
13
+ return CONFIG_FILE;
14
+ }
15
+ export function configExists() {
16
+ return existsSync(CONFIG_FILE);
17
+ }
18
+ export function loadConfig() {
19
+ if (!existsSync(CONFIG_FILE)) {
20
+ return null;
21
+ }
22
+ securePermissions(CONFIG_DIR, DIR_MODE);
23
+ securePermissions(CONFIG_FILE, FILE_MODE);
24
+ try {
25
+ const content = readFileSync(CONFIG_FILE, 'utf-8');
26
+ const parsed = JSON.parse(content);
27
+ const normalized = normalizeConfig(parsed);
28
+ if (!normalized) {
29
+ return null;
30
+ }
31
+ const errors = validateConfig(normalized);
32
+ return errors.length === 0 ? normalized : null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ export function validateConfig(config) {
39
+ const errors = [];
40
+ if (!config.agentType || typeof config.agentType !== 'string') {
41
+ errors.push('agentType is required');
42
+ }
43
+ if (!isValidWallet(config.wallet)) {
44
+ errors.push('wallet must be a valid EVM address');
45
+ }
46
+ if (!config.controlPlaneUrl || !isWsUrl(config.controlPlaneUrl)) {
47
+ errors.push('controlPlaneUrl must start with ws:// or wss://');
48
+ }
49
+ if (!config.nodeId || typeof config.nodeId !== 'string') {
50
+ errors.push('nodeId is required');
51
+ }
52
+ const provider = config.llmProvider || 'grok';
53
+ if (provider === 'grok') {
54
+ const hasStoredKey = typeof config.apiKey === 'string' && config.apiKey.length > 0 && config.apiKey !== '__ENV__';
55
+ const hasRuntimeKey = Boolean(process.env.TERMINUS_GROK_API_KEY?.trim() || process.env.XAI_API_KEY?.trim());
56
+ if (!hasStoredKey && !hasRuntimeKey) {
57
+ errors.push('grok provider requires apiKey in config or TERMINUS_GROK_API_KEY/XAI_API_KEY env');
58
+ }
59
+ }
60
+ if (provider === 'openai-compatible' && !config.llmBaseUrl) {
61
+ errors.push('openai-compatible provider requires llmBaseUrl');
62
+ }
63
+ if (provider === 'ollama' && !config.llmBaseUrl) {
64
+ errors.push('ollama provider should include llmBaseUrl');
65
+ }
66
+ return errors;
67
+ }
68
+ export function saveConfig(config) {
69
+ if (!existsSync(CONFIG_DIR)) {
70
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: DIR_MODE });
71
+ }
72
+ securePermissions(CONFIG_DIR, DIR_MODE);
73
+ const provider = config.llmProvider || 'grok';
74
+ const normalizedApiKey = provider === 'grok'
75
+ ? (config.apiKey || '__ENV__')
76
+ : (config.apiKey || '');
77
+ const normalizedProfile = normalizeNetworkProfile(config.networkProfile);
78
+ const sanitized = {
79
+ agentType: config.agentType.trim(),
80
+ wallet: config.wallet.trim(),
81
+ apiKey: normalizedApiKey.trim(),
82
+ controlPlaneUrl: config.controlPlaneUrl.trim(),
83
+ nodeId: config.nodeId.trim(),
84
+ llmProvider: provider,
85
+ llmBaseUrl: config.llmBaseUrl?.trim() || undefined,
86
+ llmModel: config.llmModel?.trim() || undefined,
87
+ networkProfile: normalizedProfile,
88
+ privateKey: undefined,
89
+ };
90
+ const validationErrors = validateConfig(sanitized);
91
+ if (validationErrors.length > 0) {
92
+ throw new Error(`Invalid configuration: ${validationErrors.join('; ')}`);
93
+ }
94
+ writeFileSync(CONFIG_FILE, JSON.stringify(sanitized, null, 2), { mode: FILE_MODE });
95
+ securePermissions(CONFIG_FILE, FILE_MODE);
96
+ }
97
+ export function generateNodeId(agentType, wallet) {
98
+ // Stable readable prefix + entropy suffix for collision resistance across operators.
99
+ const walletPrefix = wallet.slice(2, 8).toLowerCase();
100
+ const suffix = randomUUID().split('-')[0];
101
+ return `${agentType}-${walletPrefix}-${suffix}`;
102
+ }
103
+ function normalizeConfig(raw) {
104
+ if (typeof raw !== 'object' || raw === null)
105
+ return null;
106
+ const value = raw;
107
+ const llmProvider = value.llmProvider;
108
+ const provider = llmProvider === 'ollama' || llmProvider === 'openai-compatible' || llmProvider === 'grok'
109
+ ? llmProvider
110
+ : 'grok';
111
+ const networkProfile = normalizeNetworkProfile(value.networkProfile);
112
+ const llmBaseUrl = typeof value.llmBaseUrl === 'string'
113
+ ? value.llmBaseUrl.trim()
114
+ : provider === 'ollama'
115
+ ? 'http://localhost:11434'
116
+ : undefined;
117
+ return {
118
+ agentType: typeof value.agentType === 'string' ? value.agentType.trim() : '',
119
+ wallet: typeof value.wallet === 'string' ? value.wallet.trim() : '',
120
+ apiKey: typeof value.apiKey === 'string' ? value.apiKey.trim() : '',
121
+ controlPlaneUrl: typeof value.controlPlaneUrl === 'string' ? value.controlPlaneUrl.trim() : '',
122
+ nodeId: typeof value.nodeId === 'string' ? value.nodeId.trim() : '',
123
+ llmProvider: provider,
124
+ llmBaseUrl,
125
+ llmModel: typeof value.llmModel === 'string' ? value.llmModel.trim() : undefined,
126
+ networkProfile,
127
+ privateKey: undefined,
128
+ };
129
+ }
130
+ function isWsUrl(url) {
131
+ return url.startsWith('ws://') || url.startsWith('wss://');
132
+ }
133
+ function isValidWallet(wallet) {
134
+ return /^0x[a-fA-F0-9]{40}$/.test(wallet);
135
+ }
136
+ function securePermissions(path, mode) {
137
+ try {
138
+ chmodSync(path, mode);
139
+ }
140
+ catch {
141
+ // Some environments (e.g., Windows) may not apply POSIX modes.
142
+ }
143
+ }
144
+ function normalizeNetworkProfile(value) {
145
+ if (value === 'testnet' || value === 'mainnet')
146
+ return value;
147
+ return 'local';
148
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ // =============================================================================
2
+ // TERMINUS AGENT - CLI Entry Point
3
+ // =============================================================================
4
+ import { program } from 'commander';
5
+ import { initCommand } from './cli/init.js';
6
+ import { runCommand } from './cli/run.js';
7
+ import { statusCommand } from './cli/status.js';
8
+ import { doctorCommand } from './cli/doctor.js';
9
+ async function withCliErrorHandling(task) {
10
+ try {
11
+ await task();
12
+ }
13
+ catch (error) {
14
+ const message = error instanceof Error ? error.message : 'Unknown error';
15
+ console.error(`\nāŒ ${message}\n`);
16
+ process.exit(1);
17
+ }
18
+ }
19
+ program
20
+ .name('terminus-agent')
21
+ .description('Standalone agent runner for Terminus network')
22
+ .version('0.1.0');
23
+ program
24
+ .command('init')
25
+ .description('Initialize agent configuration')
26
+ .option('--agent-type <id>', 'Agent type id (example: travel-planner)')
27
+ .option('--wallet <address>', 'Wallet address for payouts')
28
+ .option('--llm-provider <provider>', 'grok | ollama | openai-compatible')
29
+ .option('--api-key <key>', 'API key for provider')
30
+ .option('--llm-base-url <url>', 'Provider base URL')
31
+ .option('--llm-model <name>', 'LLM model name')
32
+ .option('--control-plane-url <url>', 'Control plane websocket URL')
33
+ .option('--profile <profile>', 'local | testnet | mainnet')
34
+ .option('--yes', 'Non-interactive mode (use flags/env/defaults)')
35
+ .option('--force', 'Overwrite existing config without confirmation')
36
+ .action((options) => withCliErrorHandling(() => initCommand({
37
+ agentType: options.agentType,
38
+ wallet: options.wallet,
39
+ llmProvider: options.llmProvider,
40
+ apiKey: options.apiKey,
41
+ llmBaseUrl: options.llmBaseUrl,
42
+ llmModel: options.llmModel,
43
+ controlPlaneUrl: options.controlPlaneUrl,
44
+ profile: options.profile,
45
+ yes: Boolean(options.yes),
46
+ force: Boolean(options.force),
47
+ })));
48
+ program
49
+ .command('run')
50
+ .description('Start the agent and connect to Control Plane')
51
+ .action(() => withCliErrorHandling(runCommand));
52
+ program
53
+ .command('status')
54
+ .description('Check agent status')
55
+ .action(() => withCliErrorHandling(statusCommand));
56
+ program
57
+ .command('doctor')
58
+ .description('Run onboarding and runtime diagnostics')
59
+ .option('--full', 'Run connectivity/API checks in addition to static checks')
60
+ .action((options) => withCliErrorHandling(() => doctorCommand({ full: Boolean(options.full) })));
61
+ program.parse();
@@ -0,0 +1,56 @@
1
+ export interface LLMMessage {
2
+ role: 'system' | 'user' | 'assistant';
3
+ content: string;
4
+ }
5
+ export interface LLMResponse {
6
+ content: string;
7
+ model: string;
8
+ tokensUsed?: number;
9
+ }
10
+ export interface LLMProviderConfig {
11
+ provider: 'grok' | 'ollama' | 'openai-compatible';
12
+ apiKey?: string;
13
+ baseUrl?: string;
14
+ model?: string;
15
+ }
16
+ export interface LLMProvider {
17
+ name: string;
18
+ chat(messages: LLMMessage[], options?: {
19
+ maxTokens?: number;
20
+ temperature?: number;
21
+ }): Promise<LLMResponse>;
22
+ }
23
+ export declare class GrokProvider implements LLMProvider {
24
+ name: string;
25
+ private apiKey;
26
+ private model;
27
+ constructor(apiKey: string, model?: string);
28
+ chat(messages: LLMMessage[], options?: {
29
+ maxTokens?: number;
30
+ temperature?: number;
31
+ }): Promise<LLMResponse>;
32
+ }
33
+ export declare class OllamaProvider implements LLMProvider {
34
+ name: string;
35
+ private baseUrl;
36
+ private model;
37
+ constructor(baseUrl?: string, model?: string);
38
+ chat(messages: LLMMessage[], options?: {
39
+ maxTokens?: number;
40
+ temperature?: number;
41
+ }): Promise<LLMResponse>;
42
+ }
43
+ export declare class OpenAICompatibleProvider implements LLMProvider {
44
+ name: string;
45
+ private baseUrl;
46
+ private apiKey;
47
+ private model;
48
+ constructor(baseUrl: string, model: string, apiKey?: string);
49
+ chat(messages: LLMMessage[], options?: {
50
+ maxTokens?: number;
51
+ temperature?: number;
52
+ }): Promise<LLMResponse>;
53
+ }
54
+ export declare function createLLMProvider(config: LLMProviderConfig): LLMProvider;
55
+ export declare function checkOllamaAvailable(baseUrl?: string): Promise<boolean>;
56
+ export declare function listOllamaModels(baseUrl?: string): Promise<string[]>;
@@ -0,0 +1,181 @@
1
+ // =============================================================================
2
+ // TERMINUS AGENT - LLM Provider Interface
3
+ // =============================================================================
4
+ // Abstraction layer for different LLM backends.
5
+ // Supports: xAI Grok (API), Ollama (local), OpenAI-compatible (local/cloud)
6
+ // =============================================================================
7
+ // =============================================================================
8
+ // xAI Grok Provider
9
+ // =============================================================================
10
+ export class GrokProvider {
11
+ name = 'grok';
12
+ apiKey;
13
+ model;
14
+ constructor(apiKey, model = 'grok-4-1-fast-non-reasoning') {
15
+ this.apiKey = apiKey;
16
+ this.model = model;
17
+ }
18
+ async chat(messages, options) {
19
+ const response = await fetch('https://api.x.ai/v1/chat/completions', {
20
+ method: 'POST',
21
+ headers: {
22
+ 'Content-Type': 'application/json',
23
+ 'Authorization': `Bearer ${this.apiKey}`,
24
+ },
25
+ body: JSON.stringify({
26
+ model: this.model,
27
+ messages,
28
+ max_tokens: options?.maxTokens ?? 1024,
29
+ temperature: options?.temperature ?? 0.7,
30
+ }),
31
+ });
32
+ if (!response.ok) {
33
+ const error = await response.text();
34
+ throw new Error(`Grok API error: ${response.status} - ${error}`);
35
+ }
36
+ const data = await response.json();
37
+ return {
38
+ content: data.choices[0]?.message?.content || '',
39
+ model: this.model,
40
+ tokensUsed: data.usage?.total_tokens,
41
+ };
42
+ }
43
+ }
44
+ // =============================================================================
45
+ // Ollama Provider (Local LLM)
46
+ // =============================================================================
47
+ export class OllamaProvider {
48
+ name = 'ollama';
49
+ baseUrl;
50
+ model;
51
+ constructor(baseUrl = 'http://localhost:11434', model = 'llama3') {
52
+ this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
53
+ this.model = model;
54
+ }
55
+ async chat(messages, options) {
56
+ // Ollama uses /api/chat endpoint
57
+ const response = await fetch(`${this.baseUrl}/api/chat`, {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ },
62
+ body: JSON.stringify({
63
+ model: this.model,
64
+ messages,
65
+ stream: false,
66
+ options: {
67
+ num_predict: options?.maxTokens ?? 1024,
68
+ temperature: options?.temperature ?? 0.7,
69
+ },
70
+ }),
71
+ });
72
+ if (!response.ok) {
73
+ const error = await response.text();
74
+ throw new Error(`Ollama error: ${response.status} - ${error}`);
75
+ }
76
+ const data = await response.json();
77
+ return {
78
+ content: data.message?.content || '',
79
+ model: this.model,
80
+ tokensUsed: (data.eval_count || 0) + (data.prompt_eval_count || 0),
81
+ };
82
+ }
83
+ }
84
+ // =============================================================================
85
+ // OpenAI-Compatible Provider (LM Studio, LocalAI, vLLM, etc.)
86
+ // =============================================================================
87
+ export class OpenAICompatibleProvider {
88
+ name = 'openai-compatible';
89
+ baseUrl;
90
+ apiKey;
91
+ model;
92
+ constructor(baseUrl, model, apiKey) {
93
+ this.baseUrl = baseUrl.replace(/\/$/, '');
94
+ this.model = model;
95
+ const normalized = apiKey?.trim();
96
+ this.apiKey = normalized && normalized !== 'not-required' ? normalized : undefined;
97
+ }
98
+ async chat(messages, options) {
99
+ const headers = {
100
+ 'Content-Type': 'application/json',
101
+ };
102
+ if (this.apiKey) {
103
+ headers.Authorization = `Bearer ${this.apiKey}`;
104
+ }
105
+ const response = await fetch(`${this.baseUrl}/v1/chat/completions`, {
106
+ method: 'POST',
107
+ headers,
108
+ body: JSON.stringify({
109
+ model: this.model,
110
+ messages,
111
+ max_tokens: options?.maxTokens ?? 1024,
112
+ temperature: options?.temperature ?? 0.7,
113
+ }),
114
+ });
115
+ if (!response.ok) {
116
+ const error = await response.text();
117
+ throw new Error(`OpenAI-compatible API error: ${response.status} - ${error}`);
118
+ }
119
+ const data = await response.json();
120
+ return {
121
+ content: data.choices[0]?.message?.content || '',
122
+ model: this.model,
123
+ tokensUsed: data.usage?.total_tokens,
124
+ };
125
+ }
126
+ }
127
+ // =============================================================================
128
+ // Factory Function
129
+ // =============================================================================
130
+ export function createLLMProvider(config) {
131
+ switch (config.provider) {
132
+ case 'grok':
133
+ {
134
+ const apiKey = resolveGrokApiKey(config.apiKey);
135
+ if (!apiKey) {
136
+ throw new Error('Grok provider requires a key in config or TERMINUS_GROK_API_KEY/XAI_API_KEY env');
137
+ }
138
+ return new GrokProvider(apiKey, config.model);
139
+ }
140
+ case 'ollama':
141
+ return new OllamaProvider(config.baseUrl || 'http://localhost:11434', config.model || 'llama3');
142
+ case 'openai-compatible':
143
+ if (!config.baseUrl) {
144
+ throw new Error('OpenAI-compatible provider requires baseUrl');
145
+ }
146
+ return new OpenAICompatibleProvider(config.baseUrl, config.model || 'gpt-3.5-turbo', config.apiKey);
147
+ default:
148
+ throw new Error(`Unknown LLM provider: ${config.provider}`);
149
+ }
150
+ }
151
+ // =============================================================================
152
+ // Helper to detect Ollama availability
153
+ // =============================================================================
154
+ export async function checkOllamaAvailable(baseUrl = 'http://localhost:11434') {
155
+ try {
156
+ const response = await fetch(`${baseUrl}/api/tags`, { method: 'GET' });
157
+ return response.ok;
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ }
163
+ export async function listOllamaModels(baseUrl = 'http://localhost:11434') {
164
+ try {
165
+ const response = await fetch(`${baseUrl}/api/tags`, { method: 'GET' });
166
+ if (!response.ok)
167
+ return [];
168
+ const data = await response.json();
169
+ return data.models?.map(m => m.name) || [];
170
+ }
171
+ catch {
172
+ return [];
173
+ }
174
+ }
175
+ function resolveGrokApiKey(configApiKey) {
176
+ const key = configApiKey?.trim();
177
+ if (key && key !== '__ENV__')
178
+ return key;
179
+ const runtimeKey = process.env.TERMINUS_GROK_API_KEY?.trim() || process.env.XAI_API_KEY?.trim();
180
+ return runtimeKey || undefined;
181
+ }