@vectorplane/ctrl-cli 0.1.9 → 0.1.11

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
@@ -13,15 +13,28 @@ npm install -g @vectorplane/ctrl-cli
13
13
  ## Uso
14
14
 
15
15
  ```bash
16
+ vp init
16
17
  vp login
17
18
  vp logout
18
19
  vp status
19
20
  vp sync
21
+ vp context --workspace <workspace> --delivery
22
+ 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"
20
23
  vp draft create --type progress --title "Entrega concluída" --content "Resumo da mudança"
21
24
  ```
22
25
 
23
26
  ## Comandos
24
27
 
28
+ ### `vp init`
29
+
30
+ - autentica localmente se ainda não houver sessão
31
+ - 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
32
+ - detecta o agente local automaticamente, pede confirmação se houver ambiguidade e aceita `--agent`
33
+ - baixa o template oficial do backend
34
+ - grava o arquivo local e adiciona a entrada no `.gitignore`
35
+ - aceita `--force` para sobrescrever template existente
36
+ - durante execução, se outro agente passar a operar no workspace e a detecção for confiável, o setup local é reconciliado automaticamente
37
+
25
38
  ### `vp login`
26
39
 
27
40
  - abre o navegador para autenticação
@@ -53,6 +66,12 @@ vp draft create --type progress --title "Entrega concluída" --content "Resumo d
53
66
  - suporta `ui`, `ux`, `project_skeleton`, `template_engineering`, `patterns`, `progress`, `decisions` e `architecture`
54
67
  - aceita `--no-impact` para registrar explicitamente quando uma lane não foi afetada
55
68
 
69
+ ### `vp session`
70
+
71
+ - suporta `check-in`, `heartbeat` e `check-out`
72
+ - permite declarar `feature`, `task`, `component`, `role`, `owning-path`, `need`, `provide` e `status`
73
+ - deve ser usado em conjunto com `vp context --delivery` para reduzir conflito entre agentes ativos
74
+
56
75
  ### Comandos adicionais já implementados
57
76
 
58
77
  - `vp whoami`
@@ -7,6 +7,7 @@ import { ensureFreshSession, resolveWorkspaceSlug } from "../core/session.js";
7
7
  import { VectorPlaneApiClient } from "../core/api.js";
8
8
  import { getBoundWorkspace, resolveWorkspaceRoot } from "../core/workspace-binding.js";
9
9
  import { ValidationError } from "../core/errors.js";
