@clipboard-health/groundcrew 4.0.3 → 4.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 +37 -13
- package/crew.config.example.ts +5 -18
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +27 -49
- package/dist/commands/resumeWorkspace.d.ts.map +1 -1
- package/dist/commands/resumeWorkspace.js +1 -2
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +1 -7
- package/dist/commands/upgrade.d.ts +0 -11
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +14 -100
- package/dist/lib/agentLaunch.d.ts +0 -6
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +1 -12
- package/dist/lib/cmuxAdapter.d.ts +8 -0
- package/dist/lib/cmuxAdapter.d.ts.map +1 -0
- package/dist/lib/cmuxAdapter.js +163 -0
- package/dist/lib/config.d.ts +2 -76
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +29 -102
- package/dist/lib/launchCommand.d.ts +3 -3
- package/dist/lib/sandboxName.d.ts +9 -0
- package/dist/lib/sandboxName.d.ts.map +1 -0
- package/dist/lib/sandboxName.js +12 -0
- package/dist/lib/tmuxAdapter.d.ts +9 -0
- package/dist/lib/tmuxAdapter.d.ts.map +1 -0
- package/dist/lib/tmuxAdapter.js +156 -0
- package/dist/lib/workspaceAdapter.d.ts +79 -0
- package/dist/lib/workspaceAdapter.d.ts.map +1 -0
- package/dist/lib/workspaceAdapter.js +17 -0
- package/dist/lib/workspaces.d.ts +7 -55
- package/dist/lib/workspaces.d.ts.map +1 -1
- package/dist/lib/workspaces.js +8 -404
- package/package.json +1 -2
- package/dist/commands/sandbox/auth.d.ts +0 -3
- package/dist/commands/sandbox/auth.d.ts.map +0 -1
- package/dist/commands/sandbox/auth.js +0 -227
- package/dist/commands/sandbox/index.d.ts +0 -2
- package/dist/commands/sandbox/index.d.ts.map +0 -1
- package/dist/commands/sandbox/index.js +0 -47
- package/dist/commands/sandbox/inspect.d.ts +0 -2
- package/dist/commands/sandbox/inspect.d.ts.map +0 -1
- package/dist/commands/sandbox/inspect.js +0 -18
- package/dist/commands/sandbox/lifecycle.d.ts +0 -7
- package/dist/commands/sandbox/lifecycle.d.ts.map +0 -1
- package/dist/commands/sandbox/lifecycle.js +0 -68
- package/dist/commands/sandbox/model.d.ts +0 -10
- package/dist/commands/sandbox/model.d.ts.map +0 -1
- package/dist/commands/sandbox/model.js +0 -37
- package/dist/commands/sandbox/picker.d.ts +0 -20
- package/dist/commands/sandbox/picker.d.ts.map +0 -1
- package/dist/commands/sandbox/picker.js +0 -23
- package/dist/commands/setupRepos.d.ts +0 -44
- package/dist/commands/setupRepos.d.ts.map +0 -1
- package/dist/commands/setupRepos.js +0 -212
- package/dist/lib/dockerSandbox.d.ts +0 -43
- package/dist/lib/dockerSandbox.d.ts.map +0 -1
- package/dist/lib/dockerSandbox.js +0 -69
- package/dist/lib/sandboxGitDefaults.d.ts +0 -10
- package/dist/lib/sandboxGitDefaults.d.ts.map +0 -1
- package/dist/lib/sandboxGitDefaults.js +0 -31
- package/dist/lib/upgrade.d.ts +0 -66
- package/dist/lib/upgrade.d.ts.map +0 -1
- package/dist/lib/upgrade.js +0 -178
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from "../../lib/config.js";
|
|
2
|
-
import { runAuth } from "./auth.js";
|
|
3
|
-
import { runList } from "./inspect.js";
|
|
4
|
-
import { runEnsure, runRegenerate, runRemove } from "./lifecycle.js";
|
|
5
|
-
const USAGE = [
|
|
6
|
-
"Usage: crew sandbox <verb> [...args]",
|
|
7
|
-
"",
|
|
8
|
-
"Verbs:",
|
|
9
|
-
" list Show every groundcrew-owned sandbox known to sbx",
|
|
10
|
-
" ensure [<model>] Provision the sandbox for one model, or all when omitted",
|
|
11
|
-
" regenerate <model>|--all Tear down and recreate from current template/kits",
|
|
12
|
-
" auth <model>|--all Open a checkbox picker of every tool available in <model>'s",
|
|
13
|
-
" sandbox and run the login flow for each one you select;",
|
|
14
|
-
" --all loops through every configured sandbox in turn",
|
|
15
|
-
" rm <model> Remove the sandbox for a model",
|
|
16
|
-
].join("\n");
|
|
17
|
-
export async function sandboxCli(argv) {
|
|
18
|
-
const [verb, ...rest] = argv;
|
|
19
|
-
if (verb === undefined) {
|
|
20
|
-
throw new Error(USAGE);
|
|
21
|
-
}
|
|
22
|
-
switch (verb) {
|
|
23
|
-
case "list": {
|
|
24
|
-
await runList();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
case "ensure": {
|
|
28
|
-
await runEnsure(await loadConfig(), rest);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
case "regenerate": {
|
|
32
|
-
await runRegenerate(await loadConfig(), rest);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
case "auth": {
|
|
36
|
-
await runAuth(await loadConfig(), rest);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
case "rm": {
|
|
40
|
-
await runRemove(await loadConfig(), rest);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
default: {
|
|
44
|
-
throw new Error(`Unknown sandbox sub-verb: ${verb}\n${USAGE}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../../src/commands/sandbox/inspect.ts"],"names":[],"mappings":"AAKA,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAc7C"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { runCommandAsync } from "../../lib/commandRunner.js";
|
|
2
|
-
import { writeOutput } from "../../lib/util.js";
|
|
3
|
-
const SANDBOX_NAME_PREFIX = "groundcrew-";
|
|
4
|
-
export async function runList() {
|
|
5
|
-
const output = await runCommandAsync("sbx", ["ls"]);
|
|
6
|
-
const names = output
|
|
7
|
-
.split("\n")
|
|
8
|
-
.map((line) => line.trim().split(/\s+/)[0])
|
|
9
|
-
.filter((name) => name !== undefined && name.startsWith(SANDBOX_NAME_PREFIX))
|
|
10
|
-
.map((name) => name.slice(SANDBOX_NAME_PREFIX.length));
|
|
11
|
-
if (names.length === 0) {
|
|
12
|
-
writeOutput("(none)");
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
for (const name of names) {
|
|
16
|
-
writeOutput(name);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ResolvedConfig } from "../../lib/config.ts";
|
|
2
|
-
import { type SandboxModel } from "./model.ts";
|
|
3
|
-
export declare function ensureOne(config: ResolvedConfig, model: SandboxModel, alreadyExists?: boolean): Promise<void>;
|
|
4
|
-
export declare function runEnsure(config: ResolvedConfig, argv: string[]): Promise<void>;
|
|
5
|
-
export declare function runRegenerate(config: ResolvedConfig, argv: string[]): Promise<void>;
|
|
6
|
-
export declare function runRemove(config: ResolvedConfig, argv: string[]): Promise<void>;
|
|
7
|
-
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/commands/sandbox/lifecycle.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAsC,KAAK,YAAY,EAAiB,MAAM,YAAY,CAAC;AAElG,wBAAsB,SAAS,CAC7B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,YAAY,EACnB,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,IAAI,CAAC,CAQf;AAMD,wBAAsB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBrF;AAUD,wBAAsB,aAAa,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBzF;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrF"}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { resolve } from "node:path";
|
|
2
|
-
import { runCommandAsync } from "../../lib/commandRunner.js";
|
|
3
|
-
import { ensureSandbox, sandboxExists } from "../../lib/dockerSandbox.js";
|
|
4
|
-
import { writeOutput } from "../../lib/util.js";
|
|
5
|
-
import { requireOnePositional, resolveModel, sandboxModels } from "./model.js";
|
|
6
|
-
export async function ensureOne(config, model, alreadyExists) {
|
|
7
|
-
await ensureSandbox({
|
|
8
|
-
sandboxName: model.sandboxName,
|
|
9
|
-
sandbox: model.sandbox,
|
|
10
|
-
mountPath: resolve(config.workspace.projectDir),
|
|
11
|
-
gitDefaults: config.sandbox.gitDefaults,
|
|
12
|
-
...(alreadyExists === undefined ? {} : { alreadyExists }),
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
async function removeOne(model) {
|
|
16
|
-
await runCommandAsync("sbx", ["rm", "--force", model.sandboxName]);
|
|
17
|
-
}
|
|
18
|
-
export async function runEnsure(config, argv) {
|
|
19
|
-
const targets = argv.length === 0
|
|
20
|
-
? sandboxModels(config)
|
|
21
|
-
: [resolveModel(config, requireOnePositional(argv, "Usage: crew sandbox ensure [<model>]"))];
|
|
22
|
-
if (targets.length === 0) {
|
|
23
|
-
writeOutput("No sandbox models configured.");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
for (const model of targets) {
|
|
27
|
-
// oxlint-disable-next-line no-await-in-loop -- one sandbox at a time; probe then ensure
|
|
28
|
-
const existed = await sandboxExists(model.sandboxName);
|
|
29
|
-
writeOutput(existed
|
|
30
|
-
? `${model.sandboxName}: already exists`
|
|
31
|
-
: `${model.sandboxName}: creating (agent=${model.sandbox.agent}, template=${model.sandbox.template ?? "default"})`);
|
|
32
|
-
// oxlint-disable-next-line no-await-in-loop -- sbx create is intentionally sequential
|
|
33
|
-
await ensureOne(config, model, existed);
|
|
34
|
-
if (!existed) {
|
|
35
|
-
writeOutput(`${model.sandboxName}: created`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
function regenerateTargets(config, argv) {
|
|
40
|
-
const target = requireOnePositional(argv, "Usage: crew sandbox regenerate <model>|--all");
|
|
41
|
-
if (target === "--all") {
|
|
42
|
-
return sandboxModels(config);
|
|
43
|
-
}
|
|
44
|
-
return [resolveModel(config, target)];
|
|
45
|
-
}
|
|
46
|
-
export async function runRegenerate(config, argv) {
|
|
47
|
-
const targets = regenerateTargets(config, argv);
|
|
48
|
-
if (targets.length === 0) {
|
|
49
|
-
writeOutput("No sandbox models configured.");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
for (const model of targets) {
|
|
53
|
-
writeOutput(`${model.sandboxName}: removing existing sandbox...`);
|
|
54
|
-
// oxlint-disable-next-line no-await-in-loop -- sbx rm/create are intentionally sequential
|
|
55
|
-
await removeOne(model);
|
|
56
|
-
writeOutput(`${model.sandboxName}: creating (agent=${model.sandbox.agent}, template=${model.sandbox.template ?? "default"})`);
|
|
57
|
-
// oxlint-disable-next-line no-await-in-loop -- sbx rm/create are intentionally sequential
|
|
58
|
-
await ensureOne(config, model, false);
|
|
59
|
-
writeOutput(`${model.sandboxName}: regenerated`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
export async function runRemove(config, argv) {
|
|
63
|
-
const modelName = requireOnePositional(argv, "Usage: crew sandbox rm <model>");
|
|
64
|
-
const model = resolveModel(config, modelName);
|
|
65
|
-
writeOutput(`${model.sandboxName}: removing...`);
|
|
66
|
-
await removeOne(model);
|
|
67
|
-
writeOutput(`${model.sandboxName}: removed`);
|
|
68
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ResolvedConfig, SandboxDefinition } from "../../lib/config.ts";
|
|
2
|
-
export interface SandboxModel {
|
|
3
|
-
modelName: string;
|
|
4
|
-
sandbox: SandboxDefinition;
|
|
5
|
-
sandboxName: string;
|
|
6
|
-
}
|
|
7
|
-
export declare function sandboxModels(config: ResolvedConfig): SandboxModel[];
|
|
8
|
-
export declare function resolveModel(config: ResolvedConfig, modelName: string): SandboxModel;
|
|
9
|
-
export declare function requireOnePositional(argv: string[], usage: string): string;
|
|
10
|
-
//# sourceMappingURL=model.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/commands/sandbox/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAG7E,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,EAAE,CAcpE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAapF;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM1E"}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { sandboxNameFor } from "../../lib/dockerSandbox.js";
|
|
2
|
-
export function sandboxModels(config) {
|
|
3
|
-
const models = [];
|
|
4
|
-
for (const [modelName, definition] of Object.entries(config.models.definitions)) {
|
|
5
|
-
const { sandbox } = definition;
|
|
6
|
-
if (sandbox === undefined) {
|
|
7
|
-
continue;
|
|
8
|
-
}
|
|
9
|
-
models.push({
|
|
10
|
-
modelName,
|
|
11
|
-
sandbox,
|
|
12
|
-
sandboxName: sandboxNameFor({ agent: sandbox.agent }),
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
return models;
|
|
16
|
-
}
|
|
17
|
-
export function resolveModel(config, modelName) {
|
|
18
|
-
const definition = config.models.definitions[modelName];
|
|
19
|
-
if (definition === undefined) {
|
|
20
|
-
throw new Error(`crew sandbox: unknown model '${modelName}'`);
|
|
21
|
-
}
|
|
22
|
-
if (definition.sandbox === undefined) {
|
|
23
|
-
throw new Error(`crew sandbox: model '${modelName}' has no sandbox config`);
|
|
24
|
-
}
|
|
25
|
-
return {
|
|
26
|
-
modelName,
|
|
27
|
-
sandbox: definition.sandbox,
|
|
28
|
-
sandboxName: sandboxNameFor({ agent: definition.sandbox.agent }),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
export function requireOnePositional(argv, usage) {
|
|
32
|
-
const [first, ...rest] = argv;
|
|
33
|
-
if (first === undefined || rest.length > 0) {
|
|
34
|
-
throw new Error(usage);
|
|
35
|
-
}
|
|
36
|
-
return first;
|
|
37
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface ToolChoice {
|
|
2
|
-
/** Recipe key (e.g. "claude", "github"). Returned in the selection. */
|
|
3
|
-
key: string;
|
|
4
|
-
/** Human-friendly label shown in the prompt. */
|
|
5
|
-
label: string;
|
|
6
|
-
/** Auth status decoration: ✓ when authenticated, ○ otherwise. */
|
|
7
|
-
authenticated: boolean;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Show an interactive checkbox picker so the engineer chooses which
|
|
11
|
-
* tools to authenticate. Items marked `authenticated` start unchecked
|
|
12
|
-
* (no need to re-auth); unauthed items start checked (default action
|
|
13
|
-
* is "auth what's missing"). The returned array is the list of `key`
|
|
14
|
-
* values that the engineer left checked when they confirmed.
|
|
15
|
-
*
|
|
16
|
-
* Extracted to its own module so tests can vi.mock it and skip stdin
|
|
17
|
-
* interaction; the real implementation pulls @inquirer/checkbox.
|
|
18
|
-
*/
|
|
19
|
-
export declare function pickTools(choices: readonly ToolChoice[]): Promise<readonly string[]>;
|
|
20
|
-
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../src/commands/sandbox/picker.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAW1F"}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import checkbox from "@inquirer/checkbox";
|
|
2
|
-
/**
|
|
3
|
-
* Show an interactive checkbox picker so the engineer chooses which
|
|
4
|
-
* tools to authenticate. Items marked `authenticated` start unchecked
|
|
5
|
-
* (no need to re-auth); unauthed items start checked (default action
|
|
6
|
-
* is "auth what's missing"). The returned array is the list of `key`
|
|
7
|
-
* values that the engineer left checked when they confirmed.
|
|
8
|
-
*
|
|
9
|
-
* Extracted to its own module so tests can vi.mock it and skip stdin
|
|
10
|
-
* interaction; the real implementation pulls @inquirer/checkbox.
|
|
11
|
-
*/
|
|
12
|
-
export async function pickTools(choices) {
|
|
13
|
-
const selected = await checkbox({
|
|
14
|
-
message: "Select tools to authenticate (space to toggle, enter to confirm):",
|
|
15
|
-
choices: choices.map((choice) => ({
|
|
16
|
-
name: `${choice.authenticated ? "✓" : "○"} ${choice.label}`,
|
|
17
|
-
value: choice.key,
|
|
18
|
-
checked: !choice.authenticated,
|
|
19
|
-
})),
|
|
20
|
-
pageSize: Math.max(choices.length, 1),
|
|
21
|
-
});
|
|
22
|
-
return selected;
|
|
23
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `crew setup repos` — clone every entry of `workspace.knownRepositories`
|
|
3
|
-
* that does not already exist under `workspace.projectDir`. Entries
|
|
4
|
-
* shaped `<owner>/<repo>` are cloned via `gh repo clone`; bare-name
|
|
5
|
-
* entries are skipped with a hint, because they have no canonical URL
|
|
6
|
-
* we can guess at without involving the user's gh login. Idempotent.
|
|
7
|
-
*/
|
|
8
|
-
import { type ResolvedConfig } from "../lib/config.ts";
|
|
9
|
-
export interface SetupReposOptions {
|
|
10
|
-
/** Print the plan without running any clone. */
|
|
11
|
-
dryRun?: boolean;
|
|
12
|
-
/**
|
|
13
|
-
* Restrict the action to this subset of `knownRepositories`. Each entry
|
|
14
|
-
* must match an entry in the config or the call rejects before any side
|
|
15
|
-
* effect.
|
|
16
|
-
*/
|
|
17
|
-
only?: readonly string[];
|
|
18
|
-
}
|
|
19
|
-
export type SetupReposSkipKind = "bare-name" | "invalid-repository" | "invalid-target";
|
|
20
|
-
export interface SetupReposSkip {
|
|
21
|
-
repo: string;
|
|
22
|
-
kind: SetupReposSkipKind;
|
|
23
|
-
reason: string;
|
|
24
|
-
}
|
|
25
|
-
export interface SetupReposResult {
|
|
26
|
-
/** Entries already present under `projectDir`. */
|
|
27
|
-
existing: string[];
|
|
28
|
-
/** Entries that would be cloned in dry-run mode. */
|
|
29
|
-
planned: string[];
|
|
30
|
-
/** Entries successfully cloned this run. */
|
|
31
|
-
cloned: string[];
|
|
32
|
-
/** Entries skipped with a reason (e.g. bare names, invalid targets). */
|
|
33
|
-
skipped: SetupReposSkip[];
|
|
34
|
-
/** Entries that failed during clone. */
|
|
35
|
-
failed: {
|
|
36
|
-
repo: string;
|
|
37
|
-
error: Error;
|
|
38
|
-
}[];
|
|
39
|
-
/** True when `gh` is missing and at least one clone was needed. */
|
|
40
|
-
ghMissing: boolean;
|
|
41
|
-
}
|
|
42
|
-
export declare function setupRepos(config: ResolvedConfig, options: SetupReposOptions): Promise<SetupReposResult>;
|
|
43
|
-
export declare function setupReposCli(argv: string[]): Promise<void>;
|
|
44
|
-
//# sourceMappingURL=setupRepos.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"setupRepos.d.ts","sourceRoot":"","sources":["../../src/commands/setupRepos.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAInE,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,CAAC;AAEvF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,wCAAwC;IACxC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;IACzC,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AA6JD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,gBAAgB,CAAC,CA0D3B;AAcD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAajE"}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `crew setup repos` — clone every entry of `workspace.knownRepositories`
|
|
3
|
-
* that does not already exist under `workspace.projectDir`. Entries
|
|
4
|
-
* shaped `<owner>/<repo>` are cloned via `gh repo clone`; bare-name
|
|
5
|
-
* entries are skipped with a hint, because they have no canonical URL
|
|
6
|
-
* we can guess at without involving the user's gh login. Idempotent.
|
|
7
|
-
*/
|
|
8
|
-
import { mkdirSync, opendirSync, statSync } from "node:fs";
|
|
9
|
-
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
10
|
-
import { runCommandAsync } from "../lib/commandRunner.js";
|
|
11
|
-
import { loadConfig } from "../lib/config.js";
|
|
12
|
-
import { which } from "../lib/host.js";
|
|
13
|
-
import { errorMessage, log, parseDryRunPositionals, writeOutput } from "../lib/util.js";
|
|
14
|
-
function emptyResult() {
|
|
15
|
-
return {
|
|
16
|
-
existing: [],
|
|
17
|
-
planned: [],
|
|
18
|
-
cloned: [],
|
|
19
|
-
skipped: [],
|
|
20
|
-
failed: [],
|
|
21
|
-
ghMissing: false,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function selectRepositories(config, only) {
|
|
25
|
-
if (only === undefined) {
|
|
26
|
-
return config.workspace.knownRepositories;
|
|
27
|
-
}
|
|
28
|
-
const known = new Set(config.workspace.knownRepositories);
|
|
29
|
-
const unknown = only.filter((entry) => !known.has(entry));
|
|
30
|
-
if (unknown.length > 0) {
|
|
31
|
-
throw new Error(`Repositories not in workspace.knownRepositories: ${unknown.join(", ")}. Known: ${config.workspace.knownRepositories.join(", ")}`);
|
|
32
|
-
}
|
|
33
|
-
return only;
|
|
34
|
-
}
|
|
35
|
-
function pathExists(path) {
|
|
36
|
-
return statSync(path, { throwIfNoEntry: false }) !== undefined;
|
|
37
|
-
}
|
|
38
|
-
function isDirectoryEmpty(path) {
|
|
39
|
-
const directory = opendirSync(path);
|
|
40
|
-
try {
|
|
41
|
-
return directory.readSync() === null;
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
directory.closeSync();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function existingTargetPlan(target) {
|
|
48
|
-
const stats = statSync(target, { throwIfNoEntry: false });
|
|
49
|
-
if (stats === undefined) {
|
|
50
|
-
return "clone";
|
|
51
|
-
}
|
|
52
|
-
if (!stats.isDirectory()) {
|
|
53
|
-
return "skip-invalid";
|
|
54
|
-
}
|
|
55
|
-
if (pathExists(resolve(target, ".git"))) {
|
|
56
|
-
return "existing";
|
|
57
|
-
}
|
|
58
|
-
return isDirectoryEmpty(target) ? "clone" : "skip-invalid";
|
|
59
|
-
}
|
|
60
|
-
function isInsideProjectDir(projectDir, target) {
|
|
61
|
-
const relativeTarget = relative(projectDir, target);
|
|
62
|
-
return (relativeTarget.length > 0 && !relativeTarget.startsWith("..") && !isAbsolute(relativeTarget));
|
|
63
|
-
}
|
|
64
|
-
function repositoryEntryPlan(repo) {
|
|
65
|
-
const parts = repo.split("/");
|
|
66
|
-
if (parts.length === 1) {
|
|
67
|
-
return "bare-name";
|
|
68
|
-
}
|
|
69
|
-
if (parts.length === 2 && parts.every((part) => part.length > 0)) {
|
|
70
|
-
return "clone";
|
|
71
|
-
}
|
|
72
|
-
return "invalid-repository";
|
|
73
|
-
}
|
|
74
|
-
function bareNameSkip(repo, target) {
|
|
75
|
-
return {
|
|
76
|
-
repo,
|
|
77
|
-
kind: "bare-name",
|
|
78
|
-
reason: `bare name needs owner/ prefix to auto-clone; clone manually into ${target}`,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function invalidTargetSkip(repo, target) {
|
|
82
|
-
return {
|
|
83
|
-
repo,
|
|
84
|
-
kind: "invalid-target",
|
|
85
|
-
reason: `target exists but is not a git repository or empty directory: ${target}`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
function invalidRepositorySkip(repo, target) {
|
|
89
|
-
return {
|
|
90
|
-
repo,
|
|
91
|
-
kind: "invalid-repository",
|
|
92
|
-
reason: `repository must be owner/repo to auto-clone; clone manually into ${target}`,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function escapingTargetSkip(repo, projectDir, target) {
|
|
96
|
-
return {
|
|
97
|
-
repo,
|
|
98
|
-
kind: "invalid-repository",
|
|
99
|
-
reason: `repository resolves outside workspace.projectDir (${projectDir}): ${target}`,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function planClones(config, repositories) {
|
|
103
|
-
const projectDir = resolve(config.workspace.projectDir);
|
|
104
|
-
const toClone = [];
|
|
105
|
-
const existing = [];
|
|
106
|
-
const skipped = [];
|
|
107
|
-
const seen = new Set();
|
|
108
|
-
for (const entry of repositories) {
|
|
109
|
-
if (seen.has(entry)) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
seen.add(entry);
|
|
113
|
-
const target = resolve(projectDir, entry);
|
|
114
|
-
if (!isInsideProjectDir(projectDir, target)) {
|
|
115
|
-
skipped.push(escapingTargetSkip(entry, projectDir, target));
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
const targetPlan = existingTargetPlan(target);
|
|
119
|
-
if (targetPlan === "existing") {
|
|
120
|
-
existing.push(entry);
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (targetPlan === "skip-invalid") {
|
|
124
|
-
skipped.push(invalidTargetSkip(entry, target));
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const repositoryPlan = repositoryEntryPlan(entry);
|
|
128
|
-
if (repositoryPlan === "bare-name") {
|
|
129
|
-
skipped.push(bareNameSkip(entry, target));
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (repositoryPlan === "invalid-repository") {
|
|
133
|
-
skipped.push(invalidRepositorySkip(entry, target));
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
toClone.push(entry);
|
|
137
|
-
}
|
|
138
|
-
return { toClone, existing, skipped };
|
|
139
|
-
}
|
|
140
|
-
export async function setupRepos(config, options) {
|
|
141
|
-
const repositories = selectRepositories(config, options.only);
|
|
142
|
-
const plan = planClones(config, repositories);
|
|
143
|
-
const result = emptyResult();
|
|
144
|
-
result.existing = plan.existing;
|
|
145
|
-
result.skipped = plan.skipped;
|
|
146
|
-
for (const entry of plan.existing) {
|
|
147
|
-
log(`[exists] ${entry}`);
|
|
148
|
-
}
|
|
149
|
-
for (const { repo, reason } of plan.skipped) {
|
|
150
|
-
log(`[skip] ${repo} — ${reason}`);
|
|
151
|
-
}
|
|
152
|
-
if (options.dryRun === true) {
|
|
153
|
-
result.planned = plan.toClone;
|
|
154
|
-
for (const entry of plan.toClone) {
|
|
155
|
-
log(`[dry-run] would clone ${entry}`);
|
|
156
|
-
}
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
|
-
if (plan.toClone.length === 0) {
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
const ghPath = await which("gh");
|
|
163
|
-
if (ghPath === undefined) {
|
|
164
|
-
result.ghMissing = true;
|
|
165
|
-
writeOutput("gh CLI not found - install GitHub CLI from https://cli.github.com/ (or clone the missing repos manually).");
|
|
166
|
-
return result;
|
|
167
|
-
}
|
|
168
|
-
const projectDir = resolve(config.workspace.projectDir);
|
|
169
|
-
// Sequential on purpose: each `gh repo clone` inherits stdio for progress
|
|
170
|
-
// bars and auth prompts. Parallel clones would interleave output and make
|
|
171
|
-
// any interactive 2FA prompt unanswerable.
|
|
172
|
-
for (const entry of plan.toClone) {
|
|
173
|
-
const target = resolve(projectDir, entry);
|
|
174
|
-
log(`[clone] ${entry} → ${target}`);
|
|
175
|
-
try {
|
|
176
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
177
|
-
// oxlint-disable-next-line no-await-in-loop -- see comment above
|
|
178
|
-
await runCommandAsync("gh", ["repo", "clone", entry, target], {
|
|
179
|
-
stdio: "inherit",
|
|
180
|
-
timeoutMs: 0,
|
|
181
|
-
});
|
|
182
|
-
result.cloned.push(entry);
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
const wrapped = error instanceof Error ? error : new Error(errorMessage(error));
|
|
186
|
-
log(`[fail] ${entry}: ${wrapped.message}`);
|
|
187
|
-
result.failed.push({ repo: entry, error: wrapped });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return result;
|
|
191
|
-
}
|
|
192
|
-
function parseArguments(argv) {
|
|
193
|
-
const { dryRun, positionals } = parseDryRunPositionals(argv, "crew setup repos [--dry-run] [<repo>...]");
|
|
194
|
-
const options = { dryRun };
|
|
195
|
-
if (positionals.length > 0) {
|
|
196
|
-
options.only = positionals;
|
|
197
|
-
}
|
|
198
|
-
return options;
|
|
199
|
-
}
|
|
200
|
-
export async function setupReposCli(argv) {
|
|
201
|
-
const options = parseArguments(argv);
|
|
202
|
-
const config = await loadConfig();
|
|
203
|
-
const result = await setupRepos(config, options);
|
|
204
|
-
if (result.ghMissing || result.failed.length > 0) {
|
|
205
|
-
process.exitCode = 1;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
// Remaining skips mean setup is incomplete — signal that to CI gates.
|
|
209
|
-
if (result.skipped.length > 0) {
|
|
210
|
-
process.exitCode = 1;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { SandboxDefinition } from "./config.ts";
|
|
2
|
-
/**
|
|
3
|
-
* Derive a deterministic sbx sandbox name from the sbx agent so every
|
|
4
|
-
* groundcrew model that targets the same agent reuses one sandbox across
|
|
5
|
-
* repositories and tickets. Lowercased and reduced to the sbx-safe
|
|
6
|
-
* charset (`a-z0-9.+-`) so unusual agent names still round-trip cleanly.
|
|
7
|
-
* Keep the `groundcrew-` prefix stable — doctor and teardown use it to
|
|
8
|
-
* identify groundcrew-owned sandboxes.
|
|
9
|
-
*/
|
|
10
|
-
export declare function sandboxNameFor(arguments_: {
|
|
11
|
-
agent: string;
|
|
12
|
-
}): string;
|
|
13
|
-
/**
|
|
14
|
-
* Probe `sbx ls` to see whether a sandbox with `sandboxName` already
|
|
15
|
-
* exists. Used by `crew sandbox auth` to switch between create vs reuse
|
|
16
|
-
* branches without surfacing the raw sbx error on first run.
|
|
17
|
-
*/
|
|
18
|
-
export declare function sandboxExists(sandboxName: string, signal?: AbortSignal): Promise<boolean>;
|
|
19
|
-
interface EnsureSandboxArguments {
|
|
20
|
-
sandboxName: string;
|
|
21
|
-
sandbox: SandboxDefinition;
|
|
22
|
-
/**
|
|
23
|
-
* Host path bound into the sandbox at the same path. Pass the workspace
|
|
24
|
-
* `projectDir` so all per-ticket worktrees (siblings of the bare repo
|
|
25
|
-
* clone) are visible to `sbx exec -w <worktreeDir>` after creation.
|
|
26
|
-
*/
|
|
27
|
-
mountPath: string;
|
|
28
|
-
/**
|
|
29
|
-
* When true, apply the standard git defaults inside the sandbox after
|
|
30
|
-
* it exists (idempotent, runs whether the sandbox was just created or
|
|
31
|
-
* already there). See `sandboxGitDefaults.ts` for what gets set.
|
|
32
|
-
*/
|
|
33
|
-
gitDefaults: boolean;
|
|
34
|
-
/**
|
|
35
|
-
* Result of an earlier `sandboxExists` probe by the caller, used to
|
|
36
|
-
* skip the initial `sbx ls` here. Leave undefined to let this function
|
|
37
|
-
* probe on its own.
|
|
38
|
-
*/
|
|
39
|
-
alreadyExists?: boolean;
|
|
40
|
-
}
|
|
41
|
-
export declare function ensureSandbox(arguments_: EnsureSandboxArguments, signal?: AbortSignal): Promise<void>;
|
|
42
|
-
export {};
|
|
43
|
-
//# sourceMappingURL=dockerSandbox.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dockerSandbox.d.ts","sourceRoot":"","sources":["../../src/lib/dockerSandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAMpE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/F;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAsBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,sBAAsB,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAuBf"}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { runCommandAsync } from "./commandRunner.js";
|
|
2
|
-
import { applyGitDefaults } from "./sandboxGitDefaults.js";
|
|
3
|
-
/**
|
|
4
|
-
* Derive a deterministic sbx sandbox name from the sbx agent so every
|
|
5
|
-
* groundcrew model that targets the same agent reuses one sandbox across
|
|
6
|
-
* repositories and tickets. Lowercased and reduced to the sbx-safe
|
|
7
|
-
* charset (`a-z0-9.+-`) so unusual agent names still round-trip cleanly.
|
|
8
|
-
* Keep the `groundcrew-` prefix stable — doctor and teardown use it to
|
|
9
|
-
* identify groundcrew-owned sandboxes.
|
|
10
|
-
*/
|
|
11
|
-
export function sandboxNameFor(arguments_) {
|
|
12
|
-
const raw = `groundcrew-${arguments_.agent}`.toLowerCase();
|
|
13
|
-
return raw
|
|
14
|
-
.replaceAll(/[^a-z0-9.+-]+/g, "-")
|
|
15
|
-
.replaceAll(/-+/g, "-")
|
|
16
|
-
.replaceAll(/^-|-$/g, "");
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Probe `sbx ls` to see whether a sandbox with `sandboxName` already
|
|
20
|
-
* exists. Used by `crew sandbox auth` to switch between create vs reuse
|
|
21
|
-
* branches without surfacing the raw sbx error on first run.
|
|
22
|
-
*/
|
|
23
|
-
export async function sandboxExists(sandboxName, signal) {
|
|
24
|
-
const out = signal === undefined
|
|
25
|
-
? await runCommandAsync("sbx", ["ls"])
|
|
26
|
-
: await runCommandAsync("sbx", ["ls"], { signal });
|
|
27
|
-
return out.split("\n").some((line) => line.trim().split(/\s+/)[0] === sandboxName);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Idempotent guard: ensure a Docker Sandboxes container exists for the
|
|
31
|
-
* given repository + model. Probes `sbx ls`; if `sandboxName` is missing,
|
|
32
|
-
* calls `sbx create --name <name> [--template <t>] [--kit <k>]... <agent>
|
|
33
|
-
* <mountPath>` to provision it. Once the container exists (newly created
|
|
34
|
-
* or pre-existing), applies the standard git defaults when enabled.
|
|
35
|
-
* First-time agent auth still happens inside the sandbox the first time
|
|
36
|
-
* `sbx exec` runs the agent — `create` only provisions the container, it
|
|
37
|
-
* does not attach.
|
|
38
|
-
*/
|
|
39
|
-
async function resolveExistence(arguments_, signal) {
|
|
40
|
-
if (arguments_.alreadyExists === undefined) {
|
|
41
|
-
return await sandboxExists(arguments_.sandboxName, signal);
|
|
42
|
-
}
|
|
43
|
-
return arguments_.alreadyExists;
|
|
44
|
-
}
|
|
45
|
-
export async function ensureSandbox(arguments_, signal) {
|
|
46
|
-
const existed = await resolveExistence(arguments_, signal);
|
|
47
|
-
if (!existed) {
|
|
48
|
-
const createArguments = ["create", "--name", arguments_.sandboxName];
|
|
49
|
-
if (arguments_.sandbox.template !== undefined) {
|
|
50
|
-
createArguments.push("--template", arguments_.sandbox.template);
|
|
51
|
-
}
|
|
52
|
-
for (const kit of arguments_.sandbox.kits ?? []) {
|
|
53
|
-
createArguments.push("--kit", kit);
|
|
54
|
-
}
|
|
55
|
-
createArguments.push(arguments_.sandbox.agent, arguments_.mountPath);
|
|
56
|
-
const options = signal === undefined ? {} : { signal };
|
|
57
|
-
try {
|
|
58
|
-
await runCommandAsync("sbx", createArguments, options);
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
if (!(await sandboxExists(arguments_.sandboxName, signal))) {
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (arguments_.gitDefaults) {
|
|
67
|
-
await applyGitDefaults({ sandboxName: arguments_.sandboxName }, signal);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
interface ApplyGitDefaultsArguments {
|
|
2
|
-
sandboxName: string;
|
|
3
|
-
}
|
|
4
|
-
/**
|
|
5
|
-
* Apply the standard git defaults inside `sandboxName`. Idempotent —
|
|
6
|
-
* safe to call on every `ensure`/`auth` run to repair drift.
|
|
7
|
-
*/
|
|
8
|
-
export declare function applyGitDefaults(arguments_: ApplyGitDefaultsArguments, signal?: AbortSignal): Promise<void>;
|
|
9
|
-
export {};
|
|
10
|
-
//# sourceMappingURL=sandboxGitDefaults.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sandboxGitDefaults.d.ts","sourceRoot":"","sources":["../../src/lib/sandboxGitDefaults.ts"],"names":[],"mappings":"AAyBA,UAAU,yBAAyB;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,yBAAyB,EACrC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAOf"}
|