@buildifyx/desktop-agent 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.
Files changed (50) hide show
  1. package/README.md +337 -0
  2. package/package.json +32 -0
  3. package/src/audit/logger.js +30 -0
  4. package/src/cli/commands/cloud.js +89 -0
  5. package/src/cli/commands/doctor.js +18 -0
  6. package/src/cli/commands/login.js +59 -0
  7. package/src/cli/commands/logout.js +20 -0
  8. package/src/cli/commands/remote.js +64 -0
  9. package/src/cli/commands/status.js +32 -0
  10. package/src/cli/commands/update.js +31 -0
  11. package/src/cli/help.js +52 -0
  12. package/src/cli/main.js +55 -0
  13. package/src/cli/options.js +34 -0
  14. package/src/cli.js +20 -0
  15. package/src/core/dispatcher.js +49 -0
  16. package/src/core/errors.js +37 -0
  17. package/src/core/permissions.js +53 -0
  18. package/src/core/runtime.js +45 -0
  19. package/src/events/bus.js +39 -0
  20. package/src/permissions/approvals.js +47 -0
  21. package/src/permissions/controller.js +92 -0
  22. package/src/permissions/evaluator.js +93 -0
  23. package/src/permissions/manager.js +57 -0
  24. package/src/permissions/policy.js +28 -0
  25. package/src/permissions/store.js +27 -0
  26. package/src/security/path.js +61 -0
  27. package/src/security/scope.js +45 -0
  28. package/src/server.js +1 -0
  29. package/src/services/commands.js +107 -0
  30. package/src/services/files.js +104 -0
  31. package/src/services/index.js +11 -0
  32. package/src/services/system.js +17 -0
  33. package/src/transport/cloud.js +206 -0
  34. package/src/transport/mcp/manifest-store.js +32 -0
  35. package/src/transport/mcp/response.js +25 -0
  36. package/src/transport/mcp/server.js +114 -0
  37. package/src/transport/mcp/tools/commands.js +33 -0
  38. package/src/transport/mcp/tools/files.js +55 -0
  39. package/src/transport/mcp/tools/index.js +1 -0
  40. package/src/transport/mcp/tools/registry.js +92 -0
  41. package/src/transport/mcp/tools/system.js +16 -0
  42. package/src/tui/app.js +392 -0
  43. package/src/tui/commands.js +71 -0
  44. package/src/tui/index.js +37 -0
  45. package/src/tui/layout.js +48 -0
  46. package/src/tui/model.js +62 -0
  47. package/src/tui/profiles.js +32 -0
  48. package/src/utils/credentials.js +35 -0
  49. package/src/utils/text.js +180 -0
  50. package/src/version.js +111 -0
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # Buildifyx Desktop Agent
2
+
3
+ Buildifyx Desktop Agent (`bdxa`) connects a user-controlled computer to Buildifyx Cloud so ChatGPT can use approved MCP tools on that machine.
4
+
5
+ Default cloud:
6
+
7
+ ```text
8
+ https://bdxa.buildifyx.com
9
+ ```
10
+
11
+ Agent connection:
12
+
13
+ ```text
14
+ wss://bdxa.buildifyx.com/agent
15
+ ```
16
+
17
+ Current package version:
18
+
19
+ ```text
20
+ @buildifyx/desktop-agent@0.1.0
21
+ ```
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install -g @buildifyx/desktop-agent
27
+ ```
28
+
29
+ Check the installation:
30
+
31
+ ```bash
32
+ bdxa --version
33
+ bdxa doctor
34
+ ```
35
+
36
+ ## 1. Login
37
+
38
+ Ask your Buildifyx administrator for a login token, then run:
39
+
40
+ ```bash
41
+ bdxa login
42
+ ```
43
+
44
+ Paste the token when prompted.
45
+
46
+ For internal automation you can also use:
47
+
48
+ ```bash
49
+ bdxa login --token "$BUILDFIYX_LOGIN_TOKEN"
50
+ ```
51
+
52
+ Using `--token` directly may expose the token through shell history, so interactive login is preferred.
53
+
54
+ A successful login registers this computer as a device and stores its device credential locally in:
55
+
56
+ ```text
57
+ ~/.buildifyx/credentials.json
58
+ ```
59
+
60
+ On macOS and Linux the agent attempts to keep this file readable only by the current user.
61
+
62
+ Login tokens are intended to be short-lived or one-time credentials. The device credential returned by Buildifyx Cloud is what `bdxa` uses for subsequent cloud connections.
63
+
64
+ ## 2. Check login status
65
+
66
+ ```bash
67
+ bdxa status
68
+ ```
69
+
70
+ The command shows the configured cloud, device ID, account when available, credential expiry, and whether Buildifyx Cloud currently accepts the device credential.
71
+
72
+ ## 3. Connect the device
73
+
74
+ Run `bdxa` from the directory you want ChatGPT to work in:
75
+
76
+ ```bash
77
+ cd ~/projects/my-project
78
+ bdxa
79
+ ```
80
+
81
+ Or choose the workspace explicitly:
82
+
83
+ ```bash
84
+ bdxa --root ~/projects/my-project
85
+ ```
86
+
87
+ `bdxa` now connects outward to Buildifyx Cloud. The computer does not need to expose a public port for normal cloud operation.
88
+
89
+ Connection path:
90
+
91
+ ```text
92
+ ChatGPT
93
+
94
+ Buildifyx Cloud
95
+
96
+ Secure WebSocket
97
+
98
+ bdxa
99
+
100
+ Local permissions
101
+
102
+ Your files and commands
103
+ ```
104
+
105
+ The local agent remains the final permission authority. A cloud request still has to pass the permission policy running on the user's computer before a tool can execute.
106
+
107
+ ## Cloud protocol
108
+
109
+ When the agent connects, it authenticates the WebSocket handshake using the device credential and sends a `device.ready` message containing the agent version, device information, and MCP tool manifest hash.
110
+
111
+ The cloud can send a tool request such as:
112
+
113
+ ```json
114
+ {
115
+ "type": "tool.call",
116
+ "requestId": "req_123",
117
+ "tool": "read_file",
118
+ "arguments": {
119
+ "path": "README.md"
120
+ }
121
+ }
122
+ ```
123
+
124
+ The agent routes it through the same local runtime and permission system used by local MCP mode.
125
+
126
+ Successful requests return:
127
+
128
+ ```json
129
+ {
130
+ "type": "tool.result",
131
+ "requestId": "req_123",
132
+ "result": {}
133
+ }
134
+ ```
135
+
136
+ Failed requests return:
137
+
138
+ ```json
139
+ {
140
+ "type": "tool.error",
141
+ "requestId": "req_123",
142
+ "error": {
143
+ "code": "PERMISSION_DENIED",
144
+ "message": "Permission denied"
145
+ }
146
+ }
147
+ ```
148
+
149
+ The agent also sends heartbeats and reconnects automatically with backoff when the cloud connection drops.
150
+
151
+ ## Permissions
152
+
153
+ The default Auto profile allows normal reads, writes, and approved developer commands while asking before dangerous operations or access outside allowed roots.
154
+
155
+ Available profiles are:
156
+
157
+ ```text
158
+ Auto
159
+ Read only
160
+ Full access
161
+ Custom
162
+ ```
163
+
164
+ Full access is powerful and is not an operating-system sandbox. Commands run with the permissions of the operating-system user running `bdxa`.
165
+
166
+ ### ASK requests
167
+
168
+ When a rule resolves to `ASK`, the cloud request waits while the TUI displays an approval prompt:
169
+
170
+ ```text
171
+ APPROVAL REQUIRED
172
+
173
+ docker ps
174
+
175
+ > Allow once
176
+ Always allow this request
177
+ Deny
178
+ ```
179
+
180
+ The tool executes only after local approval.
181
+
182
+ If the agent is running with `--no-tui`, an ASK request returns a confirmation-required error instead of waiting indefinitely.
183
+
184
+ ### Custom command rules
185
+
186
+ Command rules match executable name plus argument prefix.
187
+
188
+ For example:
189
+
190
+ ```text
191
+ docker ALLOW
192
+ docker ps ASK
193
+ ```
194
+
195
+ The more specific rule wins, so `docker ps` asks even though the broader `docker` rule allows other Docker commands.
196
+
197
+ Commands are executed without a shell.
198
+
199
+ ## Allowed roots
200
+
201
+ The primary root is the directory supplied through `--root`, or the current working directory when omitted.
202
+
203
+ Additional roots can be managed from the TUI.
204
+
205
+ Requests outside configured roots follow the `outsideRoot` permission setting and may be allowed, denied, or require approval.
206
+
207
+ `.git` internals remain protected by the local path security layer.
208
+
209
+ ## MCP tools
210
+
211
+ The current agent exposes:
212
+
213
+ ```text
214
+ get_system_info
215
+ list_directory
216
+ read_file
217
+ write_file
218
+ edit_file
219
+ run_command
220
+ ```
221
+
222
+ The agent sends its tool count and schema hash to Buildifyx Cloud when connecting so the cloud can detect which tool definition set the device is running.
223
+
224
+ ## TUI controls
225
+
226
+ Main screen:
227
+
228
+ ```text
229
+ ↑↓ Select activity
230
+ P Permissions
231
+ R Allowed roots
232
+ C Command rules
233
+ T MCP tools
234
+ L Audit log information
235
+ ? / H Help
236
+ Q Back
237
+ Ctrl+C Quit
238
+ ```
239
+
240
+ Approval screen:
241
+
242
+ ```text
243
+ ↑↓ Select decision
244
+ Enter Confirm
245
+ Q Deny / Back
246
+ Ctrl+C Quit
247
+ ```
248
+
249
+ ## Audit log
250
+
251
+ Local activity is recorded at:
252
+
253
+ ```text
254
+ ~/.buildifyx/audit.log
255
+ ```
256
+
257
+ The audit log records tool names, permission decisions, paths, commands, status, and timing information. Write/edit payload contents are redacted rather than intentionally duplicated into the audit log.
258
+
259
+ ## Logout
260
+
261
+ ```bash
262
+ bdxa logout
263
+ ```
264
+
265
+ The agent asks Buildifyx Cloud to revoke the device credential and then removes the local credential file.
266
+
267
+ If the cloud cannot be reached, `bdxa` still removes the local credential and reports that remote revocation failed.
268
+
269
+ ## Update
270
+
271
+ ```bash
272
+ bdxa update
273
+ ```
274
+
275
+ Then restart the agent:
276
+
277
+ ```bash
278
+ bdxa
279
+ ```
280
+
281
+ ## Useful commands
282
+
283
+ ```bash
284
+ bdxa login
285
+ bdxa status
286
+ bdxa --root ~/projects
287
+ bdxa connect --root ~/projects
288
+ bdxa logout
289
+ bdxa doctor
290
+ bdxa update
291
+ bdxa --version
292
+ ```
293
+
294
+ ## Local MCP compatibility mode
295
+
296
+ For local development or debugging, the previous localhost MCP server remains available explicitly:
297
+
298
+ ```bash
299
+ bdxa local --root ~/projects --port 3333
300
+ ```
301
+
302
+ Normal users should use cloud mode by running `bdxa` without the `local` command.
303
+
304
+ ## Troubleshooting
305
+
306
+ ### `Not logged in`
307
+
308
+ Run:
309
+
310
+ ```bash
311
+ bdxa login
312
+ ```
313
+
314
+ ### `Device credential has expired`
315
+
316
+ Request a new login token from your administrator and run `bdxa login` again.
317
+
318
+ ### ASK does not execute immediately
319
+
320
+ This is expected. The request is waiting for a local approval in the TUI.
321
+
322
+ ### Cloud is temporarily unavailable
323
+
324
+ Leave `bdxa` running. The cloud client automatically retries with exponential backoff.
325
+
326
+ ### Need to inspect the current tools
327
+
328
+ Press `T` in the TUI to see the current tool count and schema hash.
329
+
330
+ ## Security notes
331
+
332
+ - Buildifyx Cloud does not replace the local permission system.
333
+ - Device authentication uses a dedicated device credential, separate from the login token.
334
+ - Command execution uses direct executable invocation rather than shell command strings.
335
+ - File access is constrained by configured roots and path checks.
336
+ - Full access is not an OS sandbox.
337
+ - Keep `~/.buildifyx/credentials.json` private.
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@buildifyx/desktop-agent",
3
+ "version": "0.1.0",
4
+ "description": "Buildifyx cloud-connected desktop agent for securely running MCP tools on a user-controlled machine.",
5
+ "type": "module",
6
+ "bin": {
7
+ "bdxa": "./src/cli.js",
8
+ "buildifyx-agent": "./src/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node ./src/cli.js",
12
+ "doctor": "node ./src/cli.js doctor",
13
+ "check": "node ./scripts/check.js",
14
+ "test": "node --test"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "files": [
20
+ "src",
21
+ "README.md"
22
+ ],
23
+ "dependencies": {
24
+ "@modelcontextprotocol/node": "2.0.0",
25
+ "@modelcontextprotocol/server": "2.0.0",
26
+ "ink": "^6.8.0",
27
+ "react": "^19.2.8",
28
+ "ws": "^8.21.3",
29
+ "zod": "4.5.4"
30
+ },
31
+ "license": "MIT"
32
+ }
@@ -0,0 +1,30 @@
1
+ import { mkdir, appendFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ export function defaultAuditPath() {
6
+ return path.join(os.homedir(), '.buildifyx', 'audit.log');
7
+ }
8
+
9
+ export function createAuditLogger({ eventBus, filePath = defaultAuditPath() }) {
10
+ let unsubscribe;
11
+
12
+ async function writeEvent(event) {
13
+ await mkdir(path.dirname(filePath), { recursive: true });
14
+ await appendFile(filePath, `${JSON.stringify(event)}\n`, 'utf8');
15
+ }
16
+
17
+ function start() {
18
+ if (unsubscribe) return;
19
+ unsubscribe = eventBus.subscribe((event) => {
20
+ void writeEvent(event).catch(() => undefined);
21
+ });
22
+ }
23
+
24
+ function stop() {
25
+ unsubscribe?.();
26
+ unsubscribe = undefined;
27
+ }
28
+
29
+ return { start, stop, filePath };
30
+ }
@@ -0,0 +1,89 @@
1
+ import os from 'node:os';
2
+ import process from 'node:process';
3
+ import { createAuditLogger } from '../../audit/logger.js';
4
+ import { createRuntime } from '../../core/runtime.js';
5
+ import { createPolicyManager } from '../../permissions/manager.js';
6
+ import { loadPolicy } from '../../permissions/store.js';
7
+ import { startTui } from '../../tui/index.js';
8
+ import { createToolManifest } from '../../transport/mcp/tools/registry.js';
9
+ import { inspectToolManifest } from '../../transport/mcp/manifest-store.js';
10
+ import { createCloudAgent } from '../../transport/cloud.js';
11
+ import { isCredentialExpired, loadCredentials } from '../../utils/credentials.js';
12
+ import { getPackageMetadata } from '../../version.js';
13
+ import { getOption, hasFlag, resolveRoot } from '../options.js';
14
+
15
+ export async function runCloud(args) {
16
+ const credentials = await loadCredentials();
17
+ if (!credentials) throw new Error('Not logged in. Run `bdxa login` first.');
18
+ if (isCredentialExpired(credentials)) throw new Error('Device credential has expired. Run `bdxa login` again.');
19
+
20
+ const root = await resolveRoot(getOption(args, '--root', process.cwd()));
21
+ const fullAccess = hasFlag(args, '--full-access');
22
+ const useTui = process.stdin.isTTY && process.stdout.isTTY && !hasFlag(args, '--no-tui');
23
+ const metadata = await getPackageMetadata();
24
+ const policy = await loadPolicy();
25
+ const policyManager = createPolicyManager(policy);
26
+ const runtime = createRuntime({ root, fullAccess, policyManager, interactive: useTui });
27
+ const audit = createAuditLogger({ eventBus: runtime.eventBus });
28
+ const manifest = createToolManifest({ fullAccess });
29
+ const manifestState = await inspectToolManifest(manifest);
30
+ audit.start();
31
+
32
+ const cloud = createCloudAgent({
33
+ cloudUrl: credentials.cloudUrl,
34
+ credentials,
35
+ runtime,
36
+ manifest,
37
+ version: metadata.version,
38
+ deviceInfo: {
39
+ name: credentials.deviceName ?? os.hostname(),
40
+ hostname: os.hostname(),
41
+ platform: process.platform,
42
+ arch: process.arch
43
+ },
44
+ eventBus: runtime.eventBus
45
+ });
46
+
47
+ let closing = false;
48
+ const shutdown = async () => {
49
+ if (closing) return;
50
+ closing = true;
51
+ audit.stop();
52
+ await cloud.stop();
53
+ };
54
+ const onSignal = async () => {
55
+ await shutdown();
56
+ process.exit(0);
57
+ };
58
+ process.once('SIGINT', onSignal);
59
+ process.once('SIGTERM', onSignal);
60
+
61
+ cloud.connect();
62
+
63
+ if (!useTui) {
64
+ console.log(`Cloud: ${cloud.endpoint}`);
65
+ console.log(`Device: ${credentials.deviceName ?? credentials.deviceId}`);
66
+ console.log(`Root: ${root}`);
67
+ console.log(`Audit: ${audit.filePath}`);
68
+ console.log(`Tools: ${manifest.count} (${manifest.shortHash})${manifestState.changed ? ' CHANGED' : ''}`);
69
+ console.log('TUI: disabled (Ask permissions return CONFIRMATION_REQUIRED)');
70
+ return;
71
+ }
72
+
73
+ const tui = startTui({
74
+ eventBus: runtime.eventBus,
75
+ approvalQueue: runtime.approvalQueue,
76
+ policyManager,
77
+ version: metadata.version,
78
+ root,
79
+ mode: fullAccess ? 'CLOUD · FULL ACCESS (not sandboxed)' : 'CLOUD · restricted',
80
+ auditPath: audit.filePath,
81
+ toolManifest: manifest,
82
+ toolManifestState: manifestState,
83
+ toolsUrl: credentials.cloudUrl,
84
+ onQuit: shutdown
85
+ });
86
+
87
+ await tui.waitUntilExit();
88
+ await shutdown();
89
+ }
@@ -0,0 +1,18 @@
1
+ import process from 'node:process';
2
+ import { getOption, resolveRoot } from '../options.js';
3
+ import { checkForUpdate, getPackageMetadata } from '../../version.js';
4
+
5
+ export async function runDoctor(args) {
6
+ const root = await resolveRoot(getOption(args, '--root', process.cwd()));
7
+ const metadata = await getPackageMetadata();
8
+ const versionStatus = await checkForUpdate(metadata.name, metadata.version);
9
+
10
+ console.log('Buildifyx Desktop Agent doctor');
11
+ console.log(`Node: ${process.version}`);
12
+ console.log(`Platform: ${process.platform} ${process.arch}`);
13
+ console.log(`Root: ${root}`);
14
+ console.log(`Version: ${metadata.version}${versionStatus.updateAvailable && versionStatus.latestVersion ? ` (latest: ${versionStatus.latestVersion})` : ''}`);
15
+ if (versionStatus.error) console.log(`Registry: unavailable (${versionStatus.error})`);
16
+ else if (!versionStatus.updateAvailable) console.log('Registry: latest');
17
+ console.log('Status: OK');
18
+ }
@@ -0,0 +1,59 @@
1
+ import os from 'node:os';
2
+ import process from 'node:process';
3
+ import readline from 'node:readline/promises';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+ import { loginDevice, DEFAULT_CLOUD_ORIGIN } from '../../transport/cloud.js';
6
+ import { saveCredentials } from '../../utils/credentials.js';
7
+ import { getPackageMetadata } from '../../version.js';
8
+ import { getOption } from '../options.js';
9
+
10
+ async function promptToken() {
11
+ if (!process.stdin.isTTY) throw new Error('Login token is required. Use --token or run bdxa login in a terminal.');
12
+ const rl = readline.createInterface({ input, output });
13
+ try {
14
+ return (await rl.question('Login token: ')).trim();
15
+ } finally {
16
+ rl.close();
17
+ }
18
+ }
19
+
20
+ export async function runLogin(args) {
21
+ const cloudUrl = getOption(args, '--cloud', DEFAULT_CLOUD_ORIGIN);
22
+ const token = getOption(args, '--token', null) ?? await promptToken();
23
+ if (!token) throw new Error('Login token cannot be empty.');
24
+
25
+ const metadata = await getPackageMetadata();
26
+ console.log(`Connecting to ${cloudUrl}...`);
27
+ const response = await loginDevice({
28
+ cloudUrl,
29
+ token,
30
+ device: {
31
+ name: os.hostname(),
32
+ hostname: os.hostname(),
33
+ platform: process.platform,
34
+ arch: process.arch,
35
+ agentVersion: metadata.version
36
+ }
37
+ });
38
+
39
+ const deviceToken = response?.credential?.token ?? response?.deviceToken;
40
+ const deviceId = response?.device?.id ?? response?.deviceId;
41
+ const expiresAt = response?.credential?.expiresAt ?? response?.expiresAt ?? null;
42
+ if (!deviceToken || !deviceId) throw new Error('Cloud login response did not include a device credential.');
43
+
44
+ await saveCredentials({
45
+ cloudUrl,
46
+ deviceId,
47
+ deviceName: response?.device?.name ?? os.hostname(),
48
+ account: response?.user?.email ?? response?.account ?? null,
49
+ deviceToken,
50
+ expiresAt,
51
+ createdAt: new Date().toISOString()
52
+ });
53
+
54
+ console.log('✓ Authentication successful');
55
+ console.log(`Device: ${response?.device?.name ?? os.hostname()}`);
56
+ if (response?.user?.email) console.log(`Account: ${response.user.email}`);
57
+ if (expiresAt) console.log(`Expires: ${expiresAt}`);
58
+ console.log('Run `bdxa` to connect this device to Buildifyx Cloud.');
59
+ }
@@ -0,0 +1,20 @@
1
+ import { logoutDevice } from '../../transport/cloud.js';
2
+ import { clearCredentials, loadCredentials } from '../../utils/credentials.js';
3
+
4
+ export async function runLogout() {
5
+ const credentials = await loadCredentials();
6
+ if (!credentials) {
7
+ console.log('Not logged in.');
8
+ return;
9
+ }
10
+
11
+ try {
12
+ await logoutDevice({ cloudUrl: credentials.cloudUrl, deviceToken: credentials.deviceToken });
13
+ } catch (error) {
14
+ console.log(`Cloud revoke failed: ${error.message}`);
15
+ console.log('Removing local credentials anyway.');
16
+ }
17
+
18
+ await clearCredentials();
19
+ console.log('✓ Logged out');
20
+ }
@@ -0,0 +1,64 @@
1
+ import process from 'node:process';
2
+ import { createAuditLogger } from '../../audit/logger.js';
3
+ import { createRuntime } from '../../core/runtime.js';
4
+ import { createPolicyManager } from '../../permissions/manager.js';
5
+ import { loadPolicy } from '../../permissions/store.js';
6
+ import { startTui } from '../../tui/index.js';
7
+ import { startMcpServer } from '../../transport/mcp/server.js';
8
+ import { getOption, hasFlag, parsePort, resolveRoot } from '../options.js';
9
+
10
+ export async function runRemote(args) {
11
+ const root = await resolveRoot(getOption(args, '--root', process.cwd()));
12
+ const port = parsePort(getOption(args, '--port', '3333'));
13
+ const fullAccess = hasFlag(args, '--full-access');
14
+ const useTui = process.stdin.isTTY && process.stdout.isTTY && !hasFlag(args, '--no-tui');
15
+
16
+ const policy = await loadPolicy();
17
+ const policyManager = createPolicyManager(policy);
18
+ const runtime = createRuntime({ root, fullAccess, policyManager, interactive: useTui });
19
+ const audit = createAuditLogger({ eventBus: runtime.eventBus });
20
+ audit.start();
21
+
22
+ const server = await startMcpServer({ root, port, fullAccess, runtime, quiet: useTui });
23
+ let closing = false;
24
+
25
+ const shutdown = async () => {
26
+ if (closing) return;
27
+ closing = true;
28
+ audit.stop();
29
+ await server.close();
30
+ };
31
+
32
+ const onSignal = async () => {
33
+ await shutdown();
34
+ process.exit(0);
35
+ };
36
+
37
+ process.once('SIGINT', onSignal);
38
+ process.once('SIGTERM', onSignal);
39
+
40
+ if (!useTui) {
41
+ console.log(`Audit: ${audit.filePath}`);
42
+ console.log(`Tools: ${server.manifest.count} (${server.manifest.shortHash})${server.manifestState.changed ? ' CHANGED' : ''}`);
43
+ console.log(`Tools: ${server.toolsUrl}`);
44
+ console.log('TUI: disabled (Ask permissions return CONFIRMATION_REQUIRED)');
45
+ return;
46
+ }
47
+
48
+ const tui = startTui({
49
+ eventBus: runtime.eventBus,
50
+ approvalQueue: runtime.approvalQueue,
51
+ policyManager,
52
+ version: server.version,
53
+ root,
54
+ mode: fullAccess ? 'FULL ACCESS (not sandboxed)' : 'restricted',
55
+ auditPath: audit.filePath,
56
+ toolManifest: server.manifest,
57
+ toolManifestState: server.manifestState,
58
+ toolsUrl: server.toolsUrl,
59
+ onQuit: shutdown
60
+ });
61
+
62
+ await tui.waitUntilExit();
63
+ await shutdown();
64
+ }
@@ -0,0 +1,32 @@
1
+ import { getDeviceMe } from '../../transport/cloud.js';
2
+ import { isCredentialExpired, loadCredentials } from '../../utils/credentials.js';
3
+
4
+ export async function runStatus() {
5
+ const credentials = await loadCredentials();
6
+ console.log('Buildifyx Desktop Agent status');
7
+
8
+ if (!credentials) {
9
+ console.log('Authentication: not logged in');
10
+ console.log('Run `bdxa login`.');
11
+ return;
12
+ }
13
+
14
+ console.log(`Cloud: ${credentials.cloudUrl}`);
15
+ console.log(`Device: ${credentials.deviceName ?? credentials.deviceId}`);
16
+ console.log(`Device ID: ${credentials.deviceId}`);
17
+ if (credentials.account) console.log(`Account: ${credentials.account}`);
18
+ if (credentials.expiresAt) console.log(`Expires: ${credentials.expiresAt}`);
19
+
20
+ if (isCredentialExpired(credentials)) {
21
+ console.log('Authentication: expired');
22
+ return;
23
+ }
24
+
25
+ try {
26
+ const remote = await getDeviceMe({ cloudUrl: credentials.cloudUrl, deviceToken: credentials.deviceToken });
27
+ console.log('Authentication: valid');
28
+ if (remote?.device?.status) console.log(`Cloud status: ${remote.device.status}`);
29
+ } catch (error) {
30
+ console.log(`Authentication: unavailable (${error.message})`);
31
+ }
32
+ }