feat: add 'squad preset install <source>' for sharing presets via repo URL (#1224)#1225
feat: add 'squad preset install <source>' for sharing presets via repo URL (#1224)#1225tamirdresher wants to merge 1 commit into
Conversation
…o URL (bradygaster#1224) Closes bradygaster#1224. Adds a new subcommand that installs a single preset from a GitHub URL or local path into \\/presets/<name>/\ — the peer-to-peer preset sharing flow that was missing in v0.10.0. SDK side (squad-sdk/src/presets/index.ts): - New \installPresetFromSource(source, options)\ function - Resolves source: GitHub URL → shallow git clone --depth 1 to OS temp; local path → use as-is - Locates preset within source via 3 patterns: - dir contains preset.json → single-preset source - dir contains presets/ subdir → require --name to pick - dir IS the presets/ dir → require --name (or auto-pick if only one) - Validates preset.json before any destructive action - Copies preset.json (with optional rename) + agents/ into squad home - Cleans up temp clones in finally block (success or failure) - Exports: installPresetFromSource, InstallPresetOptions, InstallPresetResult CLI side (squad-cli/src/cli/commands/preset.ts): - New 'install' dispatcher case + presetInstall() function - Supports --name <override>, --force - Module docstring + default usage help updated to include 'install' Supported source shapes: https://github.com/owner/repo https://github.com/owner/repo#preset-name (frag as subdir hint) https://github.com/owner/repo/tree/branch/path/... (sub-path) git\@github.com:owner/repo.git (SSH) ./local/path (single preset OR collection) Smoke tested all 6 cases locally: 1. Local single-preset → installs under manifest.name ✅ 2. Idempotent re-install fails without --force ✅ 3. --force overwrites ✅ 4. --name renames + updates manifest.name ✅ 5. Invalid source → clear error ✅ 6. GitHub URL (cloned bradygaster/squad's presets/builtin/default) ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🟡 Impact Analysis — PR #1225Risk tier: 🟡 MEDIUM 📊 Summary
🎯 Risk Factors
📦 Modules Affectedroot (1 file)
squad-cli (1 file)
squad-sdk (1 file)
|
🛫 PR Readiness Check
PR Scope: 📦🔧 Mixed (product + infrastructure)
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ✅ | Copilot threads resolved | No Copilot review threads |
| ❌ | CI passing | 7 check(s) still running |
Files Changed (3 files, +354 −1)
| File | +/− |
|---|---|
.changeset/feat-preset-install.md |
+60 −0 |
packages/squad-cli/src/cli/commands/preset.ts |
+45 −1 |
packages/squad-sdk/src/presets/index.ts |
+249 −0 |
Total: +354 −1
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
|
@bradygaster — fix for #1224. Adds Smoke-tested 6 cases on local build:
Minor bump for both sdk + cli. CHANGELOG gate should pass cleanly (changeset included). Build hit a snag locally — workspaces had a stale real-copy of squad-sdk inside |
There was a problem hiding this comment.
Pull request overview
Adds a new preset distribution workflow by introducing squad preset install <source>, allowing users to install a single preset from a GitHub repo URL (via shallow clone) or a local path into $SQUAD_HOME/presets/<name>/.
Changes:
- SDK: add
installPresetFromSource(source, options)with source resolution (local vs git clone), preset discovery rules, optional rename stamping, and--forceoverwrite behavior. - CLI: add
preset installsubcommand wiring, usage output, and install status messages. - Release: add changeset bumping CLI and SDK as
minor.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| packages/squad-sdk/src/presets/index.ts | Implements the core install-from-source logic, including git shallow-clone + preset discovery/copying. |
| packages/squad-cli/src/cli/commands/preset.ts | Adds the install subcommand routing and user-facing CLI output for installs. |
| .changeset/feat-preset-install.md | Documents the feature and bumps CLI/SDK versions. |
| // Resolve the source into a local working directory + optional sub-path inside it. | ||
| // Returns { workDir, subPath, cleanup } — caller must call cleanup() in a finally. | ||
| const { workDir, subPath, cleanup } = resolveInstallSource(source); | ||
|
|
||
| try { | ||
| // Locate the actual preset directory inside workDir | ||
| const startDir = subPath ? path.join(workDir, subPath) : workDir; | ||
| if (!storage.existsSync(startDir) || !isDirSync(startDir)) { | ||
| throw new Error(`Source path does not exist or is not a directory: ${startDir}`); | ||
| } | ||
|
|
||
| const { presetDir, defaultName } = locatePresetWithinSource(startDir, options.name); | ||
|
|
| try { | ||
| const refArgs = ref ? ['--branch', ref] : []; | ||
| const args = ['clone', '--depth', '1', ...refArgs, cloneUrl, tmpBase]; | ||
| execSync(`git ${args.map(a => /[\s"]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a).join(' ')}`, { stdio: 'pipe' }); |
| const presetsSubDir = path.join(startDir, 'presets'); | ||
| if (storage.existsSync(presetsSubDir) && isDirSync(presetsSubDir)) { | ||
| if (nameHint) { | ||
| const candidate = path.join(presetsSubDir, nameHint); | ||
| if (storage.existsSync(path.join(candidate, 'preset.json'))) { | ||
| return { presetDir: candidate, defaultName: nameHint }; |
| const force = args.includes('--force'); | ||
| const nameIdx = args.indexOf('--name'); | ||
| const nameOverride = nameIdx >= 0 ? args[nameIdx + 1] : undefined; | ||
| await presetInstall(source!, nameOverride, force); |
| * | ||
| * @throws Error on any validation failure, manifest invalidity, or destination collision. | ||
| */ | ||
| export function installPresetFromSource(source: string, options: InstallPresetOptions = {}): InstallPresetResult { |
| ` squad preset show <name>\n` + | ||
| ` squad preset apply <name> [--force]\n` + | ||
| ` squad preset save <name>\n` + | ||
| ` squad preset install <source> [--name <override>] [--force]\n` + | ||
| ` squad preset init [--remote]`, |
feat:
squad preset install <source>for sharing presets via repo URL (#1224)Closes #1224.
What
New subcommand that installs a single preset from a GitHub URL or local path into
$SQUAD_HOME/presets/<name>/:After install, the preset is a normal entry in
$SQUAD_HOME/presets/—squad preset list,squad preset apply <name>, andsquad init --preset <name>all work as today.Why
The existing
squad preset init --remoteflow shares your whole$SQUAD_HOMErepo. There was no way to install just one preset from someone else's repo. Users had to manually clone + copy + apply, or hijackSQUAD_HOME(collides with personal config).Source resolution rules
preset.json→ install that as a single presetpresets/collection → require--name(or#nameURL fragment) to pickpresets/dir → require--name, or auto-pick if only one preset presentImplementation
SDK (
packages/squad-sdk/src/presets/index.ts):installPresetFromSource(source, options)functionimport os from 'node:os'andexecSyncfromnode:child_process(ESM-safe)git clone --depth 1 [--branch <ref>]finallymanifest.nameto match installed name when--nameis usedCLI (
packages/squad-cli/src/cli/commands/preset.ts):'install'dispatcher case +presetInstall()functioninstallSmoke tests (all pass on local build)
manifest.name--forcealready exists--force--namerename + manifest rewritemanifest.nameupdatedNo preset founderrorbradygaster/squad/.../builtin/defaultbrady-defaultwith 5 agentsWhat's NOT in scope (deferred to follow-ups)
squad preset uninstall—rm -rf $SQUAD_HOME/presets/<name>works manuallysquad preset update <name>to pull fresh from originCo-authored-by: Copilot 223556219+Copilot@users.noreply.github.com