@vendian/cli 0.0.1 → 0.0.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
@@ -4,6 +4,7 @@ Command-line tools for signing in to Vendian and running local agent workflows.
4
4
 
5
5
  ```bash
6
6
  npm install -g @vendian/cli
7
+ vendian
7
8
  vendian login
8
9
  vendian cloud local serve --agents-dir ./agents
9
10
  ```
@@ -11,15 +12,19 @@ vendian cloud local serve --agents-dir ./agents
11
12
  ## Commands
12
13
 
13
14
  ```bash
15
+ vendian
14
16
  vendian login
15
17
  vendian doctor
16
18
  vendian update
17
19
  vendian cloud local serve --agents-dir ./agents
18
20
  ```
19
21
 
22
+ Run `vendian` with no arguments to open the interactive menu. It shows your
23
+ current endpoint connections and guides common local workflows.
24
+
20
25
  `vendian login` opens a browser sign-in, prepares the local runtime, and stores
21
- the credentials needed by the CLI. `vendian setup` is kept as an alias for
22
- existing users.
26
+ the credentials needed by the CLI for the selected endpoint. `vendian setup` is
27
+ kept as an alias for existing users.
23
28
 
24
29
  `vendian doctor` checks the local installation. `vendian update` refreshes the
25
30
  managed runtime without changing your agents.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendian/cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Public Vendian CLI bootstrapper and launcher",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
package/src/auth.js CHANGED
@@ -52,6 +52,32 @@ export async function loginWithVendianOAuth({ backend, apiUrl, noBrowser = false
52
52
  }
53
53
  }
54
54
 
55
+ export function loadCloudConfig(env = process.env, platform = process.platform) {
56
+ const file = cloudConfigPath(env, platform);
57
+ try {
58
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
59
+ return parsed && typeof parsed === 'object' ? parsed : {};
60
+ } catch {
61
+ return {};
62
+ }
63
+ }
64
+
65
+ export function cloudAuthStatus({ backend, apiUrl, env = process.env, platform = process.platform } = {}) {
66
+ const targetApiUrl = resolveApiUrl({ backend, apiUrl, env });
67
+ const config = loadCloudConfig(env, platform);
68
+ const profiles = config.profiles && typeof config.profiles === 'object' ? config.profiles : {};
69
+ const profile = profiles[targetApiUrl];
70
+ return {
71
+ apiUrl: targetApiUrl,
72
+ activeApiUrl: typeof config.active_api_url === 'string' ? config.active_api_url : undefined,
73
+ profile: profile && typeof profile === 'object' ? profile : undefined,
74
+ authenticated: Boolean(profile?.access_token),
75
+ email: typeof profile?.email === 'string' ? profile.email : undefined,
76
+ expiresAt: typeof profile?.expires_at === 'string' ? profile.expires_at : undefined,
77
+ profiles
78
+ };
79
+ }
80
+
55
81
  async function getOAuthConfig(apiUrl, redirectUri) {
56
82
  const url = new URL(`${apiUrl}/api/v1/cli/auth/oauth/config`);
57
83
  url.searchParams.set('redirectUri', redirectUri);
package/src/doctor.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- import { loadConfig, redact } from './config.js';
2
+ import { loadConfig } from './config.js';
3
3
  import { registryConfig } from './install.js';
4
4
  import { managedVenvPath, venvPython, venvVendian } from './paths.js';
5
5
  import { findPython, hasUv, pythonVersion, verifyVendianImports } from './python.js';
@@ -18,6 +18,5 @@ export function doctor({ env = process.env, platform = process.platform } = {})
18
18
  console.log(`Managed Python: ${fs.existsSync(pythonPath) ? pythonVersion(pythonPath) || 'present' : 'missing'}`);
19
19
  console.log(`Vendian executable: ${fs.existsSync(vendianPath) ? vendianPath : 'missing'}`);
20
20
  console.log(`Vendian imports: ${fs.existsSync(pythonPath) && verifyVendianImports(pythonPath) ? 'ok' : 'missing'}`);
21
- console.log(`GitLab host: ${registry.gitlabHost}`);
22
- console.log(`Package token: ${registry.token ? `${redact(registry.token)} (${registry.tokenSource})` : 'missing'}`);
21
+ console.log(`Package access: ${registry.token ? `configured (${registry.tokenSource})` : 'missing'}`);
23
22
  }
