capyai 0.3.0 → 0.3.1
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/package.json +1 -1
- package/src/api.ts +31 -8
- package/src/commands/setup.ts +77 -40
- package/src/types.ts +1 -0
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import * as config from "./config.js";
|
|
2
2
|
import type { Task, Thread, ThreadMessage, DiffData, Model, ListResponse, PullRequestRef } from "./types.js";
|
|
3
3
|
|
|
4
|
-
async function
|
|
5
|
-
const
|
|
6
|
-
if (!cfg.apiKey) {
|
|
7
|
-
console.error("capy: API key not configured. Run: capy init");
|
|
8
|
-
process.exit(1);
|
|
9
|
-
}
|
|
10
|
-
const url = `${cfg.server}${path}`;
|
|
4
|
+
async function rawRequest(apiKey: string, server: string, method: string, path: string, body?: unknown): Promise<any> {
|
|
5
|
+
const url = `${server}${path}`;
|
|
11
6
|
const headers: Record<string, string> = {
|
|
12
|
-
"Authorization": `Bearer ${
|
|
7
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
13
8
|
"Accept": "application/json",
|
|
14
9
|
};
|
|
15
10
|
const init: RequestInit = { method, headers };
|
|
@@ -40,6 +35,34 @@ async function request(method: string, path: string, body?: unknown): Promise<an
|
|
|
40
35
|
}
|
|
41
36
|
}
|
|
42
37
|
|
|
38
|
+
async function request(method: string, path: string, body?: unknown): Promise<any> {
|
|
39
|
+
const cfg = config.load();
|
|
40
|
+
if (!cfg.apiKey) {
|
|
41
|
+
console.error("capy: API key not configured. Run: capy init");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
return rawRequest(cfg.apiKey, cfg.server, method, path, body);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// --- Init helpers (accept key directly, before config is saved) ---
|
|
48
|
+
export interface Project {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description?: string | null;
|
|
52
|
+
taskCode: string;
|
|
53
|
+
repos: { repoFullName: string; branch: string }[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function listProjects(apiKey: string, server = "https://capy.ai/api/v1"): Promise<Project[]> {
|
|
57
|
+
const data = await rawRequest(apiKey, server, "GET", "/projects");
|
|
58
|
+
return data.items || [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function listModelsWithKey(apiKey: string, server = "https://capy.ai/api/v1"): Promise<Model[]> {
|
|
62
|
+
const data = await rawRequest(apiKey, server, "GET", "/models");
|
|
63
|
+
return data.models || [];
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
// --- Threads ---
|
|
44
67
|
export async function createThread(prompt: string, model?: string, repos?: unknown[]): Promise<Thread> {
|
|
45
68
|
const cfg = config.load();
|
package/src/commands/setup.ts
CHANGED
|
@@ -7,55 +7,92 @@ export const init = defineCommand({
|
|
|
7
7
|
async run() {
|
|
8
8
|
const p = await import("@clack/prompts");
|
|
9
9
|
const config = await import("../config.js");
|
|
10
|
+
const api = await import("../api.js");
|
|
10
11
|
|
|
11
12
|
p.intro("capy setup");
|
|
12
13
|
|
|
13
14
|
const cfg = config.load();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
projectId: () => p.text({
|
|
21
|
-
message: "Project ID",
|
|
22
|
-
initialValue: cfg.projectId || "",
|
|
23
|
-
validate: (v) => { if (!v) return "Project ID is required"; },
|
|
24
|
-
}),
|
|
25
|
-
repos: () => p.text({
|
|
26
|
-
message: "Repos (owner/repo:branch, comma-separated)",
|
|
27
|
-
initialValue: cfg.repos.map(r => `${r.repoFullName}:${r.branch}`).join(", ") || "",
|
|
28
|
-
placeholder: "owner/repo:main",
|
|
29
|
-
}),
|
|
30
|
-
defaultModel: () => p.text({
|
|
31
|
-
message: "Default model",
|
|
32
|
-
initialValue: cfg.defaultModel,
|
|
33
|
-
}),
|
|
34
|
-
reviewProvider: () => p.select({
|
|
35
|
-
message: "Review provider",
|
|
36
|
-
initialValue: cfg.quality?.reviewProvider || "greptile",
|
|
37
|
-
options: [
|
|
38
|
-
{ value: "greptile", label: "Greptile", hint: "AI code review via Greptile API" },
|
|
39
|
-
{ value: "capy", label: "Capy", hint: "GitHub unresolved review threads" },
|
|
40
|
-
{ value: "both", label: "Both", hint: "Strictest: Greptile + GitHub threads" },
|
|
41
|
-
{ value: "none", label: "None", hint: "Skip review gates" },
|
|
42
|
-
],
|
|
43
|
-
}),
|
|
15
|
+
|
|
16
|
+
// 1. API key
|
|
17
|
+
const apiKey = await p.password({
|
|
18
|
+
message: "Capy API key",
|
|
19
|
+
mask: "*",
|
|
20
|
+
validate: (v) => { if (!v) return "API key is required"; },
|
|
44
21
|
});
|
|
22
|
+
if (p.isCancel(apiKey)) { p.cancel("Setup cancelled."); process.exit(0); }
|
|
23
|
+
|
|
24
|
+
// 2. Fetch projects + models in parallel using the key
|
|
25
|
+
const s = p.spinner();
|
|
26
|
+
s.start("Fetching your projects and models...");
|
|
27
|
+
const [projects, models] = await Promise.all([
|
|
28
|
+
api.listProjects(apiKey, cfg.server),
|
|
29
|
+
api.listModelsWithKey(apiKey, cfg.server),
|
|
30
|
+
]);
|
|
31
|
+
s.stop(`Found ${projects.length} project${projects.length !== 1 ? "s" : ""}, ${models.length} models`);
|
|
45
32
|
|
|
46
|
-
if (
|
|
47
|
-
p.
|
|
48
|
-
process.exit(
|
|
33
|
+
if (!projects.length) {
|
|
34
|
+
p.log.error("No projects found for this API key. Create one at capy.ai first.");
|
|
35
|
+
process.exit(1);
|
|
49
36
|
}
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
38
|
+
// 3. Select project
|
|
39
|
+
const projectId = projects.length === 1
|
|
40
|
+
? (() => { p.log.info(`Project: ${projects[0].name} (${projects[0].taskCode})`); return projects[0].id; })()
|
|
41
|
+
: await (async () => {
|
|
42
|
+
const sel = await p.select({
|
|
43
|
+
message: "Select project",
|
|
44
|
+
initialValue: cfg.projectId || projects[0].id,
|
|
45
|
+
options: projects.map(proj => ({
|
|
46
|
+
value: proj.id,
|
|
47
|
+
label: proj.name,
|
|
48
|
+
hint: `${proj.taskCode} \u2022 ${proj.repos.length} repo${proj.repos.length !== 1 ? "s" : ""}`,
|
|
49
|
+
})),
|
|
50
|
+
});
|
|
51
|
+
if (p.isCancel(sel)) { p.cancel("Setup cancelled."); process.exit(0); }
|
|
52
|
+
return sel;
|
|
53
|
+
})();
|
|
54
|
+
|
|
55
|
+
const selectedProject = projects.find(proj => proj.id === projectId)!;
|
|
56
|
+
|
|
57
|
+
// 4. Show repos from project (auto-populated, no typing needed)
|
|
58
|
+
if (selectedProject.repos.length) {
|
|
59
|
+
p.log.info(`Repos:\n${selectedProject.repos.map(r => ` ${r.repoFullName} (${r.branch})`).join("\n")}`);
|
|
60
|
+
} else {
|
|
61
|
+
p.log.warn("No repos configured on this project. Add them at capy.ai.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 5. Select default model
|
|
65
|
+
const captainModels = models.filter(m => m.captainEligible);
|
|
66
|
+
const defaultModel = await p.select({
|
|
67
|
+
message: "Default model",
|
|
68
|
+
initialValue: cfg.defaultModel || "gpt-5.4",
|
|
69
|
+
options: captainModels.map(m => ({
|
|
70
|
+
value: m.id,
|
|
71
|
+
label: m.name || m.id,
|
|
72
|
+
hint: m.provider || undefined,
|
|
73
|
+
})),
|
|
56
74
|
});
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
if (p.isCancel(defaultModel)) { p.cancel("Setup cancelled."); process.exit(0); }
|
|
76
|
+
|
|
77
|
+
// 6. Review provider
|
|
78
|
+
const reviewProvider = await p.select({
|
|
79
|
+
message: "Review provider",
|
|
80
|
+
initialValue: cfg.quality?.reviewProvider || "greptile",
|
|
81
|
+
options: [
|
|
82
|
+
{ value: "greptile", label: "Greptile", hint: "AI code review via Greptile API" },
|
|
83
|
+
{ value: "capy", label: "Capy", hint: "GitHub unresolved review threads" },
|
|
84
|
+
{ value: "both", label: "Both", hint: "Strictest: Greptile + GitHub threads" },
|
|
85
|
+
{ value: "none", label: "None", hint: "Skip review gates" },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
if (p.isCancel(reviewProvider)) { p.cancel("Setup cancelled."); process.exit(0); }
|
|
89
|
+
|
|
90
|
+
// Save
|
|
91
|
+
cfg.apiKey = apiKey;
|
|
92
|
+
cfg.projectId = projectId as string;
|
|
93
|
+
cfg.repos = selectedProject.repos;
|
|
94
|
+
cfg.defaultModel = defaultModel as string;
|
|
95
|
+
cfg.quality.reviewProvider = reviewProvider as string;
|
|
59
96
|
|
|
60
97
|
config.save(cfg);
|
|
61
98
|
p.outro(`Config saved to ${config.CONFIG_PATH}`);
|