gbos 1.0.0 → 1.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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "gbos",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "GBOS - Command line interface for GBOS services",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "gbos": "./src/cli.js"
7
+ "gbos": "src/cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node src/index.js",
@@ -19,7 +19,9 @@
19
19
  "cli",
20
20
  "tui",
21
21
  "cloud",
22
- "devtools"
22
+ "devtools",
23
+ "development",
24
+ "ai-agent"
23
25
  ],
24
26
  "author": "Mystro Analytics",
25
27
  "license": "MIT",
package/src/cli.js CHANGED
@@ -3,69 +3,134 @@
3
3
  const { Command } = require('commander');
4
4
  const program = new Command();
5
5
 
6
+ const authCommand = require('./commands/auth');
7
+ const connectCommand = require('./commands/connect');
8
+ const logoutCommand = require('./commands/logout');
9
+ const config = require('./lib/config');
10
+
11
+ const VERSION = require('../package.json').version;
12
+
6
13
  program
7
14
  .name('gbos')
8
15
  .description('GBOS - Command line interface for GBOS services')
9
- .version('1.0.0');
16
+ .version(VERSION);
10
17
 
11
18
  program
12
19
  .command('auth')
13
20
  .description('Authenticate with GBOS services')
14
- .option('-t, --token <token>', 'Use a specific auth token')
15
- .action((options) => {
16
- console.log('Authenticating with GBOS services...');
17
- if (options.token) {
18
- console.log('Using provided token');
19
- } else {
20
- console.log('Opening browser for authentication...');
21
+ .option('-e, --email <email>', 'Email address for authentication')
22
+ .option('-f, --force', 'Force re-authentication even if already authenticated')
23
+ .action(authCommand);
24
+
25
+ program
26
+ .command('connect')
27
+ .description('Connect to a GBOS development node')
28
+ .option('-d, --dir <directory>', 'Working directory (defaults to current directory)')
29
+ .option('-a, --agent <agent>', 'Agent CLI being used (default: claude-code)')
30
+ .option('-f, --force', 'Force reconnect even if already connected')
31
+ .action(connectCommand);
32
+
33
+ program
34
+ .command('disconnect')
35
+ .description('Disconnect from the current GBOS node')
36
+ .action(async () => {
37
+ if (!config.isAuthenticated()) {
38
+ console.log('\nNot authenticated.\n');
39
+ return;
40
+ }
41
+
42
+ const connection = config.getConnection();
43
+ if (!connection) {
44
+ console.log('\nNot connected to any node.\n');
45
+ return;
46
+ }
47
+
48
+ try {
49
+ const api = require('./lib/api');
50
+ const result = await api.disconnect();
51
+
52
+ config.clearConnection();
53
+
54
+ console.log('\n✓ Disconnected from node.\n');
55
+
56
+ if (result.data) {
57
+ console.log(` Tasks completed: ${result.data.tasks_completed || 0}`);
58
+ console.log(` Tasks failed: ${result.data.tasks_failed || 0}`);
59
+ console.log(` Total time: ${result.data.total_time_minutes || 0} minutes\n`);
60
+ }
61
+ } catch (error) {
62
+ config.clearConnection();
63
+ console.log('\n✓ Disconnected (local session cleared).\n');
21
64
  }
22
- // TODO: Implement authentication logic
23
65
  });
24
66
 
25
67
  program
26
- .command('connect')
27
- .description('Connect to a GBOS service or resource')
28
- .argument('[service]', 'Service name to connect to')
29
- .option('-e, --env <environment>', 'Environment (dev, staging, prod)', 'dev')
30
- .action((service, options) => {
31
- console.log(`Connecting to GBOS...`);
32
- if (service) {
33
- console.log(`Service: ${service}`);
68
+ .command('status')
69
+ .description('Show current authentication and connection status')
70
+ .action(async () => {
71
+ const session = config.loadSession();
72
+
73
+ if (!session || !session.access_token) {
74
+ console.log('\nStatus: Not authenticated');
75
+ console.log('Run "gbos auth" to authenticate.\n');
76
+ return;
34
77
  }
35
- console.log(`Environment: ${options.env}`);
36
- // TODO: Implement connection logic
78
+
79
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
80
+ console.log('│ GBOS Status │');
81
+ console.log('├─────────────────────────────────────────────────────────────┤');
82
+ console.log(`│ Authenticated: ✓ │`);
83
+ console.log(`│ User ID: ${String(session.user_id).padEnd(42)}│`);
84
+ console.log(`│ Account ID: ${String(session.account_id).padEnd(42)}│`);
85
+
86
+ const connection = session.connection;
87
+ if (connection) {
88
+ console.log('├─────────────────────────────────────────────────────────────┤');
89
+ console.log(`│ Connected: ✓ │`);
90
+ console.log(`│ Node: ${(connection.node?.name || 'Unknown').substring(0, 42).padEnd(42)}│`);
91
+ console.log(`│ Node ID: ${String(connection.node?.id || '').padEnd(42)}│`);
92
+ console.log(`│ Connection: ${(connection.connection_id || '').substring(0, 36).padEnd(42)}│`);
93
+ } else {
94
+ console.log('├─────────────────────────────────────────────────────────────┤');
95
+ console.log(`│ Connected: ✗ (run "gbos connect") │`);
96
+ }
97
+
98
+ console.log('└─────────────────────────────────────────────────────────────┘\n');
99
+
100
+ // Show environment variables
101
+ console.log('Environment variables:');
102
+ const envVars = config.getSessionEnv();
103
+ Object.entries(envVars).forEach(([key, value]) => {
104
+ if (value) {
105
+ console.log(` export ${key}="${value}"`);
106
+ }
107
+ });
108
+ console.log('');
37
109
  });
38
110
 
111
+ program
112
+ .command('logout')
113
+ .description('Log out from GBOS services and clear credentials')
114
+ .option('-a, --all', 'Clear all stored data including machine ID')
115
+ .action(logoutCommand);
116
+
39
117
  program
40
118
  .command('help [command]')
41
119
  .description('Display help for a specific command')
42
120
  .action((command) => {
43
121
  if (command) {
44
- const cmd = program.commands.find(c => c.name() === command);
122
+ const cmd = program.commands.find((c) => c.name() === command);
45
123
  if (cmd) {
46
124
  cmd.outputHelp();
47
125
  } else {
48
126
  console.log(`Unknown command: ${command}`);
49
- console.log('Available commands: auth, connect, help, logout');
127
+ console.log('Available commands: auth, connect, disconnect, status, logout, help');
50
128
  }
51
129
  } else {
52
130
  program.outputHelp();
53
131
  }
54
132
  });
55
133
 
56
- program
57
- .command('logout')
58
- .description('Log out from GBOS services and clear credentials')
59
- .option('-a, --all', 'Clear all stored credentials')
60
- .action((options) => {
61
- console.log('Logging out from GBOS services...');
62
- if (options.all) {
63
- console.log('Clearing all stored credentials...');
64
- }
65
- console.log('Successfully logged out.');
66
- // TODO: Implement logout logic
67
- });
68
-
69
134
  // Show help by default if no command is provided
70
135
  if (process.argv.length <= 2) {
71
136
  program.outputHelp();
@@ -0,0 +1,164 @@
1
+ const api = require('../lib/api');
2
+ const config = require('../lib/config');
3
+ const readline = require('readline');
4
+
5
+ // Simple prompt for email
6
+ async function promptEmail() {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+
12
+ return new Promise((resolve) => {
13
+ rl.question('Enter your email address: ', (answer) => {
14
+ rl.close();
15
+ resolve(answer.trim());
16
+ });
17
+ });
18
+ }
19
+
20
+ // Sleep helper
21
+ function sleep(ms) {
22
+ return new Promise((resolve) => setTimeout(resolve, ms));
23
+ }
24
+
25
+ // Spinner frames
26
+ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
27
+
28
+ async function authCommand(options) {
29
+ // Check if already authenticated
30
+ if (config.isAuthenticated() && !options.force) {
31
+ const session = config.loadSession();
32
+ console.log(`\nAlready authenticated as user ID: ${session.user_id}`);
33
+ console.log(`Account ID: ${session.account_id}`);
34
+ console.log(`\nUse --force to re-authenticate or 'gbos logout' first.\n`);
35
+ return;
36
+ }
37
+
38
+ try {
39
+ // Get email from user
40
+ const email = options.email || await promptEmail();
41
+
42
+ if (!email || !email.includes('@')) {
43
+ console.error('Invalid email address.');
44
+ process.exit(1);
45
+ }
46
+
47
+ console.log(`\nInitializing authentication for: ${email}`);
48
+
49
+ // Initialize device auth flow
50
+ const initResponse = await api.initAuth({ email });
51
+ const { device_code, verification_code, verification_url_complete, interval, expires_in } = initResponse.data;
52
+
53
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
54
+ console.log('│ GBOS Authentication │');
55
+ console.log('├─────────────────────────────────────────────────────────────┤');
56
+ console.log(`│ Device Code: ${device_code} │`);
57
+ console.log('│ │');
58
+ console.log('│ Please visit the following URL to authorize: │');
59
+ console.log('└─────────────────────────────────────────────────────────────┘');
60
+ console.log(`\n${verification_url_complete}\n`);
61
+ console.log(`Code expires in ${Math.floor(expires_in / 60)} minutes.\n`);
62
+
63
+ // Try to open the URL in the default browser
64
+ const openCommand = process.platform === 'darwin' ? 'open' :
65
+ process.platform === 'win32' ? 'start' : 'xdg-open';
66
+
67
+ try {
68
+ const { exec } = require('child_process');
69
+ exec(`${openCommand} "${verification_url_complete}"`);
70
+ console.log('Opening browser...\n');
71
+ } catch (e) {
72
+ console.log('Please open the URL above in your browser.\n');
73
+ }
74
+
75
+ // Poll for authorization
76
+ const pollInterval = (interval || 5) * 1000;
77
+ const maxAttempts = Math.ceil((expires_in || 900) / (interval || 5));
78
+ let attempts = 0;
79
+ let frameIndex = 0;
80
+
81
+ process.stdout.write('Waiting for authorization... ');
82
+
83
+ while (attempts < maxAttempts) {
84
+ attempts++;
85
+
86
+ // Show spinner
87
+ process.stdout.write(`\rWaiting for authorization... ${spinnerFrames[frameIndex]} `);
88
+ frameIndex = (frameIndex + 1) % spinnerFrames.length;
89
+
90
+ await sleep(pollInterval);
91
+
92
+ try {
93
+ const statusResponse = await api.checkAuthStatus(verification_code);
94
+
95
+ if (statusResponse.status === 'approved' && statusResponse.data) {
96
+ // Clear spinner line
97
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
98
+
99
+ const { access_token, refresh_token, expires_in: tokenExpires, user_id, account_id, session_id } = statusResponse.data;
100
+
101
+ // Calculate token expiration
102
+ const tokenExpiresAt = new Date(Date.now() + tokenExpires * 1000).toISOString();
103
+
104
+ // Save session
105
+ config.saveSession({
106
+ access_token,
107
+ refresh_token,
108
+ token_expires_at: tokenExpiresAt,
109
+ user_id,
110
+ account_id,
111
+ session_id,
112
+ authenticated_at: new Date().toISOString(),
113
+ });
114
+
115
+ console.log('\n✓ Authentication successful!\n');
116
+ console.log(` User ID: ${user_id}`);
117
+ console.log(` Account ID: ${account_id}`);
118
+ console.log(` Session: ${session_id}\n`);
119
+ console.log('Run "gbos connect" to connect to a development node.\n');
120
+ return;
121
+ }
122
+
123
+ if (statusResponse.status === 'denied') {
124
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
125
+ console.log('\n✗ Authorization denied.\n');
126
+ process.exit(1);
127
+ }
128
+
129
+ if (statusResponse.status === 'expired') {
130
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
131
+ console.log('\n✗ Authorization request expired. Please try again.\n');
132
+ process.exit(1);
133
+ }
134
+
135
+ // Status is pending, continue polling
136
+ } catch (error) {
137
+ if (error.status === 410) {
138
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
139
+ console.log('\n✗ Authorization request expired. Please try again.\n');
140
+ process.exit(1);
141
+ }
142
+ if (error.status === 403) {
143
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
144
+ console.log('\n✗ Authorization denied.\n');
145
+ process.exit(1);
146
+ }
147
+ // For other errors, continue polling
148
+ }
149
+ }
150
+
151
+ process.stdout.write('\r' + ' '.repeat(50) + '\r');
152
+ console.log('\n✗ Authorization timed out. Please try again.\n');
153
+ process.exit(1);
154
+
155
+ } catch (error) {
156
+ console.error(`\nAuthentication failed: ${error.message}\n`);
157
+ if (process.env.DEBUG) {
158
+ console.error(error);
159
+ }
160
+ process.exit(1);
161
+ }
162
+ }
163
+
164
+ module.exports = authCommand;
@@ -0,0 +1,212 @@
1
+ const api = require('../lib/api');
2
+ const config = require('../lib/config');
3
+ const readline = require('readline');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+
7
+ // Simple selection prompt
8
+ async function selectOption(message, options) {
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+
14
+ console.log(`\n${message}\n`);
15
+
16
+ options.forEach((opt, index) => {
17
+ const status = opt.status ? ` [${opt.status}]` : '';
18
+ const connected = opt.is_connected ? ' (connected by another user)' : '';
19
+ console.log(` ${index + 1}. ${opt.name}${status}${connected}`);
20
+ if (opt.description) {
21
+ console.log(` ${opt.description}`);
22
+ }
23
+ });
24
+
25
+ console.log('');
26
+
27
+ return new Promise((resolve) => {
28
+ rl.question('Enter number (or q to quit): ', (answer) => {
29
+ rl.close();
30
+ if (answer.toLowerCase() === 'q') {
31
+ resolve(null);
32
+ return;
33
+ }
34
+ const index = parseInt(answer, 10) - 1;
35
+ if (index >= 0 && index < options.length) {
36
+ resolve(options[index]);
37
+ } else {
38
+ resolve(null);
39
+ }
40
+ });
41
+ });
42
+ }
43
+
44
+ // Get git info from current directory
45
+ function getGitInfo() {
46
+ try {
47
+ const gitRepoUrl = execSync('git config --get remote.origin.url', { encoding: 'utf8' }).trim();
48
+ const gitBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
49
+ return { gitRepoUrl, gitBranch };
50
+ } catch (e) {
51
+ return { gitRepoUrl: null, gitBranch: null };
52
+ }
53
+ }
54
+
55
+ async function connectCommand(options) {
56
+ // Check authentication
57
+ if (!config.isAuthenticated()) {
58
+ console.log('\nNot authenticated. Please run "gbos auth" first.\n');
59
+ process.exit(1);
60
+ }
61
+
62
+ try {
63
+ // Check if already connected
64
+ const currentConnection = config.getConnection();
65
+ if (currentConnection && !options.force) {
66
+ console.log(`\nAlready connected to node: ${currentConnection.node?.name}`);
67
+ console.log(`Connection ID: ${currentConnection.connection_id}`);
68
+ console.log(`\nUse --force to reconnect or 'gbos disconnect' first.\n`);
69
+ return;
70
+ }
71
+
72
+ console.log('\nFetching available nodes...\n');
73
+
74
+ // Fetch available nodes
75
+ const nodesResponse = await api.listNodes();
76
+ const nodes = nodesResponse.data || [];
77
+
78
+ if (nodes.length === 0) {
79
+ console.log('No development nodes available.');
80
+ console.log('Please create a development node in the GBOS web interface.\n');
81
+ process.exit(1);
82
+ }
83
+
84
+ // Group nodes by application
85
+ const nodesByApp = {};
86
+ nodes.forEach((node) => {
87
+ const appId = node.application_id || 'unassigned';
88
+ if (!nodesByApp[appId]) {
89
+ nodesByApp[appId] = {
90
+ application: node.application,
91
+ nodes: [],
92
+ };
93
+ }
94
+ nodesByApp[appId].nodes.push(node);
95
+ });
96
+
97
+ // If multiple applications, let user select one first
98
+ let selectedApp = null;
99
+ const appIds = Object.keys(nodesByApp);
100
+
101
+ if (appIds.length > 1) {
102
+ const appOptions = appIds.map((appId) => ({
103
+ id: appId,
104
+ name: nodesByApp[appId].application?.name || `Application ${appId}`,
105
+ description: `${nodesByApp[appId].nodes.length} node(s) available`,
106
+ }));
107
+
108
+ selectedApp = await selectOption('Select an application:', appOptions);
109
+
110
+ if (!selectedApp) {
111
+ console.log('Connection cancelled.\n');
112
+ return;
113
+ }
114
+ } else {
115
+ selectedApp = { id: appIds[0] };
116
+ }
117
+
118
+ // Get nodes for selected application
119
+ const appNodes = nodesByApp[selectedApp.id].nodes;
120
+
121
+ // Let user select a node
122
+ const nodeOptions = appNodes.map((node) => ({
123
+ ...node,
124
+ name: node.name,
125
+ description: node.node_type || '',
126
+ }));
127
+
128
+ const selectedNode = await selectOption('Select a development node:', nodeOptions);
129
+
130
+ if (!selectedNode) {
131
+ console.log('Connection cancelled.\n');
132
+ return;
133
+ }
134
+
135
+ // Check if node is busy
136
+ if (selectedNode.is_connected && selectedNode.active_connection) {
137
+ console.log(`\nNode "${selectedNode.name}" is already connected by another user.`);
138
+ console.log('Please select a different node.\n');
139
+ return;
140
+ }
141
+
142
+ // Get connection info
143
+ const workingDirectory = options.dir || process.cwd();
144
+ const { gitRepoUrl, gitBranch } = getGitInfo();
145
+ const agentCli = options.agent || 'claude-code';
146
+
147
+ console.log(`\nConnecting to node: ${selectedNode.name}...`);
148
+
149
+ // Connect to node
150
+ const connectResponse = await api.connectToNode(selectedNode.id, {
151
+ working_directory: workingDirectory,
152
+ git_repo_url: gitRepoUrl,
153
+ git_branch: gitBranch,
154
+ agent_cli: agentCli,
155
+ });
156
+
157
+ const { connection_id, node } = connectResponse.data;
158
+
159
+ // Save connection to session
160
+ config.saveConnection({
161
+ connection_id,
162
+ node: {
163
+ id: node.id,
164
+ uuid: node.uuid,
165
+ name: node.name,
166
+ node_type: node.node_type,
167
+ system_prompt: node.system_prompt,
168
+ application_id: selectedNode.application_id,
169
+ },
170
+ connected_at: new Date().toISOString(),
171
+ working_directory: workingDirectory,
172
+ git_repo_url: gitRepoUrl,
173
+ git_branch: gitBranch,
174
+ });
175
+
176
+ console.log('\n┌─────────────────────────────────────────────────────────────┐');
177
+ console.log('│ Connected to GBOS │');
178
+ console.log('├─────────────────────────────────────────────────────────────┤');
179
+ console.log(`│ Node: ${node.name.padEnd(42)}│`);
180
+ console.log(`│ Connection ID: ${connection_id.substring(0, 36).padEnd(42)}│`);
181
+ console.log(`│ Working Dir: ${workingDirectory.substring(0, 42).padEnd(42)}│`);
182
+ console.log('└─────────────────────────────────────────────────────────────┘');
183
+
184
+ console.log('\n✓ Successfully connected!\n');
185
+
186
+ // Show session info for other tools
187
+ console.log('Session data stored at: ~/.gbos/session.json');
188
+ console.log('\nEnvironment variables available:');
189
+ const envVars = config.getSessionEnv();
190
+ Object.entries(envVars).forEach(([key, value]) => {
191
+ if (value) {
192
+ console.log(` ${key}=${value}`);
193
+ }
194
+ });
195
+
196
+ console.log('\nOther CLI tools can access this session by reading ~/.gbos/session.json');
197
+ console.log('or by using the GBOS MCP server.\n');
198
+
199
+ } catch (error) {
200
+ if (error.code === 'NODE_BUSY') {
201
+ console.error(`\nNode is already connected to another CLI session.\n`);
202
+ } else {
203
+ console.error(`\nConnection failed: ${error.message}\n`);
204
+ }
205
+ if (process.env.DEBUG) {
206
+ console.error(error);
207
+ }
208
+ process.exit(1);
209
+ }
210
+ }
211
+
212
+ module.exports = connectCommand;
@@ -0,0 +1,57 @@
1
+ const api = require('../lib/api');
2
+ const config = require('../lib/config');
3
+
4
+ async function logoutCommand(options) {
5
+ // Check if authenticated
6
+ if (!config.isAuthenticated()) {
7
+ console.log('\nNot currently authenticated.\n');
8
+ return;
9
+ }
10
+
11
+ try {
12
+ const session = config.loadSession();
13
+
14
+ // Disconnect from node if connected
15
+ const connection = config.getConnection();
16
+ if (connection) {
17
+ console.log('Disconnecting from node...');
18
+ try {
19
+ await api.disconnect();
20
+ } catch (e) {
21
+ // Ignore disconnect errors during logout
22
+ }
23
+ }
24
+
25
+ // Call logout API
26
+ console.log('Logging out...');
27
+ try {
28
+ await api.logout();
29
+ } catch (e) {
30
+ // Ignore API errors - we'll clear local session anyway
31
+ }
32
+
33
+ // Clear local session
34
+ if (options.all) {
35
+ // Clear everything including machine ID
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+ const configDir = config.getConfigDir();
39
+
40
+ if (fs.existsSync(configDir)) {
41
+ fs.rmSync(configDir, { recursive: true });
42
+ console.log('Cleared all GBOS data.\n');
43
+ }
44
+ } else {
45
+ config.clearSession();
46
+ }
47
+
48
+ console.log('\n✓ Successfully logged out.\n');
49
+
50
+ } catch (error) {
51
+ // Clear session anyway on error
52
+ config.clearSession();
53
+ console.log('\n✓ Logged out (session cleared locally).\n');
54
+ }
55
+ }
56
+
57
+ module.exports = logoutCommand;
package/src/lib/api.js ADDED
@@ -0,0 +1,158 @@
1
+ const config = require('./config');
2
+
3
+ const API_BASE_URL = 'https://gbos-api-579767694933.us-south1.run.app/api/v1';
4
+
5
+ class GbosApiClient {
6
+ constructor() {
7
+ this.baseUrl = API_BASE_URL;
8
+ }
9
+
10
+ async request(endpoint, options = {}) {
11
+ const url = `${this.baseUrl}${endpoint}`;
12
+ const headers = {
13
+ 'Content-Type': 'application/json',
14
+ ...options.headers,
15
+ };
16
+
17
+ // Add auth header if we have a token
18
+ const token = config.getAccessToken();
19
+ if (token && !options.skipAuth) {
20
+ headers['Authorization'] = `Bearer ${token}`;
21
+ }
22
+
23
+ const response = await fetch(url, {
24
+ ...options,
25
+ headers,
26
+ body: options.body ? JSON.stringify(options.body) : undefined,
27
+ });
28
+
29
+ const data = await response.json();
30
+
31
+ if (!response.ok) {
32
+ const error = new Error(data.error || data.message || 'API request failed');
33
+ error.status = response.status;
34
+ error.code = data.code;
35
+ error.data = data;
36
+ throw error;
37
+ }
38
+
39
+ return data;
40
+ }
41
+
42
+ // Auth endpoints
43
+ async initAuth(clientInfo) {
44
+ const machineInfo = config.getMachineInfo();
45
+ return this.request('/cli/auth/init', {
46
+ method: 'POST',
47
+ body: {
48
+ client_name: 'gbos-cli',
49
+ client_version: require('../../package.json').version,
50
+ ...machineInfo,
51
+ ...clientInfo,
52
+ },
53
+ skipAuth: true,
54
+ });
55
+ }
56
+
57
+ async checkAuthStatus(verificationCode) {
58
+ return this.request(`/cli/auth/status/${verificationCode}`, {
59
+ method: 'GET',
60
+ skipAuth: true,
61
+ });
62
+ }
63
+
64
+ async refreshToken(refreshToken) {
65
+ return this.request('/cli/auth/refresh', {
66
+ method: 'POST',
67
+ body: { refresh_token: refreshToken },
68
+ skipAuth: true,
69
+ });
70
+ }
71
+
72
+ async logout() {
73
+ return this.request('/cli/auth/logout', {
74
+ method: 'POST',
75
+ });
76
+ }
77
+
78
+ async getSession() {
79
+ return this.request('/cli/auth/session', {
80
+ method: 'GET',
81
+ });
82
+ }
83
+
84
+ // Node endpoints
85
+ async listNodes(applicationId = null) {
86
+ let endpoint = '/cli/nodes';
87
+ if (applicationId) {
88
+ endpoint += `?application_id=${applicationId}`;
89
+ }
90
+ return this.request(endpoint, { method: 'GET' });
91
+ }
92
+
93
+ async connectToNode(nodeId, connectionInfo = {}) {
94
+ return this.request(`/cli/connect/${nodeId}`, {
95
+ method: 'POST',
96
+ body: connectionInfo,
97
+ });
98
+ }
99
+
100
+ async disconnect() {
101
+ return this.request('/cli/disconnect', {
102
+ method: 'POST',
103
+ });
104
+ }
105
+
106
+ async getConnectionStatus() {
107
+ return this.request('/cli/connection', {
108
+ method: 'GET',
109
+ });
110
+ }
111
+
112
+ async sendHeartbeat(taskId = null, progress = null) {
113
+ return this.request('/cli/heartbeat', {
114
+ method: 'POST',
115
+ body: {
116
+ current_task_id: taskId,
117
+ progress,
118
+ },
119
+ });
120
+ }
121
+
122
+ // Task endpoints
123
+ async getNextTask() {
124
+ return this.request('/cli/tasks/next', {
125
+ method: 'GET',
126
+ });
127
+ }
128
+
129
+ async startTask(taskId) {
130
+ return this.request(`/cli/tasks/${taskId}/start`, {
131
+ method: 'POST',
132
+ });
133
+ }
134
+
135
+ async completeTask(taskId, data = {}) {
136
+ return this.request(`/cli/tasks/${taskId}/complete`, {
137
+ method: 'POST',
138
+ body: data,
139
+ });
140
+ }
141
+
142
+ async failTask(taskId, data = {}) {
143
+ return this.request(`/cli/tasks/${taskId}/fail`, {
144
+ method: 'POST',
145
+ body: data,
146
+ });
147
+ }
148
+
149
+ // Activity logging
150
+ async logActivity(activity) {
151
+ return this.request('/cli/activity', {
152
+ method: 'POST',
153
+ body: activity,
154
+ });
155
+ }
156
+ }
157
+
158
+ module.exports = new GbosApiClient();
@@ -0,0 +1,156 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const CONFIG_DIR = path.join(os.homedir(), '.gbos');
6
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
+ const SESSION_FILE = path.join(CONFIG_DIR, 'session.json');
8
+
9
+ // Ensure config directory exists
10
+ function ensureConfigDir() {
11
+ if (!fs.existsSync(CONFIG_DIR)) {
12
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
13
+ }
14
+ }
15
+
16
+ // Get machine info for device auth
17
+ function getMachineInfo() {
18
+ return {
19
+ machine_id: getMachineId(),
20
+ machine_name: os.hostname(),
21
+ os_type: os.platform(),
22
+ os_version: os.release(),
23
+ };
24
+ }
25
+
26
+ // Generate a persistent machine ID
27
+ function getMachineId() {
28
+ ensureConfigDir();
29
+ const machineIdFile = path.join(CONFIG_DIR, '.machine_id');
30
+
31
+ if (fs.existsSync(machineIdFile)) {
32
+ return fs.readFileSync(machineIdFile, 'utf8').trim();
33
+ }
34
+
35
+ const machineId = `${os.hostname()}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
36
+ fs.writeFileSync(machineIdFile, machineId, { mode: 0o600 });
37
+ return machineId;
38
+ }
39
+
40
+ // Save session data
41
+ function saveSession(data) {
42
+ ensureConfigDir();
43
+ const session = {
44
+ ...data,
45
+ updated_at: new Date().toISOString(),
46
+ };
47
+ fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2), { mode: 0o600 });
48
+ return session;
49
+ }
50
+
51
+ // Load session data
52
+ function loadSession() {
53
+ try {
54
+ if (fs.existsSync(SESSION_FILE)) {
55
+ const data = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
56
+ return data;
57
+ }
58
+ } catch (err) {
59
+ // Ignore parse errors
60
+ }
61
+ return null;
62
+ }
63
+
64
+ // Clear session data
65
+ function clearSession() {
66
+ if (fs.existsSync(SESSION_FILE)) {
67
+ fs.unlinkSync(SESSION_FILE);
68
+ }
69
+ }
70
+
71
+ // Check if authenticated
72
+ function isAuthenticated() {
73
+ const session = loadSession();
74
+ if (!session || !session.access_token) {
75
+ return false;
76
+ }
77
+
78
+ // Check if token is expired
79
+ if (session.token_expires_at) {
80
+ const expiresAt = new Date(session.token_expires_at);
81
+ if (expiresAt < new Date()) {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ return true;
87
+ }
88
+
89
+ // Get access token
90
+ function getAccessToken() {
91
+ const session = loadSession();
92
+ return session?.access_token || null;
93
+ }
94
+
95
+ // Get current connection info
96
+ function getConnection() {
97
+ const session = loadSession();
98
+ return session?.connection || null;
99
+ }
100
+
101
+ // Save connection info
102
+ function saveConnection(connection) {
103
+ const session = loadSession() || {};
104
+ session.connection = connection;
105
+ saveSession(session);
106
+ }
107
+
108
+ // Clear connection info
109
+ function clearConnection() {
110
+ const session = loadSession();
111
+ if (session) {
112
+ delete session.connection;
113
+ saveSession(session);
114
+ }
115
+ }
116
+
117
+ // Export session as environment variables format
118
+ function getSessionEnv() {
119
+ const session = loadSession();
120
+ if (!session) return {};
121
+
122
+ return {
123
+ GBOS_ACCESS_TOKEN: session.access_token,
124
+ GBOS_ACCOUNT_ID: session.account_id,
125
+ GBOS_USER_ID: session.user_id,
126
+ GBOS_SESSION_ID: session.session_id,
127
+ GBOS_NODE_ID: session.connection?.node?.id,
128
+ GBOS_NODE_UUID: session.connection?.node?.uuid,
129
+ GBOS_CONNECTION_ID: session.connection?.connection_id,
130
+ GBOS_APPLICATION_ID: session.connection?.node?.application_id,
131
+ };
132
+ }
133
+
134
+ // Get config directory path (for other tools to access)
135
+ function getConfigDir() {
136
+ return CONFIG_DIR;
137
+ }
138
+
139
+ module.exports = {
140
+ CONFIG_DIR,
141
+ CONFIG_FILE,
142
+ SESSION_FILE,
143
+ ensureConfigDir,
144
+ getMachineInfo,
145
+ getMachineId,
146
+ saveSession,
147
+ loadSession,
148
+ clearSession,
149
+ isAuthenticated,
150
+ getAccessToken,
151
+ getConnection,
152
+ saveConnection,
153
+ clearConnection,
154
+ getSessionEnv,
155
+ getConfigDir,
156
+ };