@clipboard-health/groundcrew 4.31.0 → 4.32.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/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +11 -1
- package/dist/lib/agentLaunch.d.ts.map +1 -1
- package/dist/lib/agentLaunch.js +25 -1
- package/dist/lib/clearanceAllowlist.d.ts +8 -0
- package/dist/lib/clearanceAllowlist.d.ts.map +1 -0
- package/dist/lib/clearanceAllowlist.js +26 -0
- package/dist/lib/clearanceHosts.js +1 -10
- package/dist/lib/launchCommand.d.ts +9 -0
- package/dist/lib/launchCommand.d.ts.map +1 -1
- package/dist/lib/launchCommand.js +15 -4
- package/dist/lib/pathList.d.ts +2 -0
- package/dist/lib/pathList.d.ts.map +1 -0
- package/dist/lib/pathList.js +11 -0
- package/dist/lib/srtLaunch.d.ts +12 -0
- package/dist/lib/srtLaunch.d.ts.map +1 -1
- package/dist/lib/srtLaunch.js +3 -2
- package/docs/runners.md +6 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -50,8 +50,8 @@ crew init --global --project-dir ~/dev --repo OWNER/REPO --agent claude
|
|
|
50
50
|
|
|
51
51
|
# 3. Run the clone commands printed by `crew init`.
|
|
52
52
|
|
|
53
|
-
# 4.
|
|
54
|
-
|
|
53
|
+
# 4. Safehouse runs use groundcrew's bundled clearance allowlist automatically.
|
|
54
|
+
# Add extra hosts later via CLEARANCE_ALLOW_HOSTS or CLEARANCE_ALLOW_HOSTS_FILES.
|
|
55
55
|
|
|
56
56
|
# 5. Using Linear? Export your API key. (Jira and other trackers: see Task Pickup.)
|
|
57
57
|
export GROUNDCREW_LINEAR_API_KEY="lin_api_..."
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyLH,wBAAsB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,CAmF/C"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createBoard } from "../lib/board.js";
|
|
|
7
7
|
import { buildSources, sourcesFromConfig } from "../lib/buildSources.js";
|
|
8
8
|
import { loadConfigWithSource, worktreeBaseDir, } from "../lib/config.js";
|
|
9
9
|
import { detectHostCapabilities, which } from "../lib/host.js";
|
|
10
|
+
import { isEnvironmentAssignment } from "../lib/launchCommand.js";
|
|
10
11
|
import { resolveLocalRunner } from "../lib/localRunner.js";
|
|
11
12
|
import { gatedAgents } from "../lib/usage.js";
|
|
12
13
|
import { errorMessage, writeOutput } from "../lib/util.js";
|
|
@@ -78,12 +79,14 @@ function checkDir(path, label) {
|
|
|
78
79
|
* the executable name (first non-flag token), and any subsequent
|
|
79
80
|
* non-flag, non-flag-value token until a flag is hit. Flag tokens are
|
|
80
81
|
* dropped along with the token immediately following them (treated as
|
|
81
|
-
* the flag's value).
|
|
82
|
+
* the flag's value). `env VAR=val` assignments are skipped (they are not
|
|
83
|
+
* binaries), so the wrapped command is still found.
|
|
82
84
|
*
|
|
83
85
|
* Examples:
|
|
84
86
|
* "safehouse claude --permission-mode auto" → ["safehouse", "claude"]
|
|
85
87
|
* "claude" → ["claude"]
|
|
86
88
|
* "node --inspect script.ts" → ["node"] (script.ts skipped — flag value)
|
|
89
|
+
* "env GIT_CONFIG_COUNT=1 claude --foo" → ["env", "claude"] (assignment skipped)
|
|
87
90
|
*/
|
|
88
91
|
function commandTokensToCheck(cmd) {
|
|
89
92
|
const parts = cmd.trim().split(/\s+/);
|
|
@@ -101,6 +104,13 @@ function commandTokensToCheck(cmd) {
|
|
|
101
104
|
index += 2;
|
|
102
105
|
continue;
|
|
103
106
|
}
|
|
107
|
+
if (isEnvironmentAssignment(token)) {
|
|
108
|
+
// An `env VAR=val …` prefix: the assignment is not a binary, so don't
|
|
109
|
+
// probe it on PATH. Mirrors the launch path's inferAgentCommandName,
|
|
110
|
+
// which skips assignments to find the real agent command.
|
|
111
|
+
index += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
104
114
|
result.push(token);
|
|
105
115
|
if (result.length >= MAX_TOKENS_PER_CMD) {
|
|
106
116
|
break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agentLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/agentLaunch.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AASrB;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CA0BhE;AAsBD,UAAU,mBAAmB;IAC3B,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;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,CA+C/B;AAwBD,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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ensureClearance } from "@clipboard-health/clearance";
|
|
2
|
+
import { clearanceAllowHostsFilesFromEnvironment } from "./clearanceAllowlist.js";
|
|
2
3
|
import { hasPreLaunchEnv, } from "./config.js";
|
|
3
4
|
import { detectHostCapabilities } from "./host.js";
|
|
4
5
|
import { buildLaunchCommand } from "./launchCommand.js";
|
|
5
6
|
import { assertLocalRunnerRequirements, resolveLocalRunner } from "./localRunner.js";
|
|
6
7
|
import { sandboxNameFor } from "./sandboxName.js";
|
|
7
|
-
import { buildAndStageSrtLaunch } from "./srtLaunch.js";
|
|
8
|
+
import { buildAndStageSrtLaunch, resolveGitCommonDir } from "./srtLaunch.js";
|
|
8
9
|
import { debug, sleep } from "./util.js";
|
|
9
10
|
import { workspaces } from "./workspaces.js";
|
|
10
11
|
/**
|
|
@@ -35,9 +36,29 @@ export function composeAgentLaunch(input) {
|
|
|
35
36
|
srtAgentSettingsFile: staged?.agentFile,
|
|
36
37
|
srtSettingsDir: staged?.directory,
|
|
37
38
|
srtAgentConfigDirEnv: staged?.agentConfigDirEnv,
|
|
39
|
+
safehouseAddDirs: input.runner === "safehouse" ? resolveSafehouseAddDirs(input.worktreeDir) : undefined,
|
|
38
40
|
});
|
|
39
41
|
return { launchCommand, srtSettingsDir: staged?.directory };
|
|
40
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Filesystem paths the safehouse sandbox must be granted (read/write) beyond
|
|
45
|
+
* its automatic cwd grant, so git works for every worktree shape:
|
|
46
|
+
*
|
|
47
|
+
* - `worktreeDir` — the checkout root. A `workdir` subproject cwd's into a
|
|
48
|
+
* subdir, so without this the worktree-root `.git` gitfile is unreachable.
|
|
49
|
+
* - the **git common dir** — resolved from the worktree itself (not assumed to
|
|
50
|
+
* be `<projectDir>/<repo>/.git`), so a scripted/sparse-checkout worktree
|
|
51
|
+
* whose store lives outside the worktree tree (e.g. graft's `~/carrot/.git`)
|
|
52
|
+
* gets git access. This is the path the bare cwd grant fundamentally cannot
|
|
53
|
+
* cover, and the reason this resolution exists.
|
|
54
|
+
* Gated to the safehouse runner at the call site (srt fences its own equivalent
|
|
55
|
+
* surface — worktree root + git common dir — through its settings file; sdx/none
|
|
56
|
+
* don't use it). Deduped defensively in case git resolves either path to the
|
|
57
|
+
* same directory in an unusual checkout shape.
|
|
58
|
+
*/
|
|
59
|
+
function resolveSafehouseAddDirs(worktreeDir) {
|
|
60
|
+
return [...new Set([worktreeDir, resolveGitCommonDir(worktreeDir)])];
|
|
61
|
+
}
|
|
41
62
|
export async function prepareAgentLaunch(input) {
|
|
42
63
|
const host = await detectHostCapabilities(input.signal);
|
|
43
64
|
const runner = resolveLocalRunner(input.config.local.runner, host);
|
|
@@ -77,6 +98,9 @@ async function alreadyReady() {
|
|
|
77
98
|
}
|
|
78
99
|
async function ensureSafehouseClearance(signal) {
|
|
79
100
|
await ensureClearance({
|
|
101
|
+
envOverrides: {
|
|
102
|
+
CLEARANCE_ALLOW_HOSTS_FILES: clearanceAllowHostsFilesFromEnvironment(),
|
|
103
|
+
},
|
|
80
104
|
logger: debug,
|
|
81
105
|
...(signal === undefined
|
|
82
106
|
? {}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface ClearanceAllowHostsFilesInput {
|
|
2
|
+
defaultFile?: string | undefined;
|
|
3
|
+
existingFiles?: string | undefined;
|
|
4
|
+
}
|
|
5
|
+
export declare function clearanceAllowHostsFilesValue(input?: ClearanceAllowHostsFilesInput): string;
|
|
6
|
+
export declare function clearanceAllowHostsFilesFromEnvironment(): string;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=clearanceAllowlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clearanceAllowlist.d.ts","sourceRoot":"","sources":["../../src/lib/clearanceAllowlist.ts"],"names":[],"mappings":"AAOA,UAAU,6BAA6B;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD,wBAAgB,6BAA6B,CAAC,KAAK,GAAE,6BAAkC,GAAG,MAAM,CAa/F;AAED,wBAAgB,uCAAuC,IAAI,MAAM,CAIhE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { splitPathList } from "./pathList.js";
|
|
3
|
+
import { readEnvironmentVariable } from "./util.js";
|
|
4
|
+
const CLEARANCE_ALLOW_HOSTS_FILES = "CLEARANCE_ALLOW_HOSTS_FILES";
|
|
5
|
+
function groundcrewClearanceAllowHostsFile() {
|
|
6
|
+
return path.resolve(import.meta.dirname, "..", "..", "clearance-allow-hosts");
|
|
7
|
+
}
|
|
8
|
+
export function clearanceAllowHostsFilesValue(input = {}) {
|
|
9
|
+
const defaultFile = input.defaultFile ?? groundcrewClearanceAllowHostsFile();
|
|
10
|
+
const files = [defaultFile, ...splitPathList(input.existingFiles)];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
const uniqueFiles = [];
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
if (seen.has(file)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
seen.add(file);
|
|
18
|
+
uniqueFiles.push(file);
|
|
19
|
+
}
|
|
20
|
+
return uniqueFiles.join(path.delimiter);
|
|
21
|
+
}
|
|
22
|
+
export function clearanceAllowHostsFilesFromEnvironment() {
|
|
23
|
+
return clearanceAllowHostsFilesValue({
|
|
24
|
+
existingFiles: readEnvironmentVariable(CLEARANCE_ALLOW_HOSTS_FILES),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* validates.
|
|
15
15
|
*/
|
|
16
16
|
import { readFileSync } from "node:fs";
|
|
17
|
-
import
|
|
17
|
+
import { splitPathList } from "./pathList.js";
|
|
18
18
|
import { debug } from "./util.js";
|
|
19
19
|
/**
|
|
20
20
|
* Parse and validate clearance allow-host sources into a de-duplicated list of
|
|
@@ -51,15 +51,6 @@ export function collectAllowedDomains(input) {
|
|
|
51
51
|
}
|
|
52
52
|
return domains;
|
|
53
53
|
}
|
|
54
|
-
function splitPathList(value) {
|
|
55
|
-
if (value === undefined || value.length === 0) {
|
|
56
|
-
return [];
|
|
57
|
-
}
|
|
58
|
-
return value
|
|
59
|
-
.split(path.delimiter)
|
|
60
|
-
.map((entry) => entry.trim())
|
|
61
|
-
.filter((entry) => entry.length > 0);
|
|
62
|
-
}
|
|
63
54
|
/**
|
|
64
55
|
* Split a host source into candidate tokens. Handles env-style
|
|
65
56
|
* comma/whitespace separators and file-style newline lists with `#` comments
|
|
@@ -30,6 +30,7 @@ export declare function resolveSrtBinPath(baseUrl?: string): string;
|
|
|
30
30
|
export declare function srtBinEntry(manifest: {
|
|
31
31
|
bin?: string | Record<string, string>;
|
|
32
32
|
}): string;
|
|
33
|
+
export declare function isEnvironmentAssignment(token: string): boolean;
|
|
33
34
|
/**
|
|
34
35
|
* Infer the agent's command basename from a agent `cmd` (skipping a leading
|
|
35
36
|
* `env`/`KEY=val` prefix). Safehouse uses it to pick the matching `.sb`
|
|
@@ -102,6 +103,14 @@ interface LaunchCommandArguments {
|
|
|
102
103
|
name: string;
|
|
103
104
|
value: string;
|
|
104
105
|
} | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Extra filesystem paths granted read/write to the safehouse sandbox via
|
|
108
|
+
* `--add-dirs`, beyond safehouse's automatic cwd grant. Resolved (and deduped)
|
|
109
|
+
* by `composeAgentLaunch`'s `resolveSafehouseAddDirs` — see there for which
|
|
110
|
+
* paths and why git needs them. Empty/undefined → no `--add-dirs` flag (the
|
|
111
|
+
* pre-existing behavior). Only consumed by the safehouse wrap.
|
|
112
|
+
*/
|
|
113
|
+
safehouseAddDirs?: readonly string[] | undefined;
|
|
105
114
|
}
|
|
106
115
|
/**
|
|
107
116
|
* Build the shell command that runs inside the workspace. The prompt is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"launchCommand.d.ts","sourceRoot":"","sources":["../../src/lib/launchCommand.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAcvF;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,MAAM,CAMvF;AAsMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8B9D;AAED,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;;OAIG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,GAAG,MAAM,CA6B7E"}
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { BUILD_SECRET_NAMES, hasPreLaunchEnv, } from "./config.js";
|
|
5
|
+
import { clearanceAllowHostsFilesFromEnvironment } from "./clearanceAllowlist.js";
|
|
5
6
|
import { shellSingleQuote } from "./shell.js";
|
|
6
7
|
export { shellSingleQuote } from "./shell.js";
|
|
7
8
|
/**
|
|
@@ -100,6 +101,9 @@ function unsetEnvironmentLine(names) {
|
|
|
100
101
|
function unsetSecretsLine() {
|
|
101
102
|
return unsetEnvironmentLine(BUILD_SECRET_NAMES);
|
|
102
103
|
}
|
|
104
|
+
function safehouseClearanceWrapperCommand() {
|
|
105
|
+
return `CLEARANCE_ALLOW_HOSTS_FILES=${shellSingleQuote(clearanceAllowHostsFilesFromEnvironment())} ${shellSingleQuote(SAFEHOUSE_CLEARANCE_WRAPPER_PATH)}`;
|
|
106
|
+
}
|
|
103
107
|
function trapCleanupLine(promptDir) {
|
|
104
108
|
const cleanupCmd = `rm -rf ${shellSingleQuote(promptDir)}`;
|
|
105
109
|
return `trap ${shellSingleQuote(cleanupCmd)} EXIT`;
|
|
@@ -209,7 +213,7 @@ function tokenizeShellPrefix(command) {
|
|
|
209
213
|
}
|
|
210
214
|
return tokens;
|
|
211
215
|
}
|
|
212
|
-
function isEnvironmentAssignment(token) {
|
|
216
|
+
export function isEnvironmentAssignment(token) {
|
|
213
217
|
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
|
|
214
218
|
}
|
|
215
219
|
/**
|
|
@@ -363,7 +367,14 @@ function buildSafehouseLaunchCommand(arguments_) {
|
|
|
363
367
|
const prepareWorktreeEnvPassFlag = arguments_.secretsFile === undefined ? "" : `--env-pass=${BUILD_SECRET_NAMES.join(",")} `;
|
|
364
368
|
const preLaunchEnvNames = arguments_.definition.preLaunchEnv ?? [];
|
|
365
369
|
const agentEnvPassFlag = preLaunchEnvNames.length === 0 ? "" : `--env-pass=${preLaunchEnvNames.join(",")} `;
|
|
366
|
-
|
|
370
|
+
// safehouse reads colon-separated paths from `--add-dirs`; both wraps get the
|
|
371
|
+
// same grant so the prepareWorktree hook and the agent can each reach git.
|
|
372
|
+
// Quote the whole value so shell-special chars survive; the trailing space
|
|
373
|
+
// separates it from the next argv token. See `resolveSafehouseAddDirs` for
|
|
374
|
+
// which paths these are and why.
|
|
375
|
+
const addDirs = arguments_.safehouseAddDirs ?? [];
|
|
376
|
+
const safehouseAddDirsFlag = addDirs.length === 0 ? "" : `--add-dirs=${shellSingleQuote(addDirs.join(":"))} `;
|
|
377
|
+
const safehouseWrapper = safehouseClearanceWrapperCommand();
|
|
367
378
|
// Defensive shim+promptDir trap: by the time we arm it, `rm -rf <promptDir>`
|
|
368
379
|
// has already run (line below) so the promptDir wipe is a no-op on the happy
|
|
369
380
|
// path. Keeps the failure-window between shim creation and the explicit
|
|
@@ -382,7 +393,7 @@ function buildSafehouseLaunchCommand(arguments_) {
|
|
|
382
393
|
secretsFile: arguments_.secretsFile,
|
|
383
394
|
}));
|
|
384
395
|
if (prepareWorktreeCommand !== undefined) {
|
|
385
|
-
lines.push(`${safehouseWrapper} ${prepareWorktreeEnvPassFlag}sh -c ${shellSingleQuote(prepareWorktreeCommand)}`);
|
|
396
|
+
lines.push(`${safehouseWrapper} ${safehouseAddDirsFlag}${prepareWorktreeEnvPassFlag}sh -c ${shellSingleQuote(prepareWorktreeCommand)}`);
|
|
386
397
|
}
|
|
387
398
|
if (arguments_.secretsFile !== undefined) {
|
|
388
399
|
lines.push(unsetSecretsLine());
|
|
@@ -392,7 +403,7 @@ function buildSafehouseLaunchCommand(arguments_) {
|
|
|
392
403
|
// Running the real launch chain as `sh -c` would make it see `sh`, so use
|
|
393
404
|
// an agent-named symlink to /bin/sh. This preserves per-agent profile
|
|
394
405
|
// selection without enabling every agent profile.
|
|
395
|
-
`{ ${safehouseWrapper} ${agentEnvPassFlag}"$_safehouse_shim" -c ${shellSingleQuote(agentCommand)} sh "$_p"; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir"; trap - EXIT; exit "$_safehouse_status"; }`);
|
|
406
|
+
`{ ${safehouseWrapper} ${safehouseAddDirsFlag}${agentEnvPassFlag}"$_safehouse_shim" -c ${shellSingleQuote(agentCommand)} sh "$_p"; _safehouse_status=$?; rm -rf "$_safehouse_shim_dir"; trap - EXIT; exit "$_safehouse_status"; }`);
|
|
396
407
|
return lines.join(" && ");
|
|
397
408
|
}
|
|
398
409
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathList.d.ts","sourceRoot":"","sources":["../../src/lib/pathList.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CASjE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function splitPathList(value) {
|
|
3
|
+
const paths = [];
|
|
4
|
+
for (const entry of value?.split(path.delimiter) ?? []) {
|
|
5
|
+
const pathEntry = entry.trim();
|
|
6
|
+
if (pathEntry.length > 0) {
|
|
7
|
+
paths.push(pathEntry);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return paths;
|
|
11
|
+
}
|
package/dist/lib/srtLaunch.d.ts
CHANGED
|
@@ -16,6 +16,18 @@ export interface StagedSrtLaunch {
|
|
|
16
16
|
value: string;
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the worktree's real git common dir — the shared `.git` the srt policy
|
|
21
|
+
* fences off (read-grant + narrow write-allow + write-denies). Derived from the
|
|
22
|
+
* worktree itself rather than assuming a `<projectDir>/<repo>/.git` clone, so
|
|
23
|
+
* scripted/sparse-checkout worktrees — whose checkout is owned by an external
|
|
24
|
+
* provisioner and whose `repo` is just an alias with no clone on disk — get the
|
|
25
|
+
* correct dir instead of a phantom path that breaks git access and leaves the
|
|
26
|
+
* real common dir unfenced. For native worktrees this returns the same
|
|
27
|
+
* `<projectDir>/<repo>/.git` as before. `--path-format=absolute` keeps the path
|
|
28
|
+
* absolute regardless of git version or cwd.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveGitCommonDir(worktreeDir: string): string;
|
|
19
31
|
/**
|
|
20
32
|
* Generate the srt policies for a launch and stage them, plus — for agents that
|
|
21
33
|
* cannot run with a read-only config home (codex) — a relocated, writable
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"srtLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/srtLaunch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"srtLaunch.d.ts","sourceRoot":"","sources":["../../src/lib/srtLaunch.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAKnD,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,eAAe,CAAC;IAC5B,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,eAAe,CA6ClB"}
|
package/dist/lib/srtLaunch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { clearanceAllowHostsFilesFromEnvironment } from "./clearanceAllowlist.js";
|
|
4
5
|
import { collectAllowedDomains } from "./clearanceHosts.js";
|
|
5
6
|
import { runCommand } from "./commandRunner.js";
|
|
6
7
|
import { inferAgentCommandName } from "./launchCommand.js";
|
|
@@ -17,7 +18,7 @@ import { readEnvironmentVariable } from "./util.js";
|
|
|
17
18
|
* `<projectDir>/<repo>/.git` as before. `--path-format=absolute` keeps the path
|
|
18
19
|
* absolute regardless of git version or cwd.
|
|
19
20
|
*/
|
|
20
|
-
function resolveGitCommonDir(worktreeDir) {
|
|
21
|
+
export function resolveGitCommonDir(worktreeDir) {
|
|
21
22
|
return runCommand("git", [
|
|
22
23
|
"-C",
|
|
23
24
|
worktreeDir,
|
|
@@ -53,7 +54,7 @@ export function buildAndStageSrtLaunch(input) {
|
|
|
53
54
|
gitCommonDir: resolveGitCommonDir(input.worktreeDir),
|
|
54
55
|
allowedDomains: collectAllowedDomains({
|
|
55
56
|
hosts: readEnvironmentVariable("CLEARANCE_ALLOW_HOSTS"),
|
|
56
|
-
files:
|
|
57
|
+
files: clearanceAllowHostsFilesFromEnvironment(),
|
|
57
58
|
}),
|
|
58
59
|
};
|
|
59
60
|
const directory = mkdtempSync(path.join(os.tmpdir(), `groundcrew-srt-${input.task}-`));
|
package/docs/runners.md
CHANGED
|
@@ -13,19 +13,21 @@
|
|
|
13
13
|
|
|
14
14
|
## Safehouse Clearance Allowlist
|
|
15
15
|
|
|
16
|
-
Only applies when `local.runner` resolves to `safehouse`. Groundcrew starts `clearance` on `http://127.0.0.1:19999` and runs the agent through the bundled `safehouse-clearance` wrapper.
|
|
16
|
+
Only applies when `local.runner` resolves to `safehouse`. Groundcrew starts `clearance` on `http://127.0.0.1:19999` and runs the agent through the bundled `safehouse-clearance` wrapper. Groundcrew automatically points clearance at its shipped starter allowlist, so a fresh install does not need a `CLEARANCE_ALLOW_HOSTS_FILES` export.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Groundcrew ships that starter file at `$(npm root -g)/@clipboard-health/groundcrew/clearance-allow-hosts`, covering model APIs, Linear, Notion, Slack, Datadog, GitHub, npm, and common dev tooling.
|
|
19
|
+
|
|
20
|
+
To add ad hoc hosts for one run, use `CLEARANCE_ALLOW_HOSTS`:
|
|
19
21
|
|
|
20
22
|
```bash
|
|
21
23
|
CLEARANCE_ALLOW_HOSTS="api.openai.com,auth.openai.com,api.anthropic.com,mcp.linear.app,api.linear.app" \
|
|
22
24
|
crew run --watch
|
|
23
25
|
```
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
To keep personal hosts in a file, set `CLEARANCE_ALLOW_HOSTS_FILES` to only the additional files. Groundcrew prepends its shipped file automatically:
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
CLEARANCE_ALLOW_HOSTS_FILES="$
|
|
30
|
+
CLEARANCE_ALLOW_HOSTS_FILES="$HOME/.config/clearance/personal-allow-hosts" \
|
|
29
31
|
crew run --watch
|
|
30
32
|
```
|
|
31
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.32.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",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@anthropic-ai/sandbox-runtime": "0.0.52",
|
|
72
|
-
"@clipboard-health/clearance": "1.
|
|
72
|
+
"@clipboard-health/clearance": "1.2.0",
|
|
73
73
|
"@linear/sdk": "86.0.0",
|
|
74
74
|
"cosmiconfig": "9.0.1",
|
|
75
75
|
"tslib": "2.8.1",
|