@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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CLI oficial do VectorPlane.
4
4
 
5
- Use o comando `vp` para autenticar, verificar a sessão local e sincronizar o workspace com o produto.
5
+ Use o comando `vp` para autenticar, verificar a sessão local, sincronizar o workspace e operar o control plane com uma sessão local endurecida.
6
6
 
7
7
  ## Instalação
8
8
 
@@ -15,116 +15,73 @@ npm install -g @vectorplane/ctrl-cli
15
15
  ```bash
16
16
  vp init
17
17
  vp login
18
+ vp login --manual
19
+ vp login --device
18
20
  vp logout
19
21
  vp status
22
+ vp doctor --policy
20
23
  vp sync
21
24
  vp task templates
22
25
  vp task run --title "Entrega X" --intention "Implementar fluxo Y"
23
- vp task run --template code-review --title "Review auth" --intention "Revisar risco e cobertura do fluxo de auth"
24
- vp task claim --capability qa.test
25
- vp task execute <task-id> --step <step-id>
26
- vp task daemon --capability qa.test
26
+ vp task watch <task-id>
27
27
  vp context --search "decisões sobre autenticação"
28
- vp context --delivery --search "fluxo de login" --type decisions
29
- vp context --workspace <workspace> --delivery
30
- vp session check-in --workspace <workspace> --agent codex --type codex --client openai-codex --feature feature-key --task task-key --owning-path "src/core,src/ui" --need "api-contract" --provide "implementation" --status "starting implementation"
31
- vp draft create --type progress --title "Entrega concluída" --content "Resumo da mudança"
28
+ vp workspace webhook list
29
+ vp session check-in --workspace <workspace> --agent codex --type codex --client openai-codex
32
30
  ```
33
31
 
34
- ## Comandos
35
-
36
- ### `vp init`
37
-
38
- - autentica localmente se ainda não houver sessão
39
- - resolve e associa o workspace atual a partir do `git remote`, somente quando houver correspondência com um workspace que o usuário pode acessar
40
- - detecta o agente local automaticamente, pede confirmação se houver ambiguidade e aceita `--agent`
41
- - baixa o template oficial do backend
42
- - grava o arquivo local e adiciona a entrada no `.gitignore`
43
- - aceita `--force` para sobrescrever template existente
44
- - durante execução, se outro agente passar a operar no workspace e a detecção for confiável, o setup local é reconciliado automaticamente
32
+ ## Comandos principais
45
33
 
46
34
  ### `vp login`
47
35
 
48
- - abre o navegador para autenticação
49
- - conclui o login com callback local seguro
50
- - salva a sessão localmente
51
- - suporta `--no-browser` e `--manual`
52
- - em ambientes remotos ou isolados, o loopback local pode exigir um fluxo alternativo
53
- - a URL web do login é emitida pelo control plane; o domínio do app não deve ser hardcoded em automações externas
36
+ - suporta `browser`, `manual` e `device`
37
+ - `--manual` evita abrir o navegador automaticamente
38
+ - `--device` evita dependência de callback loopback e fecha melhor com SSH, container, CI e WSL
39
+ - a URL web do login é emitida pelo control plane
54
40
 
55
41
  ### `vp logout`
56
42
 
57
- - revoga a sessão do CLI quando possível
58
- - remove a sessão local do dispositivo
43
+ - tenta revogar a sessão remotamente
44
+ - limpa a sessão local do dispositivo
45
+ - registra auditoria local mínima da operação
46
+
47
+ ### `vp doctor`
59
48
 
60
- ### `vp sync`
49
+ - valida storage de sessão, política ativa, conectividade e ambiente local
50
+ - suporta `--policy` para checar só conformidade local
51
+ - recomenda o modo de login mais seguro para o ambiente atual
61
52
 
62
- - coleta contexto local do workspace
63
- - envia a sincronização para o VectorPlane
64
- - usa fila local quando a API estiver indisponível
53
+ ### `vp config`
54
+
55
+ - gerencia perfis, política local e envelope de privacidade
56
+ - `vp config privacy set <standard|minimal|enterprise>`
57
+ - `vp config policy set <chave> <true|false>`
65
58
 
66
59
  ### `vp status`
67
60
 
