osai-agent 4.0.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 (86) hide show
  1. package/LICENSE +7 -0
  2. package/package.json +72 -0
  3. package/src/agent/context.js +141 -0
  4. package/src/agent/loop/context-summary.js +196 -0
  5. package/src/agent/loop/directory-utils.js +102 -0
  6. package/src/agent/loop/local.js +196 -0
  7. package/src/agent/loop/loop-detection.js +288 -0
  8. package/src/agent/loop/stream-parser.js +515 -0
  9. package/src/agent/loop/tool-executor.js +470 -0
  10. package/src/agent/loop/verification.js +263 -0
  11. package/src/agent/loop/websocket.js +80 -0
  12. package/src/agent/prompt.js +259 -0
  13. package/src/agent/react-loop.js +697 -0
  14. package/src/agent/subagent.js +263 -0
  15. package/src/commands/config.js +53 -0
  16. package/src/commands/connect.js +190 -0
  17. package/src/commands/devices.js +121 -0
  18. package/src/commands/login.js +77 -0
  19. package/src/commands/logout.js +31 -0
  20. package/src/commands/mcp.js +258 -0
  21. package/src/commands/provider.js +633 -0
  22. package/src/commands/register.js +74 -0
  23. package/src/commands/run.js +150 -0
  24. package/src/commands/search.js +64 -0
  25. package/src/commands/session.js +57 -0
  26. package/src/commands/skills.js +54 -0
  27. package/src/commands/stop-subagent.js +58 -0
  28. package/src/index.js +208 -0
  29. package/src/llm/direct.js +317 -0
  30. package/src/memory/store.js +215 -0
  31. package/src/mock-readline.js +27 -0
  32. package/src/parser/dependencies.js +71 -0
  33. package/src/parser/markdown.js +505 -0
  34. package/src/parser/stream.js +96 -0
  35. package/src/prompts/modes/CODING.js +160 -0
  36. package/src/prompts/modes/GENERAL.js +105 -0
  37. package/src/prompts/modes/NETWORK.js +69 -0
  38. package/src/prompts/modes/SSH.js +53 -0
  39. package/src/prompts/systemPrompt.js +85 -0
  40. package/src/safety/check.js +210 -0
  41. package/src/services/crypto.js +78 -0
  42. package/src/services/executor.js +68 -0
  43. package/src/services/history.js +58 -0
  44. package/src/services/server-url.js +11 -0
  45. package/src/services/session.js +194 -0
  46. package/src/services/ssh.js +176 -0
  47. package/src/services/websocket.js +112 -0
  48. package/src/skills/loader.js +231 -0
  49. package/src/tools/browser.js +434 -0
  50. package/src/tools/local.js +1254 -0
  51. package/src/tools/mcp-client.js +209 -0
  52. package/src/tools/registry.js +132 -0
  53. package/src/tools/search-providers.js +237 -0
  54. package/src/tools/ssh.js +74 -0
  55. package/src/ui/App.js +2031 -0
  56. package/src/ui/animation.js +47 -0
  57. package/src/ui/components/AskUserDialog.js +33 -0
  58. package/src/ui/components/ConfirmationDialog.js +45 -0
  59. package/src/ui/components/DiffView.js +201 -0
  60. package/src/ui/components/Header.js +157 -0
  61. package/src/ui/components/HistoryPicker.js +130 -0
  62. package/src/ui/components/InputShell.js +22 -0
  63. package/src/ui/components/MessageHistory.js +1200 -0
  64. package/src/ui/components/ModalPanel.js +40 -0
  65. package/src/ui/components/ModePicker.js +161 -0
  66. package/src/ui/components/PlanDialog.js +48 -0
  67. package/src/ui/components/ProviderMenu.js +1095 -0
  68. package/src/ui/components/SavePicker.js +106 -0
  69. package/src/ui/components/SelectMenu.js +194 -0
  70. package/src/ui/components/SlashMenu.js +168 -0
  71. package/src/ui/components/SubagentPanel.js +138 -0
  72. package/src/ui/components/TextInputSafe.js +117 -0
  73. package/src/ui/components/TodoPanel.js +54 -0
  74. package/src/ui/components/ToolExecution.js +261 -0
  75. package/src/ui/components/TranscriptViewport.js +99 -0
  76. package/src/ui/diff.js +249 -0
  77. package/src/ui/h.js +7 -0
  78. package/src/ui/mouse-scroll.js +63 -0
  79. package/src/ui/slash-picker.js +58 -0
  80. package/src/ui/terminal.js +41 -0
  81. package/src/ui/theme.js +5 -0
  82. package/src/ui/welcome.js +12 -0
  83. package/src/utils/constants.js +231 -0
  84. package/src/utils/helpers.js +154 -0
  85. package/src/utils/logger.js +81 -0
  86. package/src/utils/sound.js +33 -0
