viben 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.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # Viben CLI
2
+
3
+ [![npm version](https://img.shields.io/npm/v/viben.svg)](https://www.npmjs.com/package/viben)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Command-line interface for Viben - orchestrate AI agent clusters in your local workspace.
7
+
8
+ ## Installation
9
+
10
+ ### Quick Start with npx (Recommended)
11
+
12
+ No installation needed - just run:
13
+
14
+ ```bash
15
+ npx viben --help
16
+ ```
17
+
18
+ ### Global Installation
19
+
20
+ ```bash
21
+ npm install -g viben
22
+ viben --help
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ ### Workspace Commands
28
+
29
+ ```bash
30
+ # Initialize a workspace
31
+ viben init
32
+ viben init --from <template>
33
+
34
+ # Manage configuration (git-style)
35
+ viben config list # List all config
36
+ viben config list --show-origin # Show config sources
37
+ viben config get <key> # Get a value
38
+ viben config set <key> <value> # Set a value
39
+ viben config unset <key> # Remove a value
40
+ viben config edit # Open in editor
41
+
42
+ # Manage agents
43
+ viben agent list # List all agents
44
+ viben agent create -n <id> # Create new agent
45
+ viben agent show -n <id> # Show agent details
46
+ ```
47
+
48
+ ### Server Commands
49
+
50
+ ```bash
51
+ # Start MCP server (wraps browse-mcp Python package)
52
+ viben serve # Start in stdio mode
53
+ viben serve -t sse --port 8080 # Start SSE server
54
+ viben mcp # Alias for serve
55
+ ```
56
+
57
+ ## Global Options
58
+
59
+ ```bash
60
+ --json Output in JSON format (for agent consumption)
61
+ -g, --global Use global scope (~/.viben/)
62
+ -w, --workspace Use workspace scope (.viben/)
63
+ --verbose Enable verbose output
64
+ -q, --quiet Suppress non-essential output
65
+ -v, --version Show version
66
+ -h, --help Show help
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ ### File Locations
72
+
73
+ - **Global config**: `~/.viben/config.yaml`
74
+ - **Workspace config**: `<project>/.viben/config.yaml`
75
+ - **Global agents**: `~/.viben/agents/*.yaml`
76
+ - **Workspace agents**: `<project>/.viben/agents/*.yaml`
77
+
78
+ ### Environment Variables
79
+
80
+ ```bash
81
+ VIBEN_STATE_DIR # Override state directory (default: ~/.viben)
82
+ VIBEN_AGENT # Current agent ID
83
+ VIBEN_SCOPE # Default scope (global/workspace)
84
+ ```
85
+
86
+ ### Config File Format
87
+
88
+ ```yaml
89
+ # .viben/config.yaml
90
+ version: 1
91
+
92
+ settings:
93
+ editor: code
94
+ pager: less
95
+ color: auto
96
+
97
+ agents:
98
+ - main
99
+
100
+ mcp:
101
+ enabled:
102
+ - filesystem
103
+ - git
104
+
105
+ skills:
106
+ enabled:
107
+ - code-review
108
+ ```
109
+
110
+ ## JSON Output Mode
111
+
112
+ All commands support `--json` for structured output:
113
+
114
+ ```bash
115
+ # Human-readable (default)
116
+ viben agent list
117
+ # ID Name Model Source
118
+ # main Main Agent (default) workspace
119
+
120
+ # JSON output
121
+ viben --json agent list
122
+ # {
123
+ # "success": true,
124
+ # "data": {
125
+ # "agents": [{ "id": "main", "name": "Main Agent", ... }],
126
+ # "count": 1
127
+ # }
128
+ # }
129
+ ```
130
+
131
+ ## MCP Server
132
+
133
+ The `serve` command wraps the [browse-mcp](https://pypi.org/project/browse-mcp/) Python package.
134
+
135
+ ### Requirements
136
+
137
+ - **Node.js 18+** - For the CLI
138
+ - **Python 3.10+** - For the MCP server
139
+ - **pip or uv** - For Python package installation
140
+
141
+ ### MCP Client Configuration
142
+
143
+ #### Claude Desktop
144
+
145
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
146
+
147
+ ```json
148
+ {
149
+ "mcpServers": {
150
+ "viben": {
151
+ "command": "npx",
152
+ "args": ["viben", "serve"]
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ #### Claude Code
159
+
160
+ Add to `~/.config/claude/config.json`:
161
+
162
+ ```json
163
+ {
164
+ "mcpServers": {
165
+ "viben": {
166
+ "command": "npx",
167
+ "args": ["viben", "serve"]
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ ## Links
174
+
175
+ - [GitHub Repository](https://github.com/LinXueyuanStdio/viben)
176
+ - [PyPI Package (browse-mcp)](https://pypi.org/project/browse-mcp/)
177
+ - [Documentation](https://github.com/LinXueyuanStdio/viben#readme)
178
+
179
+ ## License
180
+
181
+ MIT
package/bin/viben.js ADDED
@@ -0,0 +1,386 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Viben CLI Entry Point
5
+ *
6
+ * This script serves as the main entry point for the Viben CLI.
7
+ * It routes to either:
8
+ * - The TypeScript CLI (Commander.js) for workspace management commands
9
+ * - The Python browse-mcp wrapper for MCP server functionality
10
+ *
11
+ * Usage:
12
+ * viben init # Initialize workspace (TypeScript CLI)
13
+ * viben config list # List config (TypeScript CLI)
14
+ * viben agent list # List agents (TypeScript CLI)
15
+ * viben serve # Start MCP server (Python wrapper)
16
+ * viben mcp # Start MCP server (Python wrapper, alias)
17
+ */
18
+
19
+ import { createRequire } from 'module';
20
+ import { fileURLToPath } from 'url';
21
+ import { dirname, join } from 'path';
22
+ import { spawnSync, execSync } from 'child_process';
23
+ import { platform } from 'os';
24
+
25
+ const require = createRequire(import.meta.url);
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = dirname(__filename);
28
+
29
+ // Configuration for Python wrapper
30
+ const PYTHON_PACKAGE = 'browse-mcp';
31
+ const MIN_PYTHON_VERSION = '3.10';
32
+ const BRAND_NAME = 'Viben';
33
+
34
+ // Commands that should be handled by the TypeScript CLI
35
+ const TS_CLI_COMMANDS = ['init', 'config', 'agent'];
36
+
37
+ // Commands that should be handled by the Python wrapper
38
+ const PYTHON_COMMANDS = ['serve', 'mcp'];
39
+
40
+ // ANSI colors
41
+ const colors = {
42
+ reset: '\x1b[0m',
43
+ red: '\x1b[31m',
44
+ green: '\x1b[32m',
45
+ yellow: '\x1b[33m',
46
+ blue: '\x1b[34m',
47
+ cyan: '\x1b[36m',
48
+ bold: '\x1b[1m',
49
+ };
50
+
51
+ function info(message) {
52
+ console.log(`${colors.blue}[INFO]${colors.reset} ${message}`);
53
+ }
54
+
55
+ function success(message) {
56
+ console.log(`${colors.green}[OK]${colors.reset} ${message}`);
57
+ }
58
+
59
+ function warn(message) {
60
+ console.log(`${colors.yellow}[WARN]${colors.reset} ${message}`);
61
+ }
62
+
63
+ function error(message) {
64
+ console.error(`${colors.red}[ERROR]${colors.reset} ${message}`);
65
+ }
66
+
67
+ function commandExists(cmd) {
68
+ try {
69
+ const isWindows = platform() === 'win32';
70
+ const checkCmd = isWindows ? `where ${cmd}` : `command -v ${cmd}`;
71
+ execSync(checkCmd, { stdio: 'ignore' });
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ function getPythonVersion(pythonCmd) {
79
+ try {
80
+ const result = execSync(
81
+ `${pythonCmd} -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"`,
82
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
83
+ );
84
+ return result.trim();
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+
90
+ function versionGte(version, minVersion) {
91
+ const v1 = version.split('.').map(Number);
92
+ const v2 = minVersion.split('.').map(Number);
93
+
94
+ for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
95
+ const a = v1[i] || 0;
96
+ const b = v2[i] || 0;
97
+ if (a > b) return true;
98
+ if (a < b) return false;
99
+ }
100
+ return true;
101
+ }
102
+
103
+ function findPython() {
104
+ const candidates = ['python3', 'python'];
105
+
106
+ for (const cmd of candidates) {
107
+ if (commandExists(cmd)) {
108
+ const version = getPythonVersion(cmd);
109
+ if (version && versionGte(version, MIN_PYTHON_VERSION)) {
110
+ return { cmd, version };
111
+ }
112
+ }
113
+ }
114
+
115
+ return null;
116
+ }
117
+
118
+ function isBrowseMcpInstalled(pythonCmd) {
119
+ try {
120
+ execSync(`${pythonCmd} -c "import browse_mcp"`, {
121
+ stdio: ['pipe', 'pipe', 'pipe'],
122
+ });
123
+ return true;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ function browseMcpCommandExists() {
130
+ return commandExists('browse-mcp');
131
+ }
132
+
133
+ function installBrowseMcp(pythonCmd) {
134
+ info(`Installing ${PYTHON_PACKAGE}...`);
135
+
136
+ if (commandExists('uv')) {
137
+ info('Using uv for installation...');
138
+ let result = spawnSync('uv', ['pip', 'install', PYTHON_PACKAGE], {
139
+ stdio: 'inherit',
140
+ });
141
+ if (result.status === 0) {
142
+ success(`${PYTHON_PACKAGE} installed successfully`);
143
+ return true;
144
+ }
145
+ result = spawnSync('uv', ['pip', 'install', '--system', PYTHON_PACKAGE], {
146
+ stdio: 'inherit',
147
+ });
148
+ if (result.status === 0) {
149
+ success(`${PYTHON_PACKAGE} installed successfully`);
150
+ return true;
151
+ }
152
+ }
153
+
154
+ if (commandExists('pip3')) {
155
+ info('Using pip3 for installation...');
156
+ const result = spawnSync('pip3', ['install', PYTHON_PACKAGE], {
157
+ stdio: 'inherit',
158
+ });
159
+ if (result.status === 0) {
160
+ success(`${PYTHON_PACKAGE} installed successfully`);
161
+ return true;
162
+ }
163
+ }
164
+
165
+ if (commandExists('pip')) {
166
+ info('Using pip for installation...');
167
+ const result = spawnSync('pip', ['install', PYTHON_PACKAGE], {
168
+ stdio: 'inherit',
169
+ });
170
+ if (result.status === 0) {
171
+ success(`${PYTHON_PACKAGE} installed successfully`);
172
+ return true;
173
+ }
174
+ }
175
+
176
+ if (pythonCmd) {
177
+ info(`Using ${pythonCmd} -m pip for installation...`);
178
+ const result = spawnSync(pythonCmd, ['-m', 'pip', 'install', PYTHON_PACKAGE], {
179
+ stdio: 'inherit',
180
+ });
181
+ if (result.status === 0) {
182
+ success(`${PYTHON_PACKAGE} installed successfully`);
183
+ return true;
184
+ }
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ function runBrowseMcp(args, pythonCmd) {
191
+ if (browseMcpCommandExists()) {
192
+ const result = spawnSync('browse-mcp', args, { stdio: 'inherit' });
193
+ process.exit(result.status || 0);
194
+ }
195
+
196
+ if (pythonCmd) {
197
+ const result = spawnSync(pythonCmd, ['-m', 'browse_mcp', ...args], {
198
+ stdio: 'inherit',
199
+ });
200
+ process.exit(result.status || 0);
201
+ }
202
+
203
+ error('Could not run browse-mcp');
204
+ process.exit(1);
205
+ }
206
+
207
+ async function runTypeScriptCli(args) {
208
+ try {
209
+ const { run } = await import('../dist/index.js');
210
+ await run(['node', 'viben', ...args]);
211
+ } catch (err) {
212
+ // If dist doesn't exist, try running from source (development mode)
213
+ try {
214
+ const { run } = await import('../src/index.ts');
215
+ await run(['node', 'viben', ...args]);
216
+ } catch {
217
+ error('CLI not built. Run "npm run build" in packages/cli first.');
218
+ console.error(err);
219
+ process.exit(1);
220
+ }
221
+ }
222
+ }
223
+
224
+ function handlePythonCommand(args) {
225
+ // Remove 'serve' or 'mcp' from args if present
226
+ const filteredArgs = args.filter((a) => a !== 'serve' && a !== 'mcp');
227
+
228
+ const python = findPython();
229
+ if (!python) {
230
+ error(`Python ${MIN_PYTHON_VERSION}+ is required but not found.`);
231
+ console.log('');
232
+ console.log('Please install Python:');
233
+ if (platform() === 'darwin') {
234
+ console.log(' brew install python@3.12');
235
+ } else if (platform() === 'linux') {
236
+ console.log(' sudo apt install python3 python3-pip');
237
+ } else {
238
+ console.log(' https://www.python.org/downloads/');
239
+ }
240
+ process.exit(1);
241
+ }
242
+
243
+ const isInstalled = browseMcpCommandExists() || isBrowseMcpInstalled(python.cmd);
244
+
245
+ if (!isInstalled) {
246
+ warn(`${PYTHON_PACKAGE} is not installed.`);
247
+ console.log('');
248
+ info('Installing automatically...');
249
+ if (!installBrowseMcp(python.cmd)) {
250
+ error('Automatic installation failed.');
251
+ console.log('');
252
+ console.log('Please install manually:');
253
+ console.log(` pip install ${PYTHON_PACKAGE}`);
254
+ console.log('');
255
+ console.log('Or with uv:');
256
+ console.log(` uv pip install ${PYTHON_PACKAGE}`);
257
+ process.exit(1);
258
+ }
259
+ console.log('');
260
+ }
261
+
262
+ runBrowseMcp(filteredArgs, python.cmd);
263
+ }
264
+
265
+ function printHelp() {
266
+ const pkg = require('../package.json');
267
+ console.log(`
268
+ ${colors.cyan}${colors.bold}${BRAND_NAME} CLI${colors.reset} v${pkg.version}
269
+
270
+ Orchestrate AI agent clusters in your local workspace.
271
+
272
+ ${colors.bold}Usage:${colors.reset}
273
+ viben <command> [options]
274
+
275
+ ${colors.bold}Workspace Commands:${colors.reset}
276
+ init Initialize a Viben workspace
277
+ config <subcommand> Manage configuration (get, set, list, edit, unset)
278
+ agent <subcommand> Manage agents (list, create, show)
279
+
280
+ ${colors.bold}Server Commands:${colors.reset}
281
+ serve Start the MCP server (browse-mcp)
282
+ mcp Alias for serve
283
+
284
+ ${colors.bold}Global Options:${colors.reset}
285
+ --json Output in JSON format
286
+ -g, --global Use global scope
287
+ -w, --workspace Use workspace scope
288
+ --verbose Enable verbose output
289
+ -q, --quiet Suppress non-essential output
290
+ -v, --version Show version
291
+ -h, --help Show help
292
+
293
+ ${colors.bold}Examples:${colors.reset}
294
+ viben init # Initialize workspace
295
+ viben config list # List all config
296
+ viben config set settings.editor vim
297
+ viben agent list # List all agents
298
+ viben agent create -n my-agent
299
+ viben serve # Start MCP server
300
+ viben serve -t sse --port 8080
301
+
302
+ ${colors.bold}Environment Variables:${colors.reset}
303
+ VIBEN_STATE_DIR State directory (default: ~/.viben)
304
+ VIBEN_AGENT Current agent ID
305
+ VIBEN_SCOPE Default scope (global/workspace)
306
+
307
+ ${colors.bold}More information:${colors.reset}
308
+ https://github.com/LinXueyuanStdio/viben
309
+ `);
310
+ }
311
+
312
+ /**
313
+ * Find the first command (non-flag) argument
314
+ */
315
+ function findCommand(args) {
316
+ for (const arg of args) {
317
+ if (!arg.startsWith('-')) {
318
+ return arg;
319
+ }
320
+ }
321
+ return null;
322
+ }
323
+
324
+ async function main() {
325
+ const args = process.argv.slice(2);
326
+ const firstArg = args[0];
327
+
328
+ // Handle --install (Python package installation)
329
+ if (args.includes('--install')) {
330
+ const python = findPython();
331
+ if (!python) {
332
+ error(`Python ${MIN_PYTHON_VERSION}+ is required`);
333
+ process.exit(1);
334
+ }
335
+ if (installBrowseMcp(python.cmd)) {
336
+ success('Installation complete');
337
+ process.exit(0);
338
+ } else {
339
+ error('Installation failed');
340
+ process.exit(1);
341
+ }
342
+ }
343
+
344
+ // Handle version (only if it's the only argument)
345
+ if (args.length === 1 && (firstArg === '-v' || firstArg === '--version')) {
346
+ const pkg = require('../package.json');
347
+ console.log(`${BRAND_NAME} CLI v${pkg.version}`);
348
+ process.exit(0);
349
+ }
350
+
351
+ // Handle help (only if no command is present)
352
+ if (args.length === 0) {
353
+ printHelp();
354
+ process.exit(0);
355
+ }
356
+
357
+ // Find the first command argument (skip flags)
358
+ const command = findCommand(args);
359
+
360
+ // If only help flag and no command
361
+ if (!command && (args.includes('-h') || args.includes('--help'))) {
362
+ printHelp();
363
+ process.exit(0);
364
+ }
365
+
366
+ // Route to appropriate handler based on command
367
+ if (command && TS_CLI_COMMANDS.includes(command)) {
368
+ // TypeScript CLI commands (pass all args including global flags)
369
+ await runTypeScriptCli(args);
370
+ } else if (command && PYTHON_COMMANDS.includes(command)) {
371
+ // Python wrapper commands
372
+ handlePythonCommand(args);
373
+ } else if (command) {
374
+ // Unknown command - try TypeScript CLI (it will show proper error)
375
+ await runTypeScriptCli(args);
376
+ } else {
377
+ // No command found - show help
378
+ printHelp();
379
+ process.exit(0);
380
+ }
381
+ }
382
+
383
+ main().catch((err) => {
384
+ error(err.message || 'Unknown error');
385
+ process.exit(1);
386
+ });