10
+ import { ensureAgentSetupCurrent } from "../core/agent-setup.js";
10
11
  export async function runContextCommand(cliVersion, args) {
11
12
  const parsed = parseArgs(args);
12
13
  const delivery = getBooleanOption(parsed, "delivery");
@@ -35,6 +36,15 @@ export async function runContextCommand(cliVersion, args) {
35
36
  if (!workspace) {
36
37
  throw new ValidationError("Nenhum workspace resolvido para carregar contexto.");
37
38
  }
39
+ await ensureAgentSetupCurrent({
40
+ rootPath,
41
+ workspace,
42
+ repoUrl: git.remoteOrigin,
43
+ source: git.remoteOrigin ? "api-resolve" : "manual",
44
+ accessToken: freshSession.accessToken,
45
+ apiClient,
46
+ logger: runtime.logger,
47
+ });
38
48
  const payload = delivery
39
49
  ? await apiClient.getWorkspaceDeliveryContext(freshSession.accessToken, workspace)
40
50
  : snapshot
@@ -0,0 +1 @@
1
+ export declare function runInitCommand(cliVersion: string, args: string[]): Promise<number>;
@@ -0,0 +1,79 @@
1
+ import { getBooleanOption, getStringOption, parseArgs } from "../core/cli.js";
2
+ import { ensureSessionAvailable, loadSession, upsertProfile } from "../core/config.js";
3
+ import { collectGitContext } from "../core/git.js";
4
+ import { runLoginCommand } from "./login.js";
5
+ import { collectMachineContext, collectRuntimeContext } from "../core/machine.js";
6
+ import { loadRuntimeStatus } from "../core/runtime.js";
7
+ import { ensureFreshSession } from "../core/session.js";
8
+ import { VectorPlaneApiClient } from "../core/api.js";
9
+ import { ensureAgentSetupCurrent } from "../core/agent-setup.js";
10
+ import { resolveWorkspaceRoot } from "../core/workspace-binding.js";
11
+ import { resolveAuthorizedWorkspace } from "../core/workspace-resolution.js";
12
+ export async function runInitCommand(cliVersion, args) {
13
+ const parsed = parseArgs(args);
14
+ const force = getBooleanOption(parsed, "force");
15
+ const requestedAgent = getStringOption(parsed, "agent");
16
+ const explicitWorkspace = getStringOption(parsed, "workspace");
17
+ const loginArgs = [];
18
+ if (getBooleanOption(parsed, "manual")) {
19
+ loginArgs.push("--manual");
20
+ }
21
+ if (getBooleanOption(parsed, "no-browser")) {
22
+ loginArgs.push("--no-browser");
23
+ }
24
+ const requestedProfile = getStringOption(parsed, "profile");
25
+ if (requestedProfile) {
26
+ loginArgs.push("--profile", requestedProfile);
27
+ }
28
+ let runtime = await loadRuntimeStatus();
29
+ let session = await loadSession(runtime.profile.name);
30
+ if (!session) {
31
+ runtime.logger.info("sessão local ausente, iniciando login...");
32
+ await runLoginCommand(cliVersion, loginArgs);
33
+ runtime = await loadRuntimeStatus();
34
+ session = await ensureSessionAvailable(runtime.profile.name);
35
+ }
36
+ const [git, machine, runtimeContext] = await Promise.all([
37
+ collectGitContext(process.cwd()),
38
+ collectMachineContext(runtime.device, runtime.config),
39
+ collectRuntimeContext(cliVersion, "init", process.argv.slice(2)),
40
+ ]);
41
+ const apiClient = new VectorPlaneApiClient(runtime.profile.apiBaseUrl, runtime.config.requestTimeoutMs, runtime.logger);
42
+ const freshSession = await ensureFreshSession({
43
+ profileName: runtime.profile.name,
44
+ session,
45
+ machine,
46
+ runtime: runtimeContext,
47
+ device: runtime.device,
48
+ apiClient,
49
+ logger: runtime.logger,
50
+ });
51
+ const rootPath = resolveWorkspaceRoot(process.cwd(), git);
52
+ const workspace = await resolveAuthorizedWorkspace({
53
+ apiClient,
54
+ accessToken: freshSession.accessToken,
55
+ git,
56
+ explicitWorkspace: explicitWorkspace ?? undefined,
57
+ profile: runtime.profile,
58
+ session: freshSession,
59
+ });
60
+ const result = await ensureAgentSetupCurrent({
61
+ rootPath,
62
+ workspace,
63
+ repoUrl: git.remoteOrigin,
64
+ source: git.remoteOrigin ? "api-resolve" : "manual",
65
+ accessToken: freshSession.accessToken,
66
+ apiClient,
67
+ logger: runtime.logger,
68
+ explicitAgent: requestedAgent,
69
+ force,
70
+ });
71
+ await upsertProfile(runtime.profile.name, { workspace });
72
+ runtime.logger.success("workspace inicializado.");
73
+ process.stdout.write(`Workspace: ${workspace}\n`);
74
+ process.stdout.write(`Agente configurado: ${result.agent}\n`);
75
+ process.stdout.write(`Confiança da detecção: ${result.confidence}\n`);
76
+ process.stdout.write(`Template: ${result.templatePath} (${result.fileStatus})\n`);
77
+ return 0;
78
+ }
79
+ //# sourceMappingURL=init.js.map
@@ -1,4 +1,4 @@
1
- import { getStringOption, parseArgs } from "../core/cli.js";
1
+ import { getStringListOption, getStringOption, parseArgs } from "../core/cli.js";
2
2
  import { ensureSessionAvailable, getProfileState, updateProfileState } from "../core/config.js";
3
3
  import { collectGitContext } from "../core/git.js";
4
4
  import { collectMachineContext, collectRuntimeContext } from "../core/machine.js";
@@ -7,6 +7,26 @@ import { ensureFreshSession, resolveWorkspaceSlug } from "../core/session.js";
7
7
  import { VectorPlaneApiClient } from "../core/api.js";
8
8
  import { deriveClientInstanceId, getBoundWorkspace, resolveWorkspaceRoot } from "../core/workspace-binding.js";
9
9
  import { ValidationError } from "../core/errors.js";
10
+ import { ensureAgentSetupCurrent } from "../core/agent-setup.js";
11
+ function readCollaborationMetadata(parsed, rootPath) {
12
+ const owningPaths = getStringListOption(parsed, "owning-path");
13
+ const needs = getStringListOption(parsed, "need");
14
+ const provides = getStringListOption(parsed, "provide");
15
+ const relatedWorkspaceIds = getStringListOption(parsed, "related-workspace");
16
+ return {
17
+ source: "vp-cli",
18
+ rootPath,
19
+ featureKey: getStringOption(parsed, "feature"),
20
+ taskKey: getStringOption(parsed, "task"),
21
+ component: getStringOption(parsed, "component"),
22
+ role: getStringOption(parsed, "role"),
23
+ statusSummary: getStringOption(parsed, "status"),
24
+ owningPaths: owningPaths.length > 0 ? owningPaths : undefined,
25
+ needs: needs.length > 0 ? needs : undefined,
26
+ provides: provides.length > 0 ? provides : undefined,
27
+ relatedWorkspaceIds: relatedWorkspaceIds.length > 0 ? relatedWorkspaceIds : undefined,
28
+ };
29
+ }
10
30
  export async function runSessionCommand(cliVersion, args) {
11
31
  const parsed = parseArgs(args);
12
32
  const [subcommand] = parsed.positionals;
@@ -37,7 +57,17 @@ export async function runSessionCommand(cliVersion, args) {
37
57
  if (!workspace) {
38
58
  throw new ValidationError("Nenhum workspace resolvido para a sessão.");
39
59
  }
60
+ await ensureAgentSetupCurrent({
61
+ rootPath,
62
+ workspace,
63
+ repoUrl: git.remoteOrigin,
64
+ source: git.remoteOrigin ? "api-resolve" : "manual",
65
+ accessToken: freshSession.accessToken,
66
+ apiClient,
67
+ logger: runtime.logger,
68
+ });
40
69
  const clientInstanceId = getStringOption(parsed, "client-instance-id") ?? deriveClientInstanceId(runtime.device.machineId, rootPath, git);
70
+ const metadata = readCollaborationMetadata(parsed, rootPath);
41
71
  if (subcommand === "check-in") {
42
72
  const response = await apiClient.checkInAgent(freshSession.accessToken, {
43
73
  workspaceId: workspace,
@@ -48,10 +78,7 @@ export async function runSessionCommand(cliVersion, args) {
48
78
  repoUrl: git.remoteOrigin ?? undefined,
49
79
  branch: git.branch,
50
80
  commitSha: git.commitHash,
51
- metadata: {
52
- source: "vp-cli",
53
- rootPath,
54
- },
81
+ metadata,
55
82
  });
56
83
  const sessionId = String(response.id ?? response.sessionId ?? "");
57
84
  await updateProfileState(runtime.profile.name, {
@@ -62,6 +89,12 @@ export async function runSessionCommand(cliVersion, args) {
62
89
  });
63
90
  process.stdout.write(`Session ID: ${sessionId || "não retornado"}\n`);
64
91
  process.stdout.write(`Client Instance ID: ${clientInstanceId}\n`);
92
+ if (metadata.featureKey) {
93
+ process.stdout.write(`Feature: ${metadata.featureKey}\n`);
94
+ }
95
+ if (metadata.taskKey) {
96
+ process.stdout.write(`Task: ${metadata.taskKey}\n`);
97
+ }
65
98
  return 0;
66
99
  }
67
100
  if (subcommand === "heartbeat") {
@@ -74,16 +107,16 @@ export async function runSessionCommand(cliVersion, args) {
74
107
  clientInstanceId,
75
108
  branch: git.branch,
76
109
  commitSha: git.commitHash,
77
- metadata: {
78
- source: "vp-cli",
79
- rootPath,
80
- },
110
+ metadata,
81
111
  });
82
112
  await updateProfileState(runtime.profile.name, {
83
113
  lastCommand: "session:heartbeat",
84
114
  lastError: null,
85
115
  });
86
116
  process.stdout.write(`Heartbeat enviado para ${sessionId}\n`);
117
+ if (metadata.statusSummary) {
118
+ process.stdout.write(`Status: ${metadata.statusSummary}\n`);
119
+ }
87
120
  return 0;
88
121
  }
89
122
  if (subcommand === "check-out") {
@@ -95,6 +128,9 @@ export async function runSessionCommand(cliVersion, args) {
95
128
  sessionId,
96
129
  metadata: {
97
130
  source: "vp-cli",
131
+ featureKey: metadata.featureKey,
132
+ taskKey: metadata.taskKey,
133
+ statusSummary: metadata.statusSummary,
98
134
  },
99
135
  });
100
136
  await updateProfileState(runtime.profile.name, {
@@ -10,6 +10,7 @@ import { VectorPlaneApiClient } from "../core/api.js";
10
10
  import { collectWorkspaceContext } from "../core/workspace.js";
11
11
  import { getBoundWorkspace } from "../core/workspace-binding.js";
12
12
  import { ValidationError } from "../core/errors.js";
13
+ import { ensureAgentSetupCurrent } from "../core/agent-setup.js";
13
14
  export async function runSyncCommand(cliVersion, args) {
14
15
  const parsed = parseArgs(args);
15
16
  const force = getBooleanOption(parsed, "force");
@@ -39,6 +40,15 @@ export async function runSyncCommand(cliVersion, args) {
39
40
  if (!workspaceSlug) {
40
41
  throw new ValidationError("Nenhum workspace ativo foi resolvido. Use `vp workspace use <workspace>` ou `vp login`.");
41
42
  }
43
+ await ensureAgentSetupCurrent({
44
+ rootPath: workspaceRoot,
45
+ workspace: workspaceSlug,
46
+ repoUrl: git.remoteOrigin,
47
+ source: git.remoteOrigin ? "api-resolve" : "manual",
48
+ accessToken: freshSession.accessToken,
49
+ apiClient,
50
+ logger: runtime.logger,
51
+ });
42
52
  const workspace = await collectWorkspaceContext(process.cwd(), git);
43
53
  runtime.logger.info("consolidando contexto...");
44
54
  const timestamp = new Date().toISOString();
@@ -0,0 +1,32 @@
1
+ import type { VectorPlaneApiClient } from "./api.js";
2
+ import type { Logger } from "./logger.js";
3
+ export type SupportedAgent = "claude" | "cursor" | "windsurf" | "copilot" | "codex" | "generic";
4
+ export type DetectionConfidence = "high" | "medium" | "low";
5
+ export interface AgentDetectionResult {
6
+ selected: SupportedAgent | null;
7
+ confidence: DetectionConfidence;
8
+ reasons: string[];
9
+ candidates: SupportedAgent[];
10
+ }
11
+ export declare function detectAgentRuntime(rootPath: string): Promise<AgentDetectionResult>;
12
+ export declare function resolveAgentSelection(rootPath: string, explicitAgent?: SupportedAgent): Promise<{
13
+ agent: SupportedAgent;
14
+ detection: AgentDetectionResult;
15
+ }>;
16
+ export declare function ensureAgentSetupCurrent(params: {
17
+ rootPath: string;
18
+ workspace: string;
19
+ repoUrl: string | null;
20
+ source: "manual" | "api-resolve" | "session";
21
+ accessToken: string;
22
+ apiClient: VectorPlaneApiClient;
23
+ logger: Logger;
24
+ explicitAgent?: SupportedAgent;
25
+ force?: boolean;
26
+ }): Promise<{
27
+ agent: SupportedAgent;
28
+ templatePath: string;
29
+ fileStatus: "created" | "updated" | "unchanged";
30
+ changed: boolean;
31
+ confidence: DetectionConfidence;
32
+ }>;
@@ -0,0 +1,226 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import path from "node:path";
4
+ import readline from "node:readline/promises";
5
+ import { ValidationError } from "./errors.js";
6
+ import { bindWorkspaceToRoot, getWorkspaceBinding } from "./workspace-binding.js";
7
+ const AGENT_TEMPLATE_PATHS = {
8
+ claude: "CLAUDE.md",
9
+ cursor: ".cursor/rules/traceplane.mdc",
10
+ windsurf: ".windsurfrules",
11
+ copilot: ".github/copilot-instructions.md",
12
+ codex: "AGENTS.md",
13
+ generic: "AGENTS.md",
14
+ };
15
+ async function pathExists(filePath) {
16
+ try {
17
+ await access(filePath, fsConstants.F_OK);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function uniqueAgents(values) {
25
+ return [...new Set(values)];
26
+ }
27
+ export async function detectAgentRuntime(rootPath) {
28
+ const scores = new Map();
29
+ const reasons = new Map();
30
+ const addScore = (agent, amount, reason) => {
31
+ scores.set(agent, (scores.get(agent) ?? 0) + amount);
32
+ reasons.set(agent, [...(reasons.get(agent) ?? []), reason]);
33
+ };
34
+ const fileCandidates = [
35
+ { agent: "claude", file: path.join(rootPath, "CLAUDE.md") },
36
+ { agent: "cursor", file: path.join(rootPath, ".cursor") },
37
+ { agent: "windsurf", file: path.join(rootPath, ".windsurfrules") },
38
+ { agent: "copilot", file: path.join(rootPath, ".github", "copilot-instructions.md") },
39
+ { agent: "codex", file: path.join(rootPath, "AGENTS.md") },
40
+ ];
41
+ for (const candidate of fileCandidates) {
42
+ if (await pathExists(candidate.file)) {
43
+ addScore(candidate.agent, 25, `file:${path.relative(rootPath, candidate.file) || candidate.file}`);
44
+ }
45
+ }
46
+ const envSignals = [
47
+ { agent: "cursor", value: process.env.CURSOR_TRACE_ID, reason: "env:CURSOR_TRACE_ID" },
48
+ { agent: "windsurf", value: process.env.WINDSURF_SESSION_ID, reason: "env:WINDSURF_SESSION_ID" },
49
+ { agent: "codex", value: process.env.CODEX_ENV, reason: "env:CODEX_ENV" },
50
+ { agent: "claude", value: process.env.CLAUDECODE, reason: "env:CLAUDECODE" },
51
+ { agent: "copilot", value: process.env.GITHUB_COPILOT, reason: "env:GITHUB_COPILOT" },
52
+ ];
53
+ for (const signal of envSignals) {
54
+ if (signal.value) {
55
+ addScore(signal.agent, 100, signal.reason);
56
+ }
57
+ }
58
+ const ranked = [...scores.entries()].sort((left, right) => right[1] - left[1]);
59
+ if (ranked.length === 0) {
60
+ return {
61
+ selected: null,
62
+ confidence: "low",
63
+ reasons: [],
64
+ candidates: [],
65
+ };
66
+ }
67
+ const topScore = ranked[0][1];
68
+ const candidates = uniqueAgents(ranked.filter((entry) => entry[1] === topScore).map(([agent]) => agent));
69
+ const selected = candidates.length === 1 ? candidates[0] : null;
70
+ return {
71
+ selected,
72
+ confidence: topScore >= 100 ? "high" : candidates.length === 1 ? "medium" : "low",
73
+ reasons: selected ? (reasons.get(selected) ?? []) : candidates.flatMap((agent) => reasons.get(agent) ?? []),
74
+ candidates,
75
+ };
76
+ }
77
+ async function promptForAgent(detection) {
78
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
79
+ throw new ValidationError("Não foi possível detectar o agente local com segurança. Use --agent claude|cursor|windsurf|copilot|codex|generic.");
80
+ }
81
+ const ordered = uniqueAgents([
82
+ ...detection.candidates,
83
+ "claude",
84
+ "cursor",
85
+ "windsurf",
86
+ "copilot",
87
+ "codex",
88
+ "generic",
89
+ ]);
90
+ process.stdout.write("VectorPlane: não foi possível identificar o agente local com segurança.\n");
91
+ ordered.forEach((agent, index) => {
92
+ process.stdout.write(` ${index + 1}. ${agent}\n`);
93
+ });
94
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
95
+ try {
96
+ const answer = (await rl.question("Selecione o agente local: ")).trim();
97
+ const index = Number(answer);
98
+ if (Number.isInteger(index) && index >= 1 && index <= ordered.length) {
99
+ return ordered[index - 1];
100
+ }
101
+ if (ordered.includes(answer)) {
102
+ return answer;
103
+ }
104
+ }
105
+ finally {
106
+ rl.close();
107
+ }
108
+ throw new ValidationError("Seleção de agente inválida.");
109
+ }
110
+ async function appendGitIgnoreEntry(rootPath, relativeTarget) {
111
+ const gitIgnorePath = path.join(rootPath, ".gitignore");
112
+ const normalizedEntry = relativeTarget.replace(/\\/g, "/");
113
+ const current = await readFile(gitIgnorePath, "utf8").catch(() => "");
114
+ const lines = current.split(/\r?\n/).map((line) => line.trim());
115
+ if (lines.includes(normalizedEntry)) {
116
+ return;
117
+ }
118
+ const next = current.trimEnd();
119
+ const updated = `${next}${next ? "\n" : ""}${normalizedEntry}\n`;
120
+ await writeFile(gitIgnorePath, updated, "utf8");
121
+ }
122
+ async function writeTemplateFile(rootPath, relativeTarget, content, force) {
123
+ const absoluteTarget = path.join(rootPath, relativeTarget);
124
+ await mkdir(path.dirname(absoluteTarget), { recursive: true });
125
+ if (await pathExists(absoluteTarget)) {
126
+ const current = await readFile(absoluteTarget, "utf8");
127
+ if (current === content) {
128
+ return "unchanged";
129
+ }
130
+ if (!force) {
131
+ throw new ValidationError(`O arquivo ${relativeTarget} já existe. Use --force para sobrescrever.`);
132
+ }
133
+ await writeFile(absoluteTarget, content, "utf8");
134
+ return "updated";
135
+ }
136
+ await writeFile(absoluteTarget, content, "utf8");
137
+ return "created";
138
+ }
139
+ function getAgentTemplate(setup, agent) {
140
+ const template = setup.agents[agent];
141
+ if (!template) {
142
+ throw new ValidationError(`O backend não retornou template para o agente ${agent}.`);
143
+ }
144
+ return template;
145
+ }
146
+ export async function resolveAgentSelection(rootPath, explicitAgent) {
147
+ if (explicitAgent) {
148
+ return {
149
+ agent: explicitAgent,
150
+ detection: {
151
+ selected: explicitAgent,
152
+ confidence: "high",
153
+ reasons: ["explicit-flag"],
154
+ candidates: [explicitAgent],
155
+ },
156
+ };
157
+ }
158
+ const detection = await detectAgentRuntime(rootPath);
159
+ if (detection.selected && detection.confidence !== "low") {
160
+ return { agent: detection.selected, detection };
161
+ }
162
+ return {
163
+ agent: await promptForAgent(detection),
164
+ detection,
165
+ };
166
+ }
167
+ export async function ensureAgentSetupCurrent(params) {
168
+ const binding = await getWorkspaceBinding(params.rootPath);
169
+ let selection;
170
+ if (!params.explicitAgent && binding?.configuredAgent) {
171
+ const detection = await detectAgentRuntime(params.rootPath);
172
+ if (!detection.selected && detection.confidence === "low") {
173
+ selection = {
174
+ agent: binding.configuredAgent,
175
+ detection: {
176
+ selected: binding.configuredAgent,
177
+ confidence: "low",
178
+ reasons: ["binding-fallback"],
179
+ candidates: [binding.configuredAgent],
180
+ },
181
+ };
182
+ }
183
+ else {
184
+ selection = await resolveAgentSelection(params.rootPath, params.explicitAgent);
185
+ }
186
+ }
187
+ else {
188
+ selection = await resolveAgentSelection(params.rootPath, params.explicitAgent);
189
+ }
190
+ const { agent, detection } = selection;
191
+ const expectedPath = AGENT_TEMPLATE_PATHS[agent];
192
+ if (binding?.workspace === params.workspace && binding.configuredAgent === agent && binding.templatePath === expectedPath && await pathExists(path.join(params.rootPath, expectedPath))) {
193
+ return {
194
+ agent,
195
+ templatePath: expectedPath,
196
+ fileStatus: "unchanged",
197
+ changed: false,
198
+ confidence: detection.confidence,
199
+ };
200
+ }
201
+ const setup = await params.apiClient.getAgentSetup(params.accessToken, params.workspace);
202
+ const template = getAgentTemplate(setup, agent);
203
+ const fileStatus = await writeTemplateFile(params.rootPath, template.filename, template.content, params.force ?? false);
204
+ await appendGitIgnoreEntry(params.rootPath, template.filename);
205
+ await bindWorkspaceToRoot({
206
+ rootPath: params.rootPath,
207
+ workspace: params.workspace,
208
+ repoUrl: params.repoUrl,
209
+ source: params.source,
210
+ configuredAgent: agent,
211
+ agentConfidence: detection.confidence,
212
+ templatePath: template.filename,
213
+ agentConfiguredAt: new Date().toISOString(),
214
+ });
215
+ if (binding?.configuredAgent && binding.configuredAgent !== agent) {
216
+ params.logger.info(`agente ativo detectado mudou de ${binding.configuredAgent} para ${agent}; setup local atualizado.`);
217
+ }
218
+ return {
219
+ agent,
220
+ templatePath: template.filename,
221
+ fileStatus,
222
+ changed: true,
223
+ confidence: detection.confidence,
224
+ };
225
+ }
226
+ //# sourceMappingURL=agent-setup.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, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse } from "../types/api.js";
3
+ import type { AgentCheckoutRequest, AgentHeartbeatRequest, AgentSessionRequest, AgentSessionResponse, EventRequest, MemoryDraftCreateRequest, MemoryDraftRecord, MemoryDraftStatus, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse, WorkspaceAgentSetup } from "../types/api.js";
4
4
  export declare class VectorPlaneApiClient {
5
5
  private readonly apiBaseUrl;
6
6
  private readonly timeoutMs;
@@ -21,7 +21,7 @@ export declare class VectorPlaneApiClient {
21
21
  getWorkspaceContext(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
22
22
  getWorkspaceSnapshot(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
23
23
  getWorkspaceDeliveryContext(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
24
- getAgentSetup(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
24
+ getAgentSetup(accessToken: string, workspaceId: string): Promise<WorkspaceAgentSetup>;
25
25
  listMemoryDrafts(accessToken: string, workspaceRef: string, status?: MemoryDraftStatus): Promise<MemoryDraftRecord[]>;
26
26
  createMemoryDraft(accessToken: string, workspaceRef: string, payload: MemoryDraftCreateRequest): Promise<MemoryDraftRecord>;
27
27
  checkInAgent(accessToken: string, payload: AgentSessionRequest): Promise<AgentSessionResponse>;
@@ -5,5 +5,6 @@ export interface ParsedArgs {
5
5
  export declare function parseArgs(args: string[]): ParsedArgs;
6
6
  export declare function getStringOption(parsed: ParsedArgs, key: string): string | undefined;
7
7
  export declare function getBooleanOption(parsed: ParsedArgs, key: string): boolean;
8
+ export declare function getStringListOption(parsed: ParsedArgs, key: string): string[];
8
9
  export declare function requirePositional(parsed: ParsedArgs, index: number, message: string): string;
9
10
  export declare function parseJsonOption(value: string | undefined, fallback?: Record<string, unknown>): Record<string, unknown>;
package/dist/core/cli.js CHANGED
@@ -38,6 +38,16 @@ export function getBooleanOption(parsed, key) {
38
38
  }
39
39
  return false;
40
40
  }
41
+ export function getStringListOption(parsed, key) {
42
+ const value = getStringOption(parsed, key);
43
+ if (!value) {
44
+ return [];
45
+ }
46
+ return [...new Set(value
47
+ .split(",")
48
+ .map((item) => item.trim())
49
+ .filter(Boolean))];
50
+ }
41
51
  export function requirePositional(parsed, index, message) {
42
52
  const value = parsed.positionals[index];
43
53
  if (!value) {
@@ -3,12 +3,17 @@ import type { GitContext } from "../types/workspace.js";
3
3
  export declare function resolveWorkspaceRoot(cwd: string, git: GitContext): string;
4
4
  export declare function deriveClientInstanceId(machineId: string, workspaceRoot: string, git: GitContext): string;
5
5
  export declare function getBoundWorkspace(rootPath: string): Promise<string | null>;
6
+ export declare function getWorkspaceBinding(rootPath: string): Promise<import("../types/config.js").WorkspaceBinding>;
6
7
  export declare function getBindingStore(): Promise<WorkspaceBindingStore>;
7
8
  export declare function bindWorkspaceToRoot(params: {
8
9
  rootPath: string;
9
10
  workspace: string;
10
11
  repoUrl: string | null;
11
12
  source: "manual" | "api-resolve" | "session";
13
+ configuredAgent?: "claude" | "cursor" | "windsurf" | "copilot" | "codex" | "generic" | null;
14
+ agentConfidence?: "high" | "medium" | "low" | null;
15
+ templatePath?: string | null;
16
+ agentConfiguredAt?: string | null;
12
17
  }): Promise<void>;
13
18
  export declare function clearBoundWorkspace(rootPath: string): Promise<void>;
14
19
  export declare function resolveWorkspacePreference(profile: CliProfileConfig, boundWorkspace: string | null): string | null;
@@ -17,6 +17,10 @@ export async function getBoundWorkspace(rootPath) {
17
17
  const store = await loadWorkspaceBindings();
18
18
  return store.bindings[rootPath]?.workspace ?? null;
19
19
  }
20
+ export async function getWorkspaceBinding(rootPath) {
21
+ const store = await loadWorkspaceBindings();
22
+ return store.bindings[rootPath] ?? null;
23
+ }
20
24
  export async function getBindingStore() {
21
25
  return loadWorkspaceBindings();
22
26
  }
@@ -27,6 +31,10 @@ export async function bindWorkspaceToRoot(params) {
27
31
  repoUrl: params.repoUrl,
28
32
  resolvedAt: new Date().toISOString(),
29
33
  source: params.source,
34
+ configuredAgent: params.configuredAgent ?? null,
35
+ agentConfidence: params.agentConfidence ?? null,
36
+ templatePath: params.templatePath ?? null,
37
+ agentConfiguredAt: params.agentConfiguredAt ?? null,
30
38
  });
31
39
  }
32
40
  export async function clearBoundWorkspace(rootPath) {
@@ -0,0 +1,12 @@
1
+ import type { VectorPlaneApiClient } from "./api.js";
2
+ import type { AuthSession } from "../types/auth.js";
3
+ import type { CliProfileConfig } from "../types/config.js";
4
+ import type { GitContext } from "../types/workspace.js";
5
+ export declare function resolveAuthorizedWorkspace(params: {
6
+ apiClient: VectorPlaneApiClient;
7
+ accessToken: string;
8
+ git: GitContext;
9
+ explicitWorkspace?: string;
10
+ profile: CliProfileConfig;
11
+ session: AuthSession;
12
+ }): Promise<string>;
@@ -0,0 +1,24 @@
1
+ import { ValidationError } from "./errors.js";
2
+ export async function resolveAuthorizedWorkspace(params) {
3
+ if (params.git.remoteOrigin) {
4
+ const resolved = await params.apiClient.resolveWorkspaceByRepo(params.accessToken, {
5
+ repoUrl: params.git.remoteOrigin,
6
+ orgId: params.profile.orgId ?? undefined,
7
+ });
8
+ const resolvedWorkspace = String(resolved.workspaceId ?? resolved.id ?? resolved.slug ?? "");
9
+ const resolvedSlug = typeof resolved.slug === "string" ? resolved.slug : null;
10
+ if (params.explicitWorkspace && params.explicitWorkspace !== resolvedWorkspace && params.explicitWorkspace !== resolvedSlug) {
11
+ throw new ValidationError("O workspace solicitado não corresponde ao repositório remoto atual ou você não tem permissão para manipulá-lo.");
12
+ }
13
+ const workspace = resolvedWorkspace || resolvedSlug;
14
+ if (!workspace) {
15
+ throw new ValidationError("A API não retornou um identificador de workspace utilizável para o repositório atual.");
16
+ }
17
+ return workspace;
18
+ }
19
+ if (params.explicitWorkspace) {
20
+ return params.explicitWorkspace;
21
+ }
22
+ throw new ValidationError("Nenhum workspace resolvido para init. Configure um remote git correspondente ao workspace ou use --workspace com permissão válida.");
23
+ }
24
+ //# sourceMappingURL=workspace-resolution.js.map
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { runContextCommand } from "./commands/context.js";
7
7
  import { runDoctorCommand } from "./commands/doctor.js";
8
8
  import { runDraftCommand } from "./commands/draft.js";
9
9
  import { runEventCommand } from "./commands/event.js";
10
+ import { runInitCommand } from "./commands/init.js";
10
11
  import { runLoginCommand } from "./commands/login.js";
11
12
  import { runLogoutCommand } from "./commands/logout.js";
12
13
  import { runSessionCommand } from "./commands/session.js";
@@ -27,6 +28,7 @@ function printHelp() {
27
28
  process.stdout.write("Uso: vp <comando>\n");
28
29
  process.stdout.write("\n");
29
30
  process.stdout.write("Comandos principais:\n");
31
+ process.stdout.write(" init [--agent <tipo>] [--workspace <workspace>] [--force]\n");
30
32
  process.stdout.write(" login [--no-browser|--manual]\n");
31
33
  process.stdout.write(" logout\n");
32
34
  process.stdout.write(" sync [--force]\n");
@@ -52,6 +54,8 @@ export async function runCli(args) {
52
54
  switch (command) {
53
55
  case "login":
54
56
  return runLoginCommand(cliVersion, rest);
57
+ case "init":
58
+ return runInitCommand(cliVersion, rest);
55
59
  case "sync":
56
60
  return runSyncCommand(cliVersion, rest);
57
61
  case "logout":
@@ -6,6 +6,42 @@ export interface SyncRequest {
6
6
  hash: string;
7
7
  sent_at: string;
8
8
  }
9
+ export interface WorkspaceAgentSetupFile {
10
+ filename: string;
11
+ description?: string;
12
+ content: string;
13
+ }
14
+ export interface WorkspaceAgentSetupCli {
15
+ package: string;
16
+ command: string;
17
+ install: string;
18
+ login: string;
19
+ status: string;
20
+ sync: string;
21
+ workspaceUse: string;
22
+ bootstrap: string;
23
+ context: string;
24
+ deliveryContext: string;
25
+ draftCreate: string;
26
+ draftList: string;
27
+ sessionCheckIn: string;
28
+ sessionHeartbeat: string;
29
+ sessionCheckOut: string;
30
+ eventSend: string;
31
+ }
32
+ export interface WorkspaceAgentSetup {
33
+ workspaceId: string;
34
+ orgId: string;
35
+ mcpUrl: string;
36
+ apiUrl: string;
37
+ repoUrl: string | null;
38
+ cli: WorkspaceAgentSetupCli;
39
+ agents: Record<string, WorkspaceAgentSetupFile>;
40
+ hooks: Record<string, WorkspaceAgentSetupFile>;
41
+ githubActions: WorkspaceAgentSetupFile;
42
+ installScript: WorkspaceAgentSetupFile;
43
+ [key: string]: unknown;
44
+ }
9
45
  export interface SyncResponse {
10
46
  ok?: boolean;
11
47
  workspace?: string;
@@ -60,6 +60,10 @@ export interface WorkspaceBinding {
60
60
  repoUrl: string | null;
61
61
  resolvedAt: string;
62
62
  source: "manual" | "api-resolve" | "session";
63
+ configuredAgent?: "claude" | "cursor" | "windsurf" | "copilot" | "codex" | "generic" | null;
64
+ agentConfidence?: "high" | "medium" | "low" | null;
65
+ templatePath?: string | null;
66
+ agentConfiguredAt?: string | null;
63
67
  }
64
68
  export interface WorkspaceBindingStore {
65
69
  bindings: Record<string, WorkspaceBinding>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorplane/ctrl-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Official VectorPlane CLI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",