package/src/install.js CHANGED
@@ -59,7 +59,7 @@ export function installVendianPackages({ pythonPath, venvPath, config, env = pro
59
59
  const registry = registryConfig(config, env, platform);
60
60
  const indexes = buildIndexUrls(registry);
61
61
  if (!indexes) {
62
- throw new Error('A GitLab package token is required until backend-issued package tokens are available. Run `vendian setup` or set GITLAB_TOKEN.');
62
+ throw new Error('Package access is missing. Run `vendian login`.');
63
63
  }
64
64
 
65
65
  const specs = packageSpecs(registry);
@@ -82,7 +82,7 @@ export function installVendianPackages({ pythonPath, venvPath, config, env = pro
82
82
  status = runInherit(pythonPath, ['-m', 'pip', ...installArgs]);
83
83
  }
84
84
  if (status !== 0) {
85
- throw new Error('Could not install Vendian SDK/runtime packages. Check token access and package versions.');
85
+ throw new Error('Could not install Vendian runtime packages. Check package access and try again.');
86
86
  }
87
87
 
88
88
  writeInstallMarker(venvPath, {
package/src/main.js CHANGED
@@ -1,41 +1,46 @@
1
1
  import { doctor } from './doctor.js';
2
2
  import { forwardToPythonVendian } from './forward.js';
3
3
  import { setup } from './setup.js';
4
+ import { runTui } from './tui.js';
4
5
 
5
6
  function printHelp() {
6
- console.log(`Vendian CLI bootstrapper
7
+ console.log(`Vendian CLI
7
8
 
8
9
  Usage:
9
- vendian login Sign in and install the private Vendian CLI/runtime
10
+ vendian Open the interactive menu
11
+ vendian login Sign in and prepare the local runtime
10
12
  vendian setup Alias for vendian login
11
13
  vendian doctor Check local bootstrap health
12
14
  vendian update Update the managed Vendian CLI/runtime
13
- vendian <command> Forward to the managed Python Vendian CLI
15
+ vendian <command> Run a Vendian cloud command
14
16
 
15
17
  Examples:
16
18
  vendian login
17
- vendian login --backend prod
19
+ vendian login --backend staging
18
20
  vendian cloud local serve --agents-dir ./agents
19
21
 
20
22
  Environment:
21
23
  VENDIAN_CLI_HOME Override managed CLI home
22
24
  VENDIAN_API_URL Override Vendian backend API URL
23
- GITLAB_TOKEN Local-dev package registry token fallback
24
- GITLAB_USERNAME Local-dev package registry username fallback
25
- GITLAB_HOST Defaults to gitlab.com
26
- SDK_PUBLIC_PROJECT_ID Defaults to Vendian SDK project
27
- SDK_RUNTIME_PROJECT_ID Defaults to Vendian runtime project
28
25
  `);
29
26
  }
30
27
 
31
28
  export async function main(argv) {
32
29
  const [command, ...rest] = argv;
33
- if (!command || command === '--help' || command === '-h') {
30
+ if (!command) {
31
+ if (process.stdin.isTTY && process.stdout.isTTY) {
32
+ await runTui();
33
+ } else {
34
+ printHelp();
35
+ }
36
+ return;
37
+ }
38
+ if (command === '--help' || command === '-h') {
34
39
  printHelp();
35
40
  return;
36
41
  }
37
42
  if (command === '--version' || command === 'version') {
38
- console.log('0.1.0');
43
+ console.log('0.0.1');
39
44
  return;
40
45
  }
41
46
  if (isBootstrapCommand(command)) {
@@ -60,7 +65,8 @@ export function isBootstrapCommand(command) {
60
65
  function parseSetupOptions(args) {
61
66
  const options = {
62
67
  nonInteractive: args.includes('--yes') || args.includes('--non-interactive'),
63
- noBrowser: args.includes('--no-browser')
68
+ noBrowser: args.includes('--no-browser'),
69
+ forceAuth: args.includes('--force') || args.includes('--reauth')
64
70
  };
65
71
  for (let index = 0; index < args.length; index += 1) {
66
72
  const arg = args[index];
package/src/setup.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
- import { loginWithVendianOAuth, saveCloudToken } from './auth.js';
2
+ import { cloudAuthStatus, loginWithVendianOAuth, saveCloudToken } from './auth.js';
3
3
  import { DEFAULT_GITLAB_HOST, DEFAULT_SDK_PUBLIC_PROJECT_ID, DEFAULT_SDK_RUNTIME_PROJECT_ID } from './constants.js';
4
- import { loadConfig, saveConfig, redact } from './config.js';
4
+ import { loadConfig, saveConfig } from './config.js';
5
5
  import { installVendianPackages, registryConfig } from './install.js';
6
6
  import { managedVenvPath, venvVendian } from './paths.js';
7
7
  import { findPython, ensureVenv, pythonVersion, verifyVendianImports } from './python.js';
@@ -12,11 +12,13 @@ export async function setup({
12
12
  backend = undefined,
13
13
  apiUrl = undefined,
14
14
  noBrowser = false,
15
+ forceAuth = false,
15
16
  env = process.env,
16
17
  platform = process.platform
17
18
  } = {}) {
18
19
  const existing = loadConfig(env, platform);
19
20
  const registry = registryConfig(existing, env, platform);
21
+ const auth = cloudAuthStatus({ backend, apiUrl, env, platform });
20
22
  const next = {
21
23
  ...existing,
22
24
  gitlabHost: registry.gitlabHost || DEFAULT_GITLAB_HOST,
@@ -24,48 +26,52 @@ export async function setup({
24
26
  sdkPublicProjectId: registry.sdkProjectId || DEFAULT_SDK_PUBLIC_PROJECT_ID,
25
27
  sdkRuntimeProjectId: registry.runtimeProjectId || DEFAULT_SDK_RUNTIME_PROJECT_ID
26
28
  };
27
- let cloudAuthApiUrl;
29
+ let cloudAuthApiUrl = auth.authenticated ? auth.apiUrl : undefined;
28
30
 
29
- console.log('Vendian setup');
30
- console.log('This installs the private Vendian Python CLI/runtime into a managed local environment.');
31
+ console.log('Vendian login');
32
+ console.log('This signs in to Vendian and prepares the local runtime.');
31
33
  console.log('Agent requirements.txt files stay reserved for agent-owned dependencies.');
32
34
  console.log('');
33
35
 
34
- if (!registry.token && !nonInteractive) {
35
- console.log('Opening Vendian sign-in to get package access...');
36
+ if ((!auth.authenticated || forceAuth) && !nonInteractive) {
37
+ console.log(`Opening Vendian sign-in for ${auth.apiUrl}...`);
36
38
  const login = await loginWithVendianOAuth({ backend, apiUrl, noBrowser, env });
37
39
  saveCloudToken(login, { env, platform });
38
40
  cloudAuthApiUrl = login.apiUrl;
39
41
  const packageCredentials = login.packageCredentials;
40
- if (!packageCredentials?.token) {
42
+ if (!registry.token && !packageCredentials?.token) {
41
43
  throw new Error('Vendian login succeeded, but the backend did not return package credentials. Configure CLI_PACKAGE_REGISTRY_TOKEN on the backend.');
42
44
  }
43
- next.gitlabHost = packageCredentials.gitlabHost || next.gitlabHost;
44
- next.gitlabUsername = packageCredentials.username || next.gitlabUsername;
45
- next.sdkPublicProjectId = packageCredentials.sdkProjectId || next.sdkPublicProjectId;
46
- next.sdkRuntimeProjectId = packageCredentials.runtimeProjectId || next.sdkRuntimeProjectId;
47
- next.vendianAgentsVersion = packageCredentials.vendianAgentsVersion || next.vendianAgentsVersion;
48
- next.vendianAgentsRuntimeVersion = packageCredentials.vendianAgentsRuntimeVersion || next.vendianAgentsRuntimeVersion;
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;
49
52
 
50
- const secret = savePackageTokenSecret(packageCredentials.token, next, platform);
51
- if (secret.ok) {
52
- Object.assign(next, secret.config || {});
53
- delete next.gitlabToken;
54
- console.log(`Package token saved with ${secret.provider}.`);
55
- } else {
56
- next.gitlabToken = packageCredentials.token;
57
- console.log('Package token saved in the local Vendian CLI config file.');
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
+ }
58
62
  }
63
+ } else if (auth.authenticated) {
64
+ console.log(`Cloud authentication already saved for ${auth.apiUrl}${auth.email ? ` (${auth.email})` : ''}.`);
59
65
  } else if (registry.token) {
60
66
  if (registry.tokenSource !== 'secret-store') {
61
67
  next.gitlabToken = registry.token;
62
68
  }
63
- console.log(`Using GitLab package token ${redact(registry.token)}.`);
69
+ console.log('Using saved package access.');
64
70
  }
65
71
 
66
72
  const installRegistry = registryConfig(next, env, platform);
67
73
  if (!installRegistry.token) {
68
- throw new Error('Setup needs package access. Run interactive `vendian login` to sign in, or set GITLAB_TOKEN for local development.');
74
+ throw new Error('Package access is missing. Run interactive `vendian login` to sign in.');
69
75
  }
70
76
 
71
77
  const python = findPython(platform);
@@ -98,9 +104,9 @@ export async function setup({
98
104
  }
99
105
 
100
106
  export function setupCompletionLines({ cloudAuthApiUrl, backend, apiUrl } = {}) {
101
- const lines = ['Vendian setup complete.'];
107
+ const lines = ['Vendian login complete.'];
102
108
  if (cloudAuthApiUrl) {
103
- lines.push(`Cloud authentication saved for ${cloudAuthApiUrl}.`);
109
+ lines.push(`Connected to ${cloudAuthApiUrl}.`);
104
110
  lines.push('Next: vendian cloud local serve --agents-dir ./agents');
105
111
  return lines;
106
112
  }
package/src/tui.js ADDED
@@ -0,0 +1,119 @@
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
+ }