-
Notifications
You must be signed in to change notification settings - Fork 64
feat: add --global / -g flag for scoped package installation #452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6018a0b
0181b05
a0f2010
6dfbf6d
4963e63
144c9fa
11ee6ac
7486d4b
e07314d
0c5959d
4d465a0
1725400
98c0a02
46a7e0e
4d3c6a8
4af0d4a
07af560
e65e128
559c529
f5dceda
aeda09b
8ea0bf5
70294b3
562fdc2
ac56de0
81cdfbd
0f37a14
b10a6e5
1847f50
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| --- | ||
| title: Scoped Installation | ||
| sidebar: | ||
| order: 11 | ||
| --- | ||
|
|
||
| APM supports two installation scopes: **project** (default) and **user** (global). | ||
|
|
||
| ## Project scope (default) | ||
|
|
||
| Packages install into the current directory: | ||
|
|
||
| ```bash | ||
| apm install microsoft/apm-sample-package | ||
| ``` | ||
|
|
||
| - Manifest: `./apm.yml` | ||
| - Modules: `./apm_modules/` | ||
| - Lockfile: `./apm.lock.yaml` | ||
| - Deployed primitives: `./.github/`, `./.claude/`, `./.cursor/`, `./.opencode/` | ||
|
|
||
| This is the standard behavior. Every collaborator who clones the repo gets the same setup. | ||
|
|
||
| ## User scope (`--global`) | ||
|
|
||
| Packages install to your home directory, making them available across all projects: | ||
|
|
||
| ```bash | ||
| apm install -g microsoft/apm-sample-package | ||
| ``` | ||
|
|
||
| - Manifest: `~/.apm/apm.yml` | ||
| - Modules: `~/.apm/apm_modules/` | ||
| - Lockfile: `~/.apm/apm.lock.yaml` | ||
|
|
||
| ### Per-target support | ||
|
|
||
| Currently, only **Claude Code** fully supports user-scope primitives. **Copilot CLI** and **VS Code** are partially supported -- the tools read from user-level directories, but APM's current integrators have limitations. APM deploys primitives relative to the home directory, but some AI tools either read from a different user-level path than what APM produces, or only support workspace-level configuration. | ||
|
|
||
| APM warns during `--global` installs about targets that lack native user-level support. | ||
|
|
||
| | Target | User-level directory | Status | Why | Reference | | ||
| |--------|---------------------|--------|-----|-----------| | ||
| | Claude Code | `~/.claude/` | Supported | APM deploys to `~/.claude/` which Claude reads for user-level commands, agents, skills, hooks | [Claude Code settings](https://docs.anthropic.com/en/docs/claude-code/settings) | | ||
| | Copilot (CLI) | `~/.copilot/` | Partially supported | Copilot CLI reads agents, skills, instructions from `~/.copilot/`; Copilot CLI does not support prompts | [Agents](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-custom-agents-for-cli), [Skills](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-skills), [Instructions](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-custom-instructions) | | ||
| | VS Code | User `mcp.json` | Partially supported | VS Code reads user-level MCP servers from user `mcp.json`; APM currently only writes to workspace `.vscode/mcp.json` | [VS Code MCP servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) | | ||
| | Cursor | `~/.cursor/` | Not supported | User rules are managed via Cursor Settings UI, not the filesystem | [Cursor rules docs](https://cursor.com/docs/rules) | | ||
| | OpenCode | `~/.opencode/` | Not supported | No official documentation for user-level config | No official docs available | | ||
|
|
||
| ### Uninstalling user-scope packages | ||
|
|
||
| ```bash | ||
| apm uninstall -g microsoft/apm-sample-package | ||
| ``` | ||
|
|
||
| ## When to use each scope | ||
|
|
||
| | Use case | Scope | | ||
| |----------|-------| | ||
| | Team-shared instructions and prompts | Project (`apm install`) | | ||
| | Personal Claude Code commands and agents | User (`apm install -g`) | | ||
| | CI/CD reproducible setup | Project | | ||
| | Cross-project coding standards (Claude Code) | User | | ||
|
Comment on lines
+58
to
+63
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -56,7 +56,7 @@ | |||||||
| # --------------------------------------------------------------------------- | ||||||||
|
|
||||||||
|
|
||||||||
| def _validate_and_add_packages_to_apm_yml(packages, dry_run=False, dev=False, logger=None): | ||||||||
| def _validate_and_add_packages_to_apm_yml(packages, dry_run=False, dev=False, logger=None, manifest_path=None): | ||||||||
| """Validate packages exist and can be accessed, then add to apm.yml dependencies section. | ||||||||
|
|
||||||||
| Implements normalize-on-write: any input form (HTTPS URL, SSH URL, FQDN, shorthand) | ||||||||
|
|
@@ -68,6 +68,7 @@ def _validate_and_add_packages_to_apm_yml(packages, dry_run=False, dev=False, lo | |||||||
| dry_run: If True, only show what would be added. | ||||||||
| dev: If True, write to devDependencies instead of dependencies. | ||||||||
| logger: InstallLogger for structured output. | ||||||||
| manifest_path: Explicit path to apm.yml (defaults to cwd/apm.yml). | ||||||||
|
|
||||||||
| Returns: | ||||||||
| Tuple of (validated_packages list, _ValidationOutcome). | ||||||||
|
|
@@ -76,7 +77,7 @@ def _validate_and_add_packages_to_apm_yml(packages, dry_run=False, dev=False, lo | |||||||
| import tempfile | ||||||||
| from pathlib import Path | ||||||||
|
|
||||||||
| apm_yml_path = Path(APM_YML_FILENAME) | ||||||||
| apm_yml_path = manifest_path or Path(APM_YML_FILENAME) | ||||||||
|
|
||||||||
| # Read current apm.yml | ||||||||
| try: | ||||||||
|
|
@@ -524,8 +525,14 @@ def _check_repo_fallback(token, git_env): | |||||||
| default=None, | ||||||||
| help="Force deployment to a specific target (overrides auto-detection)", | ||||||||
| ) | ||||||||
| @click.option( | ||||||||
| "--global", "-g", "global_", | ||||||||
| is_flag=True, | ||||||||
| default=False, | ||||||||
| help="Install to user scope (~/.apm/) instead of the current project", | ||||||||
| ) | ||||||||
| @click.pass_context | ||||||||
| def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target): | ||||||||
| def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, global_): | ||||||||
| """Install APM and MCP dependencies from apm.yml (like npm install). | ||||||||
|
|
||||||||
| This command automatically detects AI runtimes from your apm.yml scripts and installs | ||||||||
|
|
@@ -544,34 +551,59 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo | |||||||
| apm install --only=mcp # Install only MCP dependencies | ||||||||
| apm install --update # Update dependencies to latest Git refs | ||||||||
| apm install --dry-run # Show what would be installed | ||||||||
| apm install -g org/pkg1 # Install to user scope (~/.apm/) | ||||||||
| """ | ||||||||
| try: | ||||||||
| # Create structured logger for install output | ||||||||
| # Create structured logger for install output early so exception | ||||||||
| # handlers can always reference it (avoids UnboundLocalError if | ||||||||
| # scope initialisation below throws). | ||||||||
| is_partial = bool(packages) | ||||||||
| logger = InstallLogger(verbose=verbose, dry_run=dry_run, partial=is_partial) | ||||||||
|
|
||||||||
| # Resolve scope | ||||||||
| from ..core.scope import InstallScope, get_deploy_root, get_apm_dir, get_manifest_path, get_modules_dir, ensure_user_dirs, warn_unsupported_user_scope | ||||||||
|
||||||||
| from ..core.scope import InstallScope, get_deploy_root, get_apm_dir, get_manifest_path, get_modules_dir, ensure_user_dirs, warn_unsupported_user_scope | |
| from ..core.scope import InstallScope, get_apm_dir, get_manifest_path, get_modules_dir, ensure_user_dirs, warn_unsupported_user_scope |
Copilot
AI
Mar 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the new user-scope flow, several operations (importing scope helpers, ensure_user_dirs(), warn_unsupported_user_scope()) run before logger is instantiated, but the surrounding except Exception block later calls logger.error(...). If an exception is raised before logger is assigned, this will trigger an UnboundLocalError and mask the original failure. Create the InstallLogger before any scope initialization (or guard the exception handler to fall back to _rich_error when logger is not available).
Copilot
AI
Mar 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InstallScope is plumbed into _install_apm_dependencies() (scope=scope), but the later MCP installation path is not scope-aware (MCPIntegrator reads apm.yml from Path('apm.yml') and uses Path.cwd() for VS Code/Cursor/OpenCode detection and config writes). This makes --global installs inconsistent: APM deps/lockfile use ~/.apm, while MCP configuration may still read/write workspace files. Consider plumbing scope/manifest_path into the MCP install flow as well so global installs do not affect the current project directory.
Copilot
AI
Mar 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In user scope, _install_apm_dependencies() sets project_root = get_deploy_root(scope) (HOME). That same project_root is later used to resolve relative local package paths in _copy_local_package() (via (project_root / local).resolve()), so apm install -g ./relative/path will incorrectly resolve against ~ instead of the current working directory. Split the concepts (e.g., deploy_root = get_deploy_root(scope) for primitives, but keep cwd = Path.cwd() for resolving local paths) and pass the correct base into _copy_local_package() / other path resolution that should remain cwd-relative.
| project_root = get_deploy_root(scope) | |
| deploy_root = get_deploy_root(scope) | |
| project_root = Path.cwd() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The per-target support table is written with
||at the start of each row (including the header and separator). In Markdown this adds an extra empty column and often renders the table incorrectly. Use a single leading|for each row so the table renders as intended.