@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.
- package/README.md +337 -0
- package/package.json +32 -0
- package/src/audit/logger.js +30 -0
- package/src/cli/commands/cloud.js +89 -0
- package/src/cli/commands/doctor.js +18 -0
- package/src/cli/commands/login.js +59 -0
- package/src/cli/commands/logout.js +20 -0
- package/src/cli/commands/remote.js +64 -0
- package/src/cli/commands/status.js +32 -0
- package/src/cli/commands/update.js +31 -0
- package/src/cli/help.js +52 -0
- package/src/cli/main.js +55 -0
- package/src/cli/options.js +34 -0
- package/src/cli.js +20 -0
- package/src/core/dispatcher.js +49 -0
- package/src/core/errors.js +37 -0
- package/src/core/permissions.js +53 -0
- package/src/core/runtime.js +45 -0
- package/src/events/bus.js +39 -0
- package/src/permissions/approvals.js +47 -0
- package/src/permissions/controller.js +92 -0
- package/src/permissions/evaluator.js +93 -0
- package/src/permissions/manager.js +57 -0
- package/src/permissions/policy.js +28 -0
- package/src/permissions/store.js +27 -0
- package/src/security/path.js +61 -0
- package/src/security/scope.js +45 -0
- package/src/server.js +1 -0
- package/src/services/commands.js +107 -0
- package/src/services/files.js +104 -0
- package/src/services/index.js +11 -0
- package/src/services/system.js +17 -0
- package/src/transport/cloud.js +206 -0
- package/src/transport/mcp/manifest-store.js +32 -0
- package/src/transport/mcp/response.js +25 -0
- package/src/transport/mcp/server.js +114 -0
- package/src/transport/mcp/tools/commands.js +33 -0
- package/src/transport/mcp/tools/files.js +55 -0
- package/src/transport/mcp/tools/index.js +1 -0
- package/src/transport/mcp/tools/registry.js +92 -0
- package/src/transport/mcp/tools/system.js +16 -0
- package/src/tui/app.js +392 -0
- package/src/tui/commands.js +71 -0
- package/src/tui/index.js +37 -0
- package/src/tui/layout.js +48 -0
- package/src/tui/model.js +62 -0
- package/src/tui/profiles.js +32 -0
- package/src/utils/credentials.js +35 -0
- package/src/utils/text.js +180 -0
- 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
|
+
}
|