68
- - mostra o estado da sessão local
69
- - exibe workspace ativo, diretório atual e última sincronização
70
-
71
- ### `vp task`
72
-
73
- - cria tasks no orchestrator do workspace
74
- - lista templates disponíveis com `vp task templates`
75
- - aceita `--template <slug>` para herdar capabilities e paths padrão do backend
76
- - lista e inspeciona tasks existentes
77
- - faz claim explícito de steps `pending_claim` atribuídos a agentes CLI
78
- - executa steps locais via `metadata.execution.command`
79
- - suporta loop controlado com `vp task daemon --capability ...`
80
- - registra delegação de step
81
- - envia callback de execução por step para concluir a pipeline
82
- - lê handoff operacional por task
83
- - consulta observabilidade por task ou workspace
84
- - consulta health do workspace ligado às tasks
85
- - para automação local real, o agente de registry deve ter `metadata.execution` com:
86
- - `type: "command"`
87
- - `command: "npm"` ou equivalente
88
- - `args`, `cwd` e `env` opcionais
89
-
90
- ### `vp draft`
91
-
92
- - cria drafts editoriais ligados ao workspace atual
93
- - lista drafts existentes para conferência rápida
94
- - suporta `ui`, `ux`, `project_skeleton`, `template_engineering`, `patterns`, `progress`, `decisions` e `architecture`
95
- - aceita `--no-impact` para registrar explicitamente quando uma lane não foi afetada
96
-
97
- ### `vp session`
98
-
99
- - suporta `check-in`, `heartbeat` e `check-out`
100
- - permite declarar `feature`, `task`, `component`, `role`, `owning-path`, `need`, `provide` e `status`
101
- - deve ser usado em conjunto com `vp context --delivery` para reduzir conflito entre agentes ativos
102
-
103
- ### Comandos adicionais já implementados
104
-
105
- - `vp whoami`
106
- - mostra a identidade autenticada
107
- - `vp doctor`
108
- - executa verificações locais e de conectividade
109
- - `vp config`
110
- - gerencia perfis e configuração local
111
- - inclui `vp config privacy` e `vp config policy`
112
- - `vp workspace`
113
- - gerencia o workspace atual
114
- - `vp session`
115
- - gerencia sessões de agente
116
- - `vp context`
117
- - carrega contexto remoto do workspace
118
- - suporta `--search "QUERY"` para busca semântica na memória
119
- - com `--delivery --search`, retorna delivery context com fragmentos semânticos relevantes
120
- - `vp bootstrap`
121
- - obtém instruções de setup do workspace
122
- - `vp event send`
123
- - envia eventos operacionais
124
- - `vp draft`
125
- - envia e consulta drafts editoriais do workspace
126
- - `vp task`
127
- - opera tasks do autonomous control plane
61
+ - mostra estado da sessão local
62
+ - exibe storage usado, último login, último refresh, último logout e falhas recentes de auth
63
+
64
+ ## Privacidade de máquina
65
+
66
+ Perfis:
67
+
68
+ - `standard`: envia o contexto operacional mínimo usual
69
+ - `minimal`: reduz ao máximo dados locais e desabilita lookup de IP/interface por padrão
70
+ - `enterprise`: permite envelope mais rico, mas continua respeitando policies locais
71
+
72
+ Policies disponíveis:
73
+
74
+ - `requireSecureStorage`
75
+ - `disableFileSessionFallback`
76
+ - `requireManualLogin`
77
+ - `requireDeviceLogin`
78
+ - `blockPublicIpLookup`
79
+ - `blockMacAddressCollection`
80
+ - `blockUsernameCollection`
81
+ - `blockNetworkInterfaceCollection`
82
+ - `blockCurrentDirectoryCollection`
83
+ - `blockHomeDirectoryCollection`
84
+ - `blockShellCollection`
128
85
 
129
86
  ## Persistência local
130
87
 
@@ -139,8 +96,14 @@ vp draft create --type progress --title "Entrega concluída" --content "Resumo d
139
96
  ## Segurança
140
97
 
141
98
  - tokens nunca são impressos no terminal
142
- - tokens nunca são enviados por query string
143
- - o callback valida o `state`
99
+ - logs mascaram chaves sensíveis e ids de sessão
144
100
  - a sessão usa secure storage do sistema quando disponível
145
- - existe fallback para arquivo local apenas quando permitido
101
+ - existe fallback para arquivo local apenas quando permitido pela policy
146
102
  - `vp logout` encerra a sessão local e tenta revogação remota
