@clipboard-health/groundcrew 1.7.4 → 1.9.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 +40 -8
- package/bin/runCli.js +5 -8
- package/clearance-allow-hosts +0 -2
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +18 -2
- package/dist/lib/config.d.ts +20 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +36 -0
- package/dist/lib/workspaces.d.ts.map +1 -1
- package/dist/lib/workspaces.js +4 -2
- package/package.json +56 -8
- package/static/groundcrew.svg +9 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<h1 align="center">groundcrew</h1>
|
|
2
2
|
<p align="center">
|
|
3
|
-
<img alt="Groundcrew logo." height="250px" src="
|
|
3
|
+
<img alt="Groundcrew logo." height="250px" src="./static/groundcrew.svg">
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
Watch a Linear project and farm out ready tickets to coding-agent CLIs running in workspaces backed by git worktrees. Workspaces are [`cmux`](https://github.com/clayton-cole/cmux) panes on macOS or `tmux` windows on Linux/macOS.
|
|
@@ -58,7 +58,7 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
|
|
|
58
58
|
|
|
59
59
|
Local setup fails before creating a worktree when the host is not macOS or `safehouse` is missing. `models.isolation`, per-model `isolation`, and per-model `sandbox` are legacy keys and now fail config validation.
|
|
60
60
|
|
|
61
|
-
6. **Set the clearance allowlist for local macOS runs.** Groundcrew starts `clearance` from `@clipboard-health/clearance` on `http://127.0.0.1:19999` (skipping the launch if something is already listening) and runs the agent through the bundled `safehouse-clearance` wrapper. Clearance refuses to start without an allowlist — see [its README](
|
|
61
|
+
6. **Set the clearance allowlist for local macOS runs.** Groundcrew starts `clearance` from `@clipboard-health/clearance` on `http://127.0.0.1:19999` (skipping the launch if something is already listening) and runs the agent through the bundled `safehouse-clearance` wrapper. Clearance refuses to start without an allowlist — see [its README](https://github.com/ClipboardHealth/core-utils/tree/main/packages/clearance) for the proxy's env vars, log paths, and DNS rules. The shortest path is to set the env before `crew run`:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
CLEARANCE_ALLOW_HOSTS="api.openai.com,auth.openai.com,api.anthropic.com,mcp.linear.app,api.linear.app" \
|
|
@@ -82,8 +82,8 @@ This installs the `crew` binary. `@clipboard-health/clearance` is pulled in tran
|
|
|
82
82
|
--codex \
|
|
83
83
|
--copy-local-codex-auth \
|
|
84
84
|
--github \
|
|
85
|
-
--git-name "
|
|
86
|
-
--git-email "
|
|
85
|
+
--git-name "Your Name" \
|
|
86
|
+
--git-email "you@users.noreply.github.com" \
|
|
87
87
|
--mcp linear \
|
|
88
88
|
--mcp slack \
|
|
89
89
|
--checkpoint
|
|
@@ -134,6 +134,7 @@ Required fields are marked **required**; everything else has a default and can b
|
|
|
134
134
|
| `models.definitions.<name>.cmd` | — | Shell command launched for the model. Local macOS runs execute in the worktree through Safehouse/clearance; remote runs execute inside the remote worktree. `{{worktree}}` is replaced before launch and legacy `{{sandbox}}` expands to an empty string. |
|
|
135
135
|
| `models.definitions.<name>.color` | — | Color for the workspace status pill (cmux only; tmux silently drops it). |
|
|
136
136
|
| `models.definitions.<name>.usage` | optional | If set, codexbar usage is fetched for this model and gated by `sessionLimitPercentage`. Omit to never gate. When `usage.codexbar.source` is omitted, groundcrew uses `auto` on macOS and `cli` elsewhere. |
|
|
137
|
+
| `models.definitions.<name>.disabled` | optional | When set to exactly `true`, drops the named shipped default (`claude` or `codex`). Doctor skips probing it; `agent-<name>` labels fall back to `models.default` with a warning. See "Disabling a shipped default" below. |
|
|
137
138
|
| `prompts.initial` | (template) | First message sent to the agent. Placeholders: `{{ticket}}`, `{{worktree}}`, `{{title}}`, `{{description}}`. |
|
|
138
139
|
| `workspaceKind` | `"auto"` | Terminal session manager. `"auto"` picks `cmux` when on PATH, else `tmux`. Set to `"cmux"` or `"tmux"` to fail loudly when the chosen backend is missing. tmux windows live in a dedicated `groundcrew` session. |
|
|
139
140
|
| `remote.provider` | `"sprite"` | Remote runner provider used for tickets labeled `agent-remote`. Sprite is currently the only provider. |
|
|
@@ -146,6 +147,36 @@ Required fields are marked **required**; everything else has a default and can b
|
|
|
146
147
|
|
|
147
148
|
The branch prefix (`<prefix>-<TICKET>`) is derived from your OS username (`os.userInfo().username`), not configured. Agent selection looks for a top-level Linear label named `agent-<model>` (e.g. `agent-claude`, `agent-codex`). Add `agent-remote` to run that ticket in the configured remote runner instead of locally; `agent-remote` is a modifier label, not a model. **`crew run` only fetches tickets with an `agent-*` label** — the GraphQL query filters them server-side, so unlabeled tickets are never returned by Linear's API and do not appear in the rendered board. Use `crew setup <TICKET>` to provision an unlabeled ticket on demand (manual setup falls back to `models.default`). The reserved label `agent-any` routes the ticket to the configured model with the most available session capacity (lowest codexbar session-used percent), skipping any model already over `sessionLimitPercentage`. With no usage data, `agent-any` resolves to `models.default`. The name `any` cannot be used in `models.definitions`. Todo tickets blocked by Linear issues that are not in `linear.statuses.terminal` are skipped until their blockers reach a terminal status.
|
|
148
149
|
|
|
150
|
+
### Disabling a shipped default
|
|
151
|
+
|
|
152
|
+
Groundcrew ships `claude` and `codex` as default model definitions, additively merged into every resolved config. If you only ever route work through one of them, set `disabled: true` on the other so doctor stops probing for the unused CLI:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// config.ts
|
|
156
|
+
export const config = {
|
|
157
|
+
// …
|
|
158
|
+
models: {
|
|
159
|
+
default: "claude",
|
|
160
|
+
definitions: {
|
|
161
|
+
codex: { disabled: true },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Effects:
|
|
168
|
+
|
|
169
|
+
- `crew doctor` does not probe the disabled model's CLI. `crew doctor || exit 1` becomes viable as a CI gate when you only have one agent installed.
|
|
170
|
+
- `agent-any` only resolves to enabled models.
|
|
171
|
+
- An `agent-<disabled>` label on a ticket (e.g. `agent-codex` after disabling codex) falls back to `models.default` with a warning in the log, so the ticket still runs and you can see the mismatch.
|
|
172
|
+
|
|
173
|
+
Rules:
|
|
174
|
+
|
|
175
|
+
- `disabled` only accepts shipped-default keys (`claude`, `codex`). A typo on the key fails loudly at config load instead of silently disabling nothing.
|
|
176
|
+
- `disabled` must be exactly the boolean `true`.
|
|
177
|
+
- It cannot be combined with `cmd`, `color`, or `usage` in the same entry — disable a model or override its fields, not both.
|
|
178
|
+
- `models.default` must point at an enabled model.
|
|
179
|
+
|
|
149
180
|
## Manual commands
|
|
150
181
|
|
|
151
182
|
```bash
|
|
@@ -167,6 +198,7 @@ crew cleanup <TICKET>
|
|
|
167
198
|
- **Safehouse-already-wrapped commands are not re-wrapped.** If a `models.definitions.<name>.cmd` already starts with `safehouse`, groundcrew assumes that command owns its Safehouse flags and does not add the `safehouse-clearance` wrapper a second time. Changing the proxy's allowlist after it's running requires killing the PID in `${XDG_CACHE_HOME:-$HOME/.cache}/clearance/clearance.pid` so the next launch picks up the new env.
|
|
168
199
|
- **Legacy Docker Sandboxes state is unmanaged.** Groundcrew no longer discovers or cleans `.sbx` worktrees or persistent Docker Sandboxes containers. If you have old state, inspect and remove it manually with `sbx`.
|
|
169
200
|
- **Remote cleanup is also conservative.** `crew cleanup` removes tracked remote worktrees and branches, but it does not kill active remote sessions. Use `crew remote sessions [<runner-name>]` to inspect sessions and, with the Sprite provider, `sprite sessions kill -s <runner-name> <session-id>` when Git reports a worktree is busy.
|
|
201
|
+
- **Dead tmux windows vanish by default.** When a wrapped agent command fails (e.g. `safehouse-clearance` not found, `npm install` crash), the tmux window closes immediately and the error scrolls into the void. Set `GROUNDCREW_KEEP_DEAD_WINDOWS=1` (any non-empty value works) in the env you launch `crew` from to flip the per-window `remain-on-exit` to `on`; the window stays open with the error visible. Close it manually with `tmux kill-window -t groundcrew:<ticket>` after diagnosis. tmux backend only.
|
|
170
202
|
- **Long-running remote shell tools block agent input.** Claude Code and similar TUI agents cannot accept a new prompt while one of their shell tools is still running. Use `crew remote ps <runner-name>` to inspect the remote process tree; interrupt the tool's child `PGID`, not the agent session `PGID`, with `crew remote interrupt <PGID> --runner <runner-name>`.
|
|
171
203
|
- **Codex auth in remote runners may need auth-file copy.** If `crew remote setup <runner-name> --codex` finishes interactive login but `codex login status` still fails inside the remote runner, rerun with `--copy-local-codex-auth` after confirming local Codex auth works.
|
|
172
204
|
- **Usage source defaults are OS-aware.** `codexbar` usage uses `--source auto` on macOS so CodexBar can prefer account/web sources and fall back as it supports. On Linux/WSL it uses `--source cli`, so install the CodexBar Linux CLI and authenticate the provider CLIs inside that environment.
|
|
@@ -176,16 +208,16 @@ crew cleanup <TICKET>
|
|
|
176
208
|
- **Project must be on a single Linear team in practice.** Cross-team projects work — the orchestrator caches the in-progress state ID per team — but every team in the project must use the same status name for `linear.statuses.inProgress`.
|
|
177
209
|
- **Claude launches in bypass-permissions mode by default.** Groundcrew creates isolated per-ticket worktrees and unattended remote sessions, so the shipped `claude` command is `claude --permission-mode bypassPermissions` to avoid workspace-trust and tool-permission prompts blocking automation. Override `models.definitions.claude.cmd` if you want a stricter mode.
|
|
178
210
|
- **Doctor's command introspection is shallow.** Doctor reports whether the host can run local tickets with macOS plus Safehouse, then tokenizes model `cmd` and checks the first two non-flag tokens against PATH (so `safehouse claude --foo` checks both `safehouse` and `claude`). Boolean flags without values, env-var assignments (`FOO=1`), shell pipelines, and subshells are not parsed — verify those manually. In particular, `npx -y claude` and `env FOO=1 claude` only check the wrapper, not the wrapped CLI.
|
|
179
|
-
- **Doctor checks every
|
|
211
|
+
- **Doctor checks every enabled model, including shipped defaults you didn't disable.** `models.definitions` includes both shipped defaults (`claude`, `codex`) by default via additive merge. If you only intend to label tickets `agent-claude` and don't have `codex` installed, set `models.definitions.codex: { disabled: true }` (see "Disabling a shipped default" under "Config reference"). Without that, doctor exits non-zero on a missing `codex` binary even though `crew run` would never route to it.
|
|
180
212
|
- **Switch to tmux if cmux is misbehaving.** Set `workspaceKind: "tmux"` to force the tmux backend when cmux's CLI/socket bridge is flaky (symptoms: `cmux --json list-workspaces` returning `Failed to write to socket (Broken pipe)` or `Socket not found at ...cmux.sock` on every tick). tmux is more reliable — just a unix socket, no GUI app — at the cost of losing cmux's status pills, notifications, and vertical-tab sidebar.
|
|
181
213
|
- **Agent CLI must accept a positional prompt.** The handoff is `<your cmd> "<prompt>"`. `claude`, `codex`, and `cursor-agent` all support this.
|
|
182
214
|
|
|
183
215
|
## Hacking on groundcrew
|
|
184
216
|
|
|
185
|
-
For developers working on the package itself,
|
|
217
|
+
For developers working on the package itself, clone this repo, run `npm install`, and the repo's `crew` / `crew:op` scripts execute groundcrew straight from TypeScript source — no build step. Package dependencies, including `@clipboard-health/clearance`, resolve through normal npm package exports.
|
|
186
218
|
|
|
187
219
|
```bash
|
|
188
|
-
cd ~/dev/c/
|
|
220
|
+
cd ~/dev/c/groundcrew
|
|
189
221
|
node --run crew -- doctor
|
|
190
222
|
|
|
191
223
|
# With 1Password for LINEAR_API_KEY:
|
|
@@ -196,4 +228,4 @@ Both forms read `${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/config.ts` by defa
|
|
|
196
228
|
|
|
197
229
|
Logs land in `${XDG_STATE_HOME:-$HOME/.local/state}/groundcrew/groundcrew.log` by default (override via `logging.file` in your config). The "Loaded config from …" line at startup tells you which config won.
|
|
198
230
|
|
|
199
|
-
Source edits in `
|
|
231
|
+
Source edits in `src/**` are picked up on the next invocation. Requires Node ≥ 24.3 (the version with native `.ts` type stripping enabled by default).
|
package/bin/runCli.js
CHANGED
|
@@ -7,9 +7,8 @@ import { pathToFileURL } from "node:url";
|
|
|
7
7
|
/**
|
|
8
8
|
* Load a side-effecting entrypoint by basename. In published/built mode,
|
|
9
9
|
* dynamically imports the compiled `dist/${name}.js` in-process. In source/dev
|
|
10
|
-
* mode (no compiled output present), spawns a child node that loads the `.ts`
|
|
11
|
-
*
|
|
12
|
-
* `@clipboard-health/*` workspace deps also resolve to source.
|
|
10
|
+
* mode (no compiled output present), spawns a child node that loads the `.ts`
|
|
11
|
+
* source while dependencies resolve through normal package exports.
|
|
13
12
|
*
|
|
14
13
|
* @param {string} packageRoot
|
|
15
14
|
* @param {string} name
|
|
@@ -22,11 +21,9 @@ export async function runCli(packageRoot, name) {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const sourcePath = join(packageRoot, "src", `${name}.ts`);
|
|
25
|
-
const result = spawnSync(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
{ stdio: "inherit" },
|
|
29
|
-
);
|
|
24
|
+
const result = spawnSync(process.execPath, [sourcePath, ...process.argv.slice(2)], {
|
|
25
|
+
stdio: "inherit",
|
|
26
|
+
});
|
|
30
27
|
|
|
31
28
|
if (result.error !== undefined) {
|
|
32
29
|
throw result.error;
|
package/clearance-allow-hosts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,
|
|
1
|
+
{"version":3,"file":"boardSource.d.ts","sourceRoot":"","sources":["../../src/lib/boardSource.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAMrB,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,0FAA0F;IAC1F,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,0FAA0F;IAC1F,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,eAAe,CAExE;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,YAAmB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAMjF;CACF;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,8DAA8D;IAC9D,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B;AAED,UAAU,eAAe;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAUpE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CAEhF;AAgMD,UAAU,aAAa;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,CAAC;CACzB;AAID;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE;IACnD,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,CAAC,CAyCzB"}
|
package/dist/lib/boardSource.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* snapshot. Owns the GraphQL queries and shape parsing so callers consume a
|
|
4
4
|
* typed `BoardState` instead of raw nodes.
|
|
5
5
|
*/
|
|
6
|
-
import { AGENT_ANY_MODEL } from "./config.js";
|
|
6
|
+
import { AGENT_ANY_MODEL, isShippedDefaultDisabled, } from "./config.js";
|
|
7
7
|
import { log } from "./util.js";
|
|
8
8
|
const AGENT_LABEL_PREFIX = "agent-";
|
|
9
9
|
const ISSUES_PAGE_SIZE = 250;
|
|
@@ -130,6 +130,7 @@ async function fetchBoard(client, config) {
|
|
|
130
130
|
.filter((node) => node.children.nodes.length === 0)
|
|
131
131
|
.map((node) => {
|
|
132
132
|
const parsedAgentLabels = parseAgentLabels(node.labels.nodes, config);
|
|
133
|
+
warnIfDisabledFallback(node.identifier, parsedAgentLabels, config);
|
|
133
134
|
const repository = parsedAgentLabels === undefined
|
|
134
135
|
? undefined
|
|
135
136
|
: parseRepository({
|
|
@@ -202,6 +203,7 @@ export async function fetchResolvedIssue(arguments_) {
|
|
|
202
203
|
// unlabeled ticket still resolves to `models.default` — different from
|
|
203
204
|
// the auto-pickup path, where unlabeled tickets are ignored.
|
|
204
205
|
const parsed = parseAgentLabels(issue.labels.nodes, config);
|
|
206
|
+
warnIfDisabledFallback(ticket, parsed, config);
|
|
205
207
|
const model = parsed === undefined || parsed.model === AGENT_ANY_MODEL ? config.models.default : parsed.model;
|
|
206
208
|
const runner = parsed?.runner ?? "local";
|
|
207
209
|
return { title: issue.title, description, repository, model, runner };
|
|
@@ -229,6 +231,7 @@ function parseAgentLabels(labels, config) {
|
|
|
229
231
|
return undefined;
|
|
230
232
|
}
|
|
231
233
|
const runner = agentLabels.some((label) => label.name === "agent-remote") ? "remote" : "local";
|
|
234
|
+
let disabledFallback;
|
|
232
235
|
for (const label of agentLabels) {
|
|
233
236
|
if (label.name === "agent-remote") {
|
|
234
237
|
continue;
|
|
@@ -243,8 +246,21 @@ function parseAgentLabels(labels, config) {
|
|
|
243
246
|
if (Object.hasOwn(config.models.definitions, name)) {
|
|
244
247
|
return { model: name, runner };
|
|
245
248
|
}
|
|
249
|
+
if (disabledFallback === undefined && isShippedDefaultDisabled(config, name)) {
|
|
250
|
+
disabledFallback = name;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const fallback = { model: config.models.default, runner };
|
|
254
|
+
if (disabledFallback !== undefined) {
|
|
255
|
+
fallback.disabledFallback = disabledFallback;
|
|
256
|
+
}
|
|
257
|
+
return fallback;
|
|
258
|
+
}
|
|
259
|
+
function warnIfDisabledFallback(ticket, parsed, config) {
|
|
260
|
+
if (parsed?.disabledFallback === undefined) {
|
|
261
|
+
return;
|
|
246
262
|
}
|
|
247
|
-
|
|
263
|
+
log(`${ticket.toLowerCase()}: agent-${parsed.disabledFallback} label refers to a disabled model; falling back to models.default (${config.models.default})`);
|
|
248
264
|
}
|
|
249
265
|
function blockersFromRelations(relations) {
|
|
250
266
|
return relations
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -49,7 +49,19 @@ export interface RemoteRunnerConfig {
|
|
|
49
49
|
worktreeRoot: string;
|
|
50
50
|
secretNames: string[];
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* User-facing model entry shape. Discriminated union so the type system
|
|
54
|
+
* mirrors the runtime contract: an entry is either a pure overlay
|
|
55
|
+
* (every concrete field optional, no `disabled` key) or a pure
|
|
56
|
+
* disable directive (`{ disabled: true }` and nothing else).
|
|
57
|
+
*/
|
|
58
|
+
type EnabledUserModelDefinition = Partial<ModelDefinition> & {
|
|
59
|
+
disabled?: never;
|
|
60
|
+
};
|
|
61
|
+
interface DisabledUserModelDefinition {
|
|
62
|
+
disabled: true;
|
|
63
|
+
}
|
|
64
|
+
type UserModelDefinition = EnabledUserModelDefinition | DisabledUserModelDefinition;
|
|
53
65
|
/**
|
|
54
66
|
* Setup command run inside sibling worktrees on the host. The host is
|
|
55
67
|
* assumed to already have the right Node and npm versions, so this skips
|
|
@@ -169,5 +181,12 @@ export interface ResolvedConfig {
|
|
|
169
181
|
file: string;
|
|
170
182
|
};
|
|
171
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* True when `name` is a shipped default the user removed via `disabled: true`.
|
|
186
|
+
* Derived from absence in `definitions` — that's the only path that removes a
|
|
187
|
+
* shipped default, codified in `failIfLegacyModelKeys` + `mergeDefinitions`.
|
|
188
|
+
* Consumers needing to distinguish disabled-by-user from unknown-label use this.
|
|
189
|
+
*/
|
|
190
|
+
export declare function isShippedDefaultDisabled(config: Pick<ResolvedConfig, "models">, name: string): boolean;
|
|
172
191
|
export declare function loadConfig(): Promise<Readonly<ResolvedConfig>>;
|
|
173
192
|
//# sourceMappingURL=config.d.ts.map
|
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":"AASA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEjD,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAAiC,CAAC;AAE1F,eAAO,MAAM,4BAA4B,YAAI,QAAQ,CAAU,CAAC;AAEhE,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AAErF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,wBAAwB,CAI5F;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAIzD,CAAC;AAEX,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACjD,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,wBAAwB,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,KAAK,
|
|
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
|
@@ -256,6 +256,25 @@ function failIfLegacyModelKeys(name, override) {
|
|
|
256
256
|
if (Object.hasOwn(override, "sandbox")) {
|
|
257
257
|
fail(`models.definitions.${name}.sandbox is no longer supported: Docker Sandboxes are no longer supported`);
|
|
258
258
|
}
|
|
259
|
+
if (Object.hasOwn(override, "disabled")) {
|
|
260
|
+
if (override["disabled"] !== true) {
|
|
261
|
+
fail(`models.definitions.${name}.disabled must be exactly \`true\` when set (got ${JSON.stringify(override["disabled"])})`);
|
|
262
|
+
}
|
|
263
|
+
const conflicting = ["cmd", "color", "usage"].filter((key) => Object.hasOwn(override, key));
|
|
264
|
+
if (conflicting.length > 0) {
|
|
265
|
+
fail(`models.definitions.${name}: cannot combine \`disabled: true\` with other fields (${conflicting.join(", ")}). Either disable the model or override its fields, not both.`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* True when `name` is a shipped default the user removed via `disabled: true`.
|
|
271
|
+
* Derived from absence in `definitions` — that's the only path that removes a
|
|
272
|
+
* shipped default, codified in `failIfLegacyModelKeys` + `mergeDefinitions`.
|
|
273
|
+
* Consumers needing to distinguish disabled-by-user from unknown-label use this.
|
|
274
|
+
*/
|
|
275
|
+
export function isShippedDefaultDisabled(config, name) {
|
|
276
|
+
return (Object.hasOwn(DEFAULT_MODEL_DEFINITIONS, name) &&
|
|
277
|
+
!Object.hasOwn(config.models.definitions, name));
|
|
259
278
|
}
|
|
260
279
|
function mergeDefinitions(user) {
|
|
261
280
|
if (user !== undefined && !isPlainObject(user)) {
|
|
@@ -267,6 +286,17 @@ function mergeDefinitions(user) {
|
|
|
267
286
|
]));
|
|
268
287
|
for (const [name, override] of Object.entries(user ?? {})) {
|
|
269
288
|
failIfLegacyModelKeys(name, override);
|
|
289
|
+
if (override.disabled === true) {
|
|
290
|
+
if (!Object.hasOwn(DEFAULT_MODEL_DEFINITIONS, name)) {
|
|
291
|
+
fail(`models.definitions.${name}: \`disabled: true\` is only valid for shipped defaults (${Object.keys(DEFAULT_MODEL_DEFINITIONS).join(", ")}). Remove the entry instead.`);
|
|
292
|
+
}
|
|
293
|
+
// Drop the key so downstream iterators (doctor, eligibility, usage) ignore
|
|
294
|
+
// the model automatically; `isShippedDefaultDisabled` lets the few consumers
|
|
295
|
+
// that need to distinguish disabled from unknown re-derive the set.
|
|
296
|
+
// oxlint-disable-next-line typescript/no-dynamic-delete -- `merged` is a fresh function-local clone of DEFAULT_MODEL_DEFINITIONS; no V8 dictionary-mode/pollution concerns
|
|
297
|
+
delete merged[name];
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
270
300
|
const base = merged[name] === undefined ? {} : cloneModelDefinition(merged[name]);
|
|
271
301
|
// Per-key spread so overriding `cmd` alone preserves the default
|
|
272
302
|
// `color` / `usage`. Brand-new entries must supply both required fields.
|
|
@@ -400,6 +430,12 @@ function validate(config) {
|
|
|
400
430
|
requireString(codexbar.provider, `${usagePath}.codexbar.provider`);
|
|
401
431
|
}
|
|
402
432
|
}
|
|
433
|
+
// Disabled-default check must run before the generic "not a key" check so
|
|
434
|
+
// the user gets the specific "is disabled" message instead of a stale-list
|
|
435
|
+
// message they can't act on without realizing they need to re-enable.
|
|
436
|
+
if (isShippedDefaultDisabled(config, config.models.default)) {
|
|
437
|
+
fail(`models.default ("${config.models.default}") is disabled. Either re-enable it or set models.default to an enabled model.`);
|
|
438
|
+
}
|
|
403
439
|
if (!(config.models.default in definitions)) {
|
|
404
440
|
fail(`models.default ("${config.models.default}") is not a key in models.definitions (have: ${Object.keys(definitions).join(", ")})`);
|
|
405
441
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAoL7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;
|
|
1
|
+
{"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/lib/workspaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG1E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,MAAM,WAAW,SAAS;IACxB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAoL7C,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,oBAAoB,CAAC;IAChC,QAAQ,EAAE,aAAa,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,gBAAgB,GAAG,mBAAmB,CAUtF;AAgOD,iBAAe,eAAe,CAC5B,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CAezB;AAED,eAAO,MAAM,UAAU;IACf,IAAI,SAAS,cAAc,QAAQ,QAAQ,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,KAAK;IACC,KAAK,SAAS,cAAc,QAAQ,MAAM,WAAW,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;CAIvF,CAAC"}
|
package/dist/lib/workspaces.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { runCommandAsync } from "./commandRunner.js";
|
|
8
8
|
import { detectHostCapabilities } from "./host.js";
|
|
9
|
-
import { errorMessage, log } from "./util.js";
|
|
9
|
+
import { errorMessage, log, readEnvironmentVariable } from "./util.js";
|
|
10
10
|
async function runWorkspaceCommand(command, arguments_, signal) {
|
|
11
11
|
return signal === undefined
|
|
12
12
|
? await runCommandAsync(command, arguments_)
|
|
@@ -272,6 +272,8 @@ const tmuxAdapter = {
|
|
|
272
272
|
async open(spec, signal) {
|
|
273
273
|
await ensureTmuxSession(signal);
|
|
274
274
|
const target = `${TMUX_SESSION}:${spec.name}`;
|
|
275
|
+
const keepDeadWindowsEnv = readEnvironmentVariable("GROUNDCREW_KEEP_DEAD_WINDOWS");
|
|
276
|
+
const keepDeadWindows = keepDeadWindowsEnv !== undefined && keepDeadWindowsEnv.length > 0;
|
|
275
277
|
await runWorkspaceCommand("tmux", [
|
|
276
278
|
"new-window",
|
|
277
279
|
"-d",
|
|
@@ -287,7 +289,7 @@ const tmuxAdapter = {
|
|
|
287
289
|
"-t",
|
|
288
290
|
target,
|
|
289
291
|
"remain-on-exit",
|
|
290
|
-
"off",
|
|
292
|
+
keepDeadWindows ? "on" : "off",
|
|
291
293
|
";",
|
|
292
294
|
"set-window-option",
|
|
293
295
|
"-t",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clipboard-health/groundcrew",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Linear-driven orchestrator that launches AI coding agents in git worktrees, with workspace lifecycle, remote runners, and usage tracking.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -11,12 +11,11 @@
|
|
|
11
11
|
"orchestrator",
|
|
12
12
|
"worktree"
|
|
13
13
|
],
|
|
14
|
-
"bugs": "https://github.com/ClipboardHealth/
|
|
14
|
+
"bugs": "https://github.com/ClipboardHealth/groundcrew/issues",
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/ClipboardHealth/
|
|
19
|
-
"directory": "packages/groundcrew"
|
|
18
|
+
"url": "git+https://github.com/ClipboardHealth/groundcrew.git"
|
|
20
19
|
},
|
|
21
20
|
"bin": {
|
|
22
21
|
"crew": "./bin/run.js"
|
|
@@ -26,7 +25,8 @@
|
|
|
26
25
|
"bin",
|
|
27
26
|
"clearance-allow-hosts",
|
|
28
27
|
"configExample.ts",
|
|
29
|
-
"dist"
|
|
28
|
+
"dist",
|
|
29
|
+
"static"
|
|
30
30
|
],
|
|
31
31
|
"type": "module",
|
|
32
32
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
"exports": {
|
|
37
37
|
"./package.json": "./package.json",
|
|
38
38
|
".": {
|
|
39
|
-
"@clipboard-health/source": "./src/index.ts",
|
|
40
39
|
"types": "./dist/index.d.ts",
|
|
41
40
|
"import": "./dist/index.js",
|
|
42
41
|
"default": "./dist/index.js"
|
|
@@ -46,11 +45,60 @@
|
|
|
46
45
|
"access": "public"
|
|
47
46
|
},
|
|
48
47
|
"scripts": {
|
|
49
|
-
"
|
|
48
|
+
"architecture:check": "depcruise src bin configExample.ts vitest.config.ts",
|
|
49
|
+
"build": "tsgo --build --force tsconfig.lib.json",
|
|
50
|
+
"cpd": "jscpd .",
|
|
51
|
+
"crew": "node ./bin/run.js",
|
|
52
|
+
"crew:op": "op run --env-file \"${XDG_CONFIG_HOME:-$HOME/.config}/groundcrew/op.env\" -- node ./bin/run.js",
|
|
53
|
+
"format": "oxfmt",
|
|
54
|
+
"format:check": "oxfmt --check",
|
|
55
|
+
"hook:pre-commit": "lint-staged",
|
|
56
|
+
"hook:pre-push": "node --run verify",
|
|
57
|
+
"knip": "knip",
|
|
58
|
+
"lint": "oxlint --deny-warnings",
|
|
59
|
+
"markdown:lint": "markdownlint-cli2",
|
|
60
|
+
"prepack": "node --run build",
|
|
61
|
+
"prepare": "husky",
|
|
62
|
+
"spell:check": "cspell --no-summary --no-progress --no-must-find-files",
|
|
63
|
+
"sync-ai-rules": "node ./node_modules/@clipboard-health/ai-rules/scripts/sync.js common",
|
|
64
|
+
"syncpack:lint": "syncpack lint",
|
|
65
|
+
"test": "vitest run --coverage",
|
|
66
|
+
"verify": "node scripts/verifyAll.mts"
|
|
50
67
|
},
|
|
51
68
|
"dependencies": {
|
|
52
|
-
"@clipboard-health/clearance": "1.0.
|
|
69
|
+
"@clipboard-health/clearance": "1.0.8",
|
|
53
70
|
"@linear/sdk": "84.0.0",
|
|
54
71
|
"tslib": "2.8.1"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@clipboard-health/ai-rules": "2.18.4",
|
|
75
|
+
"@clipboard-health/oxlint-config": "1.7.0",
|
|
76
|
+
"@nx/js": "22.7.1",
|
|
77
|
+
"@tsconfig/node24": "24.0.4",
|
|
78
|
+
"@tsconfig/strictest": "2.0.8",
|
|
79
|
+
"@types/node": "24.12.2",
|
|
80
|
+
"@typescript/native-preview": "7.0.0-dev.20260430.1",
|
|
81
|
+
"@vitest/coverage-v8": "4.1.5",
|
|
82
|
+
"cspell": "10.0.0",
|
|
83
|
+
"dependency-cruiser": "17.4.0",
|
|
84
|
+
"husky": "9.1.7",
|
|
85
|
+
"jscpd": "4.0.9",
|
|
86
|
+
"knip": "6.9.0",
|
|
87
|
+
"lint-staged": "16.4.0",
|
|
88
|
+
"markdownlint-cli2": "0.22.1",
|
|
89
|
+
"nx": "22.7.1",
|
|
90
|
+
"oxfmt": "0.47.0",
|
|
91
|
+
"oxlint": "1.60.0",
|
|
92
|
+
"oxlint-tsgolint": "0.21.1",
|
|
93
|
+
"syncpack": "15.0.0",
|
|
94
|
+
"vite": "8.0.10",
|
|
95
|
+
"vitest": "4.1.5"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": "24.14.1",
|
|
99
|
+
"npm": "~11.11.0"
|
|
100
|
+
},
|
|
101
|
+
"nx": {
|
|
102
|
+
"name": "groundcrew"
|
|
55
103
|
}
|
|
56
104
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="225 230 575 530" width="1024.0pt" height="944.0pt">
|
|
4
|
+
<path d="M 491.97 239.05 C 526.13 236.93 560.37 240.48 593.00 251.00 C 626.41 261.79 657.69 279.64 683.66 303.32 C 707.08 325.06 726.40 351.86 738.47 381.51 C 744.03 394.32 747.45 407.46 750.90 420.93 C 762.33 420.53 773.79 425.00 781.76 433.25 C 788.68 441.04 792.39 451.62 792.07 462.00 C 792.08 487.67 792.17 513.33 792.05 539.00 C 792.27 545.73 790.51 552.54 787.54 558.55 C 783.74 566.04 777.45 571.67 770.09 575.55 C 769.92 579.70 770.08 583.85 769.99 588.00 C 768.69 602.43 766.81 616.79 757.36 628.43 C 756.74 632.58 757.53 636.52 755.44 640.46 C 752.06 648.40 745.48 652.22 738.31 656.27 C 732.17 659.48 726.14 662.23 719.02 660.89 C 712.87 659.35 707.07 655.43 704.53 649.45 C 703.05 645.51 702.46 641.30 703.50 637.16 C 704.33 631.93 708.45 627.64 712.25 624.25 C 716.71 620.56 721.78 617.56 726.88 614.85 C 732.77 611.92 740.93 611.14 746.43 615.32 C 747.51 614.30 747.88 613.62 748.20 612.15 C 751.08 601.64 751.97 590.54 751.90 579.68 C 745.31 580.33 738.62 579.86 732.00 580.03 C 720.74 580.20 709.30 575.59 703.14 565.80 C 699.76 560.81 698.29 554.97 697.99 549.01 C 698.02 514.60 697.92 480.42 698.18 446.00 C 698.56 440.21 699.47 434.75 703.08 430.01 C 706.08 425.60 710.41 423.81 715.11 421.80 C 706.69 386.13 686.06 353.49 659.23 328.77 C 646.53 316.55 632.06 306.71 616.51 298.46 C 593.44 285.91 568.21 277.82 542.16 274.50 C 536.07 273.94 530.13 272.79 524.00 272.97 C 517.66 272.59 511.34 272.32 504.99 272.77 C 499.95 273.21 495.01 272.70 490.00 273.58 C 454.29 276.95 418.96 289.01 389.27 309.26 C 362.61 326.89 340.47 351.37 325.30 379.49 C 319.33 390.66 314.19 402.41 310.72 414.61 C 309.94 417.28 308.92 419.78 308.91 422.62 C 318.36 424.43 324.44 433.67 324.21 443.00 C 324.27 477.65 324.12 512.34 324.20 547.00 C 324.14 550.61 324.24 554.28 323.83 557.87 C 321.66 565.85 315.98 572.12 308.64 575.78 C 300.05 580.18 291.39 580.22 282.00 580.01 C 267.62 580.66 253.23 576.52 243.12 565.88 C 234.68 557.31 231.16 545.84 230.97 534.00 C 231.02 512.33 230.96 490.67 231.00 469.00 C 231.03 464.43 230.69 460.00 231.64 455.49 C 233.10 443.19 240.79 431.64 251.69 425.73 C 258.00 422.14 265.62 420.75 272.81 420.86 C 278.14 395.31 287.67 370.76 301.76 348.76 C 308.87 337.50 316.93 326.73 326.09 317.05 C 330.25 312.27 334.65 307.58 339.54 303.53 C 365.80 279.49 398.14 261.74 432.05 251.14 C 451.47 244.59 471.61 240.98 491.97 239.05 Z" fill="#78716c" />
|
|
5
|
+
<path d="M 356.00 444.19 C 452.27 444.23 548.55 444.15 644.82 444.23 C 649.66 444.23 654.22 444.56 657.84 448.15 C 661.26 451.27 662.52 455.45 662.47 459.99 C 662.31 525.35 662.51 590.59 662.40 656.00 C 662.38 661.55 662.63 667.04 661.94 672.56 C 659.55 678.86 653.83 683.18 647.00 683.04 C 547.66 683.01 448.31 683.05 348.96 683.02 C 340.58 682.87 333.60 675.32 333.95 667.00 C 333.95 599.00 333.96 531.00 333.94 463.00 C 333.95 460.46 333.79 457.81 334.22 455.29 C 335.23 451.68 337.14 448.37 340.50 446.48 C 345.39 443.64 350.60 444.17 356.00 444.19 Z" fill="#78716c" />
|
|
6
|
+
<path d="M 389.00 483.91 C 392.46 483.66 394.50 486.02 397.14 487.82 C 416.17 501.83 435.17 515.92 454.42 529.63 C 456.37 531.09 458.13 532.56 458.89 534.98 C 459.10 537.05 459.34 539.53 458.56 541.50 C 457.15 543.90 454.68 545.81 452.49 547.46 C 434.16 560.87 415.99 574.32 397.64 587.70 C 395.69 589.05 393.38 590.56 391.00 590.95 C 386.00 590.89 381.37 587.28 381.55 581.96 C 381.26 576.63 385.28 574.69 388.98 571.94 C 404.61 560.64 420.06 549.02 435.97 538.11 C 419.10 525.62 402.11 513.26 385.25 500.75 C 382.37 498.55 379.93 495.86 380.05 491.99 C 380.79 487.65 384.18 483.40 389.00 483.91 Z" fill="#77d94e" />
|
|
7
|
+
<path d="M 672.78 521.32 C 677.82 520.86 682.12 520.21 686.27 523.73 C 691.33 527.85 690.31 533.17 690.62 539.00 C 690.46 604.02 690.92 668.98 690.42 734.01 C 690.17 739.13 688.58 744.11 684.46 747.42 C 681.30 750.32 677.21 751.32 673.00 751.12 C 577.34 751.12 481.66 751.13 386.00 751.08 C 377.59 751.55 369.36 743.47 369.97 735.00 C 370.03 721.31 369.99 707.63 369.97 693.94 C 460.31 694.02 550.66 693.99 641.00 693.96 C 652.00 694.60 663.63 688.89 669.36 679.38 C 672.64 674.56 672.40 668.57 672.51 663.00 C 672.55 618.67 672.51 574.33 672.55 530.00 C 672.50 527.10 672.50 524.21 672.78 521.32 Z" fill="#78716c" />
|
|
8
|
+
<path d="M 473.99 577.90 C 490.65 577.77 507.33 577.94 524.00 577.84 C 527.49 577.86 531.25 577.49 534.68 578.19 C 539.56 579.86 541.50 585.54 539.02 590.00 C 537.21 593.42 533.72 594.68 530.03 594.77 C 512.35 594.96 494.68 594.68 477.00 594.87 C 473.37 594.93 470.04 594.29 467.64 591.35 C 465.40 589.27 465.80 585.98 466.16 583.24 C 467.44 580.11 470.43 577.60 473.99 577.90 Z" fill="#77d94e" />
|
|
9
|
+
</svg>
|