osai-agent 4.1.14 → 4.2.2

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 CHANGED
@@ -167,6 +167,7 @@ Upgrade to Pro for higher daily limits (500 msgs/day, 30 req/min, 5 connections)
167
167
  osai-agent config Show current configuration
168
168
  osai-agent config set-os <os> Override OS detection
169
169
  osai-agent skills Manage installed skills
170
+ osai-agent account delete Permanently delete your account and all data
170
171
  osai-agent stop-subagent Stop a running sub-agent
171
172
  osai-agent --version Show version
172
173
  osai-agent --help Show help
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osai-agent",
3
- "version": "4.1.14",
3
+ "version": "4.2.2",
4
4
  "type": "module",
5
5
  "description": "OS AI Agent - YOUR AI AGENT",
6
6
  "main": "src/index.js",
@@ -1,9 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Context Builder
3
- // =============================================================================
4
- // Builds structured context for the agent loop, including device information,
5
- // OS detection, and device capabilities. Used primarily in network/SSH mode.
6
- // =============================================================================
1
+ // Context builder — device info, OS, capabilities
7
2
 
8
3
  /**
9
4
  * Build a structured context object for the agent
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Agent Prompt Builder (v4.0 — Coding Mode + Anti-Hallucination)
3
- // =============================================================================
1
+ // Agent prompt builder — mode-specific prompts
4
2
 
5
3
  import fs from 'fs/promises';
6
4
  import path from 'path';
@@ -1,9 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Agent Loop (v4.0 — Coding Mode + Todos + Web Tools)
3
- // =============================================================================
4
- // Core agent loop: GOAL -> PLAN -> EXECUTE -> OBSERVE -> REFLECT -> REPEAT
5
- // Supports GENERAL, NETWORK, and CODING modes with full tool dispatch.
6
- // =============================================================================
1
+ // Agent loop — GOAL→PLAN→EXEC→OBSERVE→REFLECT
7
2
 
8
3
  import {
9
4
  executeLocal, readFile, writeFile, editFile, listDir, detectOS, getSystemInfo, resolvePath,
@@ -1,9 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Subagent Runner (max 1 concurrent)
3
- // =============================================================================
4
- // Spawns a read-only child agent for exploration. Results are returned to the
5
- // parent via structured [SUBAGENT_RESULT] tool output.
6
- // =============================================================================
1
+ // Subagent runner — read-only child agent (max 1)
7
2
 
8
3
  import { AgentLoop } from './react-loop.js';
9
4
  import { SUBAGENT_MAX_ITERATIONS } from '../utils/constants.js';
@@ -0,0 +1,107 @@
1
+ import Conf from 'conf';
2
+ import chalk from 'chalk';
3
+ import boxen from 'boxen';
4
+ import ora from 'ora';
5
+ import { createInterface } from 'node:readline';
6
+ import { rmSync, existsSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { toHttpUrl, DEFAULT_SERVER_URL } from '../services/server-url.js';
10
+ import { APP_VERSION } from '../utils/constants.js';
11
+
12
+ export const deleteAccount = async ({ server: serverArg }) => {
13
+ const config = new Conf({ projectName: 'osai-agent' });
14
+ const token = config.get('token');
15
+ const serverUrl = serverArg || config.get('server') || DEFAULT_SERVER_URL;
16
+ const httpUrl = toHttpUrl(serverUrl);
17
+
18
+ const headerContent = [
19
+ ` ${chalk.hex('#4a9eff').bold('OS AI AGENT')} ${chalk.gray(`v${APP_VERSION}`)}`,
20
+ '',
21
+ ` ${chalk.hex('#8ab4d8')('Your AI sysadmin, network engineer & senior developer')}`,
22
+ '',
23
+ ` ${token ? chalk.green('●') : chalk.red('○')} ${chalk.hex('#8ab4d8')('Auth:')} ${chalk.white(token ? 'Authenticated' : 'Not authenticated')}`,
24
+ ].join('\n');
25
+
26
+ console.log(boxen(headerContent, {
27
+ padding: { left: 3, right: 3 },
28
+ margin: { top: 1, bottom: 1 },
29
+ borderStyle: 'round',
30
+ borderColor: '#4a9eff',
31
+ float: 'center',
32
+ }));
33
+
34
+ if (!token) {
35
+ console.log(chalk.red('\nYou need to be logged in to delete your account.'));
36
+ console.log(chalk.yellow('Run "osai-agent register" or "osai-agent login" first.\n'));
37
+ process.exit(1);
38
+ }
39
+
40
+ console.log(boxen(
41
+ chalk.hex('#f7768e').bold('⚠ WARNING') +
42
+ chalk.hex('#c0caf5')('\n\nThis will permanently delete your account and all associated data.') +
43
+ chalk.hex('#c0caf5')('\nThis includes: devices, logs, sessions, provider settings, pro key, and more.') +
44
+ chalk.hex('#ff9e64')('\n\nThis action CANNOT be undone.') +
45
+ chalk.hex('#9aa5ce')('\n\nYou will not be able to create a new account') +
46
+ chalk.hex('#9aa5ce')('\nwith the same email or from this machine for 24 hours.'),
47
+ {
48
+ padding: 2,
49
+ borderStyle: 'round',
50
+ borderColor: '#f7768e',
51
+ textAlignment: 'center',
52
+ }
53
+ ));
54
+
55
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
56
+ const answer = await new Promise((resolve) => {
57
+ rl.question(chalk.hex('#ff9e64').bold('\nType "yes" to confirm: '), resolve);
58
+ });
59
+ rl.close();
60
+
61
+ if (answer.trim().toLowerCase() !== 'yes') {
62
+ console.log(chalk.yellow('\nAccount deletion cancelled.\n'));
63
+ process.exit(0);
64
+ }
65
+
66
+ const spinner = ora({ text: 'Deleting account...', color: 'red' }).start();
67
+
68
+ try {
69
+ const response = await fetch(`${httpUrl}/auth/account`, {
70
+ method: 'DELETE',
71
+ headers: {
72
+ 'Content-Type': 'application/json',
73
+ 'Authorization': `Bearer ${token}`,
74
+ },
75
+ });
76
+
77
+ if (!response.ok) {
78
+ const errorData = await response.json().catch(() => null);
79
+ const errorMsg = errorData?.error || response.statusText;
80
+ spinner.fail(`Failed to delete account: ${response.status} - ${errorMsg}`);
81
+ process.exit(1);
82
+ }
83
+
84
+ // Clear local config
85
+ config.clear();
86
+
87
+ // Remove entire config directory
88
+ const configDir = join(homedir(), '.config', 'osai-agent');
89
+ if (existsSync(configDir)) {
90
+ try { rmSync(configDir, { recursive: true, force: true }); } catch {}
91
+ }
92
+
93
+ spinner.succeed('Account deleted successfully');
94
+
95
+ console.log(boxen(
96
+ chalk.hex('#7aa2f7').bold(' Account Deleted') +
97
+ chalk.hex('#c0caf5')('\n\n All your data has been permanently removed.') +
98
+ chalk.hex('#9aa5ce')('\n You will be able to create a new account') +
99
+ chalk.hex('#9aa5ce')('\n with the same email or from this machine') +
100
+ chalk.hex('#9aa5ce')('\n after 24 hours.'),
101
+ { padding: 2, borderStyle: 'round', borderColor: '#4a9eff', textAlignment: 'center' }
102
+ ));
103
+ } catch (err) {
104
+ spinner.fail(`Failed to delete account: ${err.message}`);
105
+ process.exit(1);
106
+ }
107
+ };
@@ -0,0 +1,82 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createInterface } from 'node:readline';
4
+ import { toHttpUrl, DEFAULT_SERVER_URL } from '../services/server-url.js';
5
+ import Conf from 'conf';
6
+
7
+ const promptSecret = () => new Promise((resolve) => {
8
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
9
+ rl.question(chalk.hex('#ff9e64')('Enter admin secret: '), (answer) => {
10
+ rl.close();
11
+ resolve(answer.trim());
12
+ });
13
+ });
14
+
15
+ const adminFetch = async (serverArg, path, method = 'GET', body = null) => {
16
+ const config = new Conf({ projectName: 'osai-agent' });
17
+ const serverUrl = serverArg || config.get('server') || DEFAULT_SERVER_URL;
18
+ const httpUrl = toHttpUrl(serverUrl);
19
+
20
+ const secret = await promptSecret();
21
+ if (!secret) {
22
+ console.log(chalk.red('Admin secret is required.'));
23
+ process.exit(1);
24
+ }
25
+
26
+ const options = {
27
+ method,
28
+ headers: { 'Authorization': `Bearer ${secret}` },
29
+ };
30
+ if (body) {
31
+ options.headers['Content-Type'] = 'application/json';
32
+ options.body = JSON.stringify(body);
33
+ }
34
+
35
+ const response = await fetch(`${httpUrl}/auth${path}`, options);
36
+ const data = await response.json().catch(() => null);
37
+
38
+ if (!response.ok) {
39
+ console.log(chalk.red(`Error ${response.status}: ${data?.error || response.statusText}`));
40
+ process.exit(1);
41
+ }
42
+
43
+ return data;
44
+ };
45
+
46
+ export const listUsers = async ({ server }) => {
47
+ const data = await adminFetch(server, '/admin/users');
48
+ const users = data.users || [];
49
+
50
+ if (users.length === 0) {
51
+ console.log(chalk.yellow('No users found.'));
52
+ return;
53
+ }
54
+
55
+ console.log(chalk.hex('#4a9eff').bold(`\n Users (${users.length}):\n`));
56
+ for (const u of users) {
57
+ const statusBadge = u.status === 'disabled'
58
+ ? chalk.red('disabled')
59
+ : chalk.green('active');
60
+ const planBadge = u.plan === 'pro' ? chalk.hex('#ff9e64')('pro') : chalk.gray('free');
61
+ const created = new Date(u.created_at).toLocaleDateString();
62
+ const machines = (u.machine_ids || []).length;
63
+ console.log(
64
+ ` ${chalk.white(u.email)}` +
65
+ ` ${statusBadge}` +
66
+ ` ${planBadge}` +
67
+ ` ${chalk.gray(`${machines} machine(s)`)}` +
68
+ ` ${chalk.gray(`created ${created}`)}`
69
+ );
70
+ }
71
+ console.log();
72
+ };
73
+
74
+ export const disableUser = async ({ server, email }) => {
75
+ const data = await adminFetch(server, '/admin/disable-user', 'POST', { email });
76
+ const spinner = ora().succeed(chalk.green(`User disabled: ${data.email}`));
77
+ };
78
+
79
+ export const enableUser = async ({ server, email }) => {
80
+ const data = await adminFetch(server, '/admin/enable-user', 'POST', { email });
81
+ const spinner = ora().succeed(chalk.green(`User enabled: ${data.email}`));
82
+ };
package/src/index.js CHANGED
@@ -25,6 +25,8 @@ import { manageSkills } from './commands/skills.js';
25
25
  import { handleMcpCommand } from './commands/mcp.js';
26
26
  import { stopSubagent } from './commands/stop-subagent.js';
27
27
  import { claimProKey } from './commands/pro.js';
28
+ import { deleteAccount } from './commands/account.js';
29
+ import { listUsers, disableUser, enableUser } from './commands/admin.js';
28
30
  import minimist from 'minimist';
29
31
  import updateNotifier from 'update-notifier';
30
32
  import { createRequire } from 'module';
@@ -133,6 +135,37 @@ switch (cmd) {
133
135
  await claimProKey({ key: args.key || args._[1], server: args.server });
134
136
  break;
135
137
 
138
+ case 'account':
139
+ if (args._[1] === 'delete') {
140
+ await deleteAccount({ server: args.server });
141
+ } else {
142
+ console.error('Usage: osai-agent account delete');
143
+ process.exit(1);
144
+ }
145
+ break;
146
+
147
+ case 'admin':
148
+ if (args._[1] === 'users') {
149
+ await listUsers({ server: args.server });
150
+ } else if (args._[1] === 'disable') {
151
+ if (!args._[2]) { console.error('Usage: osai-agent admin disable <email>'); process.exit(1); }
152
+ await disableUser({ server: args.server, email: args._[2] });
153
+ } else if (args._[1] === 'enable') {
154
+ if (!args._[2]) { console.error('Usage: osai-agent admin enable <email>'); process.exit(1); }
155
+ await enableUser({ server: args.server, email: args._[2] });
156
+ } else {
157
+ console.log();
158
+ console.log('Usage: osai-agent admin <subcommand> [args]');
159
+ console.log();
160
+ console.log('Subcommands:');
161
+ console.log(' users List all users');
162
+ console.log(' disable <email> Disable a user account');
163
+ console.log(' enable <email> Re-enable a user account');
164
+ console.log();
165
+ process.exit(1);
166
+ }
167
+ break;
168
+
136
169
  default:
137
170
  if (cmd) {
138
171
  console.error(`Unknown command: ${cmd}`);
@@ -156,6 +189,7 @@ switch (cmd) {
156
189
  console.log(' mcp Manage MCP servers');
157
190
  console.log(' stop-subagent Stop a running subagent');
158
191
  console.log(' pro Activate a pro access code');
192
+ console.log(' account delete Permanently delete your account and all data');
159
193
  console.log();
160
194
  process.exit(cmd ? 1 : 0);
161
195
  }
@@ -1,16 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Memory Store
3
- // =============================================================================
4
- // Lightweight JSON-based memory system that stores actions, results, and
5
- // failures across sessions. Enables the agent to learn from past interactions.
6
- // Implements a 6-layer memory model inspired by Claude Code's architecture:
7
- // 1. System Prompt (static)
8
- // 2. Session Messages (current conversation)
9
- // 3. Compact History (auto-trimmed)
10
- // 4. Project Knowledge (instructions.md)
11
- // 5. Persistent Memory (this store - actions/failures/preferences)
12
- // 6. Semantic Search (future — keyword-based for now)
13
- // =============================================================================
1
+ // Memory store — persistent action/result history
14
2
  import fs from 'fs/promises';
15
3
  import path from 'path';
16
4
  import os from 'os';
@@ -1,9 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Dependency Analysis Utilities
3
- // =============================================================================
4
- // Pure functions for extracting imports and exports from source code.
5
- // Used by the GET_DEPENDENCIES tool and dependency auto-resolution.
6
- // =============================================================================
1
+ // Dependency analysis — import/export extractor
7
2
 
8
3
  /**
9
4
  * Extract exported symbol names from JS/TS source content.
@@ -1,10 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Markdown Renderer v4.0
3
- // =============================================================================
4
- // Two modes:
5
- // renderMarkdown(text) — full buffered render (après réception complète)
6
- // StreamMarkdown — classe stateful pour le streaming token-by-token
7
- // =============================================================================
1
+ // Markdown renderer — buffered + streaming
8
2
 
9
3
  import chalk from 'chalk';
10
4
 
@@ -1,9 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Safety Check System (v4.0 — Coding Mode aware)
3
- // =============================================================================
4
- // Four-tier safety classification: READ, WRITE, DANGEROUS, ASK
5
- // In CODING mode, WRITE tools auto-approved with --no-confirm.
6
- // =============================================================================
1
+ // Safety checker — READ/WRITE/DANGEROUS/ASK tiers
7
2
 
8
3
  const TIER = {
9
4
  READ: 'READ',
@@ -1,9 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Command Executor
3
- // =============================================================================
4
- // Unified command executor that dispatches to local or SSH based on device.
5
- // Acts as a bridge between the agent loop and the tool system.
6
- // =============================================================================
1
+ // Command executor — local or SSH dispatch
7
2
  import { exec } from 'child_process';
8
3
  import { connectSSH, execSSH, closeSSH } from './ssh.js';
9
4
  import { DEFAULTS } from '../utils/constants.js';
@@ -1,7 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Session Manager
3
- // Supports both LOCAL (JSON file) and CLOUD (MongoDB via server API) storage.
4
- // =============================================================================
1
+ // Session manager — local JSON + cloud MongoDB
5
2
  import fs from 'fs/promises';
6
3
  import { mkdirSync, writeFileSync } from 'fs';
7
4
  import path from 'path';
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — SSH Connection Manager
3
- // =============================================================================
1
+ // SSH connection manager
4
2
  import { Client } from 'ssh2';
5
3
  import { DEFAULTS } from '../utils/constants.js';
6
4
  import { logger } from '../utils/logger.js';
@@ -1,10 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — WebSocket Client (Standalone)
3
- // =============================================================================
4
- // Standalone WebSocket client with auto-reconnect and exponential backoff.
5
- // Used as a lower-level utility that can be imported by the agent loop or
6
- // other modules needing persistent WebSocket connections.
7
- // =============================================================================
1
+ // WebSocket client — auto-reconnect + backoff
8
2
  import WebSocket from 'ws';
9
3
  import { DEFAULTS } from '../utils/constants.js';
10
4
  import { logger } from '../utils/logger.js';
@@ -1,8 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Skills Loader
3
- // =============================================================================
4
- // Discovers and manages SKILL.md files from personal and project directories.
5
- // =============================================================================
1
+ // Skills loader — discovers SKILL.md files
6
2
 
7
3
  import fs from 'fs/promises';
8
4
  import path from 'path';
@@ -1,10 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Browser Automation Module (Playwright + HTTP Fallback)
3
- // =============================================================================
4
- // Safe, stateless, per-request browser automation for the AI agent.
5
- // Each call is fully isolated — launch → close in finally block.
6
- // Falls back to HTTP fetch if Playwright/Chromium is unavailable.
7
- // =============================================================================
1
+ // Browser automation — Playwright + HTTP fallback
8
2
 
9
3
  import { logger } from '../utils/logger.js';
10
4
  import { searchDDG, searchDDGHttp } from './search-providers.js';
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Local Tool System (v4.0 — Coding Mode + Todos)
3
- // =============================================================================
1
+ // Local tool system — command execution + file ops
4
2
  import { exec, spawn } from 'child_process';
5
3
  import { promisify } from 'util';
6
4
  import fs from 'fs/promises';
@@ -937,9 +935,7 @@ export const validateLocalCommand = (command) => {
937
935
  return { safe: true };
938
936
  };
939
937
 
940
- // =============================================================================
941
- // NEW TOOLS — GLOB, GREP, GIT, READ_FILE_RANGE, ASK_USER, DIAG_POST_EDIT
942
- // =============================================================================
938
+ // Glob, Grep, Git, ReadFileRange, AskUser, DiagPostEdit
943
939
 
944
940
  import fastGlob from 'fast-glob';
945
941
  import { execFile } from 'child_process';
@@ -1,10 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Tool Registry
3
- // =============================================================================
4
- // Central registry for all tools the agent can use. Each tool is an independent
5
- // module with a standard interface: name, description, parameters, execute(),
6
- // and safety tier.
7
- // =============================================================================
1
+ // Tool registry — central tool catalog
8
2
 
9
3
  import { logger } from '../utils/logger.js';
10
4
  import { TOOLS, SAFETY_TIERS } from '../utils/constants.js';
package/src/tools/ssh.js CHANGED
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — SSH Tool (Connection Pool + Dispatch)
3
- // =============================================================================
1
+ // SSH tool — connection pool + dispatch
4
2
  import { connectSSH, execSSH, closeSSH } from '../services/ssh.js';
5
3
  import { logger } from '../utils/logger.js';
6
4
 
@@ -544,7 +544,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
544
544
  useInput((input, key) => {
545
545
  if (!visible) return;
546
546
 
547
- // === MENU PHASE ===
547
+ // MENU PHASE
548
548
  if (phase === 'menu') {
549
549
  if (key.escape) { resetAll(); onCancel(); return; }
550
550
  if (key.upArrow) { setMenuCursor(c => (c > 0 ? c - 1 : filteredMenu.length - 1)); return; }
@@ -574,7 +574,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
574
574
  return;
575
575
  }
576
576
 
577
- // === CATALOG PHASE ===
577
+ // CATALOG PHASE
578
578
  if (phase === 'catalog') {
579
579
  if (key.escape) { setPhase('menu'); setCatalogQuery(''); setCatalogCursor(0); return; }
580
580
  if (key.upArrow) { setCatalogCursor(c => (c > 0 ? c - 1 : filteredCatalog.length - 1)); return; }
@@ -611,7 +611,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
611
611
  return;
612
612
  }
613
613
 
614
- // === BASE URL PHASE (local mode only) ===
614
+ // BASE URL PHASE (local mode only)
615
615
  if (phase === 'base_url') {
616
616
  if (key.escape) { setPhase('catalog'); setLocalBaseUrlInput(''); setApiKeyInput(''); return; }
617
617
  if (key.return) {
@@ -631,7 +631,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
631
631
  return;
632
632
  }
633
633
 
634
- // === CONFIGURED PHASE ===
634
+ // CONFIGURED PHASE
635
635
  if (phase === 'configured') {
636
636
  if (key.escape) { resetAll(); return; }
637
637
  if (key.upArrow) { setConfiguredCursor(c => (c > 0 ? c - 1 : filteredConfigured.length - 1)); return; }
@@ -648,7 +648,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
648
648
  return;
649
649
  }
650
650
 
651
- // === MODELS PHASE (full setup) ===
651
+ // MODELS PHASE (full setup)
652
652
  if (phase === 'models') {
653
653
  if (modelsLoading) {
654
654
  if (key.escape) { setPhase('catalog'); setModelsQuery(''); setModelsCursor(0); setSelectedProvider(null); setModelsData(null); return; }
@@ -669,7 +669,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
669
669
  return;
670
670
  }
671
671
 
672
- // === SWITCH MODELS PHASE (model selection for switch_model flow) ===
672
+ // SWITCH MODELS PHASE (model selection for switch_model flow)
673
673
  if (phase === 'switch_models') {
674
674
  if (key.escape) { setPhase('configured'); setModelsQuery(''); setModelsCursor(0); setSelectedProvider(null); return; }
675
675
  if (key.upArrow) { setModelsCursor(c => (c > 0 ? c - 1 : allModels.length - 1)); return; }
@@ -686,7 +686,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
686
686
  return;
687
687
  }
688
688
 
689
- // === API KEY PHASE (full setup: server mode after model select, local mode before model fetch) ===
689
+ // API KEY PHASE (full setup: server mode after model select, local mode before model fetch)
690
690
  if (phase === 'api_key') {
691
691
  if (key.escape) {
692
692
  if (isLocal) {
@@ -716,7 +716,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
716
716
  return;
717
717
  }
718
718
 
719
- // === UPDATE KEY INPUT PHASE ===
719
+ // UPDATE KEY INPUT PHASE
720
720
  if (phase === 'update_key_input') {
721
721
  if (key.escape) { setPhase('configured'); setApiKeyInput(''); setSettingError(null); return; }
722
722
  if (key.return) { handleUpdateKeySubmit(); return; }
@@ -732,7 +732,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
732
732
  const separator = '─'.repeat(52);
733
733
  const { rows } = useWindowSize();
734
734
 
735
- // === SETTING UP VIEW ===
735
+ // SETTING UP VIEW
736
736
  if (settingUp) {
737
737
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
738
738
  h(Text, { color: '#7aa2f7', bold: true }, TITLE),
@@ -743,7 +743,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
743
743
  );
744
744
  }
745
745
 
746
- // === BASE URL INPUT (local mode) ===
746
+ // BASE URL INPUT (local mode)
747
747
  if (phase === 'base_url') {
748
748
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
749
749
  h(Box, { flexDirection: 'row', alignItems: 'center' },
@@ -766,7 +766,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
766
766
  );
767
767
  }
768
768
 
769
- // === API KEY VIEW ===
769
+ // API KEY VIEW
770
770
  if (phase === 'api_key') {
771
771
  const masked = apiKeyInput ? '\u2022'.repeat(apiKeyInput.length) : '';
772
772
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
@@ -794,7 +794,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
794
794
  );
795
795
  }
796
796
 
797
- // === UPDATE KEY INPUT VIEW ===
797
+ // UPDATE KEY INPUT VIEW
798
798
  if (phase === 'update_key_input') {
799
799
  const masked = apiKeyInput ? '\u2022'.repeat(apiKeyInput.length) : '';
800
800
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
@@ -822,7 +822,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
822
822
  );
823
823
  }
824
824
 
825
- // === MODELS VIEW (reused for both full setup and switch_model) ===
825
+ // MODELS VIEW (reused for both full setup and switch_model)
826
826
  const isSwitchModels = phase === 'switch_models';
827
827
  if (phase === 'models' || isSwitchModels) {
828
828
  if (modelsLoading) {
@@ -882,7 +882,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
882
882
  );
883
883
  }
884
884
 
885
- // === CONFIGURED VIEW ===
885
+ // CONFIGURED VIEW
886
886
  if (phase === 'configured') {
887
887
  if (configuredLoading) {
888
888
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
@@ -965,7 +965,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
965
965
  );
966
966
  }
967
967
 
968
- // === CATALOG VIEW ===
968
+ // CATALOG VIEW
969
969
  if (phase === 'catalog') {
970
970
  if (catalogLoading) {
971
971
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
@@ -1055,7 +1055,7 @@ export function ProviderMenu({ visible, onSelect, onCancel, serverUrl, token, cu
1055
1055
  );
1056
1056
  }
1057
1057
 
1058
- // === MENU VIEW ===
1058
+ // MENU VIEW
1059
1059
  return h(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: '#2a2e3f', paddingX: 1, paddingY: 0, marginY: 1 },
1060
1060
  h(Box, { flexDirection: 'row', alignItems: 'center' },
1061
1061
  h(Text, { color: '#7aa2f7', bold: true }, TITLE),
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Constants & Configuration
3
- // =============================================================================
1
+ // Constants & configuration
4
2
 
5
3
  export const APP_NAME = 'osai-agent';
6
4
  import { createRequire } from 'module';
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Shared Helpers
3
- // =============================================================================
1
+ // Shared helpers
4
2
 
5
3
  /**
6
4
  * Estimate token count from text (rough: ~4 chars per token)
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — Logger
3
- // =============================================================================
1
+ // Logger
4
2
  import chalk from 'chalk';
5
3
  import { ENV_VARS } from './constants.js';
6
4
 
@@ -1,6 +1,4 @@
1
- // =============================================================================
2
- // OS AI Agent — System Sound Notification
3
- // =============================================================================
1
+ // System sound notifications
4
2
 
5
3
  /**
6
4
  * Play a system beep sound (cross-platform: Windows, macOS, Linux)