@@ -0,0 +1,77 @@
1
+ import pkg from 'node-machine-id';
2
+ const { machineIdSync } = pkg;
3
+ import chalk from 'chalk';
4
+ import Conf from 'conf';
5
+ import inquirer from 'inquirer';
6
+ import ora from 'ora';
7
+ import { ExitPromptError } from '@inquirer/core';
8
+ import { printAuthHeader, printError, printSuccess, clearScreen } from '../ui/terminal.js';
9
+ import { DEFAULT_SERVER_URL, toHttpUrl, toWsUrl } from '../services/server-url.js';
10
+ import { run } from './run.js';
11
+
12
+ const attemptLogin = async (email, password, serverInput, config) => {
13
+ const server = toWsUrl(serverInput);
14
+ const machineId = machineIdSync();
15
+ const spinner = ora('Connecting to server...').start();
16
+
17
+ try {
18
+ const response = await fetch(`${toHttpUrl(server)}/auth/login`, {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ email, password, machineId })
22
+ });
23
+ const data = await response.json();
24
+ if (!response.ok) { spinner.fail('Login refused'); printError(data.error || 'Connection error'); return 'auth_error'; }
25
+ config.set({ token: data.token, userId: data.user.id, plan: data.user.plan, server });
26
+ spinner.succeed('Session started');
27
+ printSuccess('Connected');
28
+ console.log(chalk.gray(` Plan: ${data.user.plan}`));
29
+ console.log(chalk.gray(` Use: osai-agent connect\n`));
30
+ return 'success';
31
+ } catch {
32
+ spinner.fail('Unable to connect');
33
+ return 'network_error';
34
+ }
35
+ };
36
+
37
+ export const login = async ({ server: serverOverride } = {}) => {
38
+ const config = new Conf({ projectName: 'osai-agent' });
39
+ const token = config.get('token');
40
+ if (token) {
41
+ console.log(chalk.yellow('Already logged in. Use osai-agent logout first.'));
42
+ return;
43
+ }
44
+ const prefixTheme = { idle: chalk.hex('#7aa2f7')('▸'), done: chalk.hex('#7aa2f7')('▸') };
45
+ clearScreen();
46
+ console.log();
47
+ printAuthHeader(chalk.white('Welcome Back'), 'Sign in to OS AI Agent to continue.');
48
+ console.log();
49
+
50
+ let serverInput = serverOverride || config.get('server') || DEFAULT_SERVER_URL;
51
+
52
+ let email, password;
53
+ try {
54
+ ({ email, password } = await inquirer.prompt([
55
+ { type: 'input', name: 'email', message: 'Email \u203A', theme: { prefix: prefixTheme }, validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'Invalid email' },
56
+ { type: 'password', name: 'password', message: 'Password \u203A', theme: { prefix: prefixTheme }, mask: '*' },
57
+ ]));
58
+ } catch (err) {
59
+ if (err instanceof ExitPromptError) process.exit(0);
60
+ throw err;
61
+ }
62
+
63
+ const result = await attemptLogin(email, password, serverInput, config);
64
+ if (result === 'success') { await run({}); return; }
65
+ if (result !== 'network_error') return;
66
+
67
+ try {
68
+ const { serverInput: retryServer } = await inquirer.prompt([
69
+ { type: 'input', name: 'serverInput', message: 'Server \u203A', theme: { prefix: prefixTheme }, default: serverInput }
70
+ ]);
71
+ const result2 = await attemptLogin(email, password, retryServer, config);
72
+ if (result2 === 'success') await run({});
73
+ } catch (err) {
74
+ if (err instanceof ExitPromptError) process.exit(0);
75
+ throw err;
76
+ }
77
+ };
@@ -0,0 +1,31 @@
1
+ import Conf from 'conf';
2
+ import chalk from 'chalk';
3
+ import boxen from 'boxen';
4
+ import ora from 'ora';
5
+
6
+ export const logout = () => {
7
+ const config = new Conf({ projectName: 'osai-agent' });
8
+ const token = config.get('token');
9
+ if (!token) {
10
+ console.log(chalk.yellow('Already logged out.'));
11
+ return;
12
+ }
13
+
14
+ const spinner = ora({ text: 'Logging out...', color: 'blue' }).start();
15
+
16
+ setTimeout(() => {
17
+ config.delete('token');
18
+ config.delete('userId');
19
+ config.delete('plan');
20
+ spinner.succeed('Logged out');
21
+
22
+ console.log(boxen(
23
+ chalk.hex('#7aa2f7').bold(' OS AI Agent') +
24
+ chalk.hex('#c0caf5')('\n See you soon!') +
25
+ chalk.hex('#565f89').dim('\n You have been logged out.'),
26
+ { padding: 1, borderStyle: 'round', borderColor: '#4a9eff' }
27
+ ));
28
+
29
+ process.exit(0);
30
+ }, 1000);
31
+ };
@@ -0,0 +1,258 @@
1
+ import Conf from 'conf';
2
+ import { mcpClientManager } from '../tools/mcp-client.js';
3
+
4
+ const config = new Conf({ projectName: 'osai-agent' });
5
+
6
+ function getServers() {
7
+ return config.get('mcpServers', {});
8
+ }
9
+
10
+ function saveServers(servers) {
11
+ config.set('mcpServers', servers);
12
+ }
13
+
14
+ export async function handleMcpCommand(args) {
15
+ const subcommand = args._[1] || 'list';
16
+
17
+ switch (subcommand) {
18
+ case 'list':
19
+ return listServers();
20
+ case 'add':
21
+ return addServer(args);
22
+ case 'remove':
23
+ return removeServer(args);
24
+ case 'reload':
25
+ return reloadServers();
26
+ case 'test':
27
+ return testServer(args);
28
+ default:
29
+ console.log('Usage: osai-agent mcp <list|add|remove|reload|test>');
30
+ console.log('');
31
+ console.log(' list List all MCP servers and their tools');
32
+ console.log(' add <name> Add a new MCP server (interactive wizard)');
33
+ console.log(' add <name> --command <cmd> [--args ...] Add a stdio server via CLI flags');
34
+ console.log(' add <name> --url <url> [--token <t>] Add an HTTP server via CLI flags');
35
+ console.log(' add <name> --url <url> [--header "K=V"] Add HTTP server with custom headers');
36
+ console.log(' remove <name> Remove an MCP server');
37
+ console.log(' reload Reload all MCP servers from config');
38
+ console.log(' test <name> Test connection and list its tools');
39
+ }
40
+ }
41
+
42
+ async function listServers() {
43
+ const servers = getServers();
44
+ const names = Object.keys(servers);
45
+
46
+ if (names.length === 0) {
47
+ console.log('No MCP servers configured.');
48
+ console.log('Use: osai-agent mcp add <name>');
49
+ return;
50
+ }
51
+
52
+ console.log(`MCP Servers (${names.length} configured):`);
53
+ console.log('');
54
+
55
+ for (const name of names) {
56
+ const cfg = servers[name];
57
+ const conn = mcpClientManager.getServer(name);
58
+ const status = conn && conn.connected ? '\x1b[32mconnected\x1b[0m' : '\x1b[33mnot connected\x1b[0m';
59
+ const transport = cfg.transport || 'stdio';
60
+
61
+ console.log(` ${name} [${status}] (${transport})`);
62
+ if (transport === 'stdio') {
63
+ console.log(` command: ${cfg.command} ${(cfg.args || []).join(' ')}`);
64
+ } else {
65
+ console.log(` url: ${cfg.url}`);
66
+ }
67
+
68
+ if (conn && conn.connected) {
69
+ const tools = conn.tools;
70
+ if (tools.length > 0) {
71
+ console.log(` tools (${tools.length}):`);
72
+ for (const tool of tools) {
73
+ console.log(` - ${tool.name}: ${tool.description || '(no description)'}`);
74
+ }
75
+ } else {
76
+ console.log(' (no tools advertised)');
77
+ }
78
+ }
79
+ console.log('');
80
+ }
81
+ }
82
+
83
+ async function addServer(args) {
84
+ const name = args._[2];
85
+ if (!name) {
86
+ console.log('Error: server name required');
87
+ console.log('Usage: osai-agent mcp add <name> [options]');
88
+ console.log('');
89
+ console.log(' Options (stdio):');
90
+ console.log(' --command <cmd> Command to run (e.g., node, npx)');
91
+ console.log(' --args <json|string> Arguments for the command');
92
+ console.log('');
93
+ console.log(' Options (http):');
94
+ console.log(' --url <url> Server URL (e.g., http://localhost:3001/mcp)');
95
+ console.log(' --transport <type> Transport type: http or streamable-http');
96
+ console.log(' --token <token> Bearer token for Authorization header');
97
+ console.log(' --header <key=value> Custom header (repeatable)');
98
+ console.log('');
99
+ console.log(' Examples:');
100
+ console.log(' osai-agent mcp add my-api --url http://localhost:3001/mcp --token my-token');
101
+ console.log(' osai-agent mcp add my-api --url http://localhost:3001/mcp --transport streamable-http');
102
+ console.log(' osai-agent mcp add my-tool --command npx --args @modelcontextprotocol/server-filesystem');
103
+ return;
104
+ }
105
+
106
+ const servers = getServers();
107
+ if (servers[name]) {
108
+ console.log(`Server "${name}" already exists. Use "osai-agent mcp remove ${name}" first.`);
109
+ return;
110
+ }
111
+
112
+ let serverConfig;
113
+
114
+ // --- Mode CLI flags (non-interactif) ---
115
+ if (args.url) {
116
+ const headers = {};
117
+ if (args.token) headers['Authorization'] = `Bearer ${args.token}`;
118
+ if (args.header) {
119
+ const headersList = Array.isArray(args.header) ? args.header : [args.header];
120
+ for (const h of headersList) {
121
+ const idx = h.indexOf('=');
122
+ if (idx > 0) headers[h.slice(0, idx)] = h.slice(idx + 1);
123
+ }
124
+ }
125
+ serverConfig = {
126
+ transport: args.transport || 'http',
127
+ url: args.url,
128
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
129
+ };
130
+ } else if (args.command) {
131
+ let parsedArgs = [];
132
+ if (args.args) {
133
+ try {
134
+ parsedArgs = typeof args.args === 'string' ? JSON.parse(args.args) : args.args;
135
+ } catch {
136
+ parsedArgs = [args.args];
137
+ }
138
+ }
139
+ serverConfig = { transport: 'stdio', command: args.command, args: parsedArgs };
140
+ }
141
+
142
+ // --- Mode interactif ---
143
+ if (!serverConfig) {
144
+ const { createInterface } = await import('readline');
145
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
146
+ const ask = (q) => new Promise(r => rl.question(q, r));
147
+
148
+ console.log(`\nAdding MCP server "${name}":`);
149
+ const transportType = (await ask(' Transport type (stdio/http): ')).trim().toLowerCase();
150
+
151
+ if (transportType === 'http' || transportType === 'streamable-http') {
152
+ const url = await ask(' URL (e.g., http://localhost:3001/mcp): ');
153
+ const token = await ask(' Bearer token (optional, press Enter to skip): ');
154
+ const config = { transport: transportType, url: url.trim() };
155
+ if (token.trim()) config.headers = { Authorization: `Bearer ${token.trim()}` };
156
+ serverConfig = config;
157
+ } else {
158
+ const cmd = await ask(' Command (e.g., node, npx): ');
159
+ const argsStr = await ask(' Arguments (space-separated): ');
160
+ const parsedArgs = argsStr ? argsStr.split(' ').filter(Boolean) : [];
161
+ serverConfig = { transport: 'stdio', command: cmd, args: parsedArgs };
162
+ }
163
+ rl.close();
164
+ }
165
+
166
+ servers[name] = serverConfig;
167
+ saveServers(servers);
168
+ console.log(`Server "${name}" added to config.`);
169
+
170
+ try {
171
+ const tools = await mcpClientManager.addServer(name, serverConfig);
172
+ console.log(`Connected: ${tools.length} tools available.`);
173
+ } catch (err) {
174
+ console.log(`Warning: could not connect: ${err.message}`);
175
+ console.log('The server is saved in config and will connect on next agent run.');
176
+ }
177
+ }
178
+
179
+ async function removeServer(args) {
180
+ const name = args._[2];
181
+ if (!name) {
182
+ console.log('Error: server name required');
183
+ console.log('Usage: osai-agent mcp remove <name>');
184
+ return;
185
+ }
186
+
187
+ const servers = getServers();
188
+ if (!servers[name]) {
189
+ console.log(`Server "${name}" not found.`);
190
+ return;
191
+ }
192
+
193
+ await mcpClientManager.removeServer(name);
194
+
195
+ delete servers[name];
196
+ saveServers(servers);
197
+ console.log(`Server "${name}" removed.`);
198
+ }
199
+
200
+ async function reloadServers() {
201
+ const servers = getServers();
202
+ const names = Object.keys(servers);
203
+
204
+ if (names.length === 0) {
205
+ console.log('No MCP servers configured.');
206
+ return;
207
+ }
208
+
209
+ console.log(`Reloading ${names.length} MCP server(s)...`);
210
+ const results = await mcpClientManager.reload(servers);
211
+
212
+ if (results.connected.length > 0) {
213
+ for (const r of results.connected) {
214
+ console.log(` \x1b[32m✓\x1b[0m ${r.name}: ${r.toolCount} tools`);
215
+ }
216
+ }
217
+ if (results.failed.length > 0) {
218
+ for (const r of results.failed) {
219
+ console.log(` \x1b[31m✗\x1b[0m ${r.name}: ${r.error}`);
220
+ }
221
+ }
222
+ }
223
+
224
+ async function testServer(args) {
225
+ const name = args._[2];
226
+ if (!name) {
227
+ console.log('Error: server name required');
228
+ console.log('Usage: osai-agent mcp test <name>');
229
+ return;
230
+ }
231
+
232
+ const servers = getServers();
233
+ const cfg = servers[name];
234
+ if (!cfg) {
235
+ console.log(`Server "${name}" not found in config.`);
236
+ return;
237
+ }
238
+
239
+ console.log(`Testing connection to "${name}"...`);
240
+
241
+ try {
242
+ const tools = await mcpClientManager.addServer(name, cfg);
243
+ console.log(`\x1b[32m✓ Connected\x1b[0m — ${tools.length} tools:`);
244
+ for (const tool of tools) {
245
+ console.log(` - ${tool.name}: ${tool.description || '(no description)'}`);
246
+ if (tool.inputSchema) {
247
+ const props = tool.inputSchema.properties || {};
248
+ const paramNames = Object.keys(props);
249
+ if (paramNames.length > 0) {
250
+ console.log(` params: ${paramNames.join(', ')}`);
251
+ }
252
+ }
253
+ }
254
+ await mcpClientManager.removeServer(name);
255
+ } catch (err) {
256
+ console.log(`\x1b[31m✗ Connection failed\x1b[0m: ${err.message}`);
257
+ }
258
+ }