@vendian/cli 0.0.2 → 0.0.5

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/src/process.js DELETED
@@ -1,50 +0,0 @@
1
- import { spawn, spawnSync } from 'node:child_process';
2
-
3
- export function commandExists(command) {
4
- const result = spawnSync(command, ['--version'], { stdio: 'ignore', shell: false });
5
- return result.status === 0;
6
- }
7
-
8
- export function runCapture(command, args, options = {}) {
9
- const result = spawnSync(command, args, {
10
- encoding: 'utf8',
11
- shell: false,
12
- ...options
13
- });
14
- return {
15
- ok: result.status === 0,
16
- status: result.status ?? 1,
17
- stdout: result.stdout || '',
18
- stderr: result.stderr || ''
19
- };
20
- }
21
-
22
- export function runInherit(command, args, options = {}) {
23
- const result = spawnSync(command, args, {
24
- stdio: 'inherit',
25
- shell: false,
26
- ...options
27
- });
28
- if (result.error) {
29
- throw result.error;
30
- }
31
- return result.status ?? 1;
32
- }
33
-
34
- export function spawnForward(command, args, options = {}) {
35
- return new Promise((resolve, reject) => {
36
- const child = spawn(command, args, {
37
- stdio: 'inherit',
38
- shell: false,
39
- ...options
40
- });
41
- child.on('error', reject);
42
- child.on('exit', (code, signal) => {
43
- if (signal) {
44
- reject(new Error(`command terminated by ${signal}`));
45
- return;
46
- }
47
- resolve(code ?? 1);
48
- });
49
- });
50
- }
package/src/prompt.js DELETED
@@ -1,38 +0,0 @@
1
- import readline from 'node:readline';
2
-
3
- export function prompt(question, { defaultValue, secret = false } = {}) {
4
- const suffix = defaultValue ? ` [${defaultValue}]` : '';
5
- const rl = readline.createInterface({
6
- input: process.stdin,
7
- output: process.stdout,
8
- terminal: true
9
- });
10
-
11
- if (!secret) {
12
- return new Promise((resolve) => {
13
- rl.question(`${question}${suffix}: `, (answer) => {
14
- rl.close();
15
- resolve(answer.trim() || defaultValue || '');
16
- });
17
- });
18
- }
19
-
20
- const originalWrite = rl._writeToOutput;
21
- rl._writeToOutput = function writeHidden(stringToWrite) {
22
- if (rl.stdoutMuted) {
23
- rl.output.write(stringToWrite.replace(/[^\r\n]/g, '*'));
24
- } else {
25
- originalWrite.call(rl, stringToWrite);
26
- }
27
- };
28
-
29
- return new Promise((resolve) => {
30
- rl.stdoutMuted = true;
31
- rl.question(`${question}${suffix}: `, (answer) => {
32
- rl.stdoutMuted = false;
33
- rl.output.write('\n');
34
- rl.close();
35
- resolve(answer.trim() || defaultValue || '');
36
- });
37
- });
38
- }
package/src/python.js DELETED
@@ -1,77 +0,0 @@
1
- import fs from 'node:fs';
2
- import { commandExists, runCapture, runInherit } from './process.js';
3
- import { venvPython } from './paths.js';
4
-
5
- export function findPython(platform = process.platform) {
6
- const candidates = platform === 'win32'
7
- ? [
8
- { command: 'py', args: ['-3.11'] },
9
- { command: 'python', args: [] },
10
- { command: 'python3', args: [] }
11
- ]
12
- : [
13
- { command: 'python3.12', args: [] },
14
- { command: 'python3.11', args: [] },
15
- { command: 'python3', args: [] },
16
- { command: 'python', args: [] }
17
- ];
18
-
19
- for (const candidate of candidates) {
20
- const version = runCapture(candidate.command, [
21
- ...candidate.args,
22
- '-c',
23
- 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
24
- ]);
25
- if (!version.ok) {
26
- continue;
27
- }
28
- const match = version.stdout.trim().match(/^(\d+)\.(\d+)\./);
29
- if (!match) {
30
- continue;
31
- }
32
- const major = Number(match[1]);
33
- const minor = Number(match[2]);
34
- if (major > 3 || (major === 3 && minor >= 11)) {
35
- return candidate;
36
- }
37
- }
38
- return null;
39
- }
40
-
41
- export function ensureVenv(venvPath, pythonCandidate, platform = process.platform) {
42
- const pythonPath = venvPython(venvPath, platform);
43
- if (fs.existsSync(pythonPath)) {
44
- return pythonPath;
45
- }
46
- fs.mkdirSync(venvPath, { recursive: true });
47
- const status = runInherit(pythonCandidate.command, [
48
- ...pythonCandidate.args,
49
- '-m',
50
- 'venv',
51
- venvPath
52
- ]);
53
- if (status !== 0) {
54
- throw new Error('Could not create the managed Vendian Python environment.');
55
- }
56
- return pythonPath;
57
- }
58
-
59
- export function hasUv() {
60
- return commandExists('uv');
61
- }
62
-
63
- export function pythonVersion(pythonPath) {
64
- const result = runCapture(pythonPath, [
65
- '-c',
66
- 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")'
67
- ]);
68
- return result.ok ? result.stdout.trim() : null;
69
- }
70
-
71
- export function verifyVendianImports(pythonPath) {
72
- const result = runCapture(pythonPath, [
73
- '-c',
74
- 'import vendian_agents, vendian_agents_runtime; print("ok")'
75
- ]);
76
- return result.ok;
77
- }
@@ -1,94 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
-
3
- const SERVICE = 'Vendian CLI';
4
- const PACKAGE_TOKEN_ACCOUNT = 'gitlab-package-token';
5
-
6
- function run(command, args, options = {}) {
7
- return spawnSync(command, args, {
8
- encoding: 'utf8',
9
- shell: false,
10
- ...options
11
- });
12
- }
13
-
14
- export function packageTokenSecretName() {
15
- return PACKAGE_TOKEN_ACCOUNT;
16
- }
17
-
18
- export function savePackageTokenSecret(token, config = {}, platform = process.platform) {
19
- if (!token) {
20
- return { ok: false };
21
- }
22
- if (platform === 'darwin') {
23
- const result = run('security', [
24
- 'add-generic-password',
25
- '-a',
26
- PACKAGE_TOKEN_ACCOUNT,
27
- '-s',
28
- SERVICE,
29
- '-w',
30
- token,
31
- '-U'
32
- ]);
33
- return { ok: result.status === 0, provider: 'macos-keychain' };
34
- }
35
- if (platform === 'win32') {
36
- const encrypted = run(
37
- 'powershell.exe',
38
- [
39
- '-NoProfile',
40
- '-NonInteractive',
41
- '-Command',
42
- '$secure = ConvertTo-SecureString -String $env:VENDIAN_SECRET_VALUE -AsPlainText -Force; $secure | ConvertFrom-SecureString'
43
- ],
44
- { env: { ...process.env, VENDIAN_SECRET_VALUE: token } }
45
- );
46
- if (encrypted.status !== 0 || !encrypted.stdout.trim()) {
47
- return { ok: false, provider: 'windows-dpapi' };
48
- }
49
- const nextConfig = {
50
- ...config,
51
- secretStore: {
52
- ...(config.secretStore || {}),
53
- [PACKAGE_TOKEN_ACCOUNT]: {
54
- provider: 'windows-dpapi',
55
- value: encrypted.stdout.trim()
56
- }
57
- }
58
- };
59
- return { ok: true, provider: 'windows-dpapi', config: nextConfig };
60
- }
61
- return { ok: false };
62
- }
63
-
64
- export function loadPackageTokenSecret(config = {}, platform = process.platform) {
65
- if (platform === 'darwin') {
66
- const result = run('security', [
67
- 'find-generic-password',
68
- '-a',
69
- PACKAGE_TOKEN_ACCOUNT,
70
- '-s',
71
- SERVICE,
72
- '-w'
73
- ]);
74
- return result.status === 0 ? result.stdout.trim() : '';
75
- }
76
- if (platform === 'win32') {
77
- const entry = config.secretStore?.[PACKAGE_TOKEN_ACCOUNT];
78
- if (!entry || entry.provider !== 'windows-dpapi' || !entry.value) {
79
- return '';
80
- }
81
- const result = run(
82
- 'powershell.exe',
83
- [
84
- '-NoProfile',
85
- '-NonInteractive',
86
- '-Command',
87
- '$secure = ConvertTo-SecureString -String $env:VENDIAN_SECRET_BLOB; $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure); try { [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }'
88
- ],
89
- { env: { ...process.env, VENDIAN_SECRET_BLOB: entry.value } }
90
- );
91
- return result.status === 0 ? result.stdout.trim() : '';
92
- }
93
- return '';
94
- }
package/src/setup.js DELETED
@@ -1,127 +0,0 @@
1
- import fs from 'node:fs';
2
- import { cloudAuthStatus, loginWithVendianOAuth, saveCloudToken } from './auth.js';
3
- import { DEFAULT_GITLAB_HOST, DEFAULT_SDK_PUBLIC_PROJECT_ID, DEFAULT_SDK_RUNTIME_PROJECT_ID } from './constants.js';
4
- import { loadConfig, saveConfig } from './config.js';
5
- import { installVendianPackages, registryConfig } from './install.js';
6
- import { managedVenvPath, venvVendian } from './paths.js';
7
- import { findPython, ensureVenv, pythonVersion, verifyVendianImports } from './python.js';
8
- import { savePackageTokenSecret } from './secret-store.js';
9
-
10
- export async function setup({
11
- nonInteractive = false,
12
- backend = undefined,
13
- apiUrl = undefined,
14
- noBrowser = false,
15
- forceAuth = false,
16
- env = process.env,
17
- platform = process.platform
18
- } = {}) {
19
- const existing = loadConfig(env, platform);
20
- const registry = registryConfig(existing, env, platform);
21
- const auth = cloudAuthStatus({ backend, apiUrl, env, platform });
22
- const next = {
23
- ...existing,
24
- gitlabHost: registry.gitlabHost || DEFAULT_GITLAB_HOST,
25
- gitlabUsername: registry.username || '__token__',
26
- sdkPublicProjectId: registry.sdkProjectId || DEFAULT_SDK_PUBLIC_PROJECT_ID,
27
- sdkRuntimeProjectId: registry.runtimeProjectId || DEFAULT_SDK_RUNTIME_PROJECT_ID
28
- };
29
- let cloudAuthApiUrl = auth.authenticated ? auth.apiUrl : undefined;
30
-
31
- console.log('Vendian login');
32
- console.log('This signs in to Vendian and prepares the local runtime.');
33
- console.log('Agent requirements.txt files stay reserved for agent-owned dependencies.');
34
- console.log('');
35
-
36
- if ((!auth.authenticated || forceAuth) && !nonInteractive) {
37
- console.log(`Opening Vendian sign-in for ${auth.apiUrl}...`);
38
- const login = await loginWithVendianOAuth({ backend, apiUrl, noBrowser, env });
39
- saveCloudToken(login, { env, platform });
40
- cloudAuthApiUrl = login.apiUrl;
41
- const packageCredentials = login.packageCredentials;
42
- if (!registry.token && !packageCredentials?.token) {
43
- throw new Error('Vendian login succeeded, but the backend did not return package credentials. Configure CLI_PACKAGE_REGISTRY_TOKEN on the backend.');
44
- }
45
- if (packageCredentials) {
46
- next.gitlabHost = packageCredentials.gitlabHost || next.gitlabHost;
47
- next.gitlabUsername = packageCredentials.username || next.gitlabUsername;
48
- next.sdkPublicProjectId = packageCredentials.sdkProjectId || next.sdkPublicProjectId;
49
- next.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || next.sdkRuntimeProjectId;
50
- next.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || next.vendianAgentsVersion;
51
- next.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || next.vendianAgentsRuntimeVersion;
52
-
53
- const secret = savePackageTokenSecret(packageCredentials.token, next, platform);
54
- if (secret.ok) {
55
- Object.assign(next, secret.config || {});
56
- delete next.gitlabToken;
57
- console.log('Package access saved.');
58
- } else {
59
- next.gitlabToken = packageCredentials.token;
60
- console.log('Package access saved in the local Vendian CLI config file.');
61
- }
62
- }
63
- } else if (auth.authenticated) {
64
- console.log(`Cloud authentication already saved for ${auth.apiUrl}${auth.email ? ` (${auth.email})` : ''}.`);
65
- } else if (registry.token) {
66
- if (registry.tokenSource !== 'secret-store') {
67
- next.gitlabToken = registry.token;
68
- }
69
- console.log('Using saved package access.');
70
- }
71
-
72
- const installRegistry = registryConfig(next, env, platform);
73
- if (!installRegistry.token) {
74
- throw new Error('Package access is missing. Run interactive `vendian login` to sign in.');
75
- }
76
-
77
- const python = findPython(platform);
78
- if (!python) {
79
- throw new Error('Python 3.11+ was not found. Install Python 3.11 or newer, then rerun `vendian login`.');
80
- }
81
-
82
- const venvPath = managedVenvPath(env, platform);
83
- console.log(`Managed environment: ${venvPath}`);
84
- const pythonPath = ensureVenv(venvPath, python, platform);
85
- console.log(`Python: ${pythonVersion(pythonPath) || pythonPath}`);
86
-
87
- saveConfig(next, env, platform);
88
- installVendianPackages({ pythonPath, venvPath, config: next, env, platform });
89
- saveConfig({ ...next, lastManagedUpdateAt: new Date().toISOString() }, env, platform);
90
-
91
- if (!verifyVendianImports(pythonPath)) {
92
- throw new Error('Vendian packages installed, but import verification failed.');
93
- }
94
-
95
- const vendianPath = venvVendian(venvPath, platform);
96
- if (!fs.existsSync(vendianPath)) {
97
- throw new Error('Vendian executable was not found after install.');
98
- }
99
-
100
- console.log('');
101
- for (const line of setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl })) {
102
- console.log(line);
103
- }
104
- }
105
-
106
- export function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
107
- const lines = ['Vendian login complete.'];
108
- if (cloudAuthApiUrl) {
109
- lines.push(`Connected to ${cloudAuthApiUrl}.`);
110
- lines.push('Next: vendian cloud local serve --agents-dir ./agents');
111
- return lines;
112
- }
113
-
114
- lines.push(`Next: ${cloudAuthLoginCommand({ backend, apiUrl })}`);
115
- lines.push('Then: vendian cloud local serve --agents-dir ./agents');
116
- return lines;
117
- }
118
-
119
- function cloudAuthLoginCommand({ backend, apiUrl } = {}) {
120
- if (apiUrl) {
121
- return `vendian cloud auth login --api-url ${apiUrl}`;
122
- }
123
- if (backend) {
124
- return `vendian cloud auth login --backend ${backend}`;
125
- }
126
- return 'vendian cloud auth login';
127
- }
package/src/tui.js DELETED
@@ -1,119 +0,0 @@
1
- import readline from 'node:readline/promises';
2
- import { BACKEND_TARGETS } from './constants.js';
3
- import { cloudAuthStatus } from './auth.js';
4
- import { doctor } from './doctor.js';
5
- import { forwardToPythonVendian } from './forward.js';
6
- import { setup } from './setup.js';
7
-
8
- const ENDPOINTS = [
9
- { key: 'local', label: 'Local' },
10
- { key: 'dev', label: 'Dev' },
11
- { key: 'staging', label: 'Staging' },
12
- { key: 'prod', label: 'Production' }
13
- ];
14
-
15
- export async function runTui({ env = process.env, platform = process.platform, input = process.stdin, output = process.stdout } = {}) {
16
- const rl = readline.createInterface({ input, output });
17
- try {
18
- while (true) {
19
- output.write(`${renderHome({ env, platform })}\n`);
20
- const choice = await ask(rl, 'Choose an option');
21
- if (choice === '1') {
22
- await connectEndpoint(rl, output, { env, platform });
23
- } else if (choice === '2') {
24
- const agentsDir = (await ask(rl, 'Agents directory', './agents')).trim() || './agents';
25
- await forwardToPythonVendian(['cloud', 'local', 'serve', '--agents-dir', agentsDir], { env, platform });
26
- return;
27
- } else if (choice === '3') {
28
- doctor({ env, platform });
29
- } else if (choice === '4') {
30
- await setup({ nonInteractive: true, env, platform });
31
- } else if (choice === '5') {
32
- output.write(`${helpText()}\n`);
33
- } else if (choice === '6' || choice.toLowerCase() === 'q') {
34
- return;
35
- } else {
36
- output.write('Unknown option.\n');
37
- }
38
- output.write('\n');
39
- }
40
- } finally {
41
- rl.close();
42
- }
43
- }
44
-
45
- export function renderHome({ env = process.env, platform = process.platform } = {}) {
46
- const rows = endpointRows({ env, platform });
47
- return [
48
- '',
49
- 'Vendian CLI',
50
- '-----------',
51
- 'Connections:',
52
- ...rows.map((row) => ` ${row.label.padEnd(10)} ${row.status}${row.detail ? ` - ${row.detail}` : ''}`),
53
- '',
54
- '1. Connect or switch endpoint',
55
- '2. Start local agent server',
56
- '3. Run doctor',
57
- '4. Update managed runtime',
58
- '5. Help',
59
- '6. Exit'
60
- ].join('\n');
61
- }
62
-
63
- export function endpointRows({ env = process.env, platform = process.platform } = {}) {
64
- return ENDPOINTS.map((endpoint) => {
65
- const status = cloudAuthStatus({ backend: endpoint.key, env, platform });
66
- const active = status.activeApiUrl === status.apiUrl;
67
- return {
68
- key: endpoint.key,
69
- label: endpoint.label,
70
- apiUrl: status.apiUrl,
71
- status: status.authenticated ? (active ? 'connected' : 'signed in') : 'not signed in',
72
- detail: status.authenticated ? status.email || status.apiUrl : status.apiUrl
73
- };
74
- });
75
- }
76
-
77
- async function connectEndpoint(rl, output, { env, platform }) {
78
- output.write('\nConnect endpoint:\n');
79
- ENDPOINTS.forEach((endpoint, index) => {
80
- output.write(`${index + 1}. ${endpoint.label} (${BACKEND_TARGETS[endpoint.key]})\n`);
81
- });
82
- output.write('5. Custom API URL\n');
83
- const choice = await ask(rl, 'Endpoint');
84
- if (choice === '5') {
85
- const apiUrl = (await ask(rl, 'API URL')).trim();
86
- if (!apiUrl) {
87
- output.write('API URL is required.\n');
88
- return;
89
- }
90
- await setup({ apiUrl, forceAuth: true, env, platform });
91
- return;
92
- }
93
-
94
- const endpoint = ENDPOINTS[Number(choice) - 1];
95
- if (!endpoint) {
96
- output.write('Unknown endpoint.\n');
97
- return;
98
- }
99
- await setup({ backend: endpoint.key, forceAuth: true, env, platform });
100
- }
101
-
102
- function helpText() {
103
- return [
104
- '',
105
- 'Common commands:',
106
- ' vendian login',
107
- ' vendian login --backend staging',
108
- ' vendian login --api-url http://localhost:3000',
109
- ' vendian cloud local serve --agents-dir ./agents',
110
- ' vendian doctor',
111
- ' vendian update'
112
- ].join('\n');
113
- }
114
-
115
- async function ask(rl, question, defaultValue) {
116
- const suffix = defaultValue ? ` (${defaultValue})` : '';
117
- const answer = await rl.question(`${question}${suffix}: `);
118
- return answer.trim() || defaultValue || '';
119
- }