@vectorplane/ctrl-cli 0.1.12 → 0.1.14
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 +58 -95
- package/dist/commands/context.js +14 -5
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +13 -20
- package/dist/commands/login.js +24 -3
- package/dist/commands/logout.js +3 -0
- package/dist/commands/status.js +8 -2
- package/dist/commands/task.js +44 -2
- package/dist/commands/workspace.js +65 -0
- package/dist/core/api.d.ts +23 -1
- package/dist/core/api.js +112 -0
- package/dist/core/auth.d.ts +2 -2
- package/dist/core/auth.js +89 -46
- package/dist/core/constants.js +21 -1
- package/dist/core/machine.d.ts +8 -1
- package/dist/core/machine.js +49 -18
- package/dist/index.js +5 -5
- package/dist/types/api.d.ts +46 -0
- package/dist/types/auth.d.ts +1 -0
- package/dist/types/config.d.ts +8 -0
- package/dist/types/machine.d.ts +3 -3
- package/package.json +11 -4
package/dist/core/api.js
CHANGED
|
@@ -144,6 +144,21 @@ export class VectorPlaneApiClient {
|
|
|
144
144
|
},
|
|
145
145
|
}, this.timeoutMs, this.logger);
|
|
146
146
|
}
|
|
147
|
+
async getWorkspaceSnapshotDiff(accessToken, workspaceId, params) {
|
|
148
|
+
const url = new URL(`${this.apiBaseUrl}/workspace/${workspaceId}/snapshot/diff`);
|
|
149
|
+
if (params.from) {
|
|
150
|
+
url.searchParams.set("from", params.from);
|
|
151
|
+
}
|
|
152
|
+
if (params.to) {
|
|
153
|
+
url.searchParams.set("to", params.to);
|
|
154
|
+
}
|
|
155
|
+
return requestJson(url.toString(), {
|
|
156
|
+
method: "GET",
|
|
157
|
+
headers: {
|
|
158
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
159
|
+
},
|
|
160
|
+
}, this.timeoutMs, this.logger);
|
|
161
|
+
}
|
|
147
162
|
async getWorkspaceDeliveryContext(accessToken, workspaceId) {
|
|
148
163
|
return requestJson(`${this.apiBaseUrl}/cli/workspace/${workspaceId}/delivery-context`, {
|
|
149
164
|
method: "GET",
|
|
@@ -202,6 +217,29 @@ export class VectorPlaneApiClient {
|
|
|
202
217
|
body: JSON.stringify(payload),
|
|
203
218
|
}, this.timeoutMs, this.logger);
|
|
204
219
|
}
|
|
220
|
+
async listWorkspaceWebhooks(accessToken, workspaceRef) {
|
|
221
|
+
return requestJson(`${this.apiBaseUrl}/workspace/${workspaceRef}/webhooks`, {
|
|
222
|
+
method: "GET",
|
|
223
|
+
headers: {
|
|
224
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
225
|
+
},
|
|
226
|
+
}, this.timeoutMs, this.logger);
|
|
227
|
+
}
|
|
228
|
+
async createWorkspaceWebhook(accessToken, workspaceRef, payload) {
|
|
229
|
+
return requestJson(`${this.apiBaseUrl}/workspace/${workspaceRef}/webhooks`, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: authHeaders(accessToken),
|
|
232
|
+
body: JSON.stringify(payload),
|
|
233
|
+
}, this.timeoutMs, this.logger);
|
|
234
|
+
}
|
|
235
|
+
async deleteWorkspaceWebhook(accessToken, workspaceRef, webhookId) {
|
|
236
|
+
return requestJson(`${this.apiBaseUrl}/workspace/${workspaceRef}/webhooks/${webhookId}`, {
|
|
237
|
+
method: "DELETE",
|
|
238
|
+
headers: {
|
|
239
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
240
|
+
},
|
|
241
|
+
}, this.timeoutMs, this.logger);
|
|
242
|
+
}
|
|
205
243
|
async listMemoryDrafts(accessToken, workspaceRef, status) {
|
|
206
244
|
const url = new URL(`${this.apiBaseUrl}/cli/workspace/${workspaceRef}/memory-drafts`);
|
|
207
245
|
if (status) {
|
|
@@ -421,5 +459,79 @@ export class VectorPlaneApiClient {
|
|
|
421
459
|
method: "GET",
|
|
422
460
|
}, this.timeoutMs, this.logger);
|
|
423
461
|
}
|
|
462
|
+
async streamWorkspaceEvents(accessToken, workspaceRef, params) {
|
|
463
|
+
const url = new URL(`${this.apiBaseUrl}/workspace/${workspaceRef}/events/stream`);
|
|
464
|
+
if (params.taskId) {
|
|
465
|
+
url.searchParams.set("task_id", params.taskId);
|
|
466
|
+
}
|
|
467
|
+
if (params.types && params.types.length > 0) {
|
|
468
|
+
url.searchParams.set("types", params.types.join(","));
|
|
469
|
+
}
|
|
470
|
+
this.logger.debug("http_stream_open", { url: url.toString() });
|
|
471
|
+
const response = await fetch(url.toString(), {
|
|
472
|
+
method: "GET",
|
|
473
|
+
headers: {
|
|
474
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
475
|
+
"Accept": "text/event-stream",
|
|
476
|
+
},
|
|
477
|
+
signal: params.signal,
|
|
478
|
+
});
|
|
479
|
+
if (!response.ok || !response.body) {
|
|
480
|
+
const body = await parseBody(response);
|
|
481
|
+
const message = typeof body === "string" ? body : `HTTP ${response.status}`;
|
|
482
|
+
throw new ApiError(message, response.status, body);
|
|
483
|
+
}
|
|
484
|
+
const reader = response.body.getReader();
|
|
485
|
+
const decoder = new TextDecoder();
|
|
486
|
+
let buffer = "";
|
|
487
|
+
const emitFrame = (frame) => {
|
|
488
|
+
const lines = frame.split("\n");
|
|
489
|
+
let eventName = "message";
|
|
490
|
+
const dataLines = [];
|
|
491
|
+
for (const line of lines) {
|
|
492
|
+
if (line.startsWith("event:")) {
|
|
493
|
+
eventName = line.slice(6).trim();
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (line.startsWith("data:")) {
|
|
497
|
+
dataLines.push(line.slice(5).trim());
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (dataLines.length === 0) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
try {
|
|
504
|
+
const payload = JSON.parse(dataLines.join("\n"));
|
|
505
|
+
params.onEvent({
|
|
506
|
+
type: eventName,
|
|
507
|
+
workspaceId: payload.workspaceId,
|
|
508
|
+
taskId: payload.taskId ?? null,
|
|
509
|
+
stepId: payload.stepId ?? null,
|
|
510
|
+
timestamp: payload.timestamp,
|
|
511
|
+
summary: payload.summary ?? null,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
this.logger.debug("http_stream_frame_ignored", {
|
|
516
|
+
reason: error instanceof Error ? error.message : "invalid_json",
|
|
517
|
+
eventName,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
while (true) {
|
|
522
|
+
const { done, value } = await reader.read();
|
|
523
|
+
if (done) {
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
buffer += decoder.decode(value, { stream: true });
|
|
527
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
528
|
+
while (separatorIndex >= 0) {
|
|
529
|
+
const frame = buffer.slice(0, separatorIndex);
|
|
530
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
531
|
+
emitFrame(frame);
|
|
532
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
424
536
|
}
|
|
425
537
|
//# sourceMappingURL=api.js.map
|
package/dist/core/auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CliConfig, CliProfileConfig } from "../types/config.js";
|
|
1
|
+
import type { CliConfig, CliLoginMode, CliProfileConfig } from "../types/config.js";
|
|
2
2
|
import type { AuthSession } from "../types/auth.js";
|
|
3
3
|
import type { DeviceIdentity, MachineContext, RuntimeContext } from "../types/machine.js";
|
|
4
4
|
import { VectorPlaneApiClient } from "./api.js";
|
|
@@ -13,5 +13,5 @@ export declare function runLoginFlow(params: {
|
|
|
13
13
|
device: DeviceIdentity;
|
|
14
14
|
apiClient: VectorPlaneApiClient;
|
|
15
15
|
logger: Logger;
|
|
16
|
-
|
|
16
|
+
loginMode: CliLoginMode;
|
|
17
17
|
}): Promise<AuthSession>;
|
package/dist/core/auth.js
CHANGED
|
@@ -25,48 +25,118 @@ export async function openBrowser(url) {
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
function printLoginInstructions(loginMode, loginUrl, logger) {
|
|
29
|
+
if (loginMode === "browser") {
|
|
30
|
+
logger.info("abrindo navegador para autenticação...");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (loginMode === "manual") {
|
|
34
|
+
logger.info("login manual habilitado. abra a URL abaixo no navegador:");
|
|
35
|
+
process.stdout.write(`${loginUrl}\n`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
logger.info("login device-like habilitado. use a URL abaixo em qualquer navegador confiável:");
|
|
39
|
+
process.stdout.write(`${loginUrl}\n`);
|
|
40
|
+
}
|
|
41
|
+
async function waitForCallbackCode(callbackServer, timeoutMs) {
|
|
42
|
+
try {
|
|
43
|
+
const callback = await callbackServer.waitForCallback(timeoutMs);
|
|
44
|
+
return callback.code;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw new AuthError("Falha no callback local do login do CLI.", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function waitForAuthorizedAttempt(params) {
|
|
51
|
+
const startedAt = Date.now();
|
|
52
|
+
let announcedAuthorization = false;
|
|
53
|
+
while (Date.now() - startedAt < params.timeoutMs) {
|
|
54
|
+
let status;
|
|
55
|
+
try {
|
|
56
|
+
status = await params.apiClient.getLoginAttemptStatus(params.attemptId, params.pollToken);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new AuthError("Falha ao acompanhar a autorização do login do CLI por polling.", error);
|
|
60
|
+
}
|
|
61
|
+
const code = pickAuthorizedCode(status);
|
|
62
|
+
if (code) {
|
|
63
|
+
return code;
|
|
64
|
+
}
|
|
65
|
+
if (status.status === "authorized" && !announcedAuthorization) {
|
|
66
|
+
announcedAuthorization = true;
|
|
67
|
+
params.logger.info("autorização recebida pela API. finalizando sessão...");
|
|
68
|
+
}
|
|
69
|
+
await delay(LOGIN_ATTEMPT_POLL_INTERVAL_MS);
|
|
70
|
+
}
|
|
71
|
+
throw new AuthError("Tempo esgotado aguardando a confirmação do login do CLI.");
|
|
72
|
+
}
|
|
73
|
+
function pickAuthorizedCode(status) {
|
|
74
|
+
if ((status.status === "authorized" || status.status === "completed") && typeof status.code === "string" && status.code.length > 0) {
|
|
75
|
+
return status.code;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function delay(ms) {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
setTimeout(resolve, ms);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
28
84
|
export async function runLoginFlow(params) {
|
|
29
85
|
const state = generateLoginState();
|
|
30
|
-
const
|
|
86
|
+
const useLoopback = params.loginMode !== "device";
|
|
87
|
+
const callbackServer = useLoopback
|
|
88
|
+
? await createCallbackServer(params.config.callbackHost, params.config.callbackPort, params.config.callbackPath, state)
|
|
89
|
+
: null;
|
|
31
90
|
try {
|
|
91
|
+
const redirectUri = callbackServer?.callbackUrl ?? `http://127.0.0.1/disabled-device-flow/${state}`;
|
|
32
92
|
const loginAttempt = await params.apiClient.createLoginAttempt({
|
|
33
|
-
redirectUri
|
|
93
|
+
redirectUri,
|
|
34
94
|
state,
|
|
35
95
|
client: CLI_CLIENT_ID,
|
|
36
96
|
});
|
|
37
97
|
const loginUrl = loginAttempt.loginUrl;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
process.stdout.write(`${loginUrl}\n`);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
params.logger.info("abrindo navegador para autenticação...");
|
|
98
|
+
printLoginInstructions(params.loginMode, loginUrl, params.logger);
|
|
99
|
+
if (params.loginMode === "browser") {
|
|
44
100
|
const opened = await openBrowser(loginUrl);
|
|
45
101
|
if (!opened) {
|
|
46
|
-
params.logger.warn("não foi possível abrir o navegador automaticamente.");
|
|
102
|
+
params.logger.warn("não foi possível abrir o navegador automaticamente. continue pela URL abaixo.");
|
|
47
103
|
process.stdout.write(`${loginUrl}\n`);
|
|
48
104
|
}
|
|
49
105
|
}
|
|
50
106
|
params.logger.info("aguardando confirmação...");
|
|
51
|
-
const code =
|
|
52
|
-
|
|
53
|
-
waitForAuthorizedAttempt({
|
|
107
|
+
const code = params.loginMode === "device"
|
|
108
|
+
? await waitForAuthorizedAttempt({
|
|
54
109
|
apiClient: params.apiClient,
|
|
55
110
|
attemptId: loginAttempt.attemptId,
|
|
56
111
|
pollToken: loginAttempt.pollToken,
|
|
57
112
|
timeoutMs: CALLBACK_TIMEOUT_MS,
|
|
58
113
|
logger: params.logger,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
114
|
+
})
|
|
115
|
+
: await Promise.any([
|
|
116
|
+
waitForCallbackCode(callbackServer, CALLBACK_TIMEOUT_MS),
|
|
117
|
+
waitForAuthorizedAttempt({
|
|
118
|
+
apiClient: params.apiClient,
|
|
119
|
+
attemptId: loginAttempt.attemptId,
|
|
120
|
+
pollToken: loginAttempt.pollToken,
|
|
121
|
+
timeoutMs: CALLBACK_TIMEOUT_MS,
|
|
122
|
+
logger: params.logger,
|
|
123
|
+
}),
|
|
124
|
+
]).catch((error) => {
|
|
125
|
+
throw new AuthError("Não foi possível concluir o login do CLI.", error);
|
|
126
|
+
});
|
|
63
127
|
const payload = {
|
|
64
128
|
code,
|
|
65
129
|
client: CLI_CLIENT_ID,
|
|
66
130
|
device: params.machine,
|
|
67
131
|
runtime: params.runtime,
|
|
68
132
|
};
|
|
69
|
-
|
|
133
|
+
let tokenResponse;
|
|
134
|
+
try {
|
|
135
|
+
tokenResponse = await params.apiClient.exchangeToken(payload);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new AuthError("Falha na troca do código do CLI por sessão autenticada.", error);
|
|
139
|
+
}
|
|
70
140
|
if (!tokenResponse.access_token || !tokenResponse.refresh_token) {
|
|
71
141
|
throw new AuthError("A API retornou uma sessão inválida.");
|
|
72
142
|
}
|
|
@@ -80,38 +150,11 @@ export async function runLoginFlow(params) {
|
|
|
80
150
|
obtainedAt: new Date().toISOString(),
|
|
81
151
|
expiresAt: new Date(Date.now() + tokenResponse.expires_in * 1000).toISOString(),
|
|
82
152
|
lastRefreshAt: null,
|
|
153
|
+
deviceId: params.device.machineId,
|
|
83
154
|
};
|
|
84
155
|
}
|
|
85
156
|
finally {
|
|
86
|
-
await callbackServer
|
|
157
|
+
await callbackServer?.close();
|
|
87
158
|
}
|
|
88
159
|
}
|
|
89
|
-
async function waitForAuthorizedAttempt(params) {
|
|
90
|
-
const startedAt = Date.now();
|
|
91
|
-
let announcedAuthorization = false;
|
|
92
|
-
while (Date.now() - startedAt < params.timeoutMs) {
|
|
93
|
-
const status = await params.apiClient.getLoginAttemptStatus(params.attemptId, params.pollToken);
|
|
94
|
-
const code = pickAuthorizedCode(status);
|
|
95
|
-
if (code) {
|
|
96
|
-
return code;
|
|
97
|
-
}
|
|
98
|
-
if (status.status === "authorized" && !announcedAuthorization) {
|
|
99
|
-
announcedAuthorization = true;
|
|
100
|
-
params.logger.info("autorização recebida pela API. finalizando sessão...");
|
|
101
|
-
}
|
|
102
|
-
await delay(LOGIN_ATTEMPT_POLL_INTERVAL_MS);
|
|
103
|
-
}
|
|
104
|
-
throw new AuthError("Tempo esgotado aguardando a confirmação do login do CLI.");
|
|
105
|
-
}
|
|
106
|
-
function pickAuthorizedCode(status) {
|
|
107
|
-
if ((status.status === "authorized" || status.status === "completed") && typeof status.code === "string" && status.code.length > 0) {
|
|
108
|
-
return status.code;
|
|
109
|
-
}
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
function delay(ms) {
|
|
113
|
-
return new Promise((resolve) => {
|
|
114
|
-
setTimeout(resolve, ms);
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
160
|
//# sourceMappingURL=auth.js.map
|
package/dist/core/constants.js
CHANGED
|
@@ -38,9 +38,14 @@ export const DEFAULT_CONFIG = {
|
|
|
38
38
|
requireSecureStorage: false,
|
|
39
39
|
disableFileSessionFallback: false,
|
|
40
40
|
requireManualLogin: false,
|
|
41
|
+
requireDeviceLogin: false,
|
|
41
42
|
blockPublicIpLookup: false,
|
|
42
43
|
blockMacAddressCollection: false,
|
|
43
44
|
blockUsernameCollection: false,
|
|
45
|
+
blockNetworkInterfaceCollection: false,
|
|
46
|
+
blockCurrentDirectoryCollection: false,
|
|
47
|
+
blockHomeDirectoryCollection: false,
|
|
48
|
+
blockShellCollection: false,
|
|
44
49
|
},
|
|
45
50
|
};
|
|
46
51
|
export const DEFAULT_PROFILE_STATE = {
|
|
@@ -53,8 +58,10 @@ export const DEFAULT_PROFILE_STATE = {
|
|
|
53
58
|
lastSnapshotPath: null,
|
|
54
59
|
lastSessionId: null,
|
|
55
60
|
lastLoginAt: null,
|
|
61
|
+
lastLoginMethod: null,
|
|
56
62
|
lastRefreshAt: null,
|
|
57
63
|
lastLogoutAt: null,
|
|
64
|
+
lastRemoteRevokeAt: null,
|
|
58
65
|
storageBackend: null,
|
|
59
66
|
storageProtected: null,
|
|
60
67
|
lastAuthFailure: null,
|
|
@@ -74,7 +81,20 @@ export const DEFAULT_WORKSPACE_BINDINGS = {
|
|
|
74
81
|
};
|
|
75
82
|
export const CALLBACK_TIMEOUT_MS = 120_000;
|
|
76
83
|
export const LOGIN_ATTEMPT_POLL_INTERVAL_MS = 1_000;
|
|
77
|
-
export const SENSITIVE_KEYS = [
|
|
84
|
+
export const SENSITIVE_KEYS = [
|
|
85
|
+
"token",
|
|
86
|
+
"authorization",
|
|
87
|
+
"refresh",
|
|
88
|
+
"secret",
|
|
89
|
+
"password",
|
|
90
|
+
"cookie",
|
|
91
|
+
"set-cookie",
|
|
92
|
+
"session",
|
|
93
|
+
"machineid",
|
|
94
|
+
"deviceid",
|
|
95
|
+
"polltoken",
|
|
96
|
+
"code",
|
|
97
|
+
];
|
|
78
98
|
export const SAFE_ENV_KEYS = [
|
|
79
99
|
"SHELL",
|
|
80
100
|
"TERM",
|
package/dist/core/machine.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type { CliConfig } from "../types/config.js";
|
|
1
|
+
import type { CliConfig, CliPolicyConfig, MachinePrivacyProfile } from "../types/config.js";
|
|
2
2
|
import type { DeviceIdentity, MachineContext, RuntimeContext } from "../types/machine.js";
|
|
3
|
+
export interface ExecutionEnvironmentAssessment {
|
|
4
|
+
risks: string[];
|
|
5
|
+
recommendedLoginMode: "browser" | "manual" | "device";
|
|
6
|
+
loopbackLikelySafe: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function assessExecutionEnvironment(): ExecutionEnvironmentAssessment;
|
|
9
|
+
export declare function describePrivacyEnvelope(profile: MachinePrivacyProfile, policy: CliPolicyConfig): Record<string, boolean>;
|
|
3
10
|
export declare function collectRuntimeContext(cliVersion: string, command: string, args: string[]): Promise<RuntimeContext>;
|
|
4
11
|
export declare function collectMachineContext(identity: DeviceIdentity, config: CliConfig): Promise<MachineContext>;
|
package/dist/core/machine.js
CHANGED
|
@@ -30,7 +30,7 @@ async function detectPublicIp(timeoutMs) {
|
|
|
30
30
|
clearTimeout(timer);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
function collectNetworkInterfaces() {
|
|
33
|
+
function collectNetworkInterfaces(includeMacAddress, includeAddresses) {
|
|
34
34
|
const interfaces = os.networkInterfaces();
|
|
35
35
|
const results = [];
|
|
36
36
|
for (const [name, entries] of Object.entries(interfaces)) {
|
|
@@ -38,14 +38,48 @@ function collectNetworkInterfaces() {
|
|
|
38
38
|
results.push({
|
|
39
39
|
name,
|
|
40
40
|
family: entry.family,
|
|
41
|
-
mac: entry.mac,
|
|
42
|
-
address: entry.address,
|
|
41
|
+
mac: includeMacAddress ? entry.mac : null,
|
|
42
|
+
address: includeAddresses ? entry.address : null,
|
|
43
43
|
internal: entry.internal,
|
|
44
|
-
cidr: entry.cidr ?? null,
|
|
44
|
+
cidr: includeAddresses ? (entry.cidr ?? null) : null,
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
return results.sort((left, right) => left.name.localeCompare(right.name) || left.address.localeCompare(right.address));
|
|
48
|
+
return results.sort((left, right) => left.name.localeCompare(right.name) || String(left.address).localeCompare(String(right.address)));
|
|
49
|
+
}
|
|
50
|
+
export function assessExecutionEnvironment() {
|
|
51
|
+
const risks = [];
|
|
52
|
+
if (process.env.WSL_DISTRO_NAME) {
|
|
53
|
+
risks.push("wsl");
|
|
54
|
+
}
|
|
55
|
+
if (process.env.SSH_CONNECTION || process.env.SSH_CLIENT) {
|
|
56
|
+
risks.push("ssh");
|
|
57
|
+
}
|
|
58
|
+
if (process.env.CI === "true") {
|
|
59
|
+
risks.push("ci");
|
|
60
|
+
}
|
|
61
|
+
if (process.env.CONTAINER || process.env.DOCKER_CONTAINER || process.env.KUBERNETES_SERVICE_HOST) {
|
|
62
|
+
risks.push("container");
|
|
63
|
+
}
|
|
64
|
+
const loopbackLikelySafe = risks.length === 0;
|
|
65
|
+
const recommendedLoginMode = loopbackLikelySafe
|
|
66
|
+
? "browser"
|
|
67
|
+
: risks.includes("ssh") || risks.includes("container") || risks.includes("ci")
|
|
68
|
+
? "device"
|
|
69
|
+
: "manual";
|
|
70
|
+
return { risks, recommendedLoginMode, loopbackLikelySafe };
|
|
71
|
+
}
|
|
72
|
+
export function describePrivacyEnvelope(profile, policy) {
|
|
73
|
+
return {
|
|
74
|
+
username: !policy.blockUsernameCollection && profile !== "minimal",
|
|
75
|
+
publicIp: !policy.blockPublicIpLookup && profile !== "minimal",
|
|
76
|
+
networkInterfaces: !policy.blockNetworkInterfaceCollection && profile !== "minimal",
|
|
77
|
+
networkAddresses: profile === "enterprise" && !policy.blockNetworkInterfaceCollection,
|
|
78
|
+
macAddress: profile === "enterprise" && !policy.blockMacAddressCollection,
|
|
79
|
+
currentDirectory: !policy.blockCurrentDirectoryCollection && profile !== "minimal",
|
|
80
|
+
homeDirectory: !policy.blockHomeDirectoryCollection && profile === "enterprise",
|
|
81
|
+
shell: !policy.blockShellCollection && profile !== "minimal",
|
|
82
|
+
};
|
|
49
83
|
}
|
|
50
84
|
export async function collectRuntimeContext(cliVersion, command, args) {
|
|
51
85
|
return {
|
|
@@ -62,14 +96,11 @@ export async function collectRuntimeContext(cliVersion, command, args) {
|
|
|
62
96
|
export async function collectMachineContext(identity, config) {
|
|
63
97
|
const cpus = os.cpus();
|
|
64
98
|
const privacyProfile = config.profiles[config.activeProfile]?.machinePrivacyProfile ?? "standard";
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
mac: includeMacAddress ? entry.mac : null,
|
|
71
|
-
}));
|
|
72
|
-
const publicIp = collectPublicIp ? await detectPublicIp(config.publicIpLookupTimeoutMs) : null;
|
|
99
|
+
const envelope = describePrivacyEnvelope(privacyProfile, config.policy);
|
|
100
|
+
const publicIp = envelope.publicIp ? await detectPublicIp(config.publicIpLookupTimeoutMs) : null;
|
|
101
|
+
const networkInterfaces = envelope.networkInterfaces
|
|
102
|
+
? collectNetworkInterfaces(envelope.macAddress, envelope.networkAddresses)
|
|
103
|
+
: [];
|
|
73
104
|
return {
|
|
74
105
|
machineId: identity.machineId,
|
|
75
106
|
hostname: os.hostname(),
|
|
@@ -77,19 +108,19 @@ export async function collectMachineContext(identity, config) {
|
|
|
77
108
|
osRelease: os.release(),
|
|
78
109
|
architecture: os.arch(),
|
|
79
110
|
kernelVersion: os.version(),
|
|
80
|
-
username:
|
|
111
|
+
username: envelope.username ? os.userInfo().username : null,
|
|
81
112
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
82
113
|
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
83
|
-
shell: process.env.SHELL ?? null,
|
|
84
|
-
currentDirectory: process.cwd(),
|
|
85
|
-
homeDirectory: os.homedir(),
|
|
114
|
+
shell: envelope.shell ? (process.env.SHELL ?? null) : null,
|
|
115
|
+
currentDirectory: envelope.currentDirectory ? process.cwd() : null,
|
|
116
|
+
homeDirectory: envelope.homeDirectory ? os.homedir() : null,
|
|
86
117
|
cpuModel: cpus[0]?.model ?? null,
|
|
87
118
|
cpuCount: cpus.length,
|
|
88
119
|
loadAverage: os.loadavg(),
|
|
89
120
|
memoryTotal: os.totalmem(),
|
|
90
121
|
memoryFree: os.freemem(),
|
|
91
122
|
uptimeSeconds: os.uptime(),
|
|
92
|
-
networkInterfaces
|
|
123
|
+
networkInterfaces,
|
|
93
124
|
publicIp,
|
|
94
125
|
privacyProfile,
|
|
95
126
|
};
|
package/dist/index.js
CHANGED
|
@@ -31,17 +31,17 @@ function printHelp() {
|
|
|
31
31
|
process.stdout.write("\n");
|
|
32
32
|
process.stdout.write("Comandos principais:\n");
|
|
33
33
|
process.stdout.write(" init [--agent <tipo>] [--workspace <workspace>] [--force]\n");
|
|
34
|
-
process.stdout.write(" login [--no-browser|--manual]\n");
|
|
34
|
+
process.stdout.write(" login [--no-browser|--manual|--device]\n");
|
|
35
35
|
process.stdout.write(" logout\n");
|
|
36
36
|
process.stdout.write(" sync [--force]\n");
|
|
37
37
|
process.stdout.write(" status\n");
|
|
38
|
-
process.stdout.write(" task <run|templates|list|inspect|claim|execute|daemon|approve|reject|delegate|step-update|handoff|observability|health>\n");
|
|
38
|
+
process.stdout.write(" task <run|templates|list|inspect|watch|claim|execute|daemon|approve|reject|delegate|step-update|handoff|observability|health>\n");
|
|
39
39
|
process.stdout.write(" registry <list|register|update|deactivate>\n");
|
|
40
40
|
process.stdout.write(" whoami\n");
|
|
41
|
-
process.stdout.write(" doctor\n");
|
|
41
|
+
process.stdout.write(" doctor [--policy]\n");
|
|
42
42
|
process.stdout.write(" draft <create|list>\n");
|
|
43
43
|
process.stdout.write(" config <profile|get|set>\n");
|
|
44
|
-
process.stdout.write(" workspace <current|use|resolve|clear>\n");
|
|
44
|
+
process.stdout.write(" workspace <current|use|resolve|clear|policy|webhook>\n");
|
|
45
45
|
process.stdout.write(" session <check-in|heartbeat|check-out>\n");
|
|
46
46
|
process.stdout.write(" context [--snapshot|--delivery]\n");
|
|
47
47
|
process.stdout.write(" bootstrap\n");
|
|
@@ -73,7 +73,7 @@ export async function runCli(args) {
|
|
|
73
73
|
case "whoami":
|
|
74
74
|
return runWhoAmICommand(cliVersion);
|
|
75
75
|
case "doctor":
|
|
76
|
-
return runDoctorCommand(cliVersion);
|
|
76
|
+
return runDoctorCommand(cliVersion, rest);
|
|
77
77
|
case "draft":
|
|
78
78
|
return runDraftCommand(cliVersion, rest);
|
|
79
79
|
case "config":
|
package/dist/types/api.d.ts
CHANGED
|
@@ -88,6 +88,23 @@ export interface WorkspacePolicyRuleRecord {
|
|
|
88
88
|
updatedAt: string;
|
|
89
89
|
[key: string]: unknown;
|
|
90
90
|
}
|
|
91
|
+
export type WorkspaceWebhookEventType = "task.created" | "task.blocked" | "task.completed" | "task.approval_required" | "health.degraded";
|
|
92
|
+
export interface WorkspaceWebhookRecord {
|
|
93
|
+
id: string;
|
|
94
|
+
orgId: string;
|
|
95
|
+
workspaceId: string;
|
|
96
|
+
name: string;
|
|
97
|
+
targetUrl: string;
|
|
98
|
+
secretPreview: string;
|
|
99
|
+
events: WorkspaceWebhookEventType[];
|
|
100
|
+
enabled: boolean;
|
|
101
|
+
lastDeliveryAt: string | null;
|
|
102
|
+
lastDeliveryStatus: "delivered" | "failed" | null;
|
|
103
|
+
lastError: string | null;
|
|
104
|
+
createdAt: string;
|
|
105
|
+
updatedAt: string;
|
|
106
|
+
[key: string]: unknown;
|
|
107
|
+
}
|
|
91
108
|
export interface AgentRegistryRecord {
|
|
92
109
|
id: string;
|
|
93
110
|
orgId: string | null;
|
|
@@ -291,3 +308,32 @@ export interface TaskObservabilityRecord {
|
|
|
291
308
|
timeline?: Array<Record<string, unknown>>;
|
|
292
309
|
[key: string]: unknown;
|
|
293
310
|
}
|
|
311
|
+
export interface WorkspaceStreamEventRecord {
|
|
312
|
+
type: string;
|
|
313
|
+
workspaceId: string;
|
|
314
|
+
taskId?: string | null;
|
|
315
|
+
stepId?: string | null;
|
|
316
|
+
timestamp: string;
|
|
317
|
+
summary?: Record<string, unknown> | null;
|
|
318
|
+
}
|
|
319
|
+
export interface WorkspaceSnapshotDiffRecord {
|
|
320
|
+
workspaceId: string;
|
|
321
|
+
from: {
|
|
322
|
+
reference: string;
|
|
323
|
+
version: number;
|
|
324
|
+
generatedAt: string;
|
|
325
|
+
};
|
|
326
|
+
to: {
|
|
327
|
+
reference: string;
|
|
328
|
+
version: number;
|
|
329
|
+
generatedAt: string;
|
|
330
|
+
};
|
|
331
|
+
summary: Record<string, {
|
|
332
|
+
added: number;
|
|
333
|
+
removed: number;
|
|
334
|
+
modified: number;
|
|
335
|
+
}>;
|
|
336
|
+
added: Record<string, Array<Record<string, unknown>>>;
|
|
337
|
+
removed: Record<string, Array<Record<string, unknown>>>;
|
|
338
|
+
modified: Record<string, Array<Record<string, unknown>>>;
|
|
339
|
+
}
|
package/dist/types/auth.d.ts
CHANGED
package/dist/types/config.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
export type VectorPlaneEnvironment = "production" | "preview" | "self-hosted";
|
|
2
2
|
export type MachinePrivacyProfile = "standard" | "minimal" | "enterprise";
|
|
3
3
|
export type SessionStorageBackend = "system" | "file";
|
|
4
|
+
export type CliLoginMode = "browser" | "manual" | "device";
|
|
4
5
|
export interface CliPolicyConfig {
|
|
5
6
|
requireSecureStorage: boolean;
|
|
6
7
|
disableFileSessionFallback: boolean;
|
|
7
8
|
requireManualLogin: boolean;
|
|
9
|
+
requireDeviceLogin: boolean;
|
|
8
10
|
blockPublicIpLookup: boolean;
|
|
9
11
|
blockMacAddressCollection: boolean;
|
|
10
12
|
blockUsernameCollection: boolean;
|
|
13
|
+
blockNetworkInterfaceCollection: boolean;
|
|
14
|
+
blockCurrentDirectoryCollection: boolean;
|
|
15
|
+
blockHomeDirectoryCollection: boolean;
|
|
16
|
+
blockShellCollection: boolean;
|
|
11
17
|
}
|
|
12
18
|
export interface CliProfileConfig {
|
|
13
19
|
name: string;
|
|
@@ -43,8 +49,10 @@ export interface CliProfileState {
|
|
|
43
49
|
lastSnapshotPath: string | null;
|
|
44
50
|
lastSessionId: string | null;
|
|
45
51
|
lastLoginAt: string | null;
|
|
52
|
+
lastLoginMethod: CliLoginMode | null;
|
|
46
53
|
lastRefreshAt: string | null;
|
|
47
54
|
lastLogoutAt: string | null;
|
|
55
|
+
lastRemoteRevokeAt: string | null;
|
|
48
56
|
storageBackend: SessionStorageBackend | null;
|
|
49
57
|
storageProtected: boolean | null;
|
|
50
58
|
lastAuthFailure: string | null;
|
package/dist/types/machine.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface NetworkInterfaceContext {
|
|
|
6
6
|
name: string;
|
|
7
7
|
family: string;
|
|
8
8
|
mac: string | null;
|
|
9
|
-
address: string;
|
|
9
|
+
address: string | null;
|
|
10
10
|
internal: boolean;
|
|
11
11
|
cidr: string | null;
|
|
12
12
|
}
|
|
@@ -36,8 +36,8 @@ export interface MachineContext {
|
|
|
36
36
|
timezone: string;
|
|
37
37
|
locale: string;
|
|
38
38
|
shell: string | null;
|
|
39
|
-
currentDirectory: string;
|
|
40
|
-
homeDirectory: string;
|
|
39
|
+
currentDirectory: string | null;
|
|
40
|
+
homeDirectory: string | null;
|
|
41
41
|
cpuModel: string | null;
|
|
42
42
|
cpuCount: number;
|
|
43
43
|
loadAverage: number[];
|