@google/jules-fleet 0.0.1-experimental.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 +205 -0
- package/dist/analyze/formatting.d.ts +19 -0
- package/dist/analyze/goals.d.ts +18 -0
- package/dist/analyze/handler.d.ts +23 -0
- package/dist/analyze/index.d.ts +8 -0
- package/dist/analyze/milestone.d.ts +43 -0
- package/dist/analyze/prompt.d.ts +10 -0
- package/dist/analyze/spec.d.ts +54 -0
- package/dist/analyze/triage-prompt.d.ts +16 -0
- package/dist/cli/analyze.command.d.ts +24 -0
- package/dist/cli/analyze.command.mjs +1015 -0
- package/dist/cli/commands.json +1 -0
- package/dist/cli/configure.command.d.ts +21 -0
- package/dist/cli/configure.command.mjs +623 -0
- package/dist/cli/dispatch.command.d.ts +16 -0
- package/dist/cli/dispatch.command.mjs +777 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.mjs +40 -0
- package/dist/cli/init.command.d.ts +38 -0
- package/dist/cli/init.command.mjs +1287 -0
- package/dist/cli/merge.command.d.ts +36 -0
- package/dist/cli/merge.command.mjs +859 -0
- package/dist/cli/signal.command.d.ts +2 -0
- package/dist/cli/signal.command.mjs +288 -0
- package/dist/configure/handler.d.ts +19 -0
- package/dist/configure/index.d.ts +4 -0
- package/dist/configure/labels.d.ts +6 -0
- package/dist/configure/spec.d.ts +49 -0
- package/dist/dispatch/events.d.ts +12 -0
- package/dist/dispatch/handler.d.ts +21 -0
- package/dist/dispatch/index.d.ts +5 -0
- package/dist/dispatch/spec.d.ts +47 -0
- package/dist/dispatch/status.d.ts +24 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +2105 -0
- package/dist/init/handler.d.ts +22 -0
- package/dist/init/index.d.ts +4 -0
- package/dist/init/ops/commit-files.d.ts +10 -0
- package/dist/init/ops/create-branch.d.ts +16 -0
- package/dist/init/ops/create-pr.d.ts +15 -0
- package/dist/init/ops/pr-body.d.ts +5 -0
- package/dist/init/ops/upload-secrets.d.ts +11 -0
- package/dist/init/spec.d.ts +50 -0
- package/dist/init/templates/analyze.d.ts +2 -0
- package/dist/init/templates/dispatch.d.ts +2 -0
- package/dist/init/templates/example-goal.d.ts +5 -0
- package/dist/init/templates/merge.d.ts +2 -0
- package/dist/init/templates/types.d.ts +6 -0
- package/dist/init/templates.d.ts +10 -0
- package/dist/init/types.d.ts +19 -0
- package/dist/init/wizard/headless.d.ts +8 -0
- package/dist/init/wizard/index.d.ts +3 -0
- package/dist/init/wizard/interactive.d.ts +9 -0
- package/dist/init/wizard/types.d.ts +22 -0
- package/dist/merge/handler.d.ts +21 -0
- package/dist/merge/index.d.ts +5 -0
- package/dist/merge/ops/index.d.ts +4 -0
- package/dist/merge/ops/redispatch.d.ts +8 -0
- package/dist/merge/ops/squash-merge.d.ts +8 -0
- package/dist/merge/ops/update-branch.d.ts +11 -0
- package/dist/merge/ops/wait-for-ci.d.ts +7 -0
- package/dist/merge/select/by-fleet-run.d.ts +8 -0
- package/dist/merge/select/by-label.d.ts +7 -0
- package/dist/merge/select/index.d.ts +2 -0
- package/dist/merge/spec.d.ts +99 -0
- package/dist/shared/auth/cache-plugin.d.ts +9 -0
- package/dist/shared/auth/git.d.ts +22 -0
- package/dist/shared/auth/index.d.ts +4 -0
- package/dist/shared/auth/octokit.d.ts +11 -0
- package/dist/shared/auth/resolve-key.d.ts +11 -0
- package/dist/shared/events/analyze.d.ts +37 -0
- package/dist/shared/events/configure.d.ts +21 -0
- package/dist/shared/events/dispatch.d.ts +26 -0
- package/dist/shared/events/error.d.ts +7 -0
- package/dist/shared/events/index.d.ts +16 -0
- package/dist/shared/events/init.d.ts +49 -0
- package/dist/shared/events/merge.d.ts +72 -0
- package/dist/shared/events.d.ts +1 -0
- package/dist/shared/index.d.ts +6 -0
- package/dist/shared/result/create-result-schemas.d.ts +72 -0
- package/dist/shared/result/fail.d.ts +10 -0
- package/dist/shared/result/index.d.ts +3 -0
- package/dist/shared/result/ok.d.ts +5 -0
- package/dist/shared/schemas/check-run.d.ts +16 -0
- package/dist/shared/schemas/index.d.ts +4 -0
- package/dist/shared/schemas/label.d.ts +16 -0
- package/dist/shared/schemas/pr.d.ts +19 -0
- package/dist/shared/schemas/repo-info.d.ts +16 -0
- package/dist/shared/session-dispatcher.d.ts +18 -0
- package/dist/shared/ui/assert-never.d.ts +13 -0
- package/dist/shared/ui/index.d.ts +18 -0
- package/dist/shared/ui/interactive.d.ts +19 -0
- package/dist/shared/ui/plain.d.ts +16 -0
- package/dist/shared/ui/render/analyze.d.ts +4 -0
- package/dist/shared/ui/render/configure.d.ts +4 -0
- package/dist/shared/ui/render/dispatch.d.ts +4 -0
- package/dist/shared/ui/render/error.d.ts +4 -0
- package/dist/shared/ui/render/init.d.ts +4 -0
- package/dist/shared/ui/render/merge.d.ts +4 -0
- package/dist/shared/ui/session-url.d.ts +13 -0
- package/dist/shared/ui/spec.d.ts +30 -0
- package/dist/signal/handler.d.ts +17 -0
- package/dist/signal/index.d.ts +3 -0
- package/dist/signal/spec.d.ts +60 -0
- package/package.json +76 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
["init","dispatch","configure","signal","analyze","merge"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare const _default: import("citty").CommandDef<{
|
|
2
|
+
resource: {
|
|
3
|
+
type: "positional";
|
|
4
|
+
description: string;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
delete: {
|
|
8
|
+
type: "boolean";
|
|
9
|
+
description: string;
|
|
10
|
+
default: false;
|
|
11
|
+
};
|
|
12
|
+
owner: {
|
|
13
|
+
type: "string";
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
repo: {
|
|
17
|
+
type: "string";
|
|
18
|
+
description: string;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
export default _default;
|
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/cli/configure.command.ts
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
|
|
7
|
+
// src/configure/spec.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var ConfigureAction = z.enum(["create", "delete"]);
|
|
10
|
+
var ConfigureResource = z.enum(["labels"]);
|
|
11
|
+
var ConfigureInputSchema = z.object({
|
|
12
|
+
resource: ConfigureResource,
|
|
13
|
+
action: ConfigureAction.default("create"),
|
|
14
|
+
owner: z.string().min(1),
|
|
15
|
+
repo: z.string().min(1)
|
|
16
|
+
});
|
|
17
|
+
var ConfigureErrorCode = z.enum([
|
|
18
|
+
"GITHUB_API_ERROR",
|
|
19
|
+
"UNKNOWN_ERROR"
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
// src/shared/result/create-result-schemas.ts
|
|
23
|
+
import { z as z2 } from "zod";
|
|
24
|
+
// src/shared/result/ok.ts
|
|
25
|
+
function ok(data) {
|
|
26
|
+
return { success: true, data };
|
|
27
|
+
}
|
|
28
|
+
// src/shared/result/fail.ts
|
|
29
|
+
function fail(code, message, recoverable = false, suggestion) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: { code, message, recoverable, suggestion }
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// src/configure/labels.ts
|
|
36
|
+
var FLEET_LABELS = [
|
|
37
|
+
{
|
|
38
|
+
name: "fleet-merge-ready",
|
|
39
|
+
color: "0e8a16",
|
|
40
|
+
description: "Ready for fleet sequential merge"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "fleet",
|
|
44
|
+
color: "1d76db",
|
|
45
|
+
description: "Fleet-managed issue"
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// src/configure/handler.ts
|
|
50
|
+
class ConfigureHandler {
|
|
51
|
+
octokit;
|
|
52
|
+
emit;
|
|
53
|
+
constructor(deps) {
|
|
54
|
+
this.octokit = deps.octokit;
|
|
55
|
+
this.emit = deps.emit ?? (() => {});
|
|
56
|
+
}
|
|
57
|
+
async execute(input) {
|
|
58
|
+
try {
|
|
59
|
+
this.emit({
|
|
60
|
+
type: "configure:start",
|
|
61
|
+
resource: input.resource,
|
|
62
|
+
owner: input.owner,
|
|
63
|
+
repo: input.repo
|
|
64
|
+
});
|
|
65
|
+
if (input.resource === "labels") {
|
|
66
|
+
const result = input.action === "create" ? await this.createLabels(input.owner, input.repo) : await this.deleteLabels(input.owner, input.repo);
|
|
67
|
+
this.emit({ type: "configure:done" });
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
return fail("UNKNOWN_ERROR", `Unknown resource: ${input.resource}`, false);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return fail("UNKNOWN_ERROR", error instanceof Error ? error.message : String(error), false);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async createLabels(owner, repo) {
|
|
76
|
+
const created = [];
|
|
77
|
+
const skipped = [];
|
|
78
|
+
for (const label of FLEET_LABELS) {
|
|
79
|
+
try {
|
|
80
|
+
await this.octokit.rest.issues.createLabel({
|
|
81
|
+
owner,
|
|
82
|
+
repo,
|
|
83
|
+
name: label.name,
|
|
84
|
+
color: label.color,
|
|
85
|
+
description: label.description
|
|
86
|
+
});
|
|
87
|
+
created.push(label.name);
|
|
88
|
+
this.emit({ type: "configure:label:created", name: label.name });
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : 0;
|
|
91
|
+
if (status === 422) {
|
|
92
|
+
skipped.push(label.name);
|
|
93
|
+
this.emit({ type: "configure:label:exists", name: label.name });
|
|
94
|
+
} else {
|
|
95
|
+
return fail("GITHUB_API_ERROR", `Failed to create label "${label.name}": ${error instanceof Error ? error.message : error}`, true);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return ok({ created, deleted: [], skipped });
|
|
100
|
+
}
|
|
101
|
+
async deleteLabels(owner, repo) {
|
|
102
|
+
const deleted = [];
|
|
103
|
+
const skipped = [];
|
|
104
|
+
for (const label of FLEET_LABELS) {
|
|
105
|
+
try {
|
|
106
|
+
await this.octokit.rest.issues.deleteLabel({
|
|
107
|
+
owner,
|
|
108
|
+
repo,
|
|
109
|
+
name: label.name
|
|
110
|
+
});
|
|
111
|
+
deleted.push(label.name);
|
|
112
|
+
this.emit({ type: "configure:label:created", name: label.name });
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : 0;
|
|
115
|
+
if (status === 404) {
|
|
116
|
+
skipped.push(label.name);
|
|
117
|
+
this.emit({ type: "configure:label:exists", name: label.name });
|
|
118
|
+
} else {
|
|
119
|
+
return fail("GITHUB_API_ERROR", `Failed to delete label "${label.name}": ${error instanceof Error ? error.message : error}`, true);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return ok({ created: [], deleted, skipped });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/shared/auth/octokit.ts
|
|
128
|
+
import { Octokit } from "octokit";
|
|
129
|
+
import { createAppAuth } from "@octokit/auth-app";
|
|
130
|
+
|
|
131
|
+
// src/shared/auth/cache-plugin.ts
|
|
132
|
+
function cachePlugin(octokit) {
|
|
133
|
+
const cache = new Map;
|
|
134
|
+
octokit.hook.wrap("request", async (request, options) => {
|
|
135
|
+
const key = `${options.method} ${options.url}`;
|
|
136
|
+
const cached = cache.get(key);
|
|
137
|
+
if (cached) {
|
|
138
|
+
options.headers = {
|
|
139
|
+
...options.headers,
|
|
140
|
+
"if-none-match": cached.etag
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const response = await request(options);
|
|
145
|
+
const etag = response.headers.etag;
|
|
146
|
+
if (etag) {
|
|
147
|
+
cache.set(key, { etag, data: response.data });
|
|
148
|
+
}
|
|
149
|
+
return response;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error.status === 304 && cached) {
|
|
152
|
+
return { ...error.response, data: cached.data, status: 200 };
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/shared/auth/resolve-key.ts
|
|
160
|
+
function resolvePrivateKey(base64Value, rawValue) {
|
|
161
|
+
if (base64Value) {
|
|
162
|
+
return Buffer.from(base64Value, "base64").toString("utf-8");
|
|
163
|
+
}
|
|
164
|
+
if (rawValue) {
|
|
165
|
+
return rawValue.replace(/\\n/g, `
|
|
166
|
+
`);
|
|
167
|
+
}
|
|
168
|
+
throw new Error("No private key provided. Set GITHUB_APP_PRIVATE_KEY_BASE64 (recommended) or GITHUB_APP_PRIVATE_KEY.");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/shared/auth/octokit.ts
|
|
172
|
+
var CachedOctokit = Octokit.plugin(cachePlugin);
|
|
173
|
+
function getAuthOptions() {
|
|
174
|
+
const appId = process.env.GITHUB_APP_ID;
|
|
175
|
+
const privateKeyBase64 = process.env.GITHUB_APP_PRIVATE_KEY_BASE64;
|
|
176
|
+
const privateKeyRaw = process.env.GITHUB_APP_PRIVATE_KEY;
|
|
177
|
+
const installationId = process.env.GITHUB_APP_INSTALLATION_ID;
|
|
178
|
+
if (appId && (privateKeyBase64 || privateKeyRaw) && installationId) {
|
|
179
|
+
return {
|
|
180
|
+
authStrategy: createAppAuth,
|
|
181
|
+
auth: {
|
|
182
|
+
appId,
|
|
183
|
+
privateKey: resolvePrivateKey(privateKeyBase64, privateKeyRaw),
|
|
184
|
+
installationId: Number(installationId)
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const token = process.env.GITHUB_TOKEN;
|
|
189
|
+
if (token) {
|
|
190
|
+
return { auth: token };
|
|
191
|
+
}
|
|
192
|
+
throw new Error("GitHub auth not configured. Set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY + GITHUB_APP_INSTALLATION_ID for App auth, or GITHUB_TOKEN for PAT auth.");
|
|
193
|
+
}
|
|
194
|
+
function createFleetOctokit() {
|
|
195
|
+
return new CachedOctokit(getAuthOptions());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/shared/auth/git.ts
|
|
199
|
+
import { exec } from "child_process";
|
|
200
|
+
import { promisify } from "util";
|
|
201
|
+
var execAsync = promisify(exec);
|
|
202
|
+
async function getGitRepoInfo(remoteName = "origin") {
|
|
203
|
+
const ghRepo = process.env.GITHUB_REPOSITORY;
|
|
204
|
+
if (ghRepo) {
|
|
205
|
+
const [owner, repo] = ghRepo.split("/");
|
|
206
|
+
return { owner, repo, fullName: ghRepo };
|
|
207
|
+
}
|
|
208
|
+
const { stdout } = await execAsync(`git remote get-url ${remoteName}`);
|
|
209
|
+
return parseGitRemoteUrl(stdout.trim());
|
|
210
|
+
}
|
|
211
|
+
function parseGitRemoteUrl(remoteUrl) {
|
|
212
|
+
const sshMatch = remoteUrl.match(/git@github\.com:([^/]+)\/(.+?)(\.git)?$/);
|
|
213
|
+
if (sshMatch) {
|
|
214
|
+
const [, owner, repo] = sshMatch;
|
|
215
|
+
return {
|
|
216
|
+
owner,
|
|
217
|
+
repo: repo.replace(/\.git$/, ""),
|
|
218
|
+
fullName: `${owner}/${repo.replace(/\.git$/, "")}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const httpsMatch = remoteUrl.match(/https?:\/\/github\.com\/([^/]+)\/(.+?)(\.git)?$/);
|
|
222
|
+
if (httpsMatch) {
|
|
223
|
+
const [, owner, repo] = httpsMatch;
|
|
224
|
+
return {
|
|
225
|
+
owner,
|
|
226
|
+
repo: repo.replace(/\.git$/, ""),
|
|
227
|
+
fullName: `${owner}/${repo.replace(/\.git$/, "")}`
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
throw new Error(`Unable to parse git remote URL: ${remoteUrl}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/shared/ui/interactive.ts
|
|
234
|
+
import * as p from "@clack/prompts";
|
|
235
|
+
|
|
236
|
+
// src/shared/ui/render/init.ts
|
|
237
|
+
function renderInitEvent(event, ctx) {
|
|
238
|
+
switch (event.type) {
|
|
239
|
+
case "init:start":
|
|
240
|
+
ctx.info(`Initializing fleet for ${event.owner}/${event.repo}`);
|
|
241
|
+
break;
|
|
242
|
+
case "init:branch:creating":
|
|
243
|
+
ctx.startSpinner(`Creating branch ${event.name} from ${event.base}`);
|
|
244
|
+
break;
|
|
245
|
+
case "init:branch:created":
|
|
246
|
+
ctx.stopSpinner(`Branch ${event.name} created`);
|
|
247
|
+
break;
|
|
248
|
+
case "init:file:committed":
|
|
249
|
+
ctx.info(` ✓ ${event.path}`);
|
|
250
|
+
break;
|
|
251
|
+
case "init:file:skipped":
|
|
252
|
+
ctx.warn(` ⊘ ${event.path} — ${event.reason}`);
|
|
253
|
+
break;
|
|
254
|
+
case "init:pr:creating":
|
|
255
|
+
ctx.startSpinner("Creating pull request…");
|
|
256
|
+
break;
|
|
257
|
+
case "init:pr:created":
|
|
258
|
+
ctx.stopSpinner(`PR #${event.number} created`);
|
|
259
|
+
ctx.info(` ${event.url}`);
|
|
260
|
+
break;
|
|
261
|
+
case "init:done":
|
|
262
|
+
ctx.success(`Fleet initialized — PR: ${event.prUrl}`);
|
|
263
|
+
break;
|
|
264
|
+
case "init:auth:detected":
|
|
265
|
+
ctx.success(`Auth: ${event.method === "token" ? "GITHUB_TOKEN" : "GitHub App"}`);
|
|
266
|
+
break;
|
|
267
|
+
case "init:secret:uploading":
|
|
268
|
+
ctx.startSpinner(`Uploading secret ${event.name}…`);
|
|
269
|
+
break;
|
|
270
|
+
case "init:secret:uploaded":
|
|
271
|
+
ctx.stopSpinner(`Secret ${event.name} saved`);
|
|
272
|
+
break;
|
|
273
|
+
case "init:secret:skipped":
|
|
274
|
+
ctx.warn(` ⊘ ${event.name} — ${event.reason}`);
|
|
275
|
+
break;
|
|
276
|
+
case "init:dry-run":
|
|
277
|
+
ctx.info("Would create:");
|
|
278
|
+
event.files.forEach((f) => ctx.message(` ${f}`));
|
|
279
|
+
break;
|
|
280
|
+
case "init:already-initialized":
|
|
281
|
+
ctx.warn("Repository is already initialized");
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/shared/ui/render/configure.ts
|
|
287
|
+
function renderConfigureEvent(event, ctx) {
|
|
288
|
+
switch (event.type) {
|
|
289
|
+
case "configure:start":
|
|
290
|
+
ctx.info(`Configuring ${event.resource} for ${event.owner}/${event.repo}`);
|
|
291
|
+
break;
|
|
292
|
+
case "configure:label:created":
|
|
293
|
+
ctx.info(` ✓ Label "${event.name}" created`);
|
|
294
|
+
break;
|
|
295
|
+
case "configure:label:exists":
|
|
296
|
+
ctx.warn(` ⊘ Label "${event.name}" already exists`);
|
|
297
|
+
break;
|
|
298
|
+
case "configure:secret:uploading":
|
|
299
|
+
ctx.startSpinner(`Uploading secret ${event.name}…`);
|
|
300
|
+
break;
|
|
301
|
+
case "configure:secret:uploaded":
|
|
302
|
+
ctx.stopSpinner(`Secret ${event.name} uploaded`);
|
|
303
|
+
break;
|
|
304
|
+
case "configure:done":
|
|
305
|
+
ctx.success("Configuration complete");
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/shared/ui/session-url.ts
|
|
311
|
+
var JULES_BASE_URL = "https://jules.google.com";
|
|
312
|
+
function sessionUrl(sessionId) {
|
|
313
|
+
return `${JULES_BASE_URL}/sessions/${sessionId}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/shared/ui/render/analyze.ts
|
|
317
|
+
function renderAnalyzeEvent(event, ctx) {
|
|
318
|
+
switch (event.type) {
|
|
319
|
+
case "analyze:start":
|
|
320
|
+
ctx.info(`Analyzing ${event.goalCount} goal(s) for ${event.owner}/${event.repo}`);
|
|
321
|
+
break;
|
|
322
|
+
case "analyze:goal:start":
|
|
323
|
+
if (event.total > 1) {
|
|
324
|
+
ctx.step(`[${event.index}/${event.total}] ${event.file}`);
|
|
325
|
+
} else {
|
|
326
|
+
ctx.step(event.file);
|
|
327
|
+
}
|
|
328
|
+
if (event.milestone)
|
|
329
|
+
ctx.info(` Milestone: ${event.milestone}`);
|
|
330
|
+
break;
|
|
331
|
+
case "analyze:milestone:resolved":
|
|
332
|
+
ctx.info(` Milestone "${event.title}" (#${event.id})`);
|
|
333
|
+
break;
|
|
334
|
+
case "analyze:context:fetched":
|
|
335
|
+
ctx.info(` Context: ${event.openIssues} open, ${event.closedIssues} closed, ${event.prs} PRs`);
|
|
336
|
+
break;
|
|
337
|
+
case "analyze:session:dispatching":
|
|
338
|
+
ctx.startSpinner(`Dispatching session for ${event.goal}…`);
|
|
339
|
+
break;
|
|
340
|
+
case "analyze:session:started":
|
|
341
|
+
ctx.stopSpinner(`Session started: ${event.id}`);
|
|
342
|
+
ctx.info(` ${sessionUrl(event.id)}`);
|
|
343
|
+
break;
|
|
344
|
+
case "analyze:session:failed":
|
|
345
|
+
ctx.stopSpinner();
|
|
346
|
+
ctx.error(` Failed: ${event.error}`);
|
|
347
|
+
break;
|
|
348
|
+
case "analyze:done":
|
|
349
|
+
ctx.success(`Analysis complete — ${event.sessionsStarted} session(s) from ${event.goalsProcessed} goal(s)`);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/shared/ui/render/dispatch.ts
|
|
355
|
+
function renderDispatchEvent(event, ctx) {
|
|
356
|
+
switch (event.type) {
|
|
357
|
+
case "dispatch:start":
|
|
358
|
+
ctx.info(`Dispatching from milestone ${event.milestone}`);
|
|
359
|
+
break;
|
|
360
|
+
case "dispatch:scanning":
|
|
361
|
+
ctx.startSpinner("Scanning for fleet issues…");
|
|
362
|
+
break;
|
|
363
|
+
case "dispatch:found":
|
|
364
|
+
ctx.stopSpinner(`Found ${event.count} undispatched issue(s)`);
|
|
365
|
+
break;
|
|
366
|
+
case "dispatch:issue:dispatching":
|
|
367
|
+
ctx.startSpinner(`#${event.number}: ${event.title}`);
|
|
368
|
+
break;
|
|
369
|
+
case "dispatch:issue:dispatched":
|
|
370
|
+
ctx.stopSpinner(`#${event.number} → session ${event.sessionId}`);
|
|
371
|
+
ctx.info(` ${sessionUrl(event.sessionId)}`);
|
|
372
|
+
break;
|
|
373
|
+
case "dispatch:issue:skipped":
|
|
374
|
+
ctx.warn(` ⊘ #${event.number}: ${event.reason}`);
|
|
375
|
+
break;
|
|
376
|
+
case "dispatch:done":
|
|
377
|
+
ctx.success(`Dispatch complete — ${event.dispatched} dispatched, ${event.skipped} skipped`);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/shared/ui/render/merge.ts
|
|
383
|
+
function renderMergeEvent(event, ctx) {
|
|
384
|
+
switch (event.type) {
|
|
385
|
+
case "merge:start":
|
|
386
|
+
ctx.info(`Merging ${event.prCount} PR(s) in ${event.owner}/${event.repo} [${event.mode}]`);
|
|
387
|
+
break;
|
|
388
|
+
case "merge:no-prs":
|
|
389
|
+
ctx.info("No PRs ready to merge.");
|
|
390
|
+
break;
|
|
391
|
+
case "merge:pr:processing":
|
|
392
|
+
ctx.startSpinner(`PR #${event.number}: ${event.title}${event.retry ? ` (retry ${event.retry})` : ""}`);
|
|
393
|
+
break;
|
|
394
|
+
case "merge:branch:updating":
|
|
395
|
+
ctx.startSpinner(`Updating branch for PR #${event.prNumber}…`);
|
|
396
|
+
break;
|
|
397
|
+
case "merge:branch:updated":
|
|
398
|
+
ctx.stopSpinner(`Branch updated for PR #${event.prNumber}`);
|
|
399
|
+
break;
|
|
400
|
+
case "merge:ci:waiting":
|
|
401
|
+
ctx.startSpinner(`Waiting for CI on PR #${event.prNumber}…`);
|
|
402
|
+
break;
|
|
403
|
+
case "merge:ci:check": {
|
|
404
|
+
const icon = event.status === "pass" ? "✓" : event.status === "fail" ? "✗" : "…";
|
|
405
|
+
const dur = event.duration ? ` (${event.duration}s)` : "";
|
|
406
|
+
ctx.info(` ${icon} ${event.name}${dur}`);
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
case "merge:ci:passed":
|
|
410
|
+
ctx.stopSpinner(`CI passed for PR #${event.prNumber}`);
|
|
411
|
+
break;
|
|
412
|
+
case "merge:ci:failed":
|
|
413
|
+
ctx.stopSpinner(`CI failed for PR #${event.prNumber}`);
|
|
414
|
+
break;
|
|
415
|
+
case "merge:ci:timeout":
|
|
416
|
+
ctx.stopSpinner(`CI timed out for PR #${event.prNumber}`);
|
|
417
|
+
break;
|
|
418
|
+
case "merge:ci:none":
|
|
419
|
+
ctx.stopSpinner(`No CI checks for PR #${event.prNumber}`);
|
|
420
|
+
break;
|
|
421
|
+
case "merge:pr:merging":
|
|
422
|
+
ctx.startSpinner(`Merging PR #${event.prNumber}…`);
|
|
423
|
+
break;
|
|
424
|
+
case "merge:pr:merged":
|
|
425
|
+
ctx.stopSpinner(`PR #${event.prNumber} merged ✓`);
|
|
426
|
+
break;
|
|
427
|
+
case "merge:pr:skipped":
|
|
428
|
+
ctx.warn(` ⊘ PR #${event.prNumber}: ${event.reason}`);
|
|
429
|
+
break;
|
|
430
|
+
case "merge:conflict:detected":
|
|
431
|
+
ctx.stopSpinner(`Conflict detected on PR #${event.prNumber}`);
|
|
432
|
+
break;
|
|
433
|
+
case "merge:redispatch:start":
|
|
434
|
+
ctx.startSpinner(`Re-dispatching PR #${event.oldPr}…`);
|
|
435
|
+
break;
|
|
436
|
+
case "merge:redispatch:waiting":
|
|
437
|
+
ctx.startSpinner(`Waiting for re-dispatched PR (was #${event.oldPr})…`);
|
|
438
|
+
break;
|
|
439
|
+
case "merge:redispatch:done":
|
|
440
|
+
ctx.stopSpinner(`Re-dispatched: #${event.oldPr} → #${event.newPr}`);
|
|
441
|
+
break;
|
|
442
|
+
case "merge:done":
|
|
443
|
+
ctx.success(`Merge complete — ${event.merged.length} merged, ${event.skipped.length} skipped`);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// src/shared/ui/render/error.ts
|
|
449
|
+
function renderErrorEvent(event, ctx) {
|
|
450
|
+
ctx.stopSpinner();
|
|
451
|
+
ctx.error(`[${event.code}] ${event.message}`);
|
|
452
|
+
if (event.suggestion)
|
|
453
|
+
ctx.info(` \uD83D\uDCA1 ${event.suggestion}`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/shared/ui/interactive.ts
|
|
457
|
+
class InteractiveRenderer {
|
|
458
|
+
spinner = null;
|
|
459
|
+
ctx = {
|
|
460
|
+
info: (msg) => p.log.info(msg),
|
|
461
|
+
success: (msg) => p.log.success(msg),
|
|
462
|
+
warn: (msg) => p.log.warn(msg),
|
|
463
|
+
error: (msg) => p.log.error(msg),
|
|
464
|
+
message: (msg) => p.log.message(msg),
|
|
465
|
+
step: (msg) => p.log.step(msg),
|
|
466
|
+
startSpinner: (msg) => this.startSpinner(msg),
|
|
467
|
+
stopSpinner: (msg) => this.stopSpinner(msg)
|
|
468
|
+
};
|
|
469
|
+
start(title) {
|
|
470
|
+
p.intro(title);
|
|
471
|
+
}
|
|
472
|
+
end(message) {
|
|
473
|
+
this.stopSpinner();
|
|
474
|
+
p.outro(message);
|
|
475
|
+
}
|
|
476
|
+
error(message) {
|
|
477
|
+
this.stopSpinner();
|
|
478
|
+
p.log.error(message);
|
|
479
|
+
}
|
|
480
|
+
render(event) {
|
|
481
|
+
if (event.type.startsWith("init:"))
|
|
482
|
+
return renderInitEvent(event, this.ctx);
|
|
483
|
+
if (event.type.startsWith("configure:"))
|
|
484
|
+
return renderConfigureEvent(event, this.ctx);
|
|
485
|
+
if (event.type.startsWith("analyze:"))
|
|
486
|
+
return renderAnalyzeEvent(event, this.ctx);
|
|
487
|
+
if (event.type.startsWith("dispatch:"))
|
|
488
|
+
return renderDispatchEvent(event, this.ctx);
|
|
489
|
+
if (event.type.startsWith("merge:"))
|
|
490
|
+
return renderMergeEvent(event, this.ctx);
|
|
491
|
+
if (event.type === "error")
|
|
492
|
+
return renderErrorEvent(event, this.ctx);
|
|
493
|
+
}
|
|
494
|
+
startSpinner(message) {
|
|
495
|
+
this.stopSpinner();
|
|
496
|
+
this.spinner = p.spinner();
|
|
497
|
+
this.spinner.start(message);
|
|
498
|
+
}
|
|
499
|
+
stopSpinner(message) {
|
|
500
|
+
if (this.spinner) {
|
|
501
|
+
this.spinner.stop(message);
|
|
502
|
+
this.spinner = null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/shared/ui/plain.ts
|
|
508
|
+
class PlainRenderer {
|
|
509
|
+
ctx = {
|
|
510
|
+
info: (msg) => console.log(msg),
|
|
511
|
+
success: (msg) => console.log(msg),
|
|
512
|
+
warn: (msg) => console.log(msg),
|
|
513
|
+
error: (msg) => console.error(msg),
|
|
514
|
+
message: (msg) => console.log(msg),
|
|
515
|
+
step: (msg) => console.log(msg),
|
|
516
|
+
startSpinner: (msg) => console.log(msg),
|
|
517
|
+
stopSpinner: (msg) => {
|
|
518
|
+
if (msg)
|
|
519
|
+
console.log(` ✓ ${msg}`);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
start(title) {
|
|
523
|
+
console.log(`
|
|
524
|
+
═══ ${title} ═══
|
|
525
|
+
`);
|
|
526
|
+
}
|
|
527
|
+
end(message) {
|
|
528
|
+
console.log(`
|
|
529
|
+
═══ ${message} ═══
|
|
530
|
+
`);
|
|
531
|
+
}
|
|
532
|
+
error(message) {
|
|
533
|
+
console.error(`ERROR: ${message}`);
|
|
534
|
+
}
|
|
535
|
+
render(event) {
|
|
536
|
+
if (event.type.startsWith("init:"))
|
|
537
|
+
return renderInitEvent(event, this.ctx);
|
|
538
|
+
if (event.type.startsWith("configure:"))
|
|
539
|
+
return renderConfigureEvent(event, this.ctx);
|
|
540
|
+
if (event.type.startsWith("analyze:"))
|
|
541
|
+
return renderAnalyzeEvent(event, this.ctx);
|
|
542
|
+
if (event.type.startsWith("dispatch:"))
|
|
543
|
+
return renderDispatchEvent(event, this.ctx);
|
|
544
|
+
if (event.type.startsWith("merge:"))
|
|
545
|
+
return renderMergeEvent(event, this.ctx);
|
|
546
|
+
if (event.type === "error")
|
|
547
|
+
return renderErrorEvent(event, this.ctx);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/shared/ui/index.ts
|
|
552
|
+
function isInteractive() {
|
|
553
|
+
if (process.env.CI === "true")
|
|
554
|
+
return false;
|
|
555
|
+
if (!process.stdout.isTTY)
|
|
556
|
+
return false;
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
function createRenderer(interactive) {
|
|
560
|
+
const useInteractive = interactive ?? isInteractive();
|
|
561
|
+
return useInteractive ? new InteractiveRenderer : new PlainRenderer;
|
|
562
|
+
}
|
|
563
|
+
function createEmitter(renderer) {
|
|
564
|
+
return (event) => renderer.render(event);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/cli/configure.command.ts
|
|
568
|
+
var configure_command_default = defineCommand({
|
|
569
|
+
meta: {
|
|
570
|
+
name: "configure",
|
|
571
|
+
description: "Configure fleet repo resources (labels, etc.)"
|
|
572
|
+
},
|
|
573
|
+
args: {
|
|
574
|
+
resource: {
|
|
575
|
+
type: "positional",
|
|
576
|
+
description: "Resource to configure (labels)",
|
|
577
|
+
required: true
|
|
578
|
+
},
|
|
579
|
+
delete: {
|
|
580
|
+
type: "boolean",
|
|
581
|
+
description: "Delete resources instead of creating them",
|
|
582
|
+
default: false
|
|
583
|
+
},
|
|
584
|
+
owner: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "Repository owner (auto-detected from git remote if omitted)"
|
|
587
|
+
},
|
|
588
|
+
repo: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "Repository name (auto-detected from git remote if omitted)"
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
async run({ args }) {
|
|
594
|
+
const renderer = createRenderer();
|
|
595
|
+
let owner = args.owner;
|
|
596
|
+
let repo = args.repo;
|
|
597
|
+
if (!owner || !repo) {
|
|
598
|
+
const repoInfo = await getGitRepoInfo();
|
|
599
|
+
owner = owner || repoInfo.owner;
|
|
600
|
+
repo = repo || repoInfo.repo;
|
|
601
|
+
}
|
|
602
|
+
const action = args.delete ? "delete" : "create";
|
|
603
|
+
renderer.start(`Fleet Configure — ${args.resource} (${action})`);
|
|
604
|
+
const input = ConfigureInputSchema.parse({
|
|
605
|
+
resource: args.resource,
|
|
606
|
+
action,
|
|
607
|
+
owner,
|
|
608
|
+
repo
|
|
609
|
+
});
|
|
610
|
+
const octokit = createFleetOctokit();
|
|
611
|
+
const emit = createEmitter(renderer);
|
|
612
|
+
const handler = new ConfigureHandler({ octokit, emit });
|
|
613
|
+
const result = await handler.execute(input);
|
|
614
|
+
if (!result.success) {
|
|
615
|
+
renderer.error(result.error.message);
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
renderer.end(`${args.resource} configured.`);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
export {
|
|
622
|
+
configure_command_default as default
|
|
623
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const _default: import("citty").CommandDef<{
|
|
2
|
+
milestone: {
|
|
3
|
+
type: "string";
|
|
4
|
+
description: string;
|
|
5
|
+
required: true;
|
|
6
|
+
};
|
|
7
|
+
owner: {
|
|
8
|
+
type: "string";
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
repo: {
|
|
12
|
+
type: "string";
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
}>;
|
|
16
|
+
export default _default;
|