@vectorplane/ctrl-cli 0.1.11 → 0.1.13
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 +39 -0
- package/dist/commands/context.js +31 -15
- package/dist/commands/init.js +16 -60
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.js +116 -0
- package/dist/commands/session.js +2 -10
- package/dist/commands/sync.js +4 -5
- package/dist/commands/task.d.ts +1 -0
- package/dist/commands/task.js +544 -0
- package/dist/commands/workspace.js +219 -1
- package/dist/core/agent-runtime-detection.d.ts +13 -0
- package/dist/core/agent-runtime-detection.js +117 -0
- package/dist/core/agent-setup.d.ts +1 -13
- package/dist/core/agent-setup.js +1 -108
- package/dist/core/api.d.ts +113 -1
- package/dist/core/api.js +307 -0
- package/dist/core/init-workspace.d.ts +29 -0
- package/dist/core/init-workspace.js +73 -0
- package/dist/core/runtime-agent-setup.d.ts +18 -0
- package/dist/core/runtime-agent-setup.js +13 -0
- package/dist/core/task-runner.d.ts +16 -0
- package/dist/core/task-runner.js +62 -0
- package/dist/index.js +9 -1
- package/dist/types/api.d.ts +215 -0
- package/package.json +1 -1
|
@@ -1,10 +1,222 @@
|
|
|
1
|
-
import { getStringOption, parseArgs, requirePositional } from "../core/cli.js";
|
|
1
|
+
import { getBooleanOption, getStringListOption, getStringOption, parseArgs, requirePositional } from "../core/cli.js";
|
|
2
2
|
import { ensureSessionAvailable, upsertProfile } from "../core/config.js";
|
|
3
3
|
import { collectGitContext } from "../core/git.js";
|
|
4
4
|
import { loadRuntimeStatus } from "../core/runtime.js";
|
|
5
5
|
import { VectorPlaneApiClient } from "../core/api.js";
|
|
6
6
|
import { bindWorkspaceToRoot, clearBoundWorkspace, getBindingStore, resolveWorkspaceRoot } from "../core/workspace-binding.js";
|
|
7
7
|
import { ValidationError } from "../core/errors.js";
|
|
8
|
+
function printJsonIfRequested(parsed, payload) {
|
|
9
|
+
if (!getBooleanOption(parsed, "json")) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
function printPolicyRule(rule) {
|
|
16
|
+
process.stdout.write(`Rule: ${rule.id}\n`);
|
|
17
|
+
process.stdout.write(`Nome: ${rule.name}\n`);
|
|
18
|
+
process.stdout.write(`Effect: ${rule.effect}\n`);
|
|
19
|
+
process.stdout.write(`Enabled: ${rule.enabled ? "true" : "false"}\n`);
|
|
20
|
+
if (rule.pathPrefix) {
|
|
21
|
+
process.stdout.write(`Path: ${rule.pathPrefix}\n`);
|
|
22
|
+
}
|
|
23
|
+
if (rule.capability) {
|
|
24
|
+
process.stdout.write(`Capability: ${rule.capability}\n`);
|
|
25
|
+
}
|
|
26
|
+
if (rule.approvalRole) {
|
|
27
|
+
process.stdout.write(`Approval role: ${rule.approvalRole}\n`);
|
|
28
|
+
}
|
|
29
|
+
if (rule.rationale) {
|
|
30
|
+
process.stdout.write(`Rationale: ${rule.rationale}\n`);
|
|
31
|
+
}
|
|
32
|
+
if (rule.conditions?.archetypes?.length) {
|
|
33
|
+
process.stdout.write(`Archetypes: ${rule.conditions.archetypes.join(", ")}\n`);
|
|
34
|
+
}
|
|
35
|
+
if (typeof rule.conditions?.minTargetPathCount === "number") {
|
|
36
|
+
process.stdout.write(`Min target paths: ${rule.conditions.minTargetPathCount}\n`);
|
|
37
|
+
}
|
|
38
|
+
if (rule.conditions?.requiredCapabilities?.length) {
|
|
39
|
+
process.stdout.write(`Required capabilities: ${rule.conditions.requiredCapabilities.join(", ")}\n`);
|
|
40
|
+
}
|
|
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
|
+
}
|
|
56
|
+
function resolvePolicyConditions(parsed) {
|
|
57
|
+
const archetypes = getStringListOption(parsed, "archetypes");
|
|
58
|
+
const requiredCapabilities = getStringListOption(parsed, "required-capabilities");
|
|
59
|
+
const minTargetPathCountRaw = getStringOption(parsed, "min-target-path-count");
|
|
60
|
+
const minTargetPathCount = minTargetPathCountRaw ? Number(minTargetPathCountRaw) : undefined;
|
|
61
|
+
if (minTargetPathCountRaw && (!Number.isInteger(minTargetPathCount) || (minTargetPathCount ?? 0) < 1)) {
|
|
62
|
+
throw new ValidationError("`--min-target-path-count` deve ser um inteiro maior que zero.");
|
|
63
|
+
}
|
|
64
|
+
if (archetypes.length === 0 && requiredCapabilities.length === 0 && minTargetPathCount === undefined) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...(archetypes.length > 0 ? { archetypes } : {}),
|
|
69
|
+
...(requiredCapabilities.length > 0 ? { requiredCapabilities } : {}),
|
|
70
|
+
...(minTargetPathCount !== undefined ? { minTargetPathCount } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function resolveWorkspaceApiContext() {
|
|
74
|
+
const runtime = await loadRuntimeStatus();
|
|
75
|
+
const session = await ensureSessionAvailable(runtime.profile.name);
|
|
76
|
+
const git = await collectGitContext(process.cwd());
|
|
77
|
+
const rootPath = resolveWorkspaceRoot(process.cwd(), git);
|
|
78
|
+
const store = await getBindingStore();
|
|
79
|
+
const binding = store.bindings[rootPath];
|
|
80
|
+
const workspace = binding?.workspace ?? runtime.profile.workspace;
|
|
81
|
+
if (!workspace) {
|
|
82
|
+
throw new ValidationError("Nenhum workspace resolvido. Use `vp workspace use <workspace>` ou `vp workspace resolve`.");
|
|
83
|
+
}
|
|
84
|
+
const apiClient = new VectorPlaneApiClient(runtime.profile.apiBaseUrl, runtime.config.requestTimeoutMs, runtime.logger);
|
|
85
|
+
return { runtime, session, apiClient, workspace };
|
|
86
|
+
}
|
|
87
|
+
async function runWorkspacePolicyCommand(args) {
|
|
88
|
+
const parsed = parseArgs(args);
|
|
89
|
+
const [subcommand] = parsed.positionals.slice(1);
|
|
90
|
+
const { session, apiClient, workspace } = await resolveWorkspaceApiContext();
|
|
91
|
+
if (!subcommand || subcommand === "list") {
|
|
92
|
+
const rules = await apiClient.listWorkspacePolicyRules(session.accessToken, workspace);
|
|
93
|
+
if (printJsonIfRequested(parsed, rules)) {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
if (rules.length === 0) {
|
|
97
|
+
process.stdout.write("VectorPlane: nenhuma policy rule encontrada.\n");
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
process.stdout.write(`${rule.id} | ${rule.enabled ? "enabled" : "disabled"} | ${rule.effect} | ${rule.name}\n`);
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
if (subcommand === "create") {
|
|
106
|
+
const name = getStringOption(parsed, "name")?.trim();
|
|
107
|
+
const pathPrefix = getStringOption(parsed, "path")?.trim();
|
|
108
|
+
const capability = getStringOption(parsed, "capability")?.trim();
|
|
109
|
+
const effect = getStringOption(parsed, "effect")?.trim();
|
|
110
|
+
if (!name || !effect || (!pathPrefix && !capability)) {
|
|
111
|
+
throw new ValidationError("Informe `--name`, `--effect` e pelo menos um entre `--path` ou `--capability`.");
|
|
112
|
+
}
|
|
113
|
+
const rule = await apiClient.createWorkspacePolicyRule(session.accessToken, workspace, {
|
|
114
|
+
name,
|
|
115
|
+
pathPrefix: pathPrefix || undefined,
|
|
116
|
+
capability: capability || undefined,
|
|
117
|
+
effect,
|
|
118
|
+
rationale: getStringOption(parsed, "rationale")?.trim() || undefined,
|
|
119
|
+
approvalRole: getStringOption(parsed, "approval-role")?.trim() || undefined,
|
|
120
|
+
enabled: parsed.options.enabled === undefined ? true : getBooleanOption(parsed, "enabled"),
|
|
121
|
+
conditions: resolvePolicyConditions(parsed),
|
|
122
|
+
});
|
|
123
|
+
if (!printJsonIfRequested(parsed, rule)) {
|
|
124
|
+
printPolicyRule(rule);
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
if (subcommand === "update") {
|
|
129
|
+
const ruleId = requirePositional(parsed, 2, "Informe o identificador da policy rule.");
|
|
130
|
+
const updates = {};
|
|
131
|
+
if (parsed.options.name !== undefined) {
|
|
132
|
+
updates.name = getStringOption(parsed, "name")?.trim();
|
|
133
|
+
}
|
|
134
|
+
if (parsed.options.path !== undefined) {
|
|
135
|
+
updates.pathPrefix = getStringOption(parsed, "path")?.trim() || null;
|
|
136
|
+
}
|
|
137
|
+
if (parsed.options.capability !== undefined) {
|
|
138
|
+
updates.capability = getStringOption(parsed, "capability")?.trim() || null;
|
|
139
|
+
}
|
|
140
|
+
if (parsed.options.effect !== undefined) {
|
|
141
|
+
updates.effect = getStringOption(parsed, "effect")?.trim();
|
|
142
|
+
}
|
|
143
|
+
if (parsed.options.rationale !== undefined) {
|
|
144
|
+
updates.rationale = getStringOption(parsed, "rationale")?.trim() || null;
|
|
145
|
+
}
|
|
146
|
+
if (parsed.options["approval-role"] !== undefined) {
|
|
147
|
+
updates.approvalRole = getStringOption(parsed, "approval-role")?.trim() || null;
|
|
148
|
+
}
|
|
149
|
+
if (parsed.options.enabled !== undefined) {
|
|
150
|
+
updates.enabled = getBooleanOption(parsed, "enabled");
|
|
151
|
+
}
|
|
152
|
+
if (parsed.options["clear-conditions"]) {
|
|
153
|
+
updates.conditions = null;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const conditions = resolvePolicyConditions(parsed);
|
|
157
|
+
if (conditions) {
|
|
158
|
+
updates.conditions = conditions;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (Object.keys(updates).length === 0) {
|
|
162
|
+
throw new ValidationError("Informe ao menos um campo para atualização.");
|
|
163
|
+
}
|
|
164
|
+
const rule = await apiClient.updateWorkspacePolicyRule(session.accessToken, workspace, ruleId, updates);
|
|
165
|
+
if (!printJsonIfRequested(parsed, rule)) {
|
|
166
|
+
printPolicyRule(rule);
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
throw new ValidationError("Subcomando de workspace policy não suportado.");
|
|
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
|
+
}
|
|
8
220
|
export async function runWorkspaceCommand(args) {
|
|
9
221
|
const parsed = parseArgs(args);
|
|
10
222
|
const [subcommand] = parsed.positionals;
|
|
@@ -61,6 +273,12 @@ export async function runWorkspaceCommand(args) {
|
|
|
61
273
|
process.stdout.write(`Workspace resolvido: ${workspace}\n`);
|
|
62
274
|
return 0;
|
|
63
275
|
}
|
|
276
|
+
if (subcommand === "policy") {
|
|
277
|
+
return runWorkspacePolicyCommand(args);
|
|
278
|
+
}
|
|
279
|
+
if (subcommand === "webhook") {
|
|
280
|
+
return runWorkspaceWebhookCommand(args);
|
|
281
|
+
}
|
|
64
282
|
throw new ValidationError("Subcomando de workspace não suportado.");
|
|
65
283
|
}
|
|
66
284
|
//# sourceMappingURL=workspace.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type SupportedAgent = "claude" | "cursor" | "windsurf" | "copilot" | "codex" | "generic";
|
|
2
|
+
export type DetectionConfidence = "high" | "medium" | "low";
|
|
3
|
+
export interface AgentDetectionResult {
|
|
4
|
+
selected: SupportedAgent | null;
|
|
5
|
+
confidence: DetectionConfidence;
|
|
6
|
+
reasons: string[];
|
|
7
|
+
candidates: SupportedAgent[];
|
|
8
|
+
}
|
|
9
|
+
export declare function detectAgentRuntime(rootPath: string): Promise<AgentDetectionResult>;
|
|
10
|
+
export declare function resolveAgentSelection(rootPath: string, explicitAgent?: SupportedAgent): Promise<{
|
|
11
|
+
agent: SupportedAgent;
|
|
12
|
+
detection: AgentDetectionResult;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { access } 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
|
+
async function pathExists(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
await access(filePath, fsConstants.F_OK);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function uniqueAgents(values) {
|
|
16
|
+
return [...new Set(values)];
|
|
17
|
+
}
|
|
18
|
+
export async function detectAgentRuntime(rootPath) {
|
|
19
|
+
const scores = new Map();
|
|
20
|
+
const reasons = new Map();
|
|
21
|
+
const addScore = (agent, amount, reason) => {
|
|
22
|
+
scores.set(agent, (scores.get(agent) ?? 0) + amount);
|
|
23
|
+
reasons.set(agent, [...(reasons.get(agent) ?? []), reason]);
|
|
24
|
+
};
|
|
25
|
+
const fileCandidates = [
|
|
26
|
+
{ agent: "claude", file: path.join(rootPath, "CLAUDE.md") },
|
|
27
|
+
{ agent: "cursor", file: path.join(rootPath, ".cursor") },
|
|
28
|
+
{ agent: "windsurf", file: path.join(rootPath, ".windsurfrules") },
|
|
29
|
+
{ agent: "copilot", file: path.join(rootPath, ".github", "copilot-instructions.md") },
|
|
30
|
+
{ agent: "codex", file: path.join(rootPath, "AGENTS.md") },
|
|
31
|
+
];
|
|
32
|
+
for (const candidate of fileCandidates) {
|
|
33
|
+
if (await pathExists(candidate.file)) {
|
|
34
|
+
addScore(candidate.agent, 25, `file:${path.relative(rootPath, candidate.file) || candidate.file}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const envSignals = [
|
|
38
|
+
{ agent: "cursor", value: process.env.CURSOR_TRACE_ID, reason: "env:CURSOR_TRACE_ID" },
|
|
39
|
+
{ agent: "windsurf", value: process.env.WINDSURF_SESSION_ID, reason: "env:WINDSURF_SESSION_ID" },
|
|
40
|
+
{ agent: "codex", value: process.env.CODEX_ENV, reason: "env:CODEX_ENV" },
|
|
41
|
+
{ agent: "claude", value: process.env.CLAUDECODE, reason: "env:CLAUDECODE" },
|
|
42
|
+
{ agent: "copilot", value: process.env.GITHUB_COPILOT, reason: "env:GITHUB_COPILOT" },
|
|
43
|
+
];
|
|
44
|
+
for (const signal of envSignals) {
|
|
45
|
+
if (signal.value) {
|
|
46
|
+
addScore(signal.agent, 100, signal.reason);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const ranked = [...scores.entries()].sort((left, right) => right[1] - left[1]);
|
|
50
|
+
if (ranked.length === 0) {
|
|
51
|
+
return { selected: null, confidence: "low", reasons: [], candidates: [] };
|
|
52
|
+
}
|
|
53
|
+
const topScore = ranked[0][1];
|
|
54
|
+
const candidates = uniqueAgents(ranked.filter((entry) => entry[1] === topScore).map(([agent]) => agent));
|
|
55
|
+
const selected = candidates.length === 1 ? candidates[0] : null;
|
|
56
|
+
return {
|
|
57
|
+
selected,
|
|
58
|
+
confidence: topScore >= 100 ? "high" : candidates.length === 1 ? "medium" : "low",
|
|
59
|
+
reasons: selected ? (reasons.get(selected) ?? []) : candidates.flatMap((agent) => reasons.get(agent) ?? []),
|
|
60
|
+
candidates,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async function promptForAgent(detection) {
|
|
64
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
65
|
+
throw new ValidationError("Não foi possível detectar o agente local com segurança. Use --agent claude|cursor|windsurf|copilot|codex|generic.");
|
|
66
|
+
}
|
|
67
|
+
const ordered = uniqueAgents([
|
|
68
|
+
...detection.candidates,
|
|
69
|
+
"claude",
|
|
70
|
+
"cursor",
|
|
71
|
+
"windsurf",
|
|
72
|
+
"copilot",
|
|
73
|
+
"codex",
|
|
74
|
+
"generic",
|
|
75
|
+
]);
|
|
76
|
+
process.stdout.write("VectorPlane: não foi possível identificar o agente local com segurança.\n");
|
|
77
|
+
ordered.forEach((agent, index) => {
|
|
78
|
+
process.stdout.write(` ${index + 1}. ${agent}\n`);
|
|
79
|
+
});
|
|
80
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
81
|
+
try {
|
|
82
|
+
const answer = (await rl.question("Selecione o agente local: ")).trim();
|
|
83
|
+
const index = Number(answer);
|
|
84
|
+
if (Number.isInteger(index) && index >= 1 && index <= ordered.length) {
|
|
85
|
+
return ordered[index - 1];
|
|
86
|
+
}
|
|
87
|
+
if (ordered.includes(answer)) {
|
|
88
|
+
return answer;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
rl.close();
|
|
93
|
+
}
|
|
94
|
+
throw new ValidationError("Seleção de agente inválida.");
|
|
95
|
+
}
|
|
96
|
+
export async function resolveAgentSelection(rootPath, explicitAgent) {
|
|
97
|
+
if (explicitAgent) {
|
|
98
|
+
return {
|
|
99
|
+
agent: explicitAgent,
|
|
100
|
+
detection: {
|
|
101
|
+
selected: explicitAgent,
|
|
102
|
+
confidence: "high",
|
|
103
|
+
reasons: ["explicit-flag"],
|
|
104
|
+
candidates: [explicitAgent],
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const detection = await detectAgentRuntime(rootPath);
|
|
109
|
+
if (detection.selected && detection.confidence !== "low") {
|
|
110
|
+
return { agent: detection.selected, detection };
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
agent: await promptForAgent(detection),
|
|
114
|
+
detection,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=agent-runtime-detection.js.map
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import type { VectorPlaneApiClient } from "./api.js";
|
|
2
2
|
import type { Logger } from "./logger.js";
|
|
3
|
-
|
|
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
|
-
}>;
|
|
3
|
+
import { type DetectionConfidence, type SupportedAgent } from "./agent-runtime-detection.js";
|
|
16
4
|
export declare function ensureAgentSetupCurrent(params: {
|
|
17
5
|
rootPath: string;
|
|
18
6
|
workspace: string;
|
package/dist/core/agent-setup.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { constants as fsConstants } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import readline from "node:readline/promises";
|
|
5
4
|
import { ValidationError } from "./errors.js";
|
|
6
5
|
import { bindWorkspaceToRoot, getWorkspaceBinding } from "./workspace-binding.js";
|
|
6
|
+
import { detectAgentRuntime, resolveAgentSelection, } from "./agent-runtime-detection.js";
|
|
7
7
|
const AGENT_TEMPLATE_PATHS = {
|
|
8
8
|
claude: "CLAUDE.md",
|
|
9
9
|
cursor: ".cursor/rules/traceplane.mdc",
|
|
@@ -21,92 +21,6 @@ async function pathExists(filePath) {
|
|
|
21
21
|
return false;
|
|
22
22
|
}
|
|
23
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
24
|
async function appendGitIgnoreEntry(rootPath, relativeTarget) {
|
|
111
25
|
const gitIgnorePath = path.join(rootPath, ".gitignore");
|
|
112
26
|
const normalizedEntry = relativeTarget.replace(/\\/g, "/");
|
|
@@ -143,27 +57,6 @@ function getAgentTemplate(setup, agent) {
|
|
|
143
57
|
}
|
|
144
58
|
return template;
|
|
145
59
|
}
|
|
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
60
|
export async function ensureAgentSetupCurrent(params) {
|
|
168
61
|
const binding = await getWorkspaceBinding(params.rootPath);
|
|
169
62
|
let selection;
|
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, WorkspaceAgentSetup } 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,13 +20,125 @@ 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>>;
|
|
28
|
+
getWorkspaceDeliveryContextSemantic(accessToken: string, workspaceId: string, params: {
|
|
29
|
+
query: string;
|
|
30
|
+
type?: string;
|
|
31
|
+
authority?: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<Record<string, unknown>>;
|
|
24
34
|
getAgentSetup(accessToken: string, workspaceId: string): Promise<WorkspaceAgentSetup>;
|
|
35
|
+
listWorkspacePolicyRules(accessToken: string, workspaceRef: string): Promise<WorkspacePolicyRuleRecord[]>;
|
|
36
|
+
createWorkspacePolicyRule(accessToken: string, workspaceRef: string, payload: {
|
|
37
|
+
name: string;
|
|
38
|
+
pathPrefix?: string;
|
|
39
|
+
capability?: string;
|
|
40
|
+
effect: WorkspacePolicyRuleRecord["effect"];
|
|
41
|
+
rationale?: string;
|
|
42
|
+
conditions?: WorkspacePolicyRuleRecord["conditions"];
|
|
43
|
+
approvalRole?: string;
|
|
44
|
+
enabled?: boolean;
|
|
45
|
+
}): Promise<WorkspacePolicyRuleRecord>;
|
|
46
|
+
updateWorkspacePolicyRule(accessToken: string, workspaceRef: string, ruleId: string, payload: {
|
|
47
|
+
name?: string;
|
|
48
|
+
pathPrefix?: string | null;
|
|
49
|
+
capability?: string | null;
|
|
50
|
+
effect?: WorkspacePolicyRuleRecord["effect"];
|
|
51
|
+
rationale?: string | null;
|
|
52
|
+
conditions?: WorkspacePolicyRuleRecord["conditions"] | null;
|
|
53
|
+
approvalRole?: string | null;
|
|
54
|
+
enabled?: boolean;
|
|
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
|
+
}>;
|
|
25
68
|
listMemoryDrafts(accessToken: string, workspaceRef: string, status?: MemoryDraftStatus): Promise<MemoryDraftRecord[]>;
|
|
26
69
|
createMemoryDraft(accessToken: string, workspaceRef: string, payload: MemoryDraftCreateRequest): Promise<MemoryDraftRecord>;
|
|
70
|
+
searchWorkspaceMemory(accessToken: string, workspaceRef: string, params: {
|
|
71
|
+
query: string;
|
|
72
|
+
type?: string;
|
|
73
|
+
authority?: string;
|
|
74
|
+
limit?: number;
|
|
75
|
+
}): Promise<MemorySearchRecord[]>;
|
|
76
|
+
listTasks(accessToken: string, workspaceRef: string): Promise<TaskRecord[]>;
|
|
77
|
+
createTask(accessToken: string, workspaceRef: string, payload: TaskCreateRequest): Promise<TaskRecord>;
|
|
78
|
+
listTaskTemplates(accessToken: string): Promise<TaskTemplateRecord[]>;
|
|
79
|
+
getTask(accessToken: string, workspaceRef: string, taskId: string): Promise<TaskRecord>;
|
|
80
|
+
listClaimableTaskSteps(accessToken: string, workspaceRef: string, filters?: {
|
|
81
|
+
capability?: string;
|
|
82
|
+
agentName?: string;
|
|
83
|
+
}): Promise<ClaimableTaskStepRecord[]>;
|
|
84
|
+
claimTaskStep(accessToken: string, workspaceRef: string, taskId: string, stepId: string, payload?: {
|
|
85
|
+
sessionId?: string;
|
|
86
|
+
clientInstanceId?: string;
|
|
87
|
+
agentName?: string;
|
|
88
|
+
}): Promise<ClaimedTaskStepResponse>;
|
|
89
|
+
delegateTask(accessToken: string, workspaceRef: string, taskId: string, stepId: string, toAgentId: string, reason: string): Promise<TaskRecord>;
|
|
90
|
+
callbackTaskStep(accessToken: string, workspaceRef: string, taskId: string, stepId: string, payload: {
|
|
91
|
+
status: "running" | "completed" | "failed" | "blocked";
|
|
92
|
+
agentId?: string;
|
|
93
|
+
output?: Record<string, unknown>;
|
|
94
|
+
error?: string;
|
|
95
|
+
}): Promise<TaskRecord>;
|
|
96
|
+
getTaskHandoff(accessToken: string, workspaceRef: string, taskId: string): Promise<TaskHandoffRecord>;
|
|
97
|
+
getTaskObservability(accessToken: string, workspaceRef: string, taskId: string): Promise<TaskObservabilityRecord>;
|
|
98
|
+
getWorkspaceTaskObservability(accessToken: string, workspaceRef: string): Promise<TaskObservabilityRecord>;
|
|
99
|
+
approveTask(accessToken: string, workspaceRef: string, taskId: string, payload: {
|
|
100
|
+
approved: boolean;
|
|
101
|
+
approvedBy?: string;
|
|
102
|
+
rationale?: string;
|
|
103
|
+
}): Promise<TaskRecord>;
|
|
104
|
+
listAgentRegistry(accessToken: string): Promise<AgentRegistryRecord[]>;
|
|
105
|
+
createRegistryAgent(accessToken: string, payload: {
|
|
106
|
+
orgId?: string;
|
|
107
|
+
name: string;
|
|
108
|
+
runtimeType: string;
|
|
109
|
+
provider: string;
|
|
110
|
+
model: string;
|
|
111
|
+
capabilities: string[];
|
|
112
|
+
costProfile: string;
|
|
113
|
+
latencyProfile: string;
|
|
114
|
+
reliabilityScore: number;
|
|
115
|
+
status?: string;
|
|
116
|
+
metadata?: Record<string, unknown>;
|
|
117
|
+
}): Promise<AgentRegistryRecord>;
|
|
118
|
+
updateRegistryAgent(accessToken: string, agentId: string, payload: {
|
|
119
|
+
name?: string;
|
|
120
|
+
provider?: string;
|
|
121
|
+
model?: string;
|
|
122
|
+
capabilities?: string[];
|
|
123
|
+
costProfile?: string;
|
|
124
|
+
latencyProfile?: string;
|
|
125
|
+
reliabilityScore?: number;
|
|
126
|
+
status?: string;
|
|
127
|
+
metadata?: Record<string, unknown>;
|
|
128
|
+
}): Promise<AgentRegistryRecord>;
|
|
129
|
+
deactivateRegistryAgent(accessToken: string, agentId: string): Promise<{
|
|
130
|
+
deactivated: boolean;
|
|
131
|
+
}>;
|
|
132
|
+
getWorkspaceHealth(accessToken: string, workspaceRef: string): Promise<Record<string, unknown>>;
|
|
27
133
|
checkInAgent(accessToken: string, payload: AgentSessionRequest): Promise<AgentSessionResponse>;
|
|
28
134
|
heartbeatAgent(accessToken: string, payload: AgentHeartbeatRequest): Promise<AgentSessionResponse>;
|
|
29
135
|
checkOutAgent(accessToken: string, payload: AgentCheckoutRequest): Promise<AgentSessionResponse>;
|
|
30
136
|
getHealth(): Promise<Record<string, unknown>>;
|
|
31
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>;
|
|
32
144
|
}
|