Skip to content

feat: add 'squad preset install <source>' for sharing presets via repo URL (#1224)#1225

Open
tamirdresher wants to merge 1 commit into
bradygaster:devfrom
tamirdresher:feat/1224-preset-install
Open

feat: add 'squad preset install <source>' for sharing presets via repo URL (#1224)#1225
tamirdresher wants to merge 1 commit into
bradygaster:devfrom
tamirdresher:feat/1224-preset-install

Conversation

@tamirdresher

Copy link
Copy Markdown
Collaborator

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>/:

# GitHub URLs
squad preset install https://github.com/tamir/my-presets#my-awesome-team
squad preset install https://github.com/tamir/my-presets/tree/main/presets/my-awesome-team
squad preset install git@github.com:tamir/my-presets.git#my-awesome-team

# Local paths
squad preset install ./my-awesome-team             # single preset
squad preset install ./my-presets-collection --name my-awesome-team   # collection

# Modifiers
squad preset install <source> --name corp-team    # rename on install
squad preset install <source> --force             # overwrite existing

After install, the preset is a normal entry in $SQUAD_HOME/presets/squad preset list, squad preset apply <name>, and squad init --preset <name> all work as today.

Why

The existing squad preset init --remote flow shares your whole $SQUAD_HOME repo. There was no way to install just one preset from someone else's repo. Users had to manually clone + copy + apply, or hijack SQUAD_HOME (collides with personal config).

Source resolution rules

  1. Source dir contains preset.json → install that as a single preset
  2. Source dir contains a presets/ collection → require --name (or #name URL fragment) to pick
  3. Source dir IS the presets/ dir → require --name, or auto-pick if only one preset present

Implementation

SDK (packages/squad-sdk/src/presets/index.ts):

  • New installPresetFromSource(source, options) function
  • Top-level import os from 'node:os' and execSync from node:child_process (ESM-safe)
  • Shallow clones with git clone --depth 1 [--branch <ref>]
  • Always cleans up temp clones in finally
  • Stamps manifest.name to match installed name when --name is used

CLI (packages/squad-cli/src/cli/commands/preset.ts):

  • New 'install' dispatcher case + presetInstall() function
  • Module docstring + default usage help updated to include install

Smoke tests (all pass on local build)

# Case Result
1 Install single preset from local path ✅ Installed under manifest.name
2 Re-install without --force ✅ Fails with already exists
3 Re-install with --force ✅ Overwrites
4 --name rename + manifest rewrite ✅ Installed as override, manifest.name updated
5 Invalid source (no preset.json/no presets/) ✅ Clear No preset found error
6 GitHub URL — cloned bradygaster/squad/.../builtin/default ✅ Installed brady-default with 5 agents

What's NOT in scope (deferred to follow-ups)

  • squad preset uninstallrm -rf $SQUAD_HOME/presets/<name> works manually
  • squad preset update <name> to pull fresh from origin
  • Public preset registry / discovery catalog

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

…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>
Copilot AI review requested due to automatic review settings June 7, 2026 16:35
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🟡 Impact Analysis — PR #1225

Risk tier: 🟡 MEDIUM

📊 Summary

Metric Count
Files changed 3
Files added 1
Files modified 2
Files deleted 0
Modules touched 3
Critical files 1

🎯 Risk Factors

  • 3 files changed (≤5 → LOW)
  • 3 modules touched (2-4 → MEDIUM)
  • Critical files touched: packages/squad-sdk/src/presets/index.ts

📦 Modules Affected

root (1 file)
  • .changeset/feat-preset-install.md
squad-cli (1 file)
  • packages/squad-cli/src/cli/commands/preset.ts
squad-sdk (1 file)
  • packages/squad-sdk/src/presets/index.ts

⚠️ Critical Files

  • packages/squad-sdk/src/presets/index.ts

This report is generated automatically for every PR. See #733 for details.

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 4cafee9

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 2 item(s) to address before review

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.

@tamirdresher

Copy link
Copy Markdown
Collaborator Author

@bradygaster — fix for #1224. Adds squad preset install <source> (GitHub URL, SSH URL, or local path).

Smoke-tested 6 cases on local build:

  1. Local single-preset install
  2. Idempotency (re-install fails without --force)
  3. --force overwrites
  4. --name rename + manifest rewrite
  5. Invalid source → clear error
  6. Real GitHub URL — https://github.com/bradygaster/squad/tree/main/packages/squad-sdk/src/presets/builtin/default → installed as brady-default with all 5 agents

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 packages/squad-cli/node_modules/@bradygaster/ instead of a junction. npm install after removing it restored the workspace link. Worth a clean script in the squad-cli package if this happens again, but not blocking this PR.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --force overwrite behavior.
  • CLI: add preset install subcommand 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.

Comment on lines +292 to +304
// 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);

Comment on lines +415 to +418
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' });
Comment on lines +449 to +454
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 };
Comment on lines +82 to +85
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 {
Comment on lines 93 to 97
` 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]`,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add 'squad preset install <source>' for sharing presets via repo URL

2 participants