991516a1 Andy McCall 6 days ago ↶
1
// New Repository… (spec §8.5): a repository on a hub under you or one of
2
// your organisations, optionally with an assistant's starting files (#347),
3
// then cloned into a folder you choose.
4
5
import * as vscode from "vscode";
6
import type * as api from "./hubapi";
7
import { HubAuth } from "./auth";
8
9
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
10
11
export async function newRepository(auth: HubAuth): Promise<void> {
12
const hubs = auth.all().map((s) => s.base);
13
const base = hubs.length === 1 ? hubs[0] : await vscode.window.showQuickPick(hubs, { placeHolder: "Create the repository on" });
14
if (!base) {
15
if (!hubs.length) {
16
void vscode.window.showWarningMessage("OxygenHub: sign in to a hub first.");
17
}
18
return;
19
}
20
const client = auth.clientFor(base);
21
const [me, orgs] = await Promise.all([client.me(), client.orgs().catch(() => [] as api.Organisation[])]);
22
const owner = orgs.length
23
? (await vscode.window.showQuickPick([{ label: me.username, description: "you" }, ...orgs.map((o) => ({ label: o.name, description: o.display_name }))], { placeHolder: "Owner" }))?.label
24
: me.username;
25
if (!owner) {
26
return;
27
}
28
const name = await vscode.window.showInputBox({ prompt: `Repository name under ${owner}`, validateInput: (v) => (NAME.test(v) ? undefined : "Letters, digits, '.', '_' and '-', starting with a letter or digit") });
29
if (!name) {
30
return;
31
}
32
const description = (await vscode.window.showInputBox({ prompt: "What is it for? (optional; an assistant can draft its starting files from this)" })) ?? "";
33
const vis = await vscode.window.showQuickPick(
34
[
35
{ label: "$(lock) Private", value: "private" as const },
36
{ label: "$(organization) Internal", description: "everyone signed in to the hub", value: "internal" as const },
37
{ label: "$(globe) Public", value: "public" as const },
38
],
39
{ placeHolder: "Visibility" },
40
);
41
if (!vis) {
42
return;
43
}
44
try {
45
const repo = await client.createRepository({ owner, name, description, visibility: vis.value });
46
if (description.trim()) {
47
await offerStarter(client, repo, description.trim());
48
}
49
const pick = await vscode.window.showInformationMessage(`OxygenHub: created ${owner}/${name}.`, "Clone", "Open on Hub");
50
if (pick === "Open on Hub") {
51
void vscode.env.openExternal(vscode.Uri.parse(`${base}/${owner}/${name}`));
52
} else if (pick === "Clone") {
53
const parent = await vscode.window.showOpenDialog({ canSelectFolders: true, canSelectFiles: false, openLabel: "Clone Here" });
54
if (parent?.[0]) {
55
const t = vscode.window.createTerminal({ name: "OxygenHub", cwd: parent[0].fsPath });
56
t.show();
57
t.sendText(`oxy clone ${repo.clone_url} ${name}`);
58
}
59
}
60
} catch (e) {
61
void vscode.window.showErrorMessage(`OxygenHub: ${(e as Error).message}`);
62
}
63
}
64
65
/** When the owner has an assistant, offer its starting files, and wait for them. */
66
async function offerStarter(client: ReturnType<HubAuth["clientFor"]>, repo: api.Repository, description: string): Promise<void> {
67
const { assistants } = await client.starterAssistants(repo.owner).catch(() => ({ assistants: [] as api.StarterAssistant[] }));
68
if (!assistants.length) {
69
return;
70
}
71
const pick = await vscode.window.showQuickPick(
72
[{ label: "$(close) No, start empty" }, ...assistants.map((a) => ({ label: `$(sparkle) Ask ${a.name}`, description: `${a.model}, from ${a.from}`, assistant: a.name }))],
73
{ placeHolder: "Ask an assistant for its starting files (a README, build files, a first test)?" },
74
);
75
if (!pick || !("assistant" in pick)) {
76
return;
77
}
78
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: `OxygenHub: ${pick.assistant} is drafting the starting files…`, cancellable: true }, async (_p, token) => {
79
let s = await client.startStarter(repo.owner, repo.name, { description, assistant: pick.assistant });
80
// A model may take a few minutes; the hub commits the files when it is done.
81
for (let i = 0; i < 120 && s.status === "running" && !token.isCancellationRequested; i++) {
82
await new Promise((r) => setTimeout(r, 3000));
83
s = await client.starterStatus(repo.owner, repo.name);
84
}
85
if (s.status === "done") {
86
void vscode.window.showInformationMessage(`OxygenHub: ${s.assistant} committed ${s.files?.length ?? 0} starting file(s).`);
87
} else if (s.status === "failed") {
88
void vscode.window.showWarningMessage(`OxygenHub: the starter failed: ${s.error}`);
89
} else if (s.status === "running") {
90
void vscode.window.showInformationMessage("OxygenHub: the starter is still being drafted; the hub commits it when done.");
91
}
92
});
93
}