OXYGENHUB
◆ main oxygenhub-vscode / src / newrepo.ts
4.6 KiB · last changed OxygenHub 0.13.0: Drops in the editor with Share as a Drop (closes #7); Flow approvals, expedite, reports, timings and test results (closes #8); ready/draft, landable, trust reports, asset downloads and new repositories (closes #9) 6 days ago source blame history raw
991516a1 Andy McCall ↶ 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
}