@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/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
@@ -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
- noBrowser?: boolean;
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 callbackServer = await createCallbackServer(params.config.callbackHost, params.config.callbackPort, params.config.callbackPath, state);
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: callbackServer.callbackUrl,
93
+ redirectUri,
34
94
  state,
35
95
  client: CLI_CLIENT_ID,
36
96
  });
37
97
  const loginUrl = loginAttempt.loginUrl;
38
- if (params.noBrowser) {
39
- params.logger.info("login manual habilitado. abra a URL abaixo no navegador:");
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 = await Promise.any([
52
- callbackServer.waitForCallback(CALLBACK_TIMEOUT_MS).then((callback) => callback.code),
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
- ]).catch((error) => {
61
- throw new AuthError("Não foi possível concluir o login do CLI.", error);
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
- const tokenResponse = await params.apiClient.exchangeToken(payload);
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.close();
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
@@ -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 = ["token", "authorization", "refresh", "secret", "password", "cookie"];
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",
@@ -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>;
@@ -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 collectPublicIp = !config.policy.blockPublicIpLookup && privacyProfile !== "minimal";
66
- const includeMacAddress = !config.policy.blockMacAddressCollection && privacyProfile === "enterprise";
67
- const includeUsername = !config.policy.blockUsernameCollection && privacyProfile !== "minimal";
68
- const networkInterfaces = collectNetworkInterfaces().map((entry) => ({
69
- ...entry,
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: includeUsername ? os.userInfo().username : null,
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: privacyProfile === "minimal" ? [] : 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":
@@ -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
+ }
@@ -56,4 +56,5 @@ export interface AuthSession {
56
56
  obtainedAt: string;
57
57
  expiresAt: string;
58
58
  lastRefreshAt: string | null;
59
+ deviceId?: string | null;
59
60
  }
@@ -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;
@@ -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[];