@clipboard-health/groundcrew 3.2.0 → 3.3.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 +2 -2
- package/crew.config.example.ts +19 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/commands/sandbox/auth.d.ts +3 -0
- package/dist/commands/sandbox/auth.d.ts.map +1 -0
- package/dist/commands/sandbox/auth.js +227 -0
- package/dist/commands/sandbox/index.d.ts +2 -0
- package/dist/commands/sandbox/index.d.ts.map +1 -0
- package/dist/commands/sandbox/index.js +47 -0
- package/dist/commands/sandbox/inspect.d.ts +2 -0
- package/dist/commands/sandbox/inspect.d.ts.map +1 -0
- package/dist/commands/sandbox/inspect.js +18 -0
- package/dist/commands/sandbox/lifecycle.d.ts +7 -0
- package/dist/commands/sandbox/lifecycle.d.ts.map +1 -0
- package/dist/commands/sandbox/lifecycle.js +68 -0
- package/dist/commands/sandbox/model.d.ts +10 -0
- package/dist/commands/sandbox/model.d.ts.map +1 -0
- package/dist/commands/sandbox/model.js +37 -0
- package/dist/commands/sandbox/picker.d.ts +20 -0
- package/dist/commands/sandbox/picker.d.ts.map +1 -0
- package/dist/commands/sandbox/picker.js +23 -0
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +1 -0
- package/dist/lib/config.d.ts +71 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +79 -1
- package/dist/lib/dockerSandbox.d.ts +12 -8
- package/dist/lib/dockerSandbox.d.ts.map +1 -1
- package/dist/lib/dockerSandbox.js +33 -22
- package/dist/lib/sandboxGitDefaults.d.ts +10 -0
- package/dist/lib/sandboxGitDefaults.d.ts.map +1 -0
- package/dist/lib/sandboxGitDefaults.js +31 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -522,9 +522,9 @@ Cross-team projects work — the orchestrator caches the in-progress state ID pe
|
|
|
522
522
|
</details>
|
|
523
523
|
|
|
524
524
|
<details>
|
|
525
|
-
<summary>Claude launches in
|
|
525
|
+
<summary>Claude launches in auto mode by default</summary>
|
|
526
526
|
|
|
527
|
-
Groundcrew creates isolated per-ticket worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode
|
|
527
|
+
Groundcrew creates isolated per-ticket worktrees for unattended runs, so the shipped `claude` command is `claude --permission-mode auto` to let Claude proceed without stopping for clarifying questions while keeping its built-in safety prompts intact. Override `models.definitions.claude.cmd` for `bypassPermissions` if you need to suppress tool-permission prompts entirely, or for a stricter mode.
|
|
528
528
|
|
|
529
529
|
</details>
|
|
530
530
|
|
package/crew.config.example.ts
CHANGED
|
@@ -92,6 +92,25 @@ export default {
|
|
|
92
92
|
// // macOS when you need an agent to use Docker safely.
|
|
93
93
|
// local: { runner: "auto" },
|
|
94
94
|
//
|
|
95
|
+
// // Additional auth recipes for `crew sandbox auth <model> <tool>`. The
|
|
96
|
+
// // shipped recipes (claude/codex/cursor agents + github tool) are merged
|
|
97
|
+
// // with whatever you declare here; your recipe wins on key collision.
|
|
98
|
+
// // Describe each tool's in-sandbox login + status commands and a regex
|
|
99
|
+
// // that matches its logged-in output. Omit `kind` for cross-cutting
|
|
100
|
+
// // tools that should appear in every sandbox's picker; set
|
|
101
|
+
// // `kind: "agent"` to scope a recipe to a single sbx agent.
|
|
102
|
+
// sandbox: {
|
|
103
|
+
// authRecipes: {
|
|
104
|
+
// gcloud: {
|
|
105
|
+
// displayName: "gcloud",
|
|
106
|
+
// binary: "gcloud",
|
|
107
|
+
// loginArgs: ["auth", "login", "--no-launch-browser"],
|
|
108
|
+
// statusArgs: ["auth", "list", "--filter=status:ACTIVE", "--format=value(account)"],
|
|
109
|
+
// authenticatedPattern: /@/,
|
|
110
|
+
// },
|
|
111
|
+
// },
|
|
112
|
+
// },
|
|
113
|
+
//
|
|
95
114
|
// prompts: {
|
|
96
115
|
// // Keep personal workflow instructions next to this config, for example
|
|
97
116
|
// // `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/initial-prompt.md`.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAyKA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BvD"}
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { initConfigCli } from "./commands/init.js";
|
|
|
5
5
|
import { interruptWorkspaceCli } from "./commands/interruptWorkspace.js";
|
|
6
6
|
import { orchestrate } from "./commands/orchestrator.js";
|
|
7
7
|
import { resumeWorkspaceCli } from "./commands/resumeWorkspace.js";
|
|
8
|
+
import { sandboxCli } from "./commands/sandbox/index.js";
|
|
8
9
|
import { setupReposCli } from "./commands/setupRepos.js";
|
|
9
10
|
import { setupWorkspaceCli } from "./commands/setupWorkspace.js";
|
|
10
11
|
import { errorMessage, readTicketArgument, writeError, writeOutput } from "./lib/util.js";
|
|
@@ -108,6 +109,11 @@ const SUBCOMMANDS = {
|
|
|
108
109
|
usage: "<ticket>",
|
|
109
110
|
invoke: resumeWorkspaceCli,
|
|
110
111
|
},
|
|
112
|
+
sandbox: {
|
|
113
|
+
summary: "Manage Docker Sandboxes (sbx) for configured models",
|
|
114
|
+
usage: "<list|ensure|regenerate|auth|rm> [...args]",
|
|
115
|
+
invoke: sandboxCli,
|
|
116
|
+
},
|
|
111
117
|
setup: {
|
|
112
118
|
summary: "Project-level setup commands (currently: repos)",
|
|
113
119
|
usage: "repos [--dry-run] [<repo>...]",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/commands/sandbox/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAuNtE,wBAAsB,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAcnF"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { runCommandAsync } from "../../lib/commandRunner.js";
|
|
2
|
+
import { writeOutput } from "../../lib/util.js";
|
|
3
|
+
import { ensureOne } from "./lifecycle.js";
|
|
4
|
+
import { resolveModel, sandboxModels } from "./model.js";
|
|
5
|
+
import { pickTools } from "./picker.js";
|
|
6
|
+
/**
|
|
7
|
+
* Built-in recipes shipped with crew. Users register additional tools
|
|
8
|
+
* by adding entries under `sandbox.authRecipes` in `crew.config.ts`;
|
|
9
|
+
* a user recipe under the same key overrides the built-in.
|
|
10
|
+
*
|
|
11
|
+
* `kind: "agent"` recipes only appear in the picker when the current
|
|
12
|
+
* sandbox's agent matches the recipe key. `kind: "tool"` (the default
|
|
13
|
+
* for user recipes) is cross-cutting and always appears.
|
|
14
|
+
*/
|
|
15
|
+
const BUILTIN_AUTH_RECIPES = {
|
|
16
|
+
claude: {
|
|
17
|
+
displayName: "Claude",
|
|
18
|
+
loginArgs: ["auth", "login"],
|
|
19
|
+
statusArgs: ["auth", "status"],
|
|
20
|
+
authenticatedPattern: /"loggedIn"\s*:\s*true/,
|
|
21
|
+
kind: "agent",
|
|
22
|
+
},
|
|
23
|
+
codex: {
|
|
24
|
+
displayName: "Codex",
|
|
25
|
+
// `--device-auth` keeps the OAuth flow headless: codex prints a URL
|
|
26
|
+
// and a code instead of trying to open a browser inside the sandbox.
|
|
27
|
+
loginArgs: ["login", "--device-auth"],
|
|
28
|
+
statusArgs: ["login", "status"],
|
|
29
|
+
// Match "Logged in using …" but not a hypothetical "Not logged in".
|
|
30
|
+
authenticatedPattern: /(^|\W)Logged in using\b/i,
|
|
31
|
+
kind: "agent",
|
|
32
|
+
},
|
|
33
|
+
cursor: {
|
|
34
|
+
displayName: "Cursor",
|
|
35
|
+
binary: "cursor-agent",
|
|
36
|
+
loginArgs: ["login"],
|
|
37
|
+
statusArgs: ["status"],
|
|
38
|
+
// Authenticated output is "✓ Logged in as <email>"; the unauthenticated
|
|
39
|
+
// output is "Not logged in", which a loose /Logged in/i would falsely
|
|
40
|
+
// match.
|
|
41
|
+
authenticatedPattern: /Logged in as\b/i,
|
|
42
|
+
kind: "agent",
|
|
43
|
+
// cursor-agent tries to open a browser by default and silently
|
|
44
|
+
// writes a partial auth file when xdg-open fails; this env var
|
|
45
|
+
// switches it to a device-code flow that works without a browser.
|
|
46
|
+
env: { NO_OPEN_BROWSER: "1" },
|
|
47
|
+
},
|
|
48
|
+
github: {
|
|
49
|
+
displayName: "GitHub CLI",
|
|
50
|
+
binary: "gh",
|
|
51
|
+
loginArgs: ["auth", "login"],
|
|
52
|
+
statusArgs: ["auth", "status"],
|
|
53
|
+
authenticatedPattern: /Logged in to github\.com/i,
|
|
54
|
+
kind: "tool",
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
function binaryFor(toolKey, recipe) {
|
|
58
|
+
return recipe.binary ?? toolKey;
|
|
59
|
+
}
|
|
60
|
+
function envFlags(recipe) {
|
|
61
|
+
const entries = Object.entries(recipe.env ?? {});
|
|
62
|
+
return entries.flatMap(([key, value]) => ["-e", `${key}=${value}`]);
|
|
63
|
+
}
|
|
64
|
+
// User-supplied recipes can carry arbitrary tokens; wrap each in single
|
|
65
|
+
// quotes so spaces and shell metacharacters can't change how the in-sandbox
|
|
66
|
+
// shell tokenizes the status command.
|
|
67
|
+
function shellQuote(value) {
|
|
68
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
69
|
+
}
|
|
70
|
+
async function probeAuthStatus(sandboxName, toolKey, recipe) {
|
|
71
|
+
// Some CLIs print status to stderr instead of stdout (codex does
|
|
72
|
+
// this). Fold stderr into stdout via the in-sandbox shell so the
|
|
73
|
+
// pattern match sees the message regardless of which stream it
|
|
74
|
+
// landed on.
|
|
75
|
+
const innerCommand = `${[binaryFor(toolKey, recipe), ...recipe.statusArgs]
|
|
76
|
+
.map(shellQuote)
|
|
77
|
+
.join(" ")} 2>&1`;
|
|
78
|
+
try {
|
|
79
|
+
const output = await runCommandAsync("sbx", [
|
|
80
|
+
"exec",
|
|
81
|
+
...envFlags(recipe),
|
|
82
|
+
sandboxName,
|
|
83
|
+
"sh",
|
|
84
|
+
"-c",
|
|
85
|
+
innerCommand,
|
|
86
|
+
]);
|
|
87
|
+
// Reset lastIndex so a /g or /y user recipe doesn't carry state
|
|
88
|
+
// across probes and return a false negative.
|
|
89
|
+
recipe.authenticatedPattern.lastIndex = 0;
|
|
90
|
+
return recipe.authenticatedPattern.test(output);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function loginAndVerify(input) {
|
|
97
|
+
const { sandboxName, toolKey, recipe, modelName, gitDefaults } = input;
|
|
98
|
+
const binary = binaryFor(toolKey, recipe);
|
|
99
|
+
writeOutput(`${sandboxName}: launching '${recipe.displayName}' login...`);
|
|
100
|
+
writeOutput("Complete the login flow in the prompts/browser, then return here.");
|
|
101
|
+
await runCommandAsync("sbx", ["exec", "-it", ...envFlags(recipe), sandboxName, binary, ...recipe.loginArgs], { stdio: "inherit" });
|
|
102
|
+
writeOutput("");
|
|
103
|
+
writeOutput(`${sandboxName}: verifying '${recipe.displayName}' authentication...`);
|
|
104
|
+
const authenticated = await probeAuthStatus(sandboxName, toolKey, recipe);
|
|
105
|
+
if (authenticated) {
|
|
106
|
+
writeOutput(`${sandboxName}: '${recipe.displayName}' authenticated.`);
|
|
107
|
+
if (gitDefaults && toolKey === "github" && binary === "gh") {
|
|
108
|
+
await runGhSetupGit(sandboxName);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
writeOutput(`${sandboxName}: could not confirm '${recipe.displayName}' authentication — re-run 'crew sandbox auth ${modelName}' to retry.`);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Register `gh` as git's credential helper inside the sandbox so HTTPS
|
|
116
|
+
* pushes succeed without prompting. Best-effort — a failure here doesn't
|
|
117
|
+
* undo the login itself, so we warn and move on.
|
|
118
|
+
*/
|
|
119
|
+
async function runGhSetupGit(sandboxName) {
|
|
120
|
+
writeOutput(`${sandboxName}: wiring 'gh' as git credential helper...`);
|
|
121
|
+
try {
|
|
122
|
+
await runCommandAsync("sbx", ["exec", sandboxName, "gh", "auth", "setup-git"]);
|
|
123
|
+
writeOutput(`${sandboxName}: 'gh auth setup-git' done.`);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
writeOutput(`${sandboxName}: warning — 'gh auth setup-git' failed: ${String(error)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function availableRecipes(config) {
|
|
130
|
+
const merged = {
|
|
131
|
+
...BUILTIN_AUTH_RECIPES,
|
|
132
|
+
...config.sandbox.authRecipes,
|
|
133
|
+
};
|
|
134
|
+
return Object.entries(merged)
|
|
135
|
+
.map(([key, recipe]) => ({ key, recipe }))
|
|
136
|
+
.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
137
|
+
}
|
|
138
|
+
function shouldShowInPicker(entry, currentAgent) {
|
|
139
|
+
// Tools (the default) appear in every sandbox. Agent recipes only
|
|
140
|
+
// appear when they match the current sandbox's agent, so opening
|
|
141
|
+
// 'crew sandbox auth codex' doesn't list Claude or Cursor.
|
|
142
|
+
const kind = entry.recipe.kind ?? "tool";
|
|
143
|
+
return kind === "tool" || entry.key === currentAgent;
|
|
144
|
+
}
|
|
145
|
+
const AUTH_USAGE = "Usage: crew sandbox auth <model> | --all";
|
|
146
|
+
function parseAuthArgs(config, argv) {
|
|
147
|
+
const positionals = [];
|
|
148
|
+
let all = false;
|
|
149
|
+
for (const argument of argv) {
|
|
150
|
+
if (argument === "--all") {
|
|
151
|
+
all = true;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (argument.startsWith("-")) {
|
|
155
|
+
throw new Error(`crew sandbox auth: unknown option '${argument}'`);
|
|
156
|
+
}
|
|
157
|
+
positionals.push(argument);
|
|
158
|
+
}
|
|
159
|
+
if (all && positionals.length > 0) {
|
|
160
|
+
throw new Error("crew sandbox auth: --all cannot be combined with a model name");
|
|
161
|
+
}
|
|
162
|
+
if (all) {
|
|
163
|
+
const models = sandboxModels(config);
|
|
164
|
+
if (models.length === 0) {
|
|
165
|
+
throw new Error("crew sandbox auth --all: no sandbox-bearing models configured");
|
|
166
|
+
}
|
|
167
|
+
return { models: models.map((model) => ({ modelName: model.modelName, model })) };
|
|
168
|
+
}
|
|
169
|
+
const [modelName, ...extras] = positionals;
|
|
170
|
+
if (modelName === undefined || extras.length > 0) {
|
|
171
|
+
throw new Error(AUTH_USAGE);
|
|
172
|
+
}
|
|
173
|
+
return { models: [{ modelName, model: resolveModel(config, modelName) }] };
|
|
174
|
+
}
|
|
175
|
+
export async function runAuth(config, argv) {
|
|
176
|
+
const { models } = parseAuthArgs(config, argv);
|
|
177
|
+
for (const [index, { modelName, model }] of models.entries()) {
|
|
178
|
+
if (models.length > 1) {
|
|
179
|
+
writeOutput("");
|
|
180
|
+
writeOutput(`════ ${modelName} (${index + 1}/${models.length}) ════`);
|
|
181
|
+
}
|
|
182
|
+
writeOutput(`${model.sandboxName}: ensuring sandbox is up...`);
|
|
183
|
+
// oxlint-disable-next-line no-await-in-loop -- each sandbox is interactive; running them sequentially keeps the prompts coherent
|
|
184
|
+
await ensureOne(config, model);
|
|
185
|
+
writeOutput("");
|
|
186
|
+
// oxlint-disable-next-line no-await-in-loop -- intentionally sequential, see above
|
|
187
|
+
await runAuthInteractive(config, model, modelName);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function runAuthInteractive(config, model, modelName) {
|
|
191
|
+
const recipes = availableRecipes(config).filter((entry) => shouldShowInPicker(entry, model.sandbox.agent));
|
|
192
|
+
writeOutput(`${model.sandboxName}: probing authentication status for ${recipes.length} tools...`);
|
|
193
|
+
const statuses = await Promise.all(recipes.map(async ({ key, recipe }) => ({
|
|
194
|
+
key,
|
|
195
|
+
recipe,
|
|
196
|
+
authenticated: await probeAuthStatus(model.sandboxName, key, recipe),
|
|
197
|
+
})));
|
|
198
|
+
const choices = statuses.map(({ key, recipe, authenticated }) => ({
|
|
199
|
+
key,
|
|
200
|
+
label: `${recipe.displayName} (${key})`,
|
|
201
|
+
authenticated,
|
|
202
|
+
}));
|
|
203
|
+
writeOutput("");
|
|
204
|
+
const selectedKeys = await pickTools(choices);
|
|
205
|
+
if (selectedKeys.length === 0) {
|
|
206
|
+
writeOutput("Nothing selected. Exiting.");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const selectedRecipes = new Map(statuses.map((entry) => [entry.key, entry.recipe]));
|
|
210
|
+
for (const key of selectedKeys) {
|
|
211
|
+
const recipe = selectedRecipes.get(key);
|
|
212
|
+
/* v8 ignore next 3 @preserve - defensive; selectedKeys come from the same map */
|
|
213
|
+
if (recipe === undefined) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
writeOutput("");
|
|
217
|
+
writeOutput(`── ${recipe.displayName} ──`);
|
|
218
|
+
// oxlint-disable-next-line no-await-in-loop -- each login is interactive; running them sequentially keeps the prompts coherent
|
|
219
|
+
await loginAndVerify({
|
|
220
|
+
sandboxName: model.sandboxName,
|
|
221
|
+
toolKey: key,
|
|
222
|
+
recipe,
|
|
223
|
+
modelName,
|
|
224
|
+
gitDefaults: config.sandbox.gitDefaults,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/sandbox/index.ts"],"names":[],"mappings":"AAkBA,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B9D"}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOhF,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6B/B;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAOhF,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA6B/B;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE;IAC9C,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhB"}
|
package/dist/lib/agentLaunch.js
CHANGED
package/dist/lib/config.d.ts
CHANGED
|
@@ -58,13 +58,49 @@ export interface SandboxDefinition {
|
|
|
58
58
|
*/
|
|
59
59
|
setupCommand?: string;
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Recipe used by `crew sandbox auth <model>` to drive an interactive
|
|
63
|
+
* login flow inside a sbx sandbox and then verify it. The flow is
|
|
64
|
+
* picker-driven — no positional `<tool>` argument; the picker lists
|
|
65
|
+
* every recipe visible to the current sandbox.
|
|
66
|
+
*
|
|
67
|
+
* `binary` defaults to the recipe key (typically the agent or CLI name).
|
|
68
|
+
* `authenticatedPattern` matches against combined stdout+stderr from
|
|
69
|
+
* `statusArgs` — exit code alone isn't reliable because some CLIs
|
|
70
|
+
* report "not logged in" while still exiting 0.
|
|
71
|
+
* `kind` controls visibility in the interactive picker: `"agent"`
|
|
72
|
+
* recipes are scoped to a specific sbx agent and only appear when you
|
|
73
|
+
* `auth` against that agent's sandbox; `"tool"` recipes (default)
|
|
74
|
+
* appear in every sandbox's picker because they're cross-cutting
|
|
75
|
+
* (github, npm, gcloud, …). Defaults to `"tool"` when omitted.
|
|
76
|
+
*
|
|
77
|
+
* Ship-side recipes for `claude`, `codex`, and `cursor` live in
|
|
78
|
+
* `src/commands/sandbox/auth.ts`; users register additional tools
|
|
79
|
+
* under `sandbox.authRecipes` in their config.
|
|
80
|
+
*/
|
|
81
|
+
export interface AuthRecipe {
|
|
82
|
+
displayName: string;
|
|
83
|
+
binary?: string;
|
|
84
|
+
loginArgs: readonly string[];
|
|
85
|
+
statusArgs: readonly string[];
|
|
86
|
+
authenticatedPattern: RegExp;
|
|
87
|
+
kind?: "agent" | "tool";
|
|
88
|
+
/**
|
|
89
|
+
* Environment variables passed to `sbx exec` for both the login and
|
|
90
|
+
* status calls. Use this for CLIs whose default flow assumes a
|
|
91
|
+
* browser or other host-only feature — e.g. cursor-agent wants
|
|
92
|
+
* `NO_OPEN_BROWSER=1` to print a device code instead of trying to
|
|
93
|
+
* launch a browser inside the sandbox.
|
|
94
|
+
*/
|
|
95
|
+
env?: Record<string, string>;
|
|
96
|
+
}
|
|
61
97
|
export interface ModelDefinition {
|
|
62
98
|
/**
|
|
63
99
|
* Shell command launched for the model. Wrapped with Safehouse/clearance
|
|
64
100
|
* for execution. The rendered prompt is appended as a single quoted
|
|
65
101
|
* positional argument. `{{worktree}}` is replaced before launch.
|
|
66
102
|
*
|
|
67
|
-
* Keep this agent-native (e.g., `claude --permission-mode
|
|
103
|
+
* Keep this agent-native (e.g., `claude --permission-mode auto`).
|
|
68
104
|
* Groundcrew adds the Safehouse wrapper.
|
|
69
105
|
*/
|
|
70
106
|
cmd: string;
|
|
@@ -192,6 +228,32 @@ export interface Config {
|
|
|
192
228
|
local?: {
|
|
193
229
|
runner?: LocalRunnerSetting;
|
|
194
230
|
};
|
|
231
|
+
/**
|
|
232
|
+
* Sandbox-wide settings. `authRecipes` lets users register additional
|
|
233
|
+
* tools (github, npm, gcloud, …) for `crew sandbox auth <model>` to
|
|
234
|
+
* authenticate inside the sandbox. The auth flow is picker-driven —
|
|
235
|
+
* registered recipes show up in the picker alongside the shipped ones,
|
|
236
|
+
* and a user recipe under the same key (e.g. "claude") overrides the
|
|
237
|
+
* shipped one.
|
|
238
|
+
*/
|
|
239
|
+
sandbox?: {
|
|
240
|
+
authRecipes?: Record<string, AuthRecipe>;
|
|
241
|
+
/**
|
|
242
|
+
* When true (default), every `crew sandbox ensure` / `auth` run applies
|
|
243
|
+
* a small set of git defaults inside the sandbox so robot commits push
|
|
244
|
+
* over `gh`-managed HTTPS regardless of how the user cloned the repo:
|
|
245
|
+
*
|
|
246
|
+
* - disable GPG signing for commits and tags
|
|
247
|
+
* - rewrite `git@github.com:` and `ssh://git@github.com/` URLs to
|
|
248
|
+
* `https://github.com/` so push uses gh's credential helper
|
|
249
|
+
* - after a successful `github` auth recipe login, run
|
|
250
|
+
* `gh auth setup-git` inside the sandbox
|
|
251
|
+
*
|
|
252
|
+
* Set `false` to skip both the git-config block and the post-login
|
|
253
|
+
* `gh auth setup-git` step.
|
|
254
|
+
*/
|
|
255
|
+
gitDefaults?: boolean;
|
|
256
|
+
};
|
|
195
257
|
logging?: {
|
|
196
258
|
/**
|
|
197
259
|
* Append-mode log file destination. `log()` and `logEvent()` tee here
|
|
@@ -261,6 +323,14 @@ export interface ResolvedConfig {
|
|
|
261
323
|
local: {
|
|
262
324
|
runner: LocalRunnerSetting;
|
|
263
325
|
};
|
|
326
|
+
/**
|
|
327
|
+
* Sandbox-wide settings. Always present after defaults; `authRecipes`
|
|
328
|
+
* is `{}` when the user provides none.
|
|
329
|
+
*/
|
|
330
|
+
sandbox: {
|
|
331
|
+
authRecipes: Record<string, AuthRecipe>;
|
|
332
|
+
gitDefaults: boolean;
|
|
333
|
+
};
|
|
264
334
|
logging: {
|
|
265
335
|
file: string;
|
|
266
336
|
};
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.js
CHANGED
|
@@ -42,7 +42,7 @@ const DEFAULT_ORCHESTRATOR = {
|
|
|
42
42
|
};
|
|
43
43
|
const DEFAULT_MODEL_DEFINITIONS = {
|
|
44
44
|
claude: {
|
|
45
|
-
cmd: "claude --permission-mode
|
|
45
|
+
cmd: "claude --permission-mode auto",
|
|
46
46
|
color: "#C15F3C",
|
|
47
47
|
usage: { codexbar: { provider: "claude" } },
|
|
48
48
|
},
|
|
@@ -132,6 +132,15 @@ function normalizeOptionalString(value, path) {
|
|
|
132
132
|
}
|
|
133
133
|
return value.trim();
|
|
134
134
|
}
|
|
135
|
+
function normalizeOptionalBoolean(value, path) {
|
|
136
|
+
if (value === undefined) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (typeof value !== "boolean") {
|
|
140
|
+
fail(`${path} must be a boolean`);
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
135
144
|
function normalizeOptionalStringArray(value, path) {
|
|
136
145
|
if (value === undefined) {
|
|
137
146
|
return undefined;
|
|
@@ -325,6 +334,11 @@ function requireObject(value, path) {
|
|
|
325
334
|
fail(`${path} must be an object (got ${JSON.stringify(value)})`);
|
|
326
335
|
}
|
|
327
336
|
}
|
|
337
|
+
function requireOptionalObject(value, path) {
|
|
338
|
+
if (value !== undefined && !isPlainObject(value)) {
|
|
339
|
+
fail(`${path} must be an object`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
328
342
|
function normalizeProject(value, index) {
|
|
329
343
|
const path = `linear.projects[${index}]`;
|
|
330
344
|
if (!isPlainObject(value)) {
|
|
@@ -429,6 +443,65 @@ function normalizeProjects(linear) {
|
|
|
429
443
|
});
|
|
430
444
|
return resolved;
|
|
431
445
|
}
|
|
446
|
+
function normalizeAuthRecipes(value, path) {
|
|
447
|
+
if (value === undefined) {
|
|
448
|
+
return {};
|
|
449
|
+
}
|
|
450
|
+
if (!isPlainObject(value)) {
|
|
451
|
+
fail(`${path} must be an object`);
|
|
452
|
+
}
|
|
453
|
+
const recipes = {};
|
|
454
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
455
|
+
const recipePath = `${path}.${key}`;
|
|
456
|
+
if (!isPlainObject(raw)) {
|
|
457
|
+
fail(`${recipePath} must be an object`);
|
|
458
|
+
}
|
|
459
|
+
const { displayName, binary, loginArgs, statusArgs, authenticatedPattern, kind, env } = raw;
|
|
460
|
+
requireString(displayName, `${recipePath}.displayName`);
|
|
461
|
+
const loginArray = normalizeOptionalStringArray(loginArgs, `${recipePath}.loginArgs`);
|
|
462
|
+
const statusArray = normalizeOptionalStringArray(statusArgs, `${recipePath}.statusArgs`);
|
|
463
|
+
if (loginArray === undefined) {
|
|
464
|
+
fail(`${recipePath}.loginArgs is required`);
|
|
465
|
+
}
|
|
466
|
+
if (statusArray === undefined) {
|
|
467
|
+
fail(`${recipePath}.statusArgs is required`);
|
|
468
|
+
}
|
|
469
|
+
if (!(authenticatedPattern instanceof RegExp)) {
|
|
470
|
+
fail(`${recipePath}.authenticatedPattern must be a RegExp`);
|
|
471
|
+
}
|
|
472
|
+
const recipe = {
|
|
473
|
+
displayName,
|
|
474
|
+
loginArgs: loginArray,
|
|
475
|
+
statusArgs: statusArray,
|
|
476
|
+
authenticatedPattern,
|
|
477
|
+
};
|
|
478
|
+
const binaryString = normalizeOptionalString(binary, `${recipePath}.binary`);
|
|
479
|
+
if (binaryString !== undefined) {
|
|
480
|
+
recipe.binary = binaryString;
|
|
481
|
+
}
|
|
482
|
+
if (kind !== undefined) {
|
|
483
|
+
if (kind !== "agent" && kind !== "tool") {
|
|
484
|
+
fail(`${recipePath}.kind must be "agent" or "tool"`);
|
|
485
|
+
}
|
|
486
|
+
recipe.kind = kind;
|
|
487
|
+
}
|
|
488
|
+
if (env !== undefined) {
|
|
489
|
+
if (!isPlainObject(env)) {
|
|
490
|
+
fail(`${recipePath}.env must be an object`);
|
|
491
|
+
}
|
|
492
|
+
const normalizedEnv = {};
|
|
493
|
+
for (const [envKey, envValue] of Object.entries(env)) {
|
|
494
|
+
if (typeof envValue !== "string") {
|
|
495
|
+
fail(`${recipePath}.env.${envKey} must be a string`);
|
|
496
|
+
}
|
|
497
|
+
normalizedEnv[envKey] = envValue;
|
|
498
|
+
}
|
|
499
|
+
recipe.env = normalizedEnv;
|
|
500
|
+
}
|
|
501
|
+
recipes[key] = recipe;
|
|
502
|
+
}
|
|
503
|
+
return recipes;
|
|
504
|
+
}
|
|
432
505
|
function applyDefaults(user) {
|
|
433
506
|
// Guard the top-level shape before reading nested fields, so a
|
|
434
507
|
// malformed runtime config produces a `groundcrew config: ...` error
|
|
@@ -443,6 +516,7 @@ function applyDefaults(user) {
|
|
|
443
516
|
if (Object.hasOwn(user, "remote")) {
|
|
444
517
|
fail("remote is no longer supported: groundcrew runs locally via safehouse/sdx/none; remove the remote block from your config");
|
|
445
518
|
}
|
|
519
|
+
requireOptionalObject(user.sandbox, "sandbox");
|
|
446
520
|
const userLocal = user.local;
|
|
447
521
|
if (userLocal !== undefined && !isPlainObject(userLocal)) {
|
|
448
522
|
fail("local must be an object");
|
|
@@ -470,6 +544,10 @@ function applyDefaults(user) {
|
|
|
470
544
|
local: {
|
|
471
545
|
runner: normalizeLocalRunner(userLocal?.runner, "local.runner") ?? "auto",
|
|
472
546
|
},
|
|
547
|
+
sandbox: {
|
|
548
|
+
authRecipes: normalizeAuthRecipes(user.sandbox?.authRecipes, "sandbox.authRecipes"),
|
|
549
|
+
gitDefaults: normalizeOptionalBoolean(user.sandbox?.gitDefaults, "sandbox.gitDefaults") ?? true,
|
|
550
|
+
},
|
|
473
551
|
logging: {
|
|
474
552
|
file: expandHome(normalizeOptionalString(user.logging?.file, "logging.file") ?? defaultLogFile()),
|
|
475
553
|
},
|
|
@@ -25,15 +25,19 @@ interface EnsureSandboxArguments {
|
|
|
25
25
|
* clone) are visible to `sbx exec -w <worktreeDir>` after creation.
|
|
26
26
|
*/
|
|
27
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;
|
|
28
40
|
}
|
|
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. First-time agent auth still happens inside
|
|
34
|
-
* the sandbox the first time `sbx exec` runs the agent — `create` only
|
|
35
|
-
* provisions the container, it does not attach.
|
|
36
|
-
*/
|
|
37
41
|
export declare function ensureSandbox(arguments_: EnsureSandboxArguments, signal?: AbortSignal): Promise<void>;
|
|
38
42
|
export {};
|
|
39
43
|
//# sourceMappingURL=dockerSandbox.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dockerSandbox.d.ts","sourceRoot":"","sources":["../../src/lib/dockerSandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import { runCommandAsync } from "./commandRunner.js";
|
|
2
|
+
import { applyGitDefaults } from "./sandboxGitDefaults.js";
|
|
2
3
|
/**
|
|
3
4
|
* Derive a deterministic sbx sandbox name from the sbx agent so every
|
|
4
5
|
* groundcrew model that targets the same agent reuses one sandbox across
|
|
@@ -29,30 +30,40 @@ export async function sandboxExists(sandboxName, signal) {
|
|
|
29
30
|
* Idempotent guard: ensure a Docker Sandboxes container exists for the
|
|
30
31
|
* given repository + model. Probes `sbx ls`; if `sandboxName` is missing,
|
|
31
32
|
* calls `sbx create --name <name> [--template <t>] [--kit <k>]... <agent>
|
|
32
|
-
* <mountPath>` to provision it.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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.
|
|
35
38
|
*/
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const createArguments = ["create", "--name", arguments_.sandboxName];
|
|
41
|
-
if (arguments_.sandbox.template !== undefined) {
|
|
42
|
-
createArguments.push("--template", arguments_.sandbox.template);
|
|
43
|
-
}
|
|
44
|
-
for (const kit of arguments_.sandbox.kits ?? []) {
|
|
45
|
-
createArguments.push("--kit", kit);
|
|
39
|
+
async function resolveExistence(arguments_, signal) {
|
|
40
|
+
if (arguments_.alreadyExists === undefined) {
|
|
41
|
+
return await sandboxExists(arguments_.sandboxName, signal);
|
|
46
42
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
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);
|
|
55
59
|
}
|
|
56
|
-
|
|
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);
|
|
57
68
|
}
|
|
58
69
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { runCommandAsync } from "./commandRunner.js";
|
|
2
|
+
/**
|
|
3
|
+
* Git defaults applied inside every sandbox when `sandbox.gitDefaults`
|
|
4
|
+
* is enabled (the default).
|
|
5
|
+
*
|
|
6
|
+
* - Disable GPG signing — robot commits inside the sandbox have no key
|
|
7
|
+
* and would otherwise fail or end up unsigned silently.
|
|
8
|
+
* - Rewrite GitHub SSH URLs to HTTPS so push/fetch go through the `gh`
|
|
9
|
+
* credential helper (wired by `gh auth setup-git` after a successful
|
|
10
|
+
* `crew sandbox auth` github login), regardless of how the user
|
|
11
|
+
* originally cloned the repo on the host.
|
|
12
|
+
*
|
|
13
|
+
* `url.<base>.insteadOf` is multi-valued in git; `--unset-all` before
|
|
14
|
+
* `--add` keeps the set identical across repeated runs instead of
|
|
15
|
+
* appending duplicates.
|
|
16
|
+
*/
|
|
17
|
+
const GIT_DEFAULT_COMMANDS = [
|
|
18
|
+
"git config --global commit.gpgsign false",
|
|
19
|
+
"git config --global tag.gpgsign false",
|
|
20
|
+
'(git config --global --unset-all url."https://github.com/".insteadOf 2>/dev/null || true)',
|
|
21
|
+
'git config --global --add url."https://github.com/".insteadOf "git@github.com:"',
|
|
22
|
+
'git config --global --add url."https://github.com/".insteadOf "ssh://git@github.com/"',
|
|
23
|
+
].join(" && ");
|
|
24
|
+
/**
|
|
25
|
+
* Apply the standard git defaults inside `sandboxName`. Idempotent —
|
|
26
|
+
* safe to call on every `ensure`/`auth` run to repair drift.
|
|
27
|
+
*/
|
|
28
|
+
export async function applyGitDefaults(arguments_, signal) {
|
|
29
|
+
const options = signal === undefined ? {} : { signal };
|
|
30
|
+
await runCommandAsync("sbx", ["exec", arguments_.sandboxName, "sh", "-c", GIT_DEFAULT_COMMANDS], options);
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle and usage tracking.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@clipboard-health/clearance": "1.0.8",
|
|
71
|
+
"@inquirer/checkbox": "5.1.5",
|
|
71
72
|
"@linear/sdk": "85.0.0",
|
|
72
73
|
"cosmiconfig": "9.0.1",
|
|
73
74
|
"tslib": "2.8.1",
|