@vectorplane/ctrl-cli 0.1.10 → 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 +11 -0
- package/dist/commands/context.js +10 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +79 -0
- package/dist/commands/session.js +10 -0
- package/dist/commands/sync.js +10 -0
- package/dist/core/agent-setup.d.ts +32 -0
- package/dist/core/agent-setup.js +226 -0
- package/dist/core/api.d.ts +2 -2
- package/dist/core/workspace-binding.d.ts +5 -0
- package/dist/core/workspace-binding.js +8 -0
- package/dist/core/workspace-resolution.d.ts +12 -0
- package/dist/core/workspace-resolution.js +24 -0
- package/dist/index.js +4 -0
- package/dist/types/api.d.ts +36 -0
- package/dist/types/config.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ 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
|
|
@@ -24,6 +25,16 @@ vp draft create --type progress --title "Entrega concluída" --content "Resumo d
|
|
|
24
25
|
|
|
25
26
|
## Comandos
|
|
26
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
|
+
|
|
27
38
|
### `vp login`
|
|
28
39
|
|
|
29
40
|
- abre o navegador para autenticação
|
package/dist/commands/context.js
CHANGED
|
@@ -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
|
package/dist/commands/session.js
CHANGED
|
@@ -7,6 +7,7 @@ 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";
|
|
10
11
|
function readCollaborationMetadata(parsed, rootPath) {
|
|
11
12
|
const owningPaths = getStringListOption(parsed, "owning-path");
|
|
12
13
|
const needs = getStringListOption(parsed, "need");
|
|
@@ -56,6 +57,15 @@ export async function runSessionCommand(cliVersion, args) {
|
|
|
56
57
|
if (!workspace) {
|
|
57
58
|
throw new ValidationError("Nenhum workspace resolvido para a sessão.");
|
|
58
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
|
+
});
|
|
59
69
|
const clientInstanceId = getStringOption(parsed, "client-instance-id") ?? deriveClientInstanceId(runtime.device.machineId, rootPath, git);
|
|
60
70
|
const metadata = readCollaborationMetadata(parsed, rootPath);
|
|
61
71
|
if (subcommand === "check-in") {
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
package/dist/core/api.d.ts
CHANGED
|
@@ -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<
|
|
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>;
|
|
@@ -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":
|
package/dist/types/api.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -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>;
|