103
+ - o CLI continua sendo thin client: autorização e regra crítica permanecem no backend
104
+
105
+ ## Tutoriais relacionados
106
+
107
+ - setup completo do ecossistema: [/home/developer/Documentos/Projetos/conductor-edge-ia/docs/tutorials/setup-ecossistema.md](/home/developer/Documentos/Projetos/conductor-edge-ia/docs/tutorials/setup-ecossistema.md)
108
+ - operação de workspace: [/home/developer/Documentos/Projetos/conductor-edge-ia/docs/tutorials/operacao-workspace.md](/home/developer/Documentos/Projetos/conductor-edge-ia/docs/tutorials/operacao-workspace.md)
109
+ - memória e agentes: [/home/developer/Documentos/Projetos/conductor-edge-ia/docs/tutorials/memoria-e-agentes.md](/home/developer/Documentos/Projetos/conductor-edge-ia/docs/tutorials/memoria-e-agentes.md)
@@ -12,6 +12,7 @@ export async function runContextCommand(cliVersion, args) {
12
12
  const parsed = parseArgs(args);
13
13
  const delivery = getBooleanOption(parsed, "delivery");
14
14
  const snapshot = getBooleanOption(parsed, "snapshot");
15
+ const diff = getBooleanOption(parsed, "diff");
15
16
  const search = getStringOption(parsed, "search")?.trim();
16
17
  const runtime = await loadRuntimeStatus();
17
18
  const session = await ensureSessionAvailable(runtime.profile.name);
@@ -38,6 +39,9 @@ export async function runContextCommand(cliVersion, args) {
38
39
  throw new ValidationError("Nenhum workspace resolvido para carregar contexto.");
39
40
  }
40
41
  await ensureRuntimeAgentSetup({ rootPath, workspace, git, session: freshSession, apiClient, logger: runtime.logger });
42
+ if (diff && (delivery || snapshot || search)) {
43
+ throw new ValidationError("`--diff` não pode ser combinado com `--delivery`, `--snapshot` ou `--search`.");
44
+ }
41
45
  const payload = search
42
46
  ? (delivery
43
47
  ? await apiClient.getWorkspaceDeliveryContextSemantic(freshSession.accessToken, workspace, {
@@ -52,11 +56,16 @@ export async function runContextCommand(cliVersion, args) {
52
56
  authority: getStringOption(parsed, "authority")?.trim(),
53
57
  limit: Number(getStringOption(parsed, "limit") ?? "0") || undefined,
54
58
  }))
55
- : delivery
56
- ? await apiClient.getWorkspaceDeliveryContext(freshSession.accessToken, workspace)
57
- : snapshot
58
- ? await apiClient.getWorkspaceSnapshot(freshSession.accessToken, workspace)
59
- : await apiClient.getWorkspaceContext(freshSession.accessToken, workspace);
59
+ : diff
60
+ ? await apiClient.getWorkspaceSnapshotDiff(freshSession.accessToken, workspace, {
61
+ from: getStringOption(parsed, "from")?.trim() || "latest-1",
62
+ to: getStringOption(parsed, "to")?.trim() || "latest",
63
+ })
64
+ : delivery
65
+ ? await apiClient.getWorkspaceDeliveryContext(freshSession.accessToken, workspace)
66
+ : snapshot
67
+ ? await apiClient.getWorkspaceSnapshot(freshSession.accessToken, workspace)
68
+ : await apiClient.getWorkspaceContext(freshSession.accessToken, workspace);
60
69
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
61
70
  return 0;
62
71
  }
@@ -1 +1 @@
1
- export declare function runDoctorCommand(cliVersion: string): Promise<number>;
1
+ export declare function runDoctorCommand(cliVersion: string, args?: string[]): Promise<number>;
@@ -2,10 +2,11 @@ import { access } from "node:fs/promises";
2
2
  import { constants as fsConstants } from "node:fs";
3
3
  import { configDirectoryExists, ensureSessionAvailable, getConfigDirectoryPath, getSessionStorageInfo } from "../core/config.js";
4
4
  import { collectGitContext } from "../core/git.js";
5
- import { collectMachineContext, collectRuntimeContext } from "../core/machine.js";
5
+ import { assessExecutionEnvironment, collectMachineContext, collectRuntimeContext, describePrivacyEnvelope } from "../core/machine.js";
6
6
  import { loadRuntimeStatus } from "../core/runtime.js";
7
7
  import { ensureFreshSession } from "../core/session.js";
8
8
  import { VectorPlaneApiClient } from "../core/api.js";
9
+ import { getBooleanOption, parseArgs } from "../core/cli.js";
9
10
  async function writable(filePath) {
10
11
  try {
11
12
  await access(filePath, fsConstants.W_OK);
@@ -18,23 +19,9 @@ async function writable(filePath) {
18
19
  function printCheck(label, ok, details) {
19
20
  process.stdout.write(`${ok ? "OK" : "FAIL"} ${label}: ${details}\n`);
20
21
  }
21
- function detectExecutionRisks() {
22
- const risks = [];
23
- if (process.env.WSL_DISTRO_NAME) {
24
- risks.push("wsl");
25
- }
26
- if (process.env.SSH_CONNECTION || process.env.SSH_CLIENT) {
27
- risks.push("ssh");
28
- }
29
- if (process.env.CI === "true") {
30
- risks.push("ci");
31
- }
32
- if (process.env.CONTAINER || process.env.DOCKER_CONTAINER) {
33
- risks.push("container");
34
- }
35
- return risks;
36
- }
37
- export async function runDoctorCommand(cliVersion) {
22
+ export async function runDoctorCommand(cliVersion, args = []) {
23
+ const parsed = parseArgs(args);
24
+ const policyOnly = getBooleanOption(parsed, "policy");
38
25
  const runtime = await loadRuntimeStatus();
39
26
  const configDirectory = await getConfigDirectoryPath();
40
27
  const [hasConfigDir, git, canWriteConfig] = await Promise.all([
@@ -43,15 +30,21 @@ export async function runDoctorCommand(cliVersion) {
43
30
  writable(configDirectory),
44
31
  ]);
45
32
  const storage = await getSessionStorageInfo(runtime.profile.name);
46
- const risks = detectExecutionRisks();
33
+ const environment = assessExecutionEnvironment();
34
+ const privacyEnvelope = describePrivacyEnvelope(runtime.profile.machinePrivacyProfile, runtime.config.policy);
47
35
  printCheck("config_dir", hasConfigDir, configDirectory);
48
36
  printCheck("config_dir_writable", canWriteConfig, canWriteConfig ? "gravável" : "sem permissão de escrita");
49
37
  printCheck("git", git.isRepository, git.isRepository ? (git.rootPath ?? process.cwd()) : "repositório não detectado");
50
38
  printCheck("node", true, process.version);
51
39
  printCheck("privacy_profile", true, runtime.profile.machinePrivacyProfile);
40
+ printCheck("privacy_envelope", true, JSON.stringify(privacyEnvelope));
52
41
  printCheck("session_storage", (storage?.protected ?? false) || runtime.config.preferredSessionStorage === "system", storage ? `${storage.backend}:${storage.protected ? "protected" : "file"}` : runtime.config.preferredSessionStorage);
53
42
  printCheck("policy_secure_storage", !runtime.config.policy.requireSecureStorage || storage?.protected === true, JSON.stringify(runtime.config.policy));
54
- printCheck("execution_environment", risks.length === 0, risks.length === 0 ? "local" : risks.join(","));
43
+ printCheck("execution_environment", environment.loopbackLikelySafe, environment.risks.length === 0 ? "local" : environment.risks.join(","));
44
+ printCheck("login_recommendation", true, environment.recommendedLoginMode);
45
+ if (policyOnly) {
46
+ return 0;
47
+ }
55
48
  const apiClient = new VectorPlaneApiClient(runtime.profile.apiBaseUrl, runtime.config.requestTimeoutMs, runtime.logger);
56
49
  try {
57
50
  const health = await apiClient.getHealth();
@@ -1,18 +1,37 @@
1
1
  import { getBooleanOption, getStringOption, parseArgs } from "../core/cli.js";
2
2
  import { runLoginFlow } from "../core/auth.js";
3
3
  import { getSessionStorageInfo, saveSession, setActiveProfile, updateProfileState, upsertProfile } from "../core/config.js";
4
- import { collectMachineContext, collectRuntimeContext } from "../core/machine.js";
4
+ import { assessExecutionEnvironment, collectMachineContext, collectRuntimeContext } from "../core/machine.js";
5
5
  import { loadRuntimeStatus } from "../core/runtime.js";
6
6
  import { VectorPlaneApiClient } from "../core/api.js";
7
+ function resolveLoginMode(args) {
8
+ if (args.device || args.requireDeviceLogin) {
9
+ return "device";
10
+ }
11
+ if (args.manual || args.noBrowser || args.requireManualLogin) {
12
+ return "manual";
13
+ }
14
+ return "browser";
15
+ }
7
16
  export async function runLoginCommand(cliVersion, args) {
8
17
  const parsed = parseArgs(args);
9
18
  const requestedProfile = getStringOption(parsed, "profile");
10
- const noBrowser = getBooleanOption(parsed, "no-browser") || getBooleanOption(parsed, "manual");
11
19
  if (requestedProfile) {
12
20
  await upsertProfile(requestedProfile, { name: requestedProfile });
13
21
  await setActiveProfile(requestedProfile);
14
22
  }
15
23
  const runtime = await loadRuntimeStatus();
24
+ const loginMode = resolveLoginMode({
25
+ noBrowser: getBooleanOption(parsed, "no-browser"),
26
+ manual: getBooleanOption(parsed, "manual"),
27
+ device: getBooleanOption(parsed, "device"),
28
+ requireManualLogin: runtime.config.policy.requireManualLogin,
29
+ requireDeviceLogin: runtime.config.policy.requireDeviceLogin,
30
+ });
31
+ const environment = assessExecutionEnvironment();
32
+ if (environment.recommendedLoginMode === "device" && loginMode === "browser") {
33
+ runtime.logger.warn("o ambiente atual parece pouco confiável para loopback. considere `vp login --device`.");
34
+ }
16
35
  const machine = await collectMachineContext(runtime.device, runtime.config);
17
36
  const runtimeContext = await collectRuntimeContext(cliVersion, "login", process.argv.slice(2));
18
37
  const apiClient = new VectorPlaneApiClient(runtime.profile.apiBaseUrl, runtime.config.requestTimeoutMs, runtime.logger);
@@ -24,7 +43,7 @@ export async function runLoginCommand(cliVersion, args) {
24
43
  device: runtime.device,
25
44
  apiClient,
26
45
  logger: runtime.logger,
27
- noBrowser: noBrowser || runtime.config.policy.requireManualLogin,
46
+ loginMode,
28
47
  });
29
48
  await saveSession(session, runtime.profile.name);
30
49
  const storage = await getSessionStorageInfo(runtime.profile.name);
@@ -34,6 +53,7 @@ export async function runLoginCommand(cliVersion, args) {
34
53
  lastWorkspace: session.workspace,
35
54
  lastError: null,
36
55
  lastLoginAt: session.obtainedAt,
56
+ lastLoginMethod: loginMode,
37
57
  lastSessionId: session.sessionId,
38
58
  storageBackend: storage?.backend ?? null,
39
59
  storageProtected: storage?.protected ?? null,
@@ -42,6 +62,7 @@ export async function runLoginCommand(cliVersion, args) {
42
62
  });
43
63
  runtime.logger.success("login realizado com sucesso.");
44
64
  process.stdout.write(`Perfil: ${runtime.profile.name}\n`);
65
+ process.stdout.write(`Modo de login: ${loginMode}\n`);
45
66
  process.stdout.write(`Workspace ativo: ${session.workspace}\n`);
46
67
  return 0;
47
68
  }
@@ -15,8 +15,10 @@ export async function runLogoutCommand() {
15
15
  return 0;
16
16
  }
17
17
  const apiClient = new VectorPlaneApiClient(runtime.profile.apiBaseUrl, runtime.config.requestTimeoutMs, runtime.logger);
18
+ let remoteRevokedAt = null;
18
19
  try {
19
20
  await revokeSession({ session, apiClient });
21
+ remoteRevokedAt = new Date().toISOString();
20
22
  }
21
23
  catch (error) {
22
24
  runtime.logger.warn("não foi possível revogar a sessão remotamente. removendo a sessão local mesmo assim.");
@@ -29,6 +31,7 @@ export async function runLogoutCommand() {
29
31
  await updateProfileState(runtime.profile.name, {
30
32
  lastCommand: "logout",
31
33
  lastLogoutAt: loggedOutAt,
34
+ lastRemoteRevokeAt: remoteRevokedAt,
32
35
  lastSessionId: null,
33
36
  });
34
37
  runtime.logger.success("sessão local encerrada.");
@@ -35,16 +35,22 @@ export async function runStatusCommand() {
35
35
  process.stdout.write(`Sessão CLI: ${session.sessionId}\n`);
36
36
  process.stdout.write(`Expira em: ${session.expiresAt}\n`);
37
37
  process.stdout.write(`Último refresh: ${profileState.lastRefreshAt ?? session.lastRefreshAt ?? "nunca"}\n`);
38
+ process.stdout.write(`Último login: ${profileState.lastLoginAt ?? "desconhecido"}\n`);
39
+ process.stdout.write(`Último método de login: ${profileState.lastLoginMethod ?? "desconhecido"}\n`);
40
+ process.stdout.write(`Último logout: ${profileState.lastLogoutAt ?? "nunca"}\n`);
41
+ process.stdout.write(`Última revogação remota: ${profileState.lastRemoteRevokeAt ?? "nunca"}\n`);
38
42
  if (git.branch) {
39
43
  process.stdout.write(`Branch: ${git.branch}\n`);
40
44
  }
41
45
  process.stdout.write(`Última sincronização: ${profileState.lastSyncAt ?? "nunca"}\n`);
42
46
  process.stdout.write(`Status do último sync: ${profileState.lastSyncStatus ?? "desconhecido"}\n`);
43
- process.stdout.write(`Último login: ${profileState.lastLoginAt ?? "desconhecido"}\n`);
44
- process.stdout.write(`Último logout: ${profileState.lastLogoutAt ?? "nunca"}\n`);
45
47
  if (profileState.lastSnapshotPath) {
46
48
  process.stdout.write(`Último snapshot local: ${profileState.lastSnapshotPath}\n`);
47
49
  }
50
+ if (profileState.lastAuthFailure) {
51
+ process.stdout.write(`Última falha de auth: ${profileState.lastAuthFailure}\n`);
52
+ process.stdout.write(`Falha registrada em: ${profileState.lastAuthFailureAt ?? "desconhecido"}\n`);
53
+ }
48
54
  if (profileState.lastError) {
49
55
  process.stdout.write(`Último erro: ${profileState.lastError}\n`);
50
56
  }
@@ -157,6 +157,12 @@ function printObservability(payload) {
157
157
  process.stdout.write(`Delegadas: ${String(payload.delegatedTasks ?? 0)}\n`);
158
158
  process.stdout.write(`Success rate: ${String(payload.successRate ?? 0)}\n`);
159
159
  }
160
+ function printWatchEvent(event) {
161
+ const scope = event.taskId ? `task=${event.taskId}` : `workspace=${event.workspaceId}`;
162
+ const step = event.stepId ? ` step=${event.stepId}` : "";
163
+ const summary = event.summary ? ` ${JSON.stringify(event.summary)}` : "";
164
+ process.stdout.write(`[${event.timestamp}] ${event.type} ${scope}${step}${summary}\n`);
165
+ }
160
166
  async function runTaskList(cliVersion, args) {
161
167
  const { parsed, apiClient, freshSession, workspace } = await resolveAuthenticatedWorkspace(cliVersion, args);
162
168
  const tasks = await apiClient.listTasks(freshSession.accessToken, workspace);
@@ -433,6 +439,40 @@ async function runTaskObservability(cliVersion, args) {
433
439
  }
434
440
  return 0;
435
441
  }
442
+ async function runTaskWatch(cliVersion, args) {
443
+ const { apiClient, freshSession, workspace } = await resolveAuthenticatedWorkspace(cliVersion, args);
444
+ const parsed = parseArgs(args);
445
+ const taskId = requirePositional(parsed, 0, "Uso: vp task watch <taskId> [--json]");
446
+ const task = await apiClient.getTask(freshSession.accessToken, workspace, taskId);
447
+ if (printJsonIfRequested(parsed, task)) {
448
+ return 0;
449
+ }
450
+ printTask(task);
451
+ const controller = new AbortController();
452
+ let exitCode = 0;
453
+ await apiClient.streamWorkspaceEvents(freshSession.accessToken, workspace, {
454
+ taskId,
455
+ signal: controller.signal,
456
+ onEvent: (event) => {
457
+ if (event.type === "ready" || event.type === "heartbeat") {
458
+ return;
459
+ }
460
+ printWatchEvent(event);
461
+ if (event.type === "task.failed" || event.type === "task.blocked") {
462
+ exitCode = 1;
463
+ }
464
+ if (event.type === "task.completed" || event.type === "task.failed" || event.type === "task.blocked") {
465
+ controller.abort();
466
+ }
467
+ },
468
+ }).catch((error) => {
469
+ if (controller.signal.aborted) {
470
+ return;
471
+ }
472
+ throw error;
473
+ });
474
+ return exitCode;
475
+ }
436
476
  async function runTaskHealth(cliVersion, args) {
437
477
  const { apiClient, freshSession, workspace } = await resolveAuthenticatedWorkspace(cliVersion, args);
438
478
  const parsed = parseArgs(args);
@@ -465,7 +505,7 @@ async function runTaskApproval(cliVersion, args, approved) {
465
505
  }
466
506
  export async function runTaskCommand(cliVersion, args) {
467
507
  const parsed = parseArgs(args);
468
- const subcommand = requirePositional(parsed, 0, "Uso: vp task <run|templates|list|inspect|claim|execute|daemon|approve|reject|delegate|step-update|handoff|observability|health> [...]");
508
+ const subcommand = requirePositional(parsed, 0, "Uso: vp task <run|templates|list|inspect|watch|claim|execute|daemon|approve|reject|delegate|step-update|handoff|observability|health> [...]");
469
509
  switch (subcommand) {
470
510
  case "run":
471
511
  return runTaskRun(cliVersion, args.slice(1));
@@ -475,6 +515,8 @@ export async function runTaskCommand(cliVersion, args) {
475
515
  return runTaskList(cliVersion, args.slice(1));
476
516
  case "inspect":
477
517
  return runTaskInspect(cliVersion, args.slice(1));
518
+ case "watch":
519
+ return runTaskWatch(cliVersion, args.slice(1));
478
520
  case "claim":
479
521
  return runTaskClaim(cliVersion, args.slice(1));
480
522
  case "execute":
@@ -496,7 +538,7 @@ export async function runTaskCommand(cliVersion, args) {
496
538
  case "health":
497
539
  return runTaskHealth(cliVersion, args.slice(1));
498
540
  default:
499
- throw new ValidationError("Uso: vp task <run|templates|list|inspect|claim|execute|daemon|approve|reject|delegate|step-update|handoff|observability|health> [...]");
541
+ throw new ValidationError("Uso: vp task <run|templates|list|inspect|watch|claim|execute|daemon|approve|reject|delegate|step-update|handoff|observability|health> [...]");
500
542
  }
501
543
  }
502
544
  //# sourceMappingURL=task.js.map
@@ -39,6 +39,20 @@ function printPolicyRule(rule) {
39
39
  process.stdout.write(`Required capabilities: ${rule.conditions.requiredCapabilities.join(", ")}\n`);
40
40
  }
41
41
  }
42
+ function printWebhook(hook) {
43
+ process.stdout.write(`Webhook: ${hook.id}\n`);
44
+ process.stdout.write(`Nome: ${hook.name}\n`);
45
+ process.stdout.write(`URL: ${hook.targetUrl}\n`);
46
+ process.stdout.write(`Enabled: ${hook.enabled ? "true" : "false"}\n`);
47
+ process.stdout.write(`Secret: ${hook.secretPreview}\n`);
48
+ process.stdout.write(`Eventos: ${hook.events.join(", ")}\n`);
49
+ if (hook.lastDeliveryStatus) {
50
+ process.stdout.write(`Última entrega: ${hook.lastDeliveryStatus}${hook.lastDeliveryAt ? ` em ${hook.lastDeliveryAt}` : ""}\n`);
51
+ }
52
+ if (hook.lastError) {
53
+ process.stdout.write(`Erro: ${hook.lastError}\n`);
54
+ }
55
+ }
42
56
  function resolvePolicyConditions(parsed) {
43
57
  const archetypes = getStringListOption(parsed, "archetypes");
44
58
  const requiredCapabilities = getStringListOption(parsed, "required-capabilities");
@@ -155,6 +169,54 @@ async function runWorkspacePolicyCommand(args) {
155
169
  }
156
170
  throw new ValidationError("Subcomando de workspace policy não suportado.");
157
171
  }
172
+ async function runWorkspaceWebhookCommand(args) {
173
+ const parsed = parseArgs(args);
174
+ const [subcommand] = parsed.positionals.slice(1);
175
+ const { session, apiClient, workspace } = await resolveWorkspaceApiContext();
176
+ if (!subcommand || subcommand === "list") {
177
+ const hooks = await apiClient.listWorkspaceWebhooks(session.accessToken, workspace);
178
+ if (printJsonIfRequested(parsed, hooks)) {
179
+ return 0;
180
+ }
181
+ if (hooks.length === 0) {
182
+ process.stdout.write("VectorPlane: nenhum webhook encontrado.\n");
183
+ return 0;
184
+ }
185
+ for (const hook of hooks) {
186
+ process.stdout.write(`${hook.id} | ${hook.enabled ? "enabled" : "disabled"} | ${hook.name} | ${hook.targetUrl}\n`);
187
+ }
188
+ return 0;
189
+ }
190
+ if (subcommand === "create") {
191
+ const name = getStringOption(parsed, "name")?.trim();
192
+ const targetUrl = getStringOption(parsed, "url")?.trim();
193
+ const secret = getStringOption(parsed, "secret")?.trim();
194
+ const events = getStringListOption(parsed, "events");
195
+ if (!name || !targetUrl || !secret || events.length === 0) {
196
+ throw new ValidationError("Informe `--name`, `--url`, `--secret` e `--events`.");
197
+ }
198
+ const hook = await apiClient.createWorkspaceWebhook(session.accessToken, workspace, {
199
+ name,
200
+ targetUrl,
201
+ secret,
202
+ events,
203
+ enabled: parsed.options.enabled === undefined ? true : getBooleanOption(parsed, "enabled"),
204
+ });
205
+ if (!printJsonIfRequested(parsed, hook)) {
206
+ printWebhook(hook);
207
+ }
208
+ return 0;
209
+ }
210
+ if (subcommand === "delete") {
211
+ const webhookId = requirePositional(parsed, 2, "Informe o identificador do webhook.");
212
+ const result = await apiClient.deleteWorkspaceWebhook(session.accessToken, workspace, webhookId);
213
+ if (!printJsonIfRequested(parsed, result)) {
214
+ process.stdout.write(`Webhook removido: ${result.webhookId}\n`);
215
+ }
216
+ return 0;
217
+ }
218
+ throw new ValidationError("Subcomando de workspace webhook não suportado.");
219
+ }
158
220
  export async function runWorkspaceCommand(args) {
159
221
  const parsed = parseArgs(args);
160
222
  const [subcommand] = parsed.positionals;
@@ -214,6 +276,9 @@ export async function runWorkspaceCommand(args) {
214
276
  if (subcommand === "policy") {
215
277
  return runWorkspacePolicyCommand(args);
216
278
  }
279
+ if (subcommand === "webhook") {
280
+ return runWorkspaceWebhookCommand(args);
281
+ }
217
282
  throw new ValidationError("Subcomando de workspace não suportado.");
218
283
  }
219
284
  //# sourceMappingURL=workspace.js.map
@@ -1,6 +1,6 @@
1
1
  import type { Logger } from "./logger.js";
2
2
  import type { CurrentUserResponse, AuthCodeExchangeRequest, AuthTokenExchangeResponse, CreateLoginAttemptRequest, CreateLoginAttemptResponse, LoginAttemptStatusResponse, RefreshTokenRequest } from "../types/auth.js";
3
- import type { AgentCheckoutRequest, AgentHeartbeatRequest, AgentSessionRequest, AgentSessionResponse, EventRequest, MemoryDraftCreateRequest, MemoryDraftRecord, MemoryDraftStatus, MemorySearchRecord, ClaimableTaskStepRecord, ClaimedTaskStepResponse, TaskCreateRequest, TaskHandoffRecord, TaskObservabilityRecord, TaskRecord, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse, WorkspaceAgentSetup, WorkspacePolicyRuleRecord, AgentRegistryRecord, TaskTemplateRecord } from "../types/api.js";
3
+ import type { AgentCheckoutRequest, AgentHeartbeatRequest, AgentSessionRequest, AgentSessionResponse, EventRequest, MemoryDraftCreateRequest, MemoryDraftRecord, MemoryDraftStatus, MemorySearchRecord, ClaimableTaskStepRecord, ClaimedTaskStepResponse, TaskCreateRequest, TaskHandoffRecord, TaskObservabilityRecord, TaskRecord, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse, WorkspaceAgentSetup, WorkspaceSnapshotDiffRecord, WorkspacePolicyRuleRecord, WorkspaceWebhookRecord, AgentRegistryRecord, TaskTemplateRecord, WorkspaceStreamEventRecord } from "../types/api.js";
4
4
  export declare class VectorPlaneApiClient {
5
5
  private readonly apiBaseUrl;
6
6
  private readonly timeoutMs;
@@ -20,6 +20,10 @@ export declare class VectorPlaneApiClient {
20
20
  resolveWorkspaceByRepo(accessToken: string, payload: ResolveWorkspaceRequest): Promise<ResolveWorkspaceResponse>;
21
21
  getWorkspaceContext(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
22
22
  getWorkspaceSnapshot(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
23
+ getWorkspaceSnapshotDiff(accessToken: string, workspaceId: string, params: {
24
+ from?: string;
25
+ to?: string;
26
+ }): Promise<WorkspaceSnapshotDiffRecord>;
23
27
  getWorkspaceDeliveryContext(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
24
28
  getWorkspaceDeliveryContextSemantic(accessToken: string, workspaceId: string, params: {
25
29
  query: string;
@@ -49,6 +53,18 @@ export declare class VectorPlaneApiClient {
49
53
  approvalRole?: string | null;
50
54
  enabled?: boolean;
51
55
  }): Promise<WorkspacePolicyRuleRecord>;
56
+ listWorkspaceWebhooks(accessToken: string, workspaceRef: string): Promise<WorkspaceWebhookRecord[]>;
57
+ createWorkspaceWebhook(accessToken: string, workspaceRef: string, payload: {
58
+ name: string;
59
+ targetUrl: string;
60
+ secret: string;
61
+ events: WorkspaceWebhookRecord["events"];
62
+ enabled?: boolean;
63
+ }): Promise<WorkspaceWebhookRecord>;
64
+ deleteWorkspaceWebhook(accessToken: string, workspaceRef: string, webhookId: string): Promise<{
65
+ deleted: boolean;
66
+ webhookId: string;
67
+ }>;
52
68
  listMemoryDrafts(accessToken: string, workspaceRef: string, status?: MemoryDraftStatus): Promise<MemoryDraftRecord[]>;
53
69
  createMemoryDraft(accessToken: string, workspaceRef: string, payload: MemoryDraftCreateRequest): Promise<MemoryDraftRecord>;
54
70
  searchWorkspaceMemory(accessToken: string, workspaceRef: string, params: {
@@ -119,4 +135,10 @@ export declare class VectorPlaneApiClient {
119
135
  checkOutAgent(accessToken: string, payload: AgentCheckoutRequest): Promise<AgentSessionResponse>;
120
136
  getHealth(): Promise<Record<string, unknown>>;
121
137
  getReady(): Promise<Record<string, unknown>>;
138
+ streamWorkspaceEvents(accessToken: string, workspaceRef: string, params: {
139
+ taskId?: string;
140
+ types?: string[];
141
+ signal?: AbortSignal;
142
+ onEvent: (event: WorkspaceStreamEventRecord) => void;
143
+ }): Promise<void>;
122
144
  }