capyai 0.2.0
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 +79 -0
- package/bin/capy.js +61 -0
- package/bin/capy.ts +62 -0
- package/dist/capy.js +1619 -0
- package/package.json +36 -0
- package/src/api.ts +125 -0
- package/src/cli.ts +722 -0
- package/src/config.ts +93 -0
- package/src/format.ts +45 -0
- package/src/github.ts +112 -0
- package/src/greptile.ts +151 -0
- package/src/quality.ts +131 -0
- package/src/types.ts +181 -0
- package/src/watch.ts +61 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
export interface QualityConfig {
|
|
2
|
+
minReviewScore: number;
|
|
3
|
+
requireCI: boolean;
|
|
4
|
+
requireTests: boolean;
|
|
5
|
+
requireLinearLink: boolean;
|
|
6
|
+
reviewProvider: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RepoRef {
|
|
10
|
+
repoFullName: string;
|
|
11
|
+
branch: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CapyConfig {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
projectId: string;
|
|
17
|
+
server: string;
|
|
18
|
+
repos: RepoRef[];
|
|
19
|
+
defaultModel: string;
|
|
20
|
+
quality: QualityConfig;
|
|
21
|
+
watchInterval: number;
|
|
22
|
+
notifyCommand: string;
|
|
23
|
+
greptileApiKey?: string;
|
|
24
|
+
approveCommand?: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Credits {
|
|
29
|
+
llm?: number;
|
|
30
|
+
vm?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Jam {
|
|
34
|
+
model?: string;
|
|
35
|
+
status?: string;
|
|
36
|
+
credits?: Credits | number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PullRequestRef {
|
|
40
|
+
number?: number;
|
|
41
|
+
url?: string;
|
|
42
|
+
state?: string;
|
|
43
|
+
repoFullName?: string;
|
|
44
|
+
headRef?: string;
|
|
45
|
+
baseRef?: string;
|
|
46
|
+
title?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Task {
|
|
50
|
+
id: string;
|
|
51
|
+
identifier: string;
|
|
52
|
+
title: string;
|
|
53
|
+
status: string;
|
|
54
|
+
prompt?: string;
|
|
55
|
+
createdAt?: string;
|
|
56
|
+
pullRequest?: PullRequestRef;
|
|
57
|
+
jams?: Jam[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Thread {
|
|
61
|
+
id: string;
|
|
62
|
+
title?: string;
|
|
63
|
+
status: string;
|
|
64
|
+
tasks?: Task[];
|
|
65
|
+
pullRequests?: PullRequestRef[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ThreadMessage {
|
|
69
|
+
source: string;
|
|
70
|
+
content: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DiffFile {
|
|
74
|
+
path: string;
|
|
75
|
+
filename?: string;
|
|
76
|
+
state?: string;
|
|
77
|
+
additions?: number;
|
|
78
|
+
deletions?: number;
|
|
79
|
+
patch?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface DiffData {
|
|
83
|
+
source?: string;
|
|
84
|
+
stats?: { additions?: number; deletions?: number; files?: number };
|
|
85
|
+
files?: DiffFile[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface Model {
|
|
89
|
+
id: string;
|
|
90
|
+
provider?: string;
|
|
91
|
+
captainEligible?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface StatusCheck {
|
|
95
|
+
name?: string;
|
|
96
|
+
context?: string;
|
|
97
|
+
conclusion?: string;
|
|
98
|
+
status?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface PRData {
|
|
102
|
+
state: string;
|
|
103
|
+
mergeable?: string;
|
|
104
|
+
mergedAt?: string;
|
|
105
|
+
closedAt?: string;
|
|
106
|
+
headRefName?: string;
|
|
107
|
+
baseRefName?: string;
|
|
108
|
+
title?: string;
|
|
109
|
+
body?: string;
|
|
110
|
+
url?: string;
|
|
111
|
+
number?: number;
|
|
112
|
+
additions?: number;
|
|
113
|
+
deletions?: number;
|
|
114
|
+
changedFiles?: number;
|
|
115
|
+
reviewDecision?: string;
|
|
116
|
+
statusCheckRollup?: StatusCheck[];
|
|
117
|
+
reviews?: unknown[];
|
|
118
|
+
comments?: unknown[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface CIStatus {
|
|
122
|
+
total: number;
|
|
123
|
+
passing: number;
|
|
124
|
+
failing: { name: string; conclusion?: string; status?: string }[];
|
|
125
|
+
pending: { name: string; status?: string }[];
|
|
126
|
+
allGreen: boolean;
|
|
127
|
+
noChecks: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface QualityGate {
|
|
131
|
+
name: string;
|
|
132
|
+
pass: boolean;
|
|
133
|
+
detail: string;
|
|
134
|
+
failing?: { name: string; conclusion?: string; status?: string }[];
|
|
135
|
+
pending?: { name: string; status?: string }[];
|
|
136
|
+
issues?: UnaddressedIssue[];
|
|
137
|
+
threads?: { body: string; author: string }[];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface QualityResult {
|
|
141
|
+
pass: boolean;
|
|
142
|
+
passed: number;
|
|
143
|
+
total: number;
|
|
144
|
+
gates: QualityGate[];
|
|
145
|
+
summary: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface GreptileReview {
|
|
149
|
+
score: number | null;
|
|
150
|
+
issueCount: number;
|
|
151
|
+
logic: number;
|
|
152
|
+
syntax: number;
|
|
153
|
+
style: number;
|
|
154
|
+
body: string;
|
|
155
|
+
url?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface UnaddressedIssue {
|
|
159
|
+
body: string;
|
|
160
|
+
file: string;
|
|
161
|
+
line: string | number;
|
|
162
|
+
hasSuggestion: boolean;
|
|
163
|
+
suggestedCode: string | null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface WatchEntry {
|
|
167
|
+
id: string;
|
|
168
|
+
type: string;
|
|
169
|
+
intervalMin: number;
|
|
170
|
+
created: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ApiResponse {
|
|
174
|
+
error?: { message?: string; code?: string };
|
|
175
|
+
[key: string]: unknown;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface ListResponse<T> {
|
|
179
|
+
items?: T[];
|
|
180
|
+
[key: string]: unknown;
|
|
181
|
+
}
|
package/src/watch.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import * as config from "./config.js";
|
|
5
|
+
import type { WatchEntry } from "./types.js";
|
|
6
|
+
|
|
7
|
+
function getCrontab(): string {
|
|
8
|
+
try { return execSync("crontab -l 2>/dev/null", { encoding: "utf8" }); } catch { return ""; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function setCrontab(content: string): void {
|
|
12
|
+
execSync(`echo ${JSON.stringify(content)} | crontab -`, { encoding: "utf8" });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function add(id: string, type: string, intervalMin: number): boolean {
|
|
16
|
+
const watchDir = config.WATCH_DIR;
|
|
17
|
+
fs.mkdirSync(watchDir, { recursive: true });
|
|
18
|
+
|
|
19
|
+
const thisDir = path.dirname(new URL(import.meta.url).pathname);
|
|
20
|
+
const binPath = path.resolve(thisDir, "..", "bin", "capy.ts");
|
|
21
|
+
const runtime = typeof Bun !== "undefined" ? "bun" : "node";
|
|
22
|
+
const tag = `# capy-watch:${id}`;
|
|
23
|
+
const cronLine = `*/${intervalMin} * * * * ${runtime} ${binPath} _poll ${id} ${type} ${tag}`;
|
|
24
|
+
|
|
25
|
+
let crontab = getCrontab();
|
|
26
|
+
if (crontab.includes(`capy-watch:${id}`)) return false;
|
|
27
|
+
|
|
28
|
+
crontab = crontab.trimEnd() + "\n" + cronLine + "\n";
|
|
29
|
+
setCrontab(crontab);
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(path.join(watchDir, `${id}.json`), JSON.stringify({
|
|
32
|
+
id, type, intervalMin, created: new Date().toISOString(),
|
|
33
|
+
}));
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function remove(id: string): void {
|
|
38
|
+
let crontab = getCrontab();
|
|
39
|
+
const lines = crontab.split("\n").filter(l => !l.includes(`capy-watch:${id}`));
|
|
40
|
+
setCrontab(lines.join("\n") + "\n");
|
|
41
|
+
try { fs.unlinkSync(path.join(config.WATCH_DIR, `${id}.json`)); } catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function list(): WatchEntry[] {
|
|
45
|
+
try {
|
|
46
|
+
return fs.readdirSync(config.WATCH_DIR)
|
|
47
|
+
.filter(f => f.endsWith(".json"))
|
|
48
|
+
.map(f => JSON.parse(fs.readFileSync(path.join(config.WATCH_DIR, f), "utf8")));
|
|
49
|
+
} catch { return []; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function notify(text: string): boolean {
|
|
53
|
+
const cfg = config.load();
|
|
54
|
+
const cmd = cfg.notifyCommand || "openclaw system event --text {text} --mode now";
|
|
55
|
+
try {
|
|
56
|
+
execSync(cmd.replace("{text}", JSON.stringify(text)), {
|
|
57
|
+
encoding: "utf8", timeout: 15000,
|
|
58
|
+
});
|
|
59
|
+
return true;
|
|
60
|
+
} catch { return false; }
|
|
61
|
+
}
|