Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `apm config set prefer-ssh true` / `apm config set allow-protocol-fallback true` persist transport preferences to `~/.apm/config.json` so SSH-only and corporate GHES users no longer need to re-pass `--ssh` / `--allow-protocol-fallback` (or export env vars in shell profiles) on every `apm install`. Resolution order: CLI flag > `APM_GIT_PROTOCOL` / `APM_ALLOW_PROTOCOL_FALLBACK` env var > `apm config` > built-in default. `apm config unset prefer-ssh` and `apm config unset allow-protocol-fallback` remove the persisted value. (#1243)
- `apm pack --marketplace=FORMATS` filters which marketplace formats are built in a single run; accepts comma-separated names and sentinels `all`/`none`. (#1317)
- `apm pack --marketplace-path FORMAT=PATH` overrides the output path for a specific marketplace format at invocation time. Env var overrides (`APM_MARKETPLACE_<FORMAT>_PATH`) are planned for v0.15. (#1317)
- `apm pack --json` emits a stable JSON contract to stdout (`{ok, dry_run, warnings, errors, marketplace: {outputs: [{format, path, ...}]}}`); all logs move to stderr so downstream tooling can `jq` the output safely. (#1317)
- `marketplace.outputs` in `apm.yml` now accepts a map form keyed by format name (`outputs: {claude: {}, codex: {path: ...}}`), replacing the deprecated list form; the list form still parses with a one-cycle deprecation warning. (#1317)
- `apm marketplace init` now scaffolds the explicit map-form `outputs: {claude: {}}` so the default state is observable in the manifest. (#1317)

### Fixed

- Copilot, Codex, Cursor, Claude, Windsurf, OpenCode, and Gemini adapters handle MCP v0.1 `runtimeArguments`/`packageArguments` with `variables` (no `type` key), matching the VS Code fix from #1444. (#1461, closes #1452, thanks @sergio-sisternes-epam)
Expand Down
10 changes: 10 additions & 0 deletions docs/src/content/docs/getting-started/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,16 @@ apm install --https
export APM_GIT_PROTOCOL=https
```

To persist the HTTPS preference across all future installs without re-exporting the variable:

```bash
apm config set prefer-ssh false # explicit: never prefer SSH for shorthand deps
# or, if you want APM to always try HTTPS for shorthand deps:
# export APM_GIT_PROTOCOL=https # process-scoped; add to shell profile for persistence
```

See [apm config](../../reference/cli/config/) for the full transport-preference config surface.

## Choosing transport (SSH vs HTTPS)

Authentication and transport are independent decisions:
Expand Down
32 changes: 29 additions & 3 deletions docs/src/content/docs/reference/cli/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ Write `KEY` to `~/.apm/config.json`. Validates the value before writing:

### `apm config unset KEY`

Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Only `temp-dir` and `copilot-cowork-skills-dir` are unsettable; boolean keys are reset by `set`-ing them to their default.
Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. All settable keys are unsettable: `temp-dir`, `copilot-cowork-skills-dir`, `prefer-ssh`, and `allow-protocol-fallback`. After unsetting a key the effective value falls back to the environment variable, then the built-in default.

## Configuration keys

| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `auto-integrate` | boolean | `true` | Auto-discover `.prompt.md` files under `.github/prompts/` and `.apm/prompts/` and merge them into compiled `AGENTS.md` output. |
| `temp-dir` | path | system temp | Directory used for clone and download operations. Useful when the OS temp directory is locked down (for example, corporate Windows endpoints rejecting `%TEMP%` with `[WinError 5]`). |
| `allow-protocol-fallback` | boolean | `false` | Enable the legacy cross-protocol fallback chain. When true, APM retries a failed clone with the opposite protocol (SSH→HTTPS or HTTPS→SSH). Equivalent to `--allow-protocol-fallback` or `APM_ALLOW_PROTOCOL_FALLBACK=1`. |
| `prefer-ssh` | boolean | `false` | Prefer SSH transport for shorthand (`owner/repo`) dependencies. Equivalent to `--ssh` or `APM_GIT_PROTOCOL=ssh`. |
| `copilot-cowork-skills-dir` | absolute path | auto-detected | Override the resolved Cowork OneDrive skills directory. Requires the `copilot-cowork` experimental flag for `set`. |

### Resolution order
Expand All @@ -63,6 +65,13 @@ Remove `KEY` from `~/.apm/config.json`. No-op if the key is not set. Only `temp-
2. Value in `~/.apm/config.json`
3. Built-in default (system temp / platform auto-detection)

`allow-protocol-fallback` and `prefer-ssh` follow the layered transport precedence:

1. CLI flag (`--allow-protocol-fallback`, `--ssh`) — highest priority
2. Environment variable (`APM_ALLOW_PROTOCOL_FALLBACK=1`, `APM_GIT_PROTOCOL=ssh`)
3. Value in `~/.apm/config.json` (`apm config set …`)
4. Built-in default (`false` / no preference)

## Examples

Show everything:
Expand All @@ -78,6 +87,22 @@ apm config get auto-integrate
apm config set auto-integrate false
```

Persist SSH transport preference (no more `--ssh` on every install):

```bash
apm config set prefer-ssh true
apm config get prefer-ssh
# Remove the persisted preference:
apm config unset prefer-ssh
```

Persist cross-protocol fallback (useful when migrating from SSH to HTTPS or vice versa):

```bash
apm config set allow-protocol-fallback true
apm config get allow-protocol-fallback
```

Pin a writable temp directory on Windows:

```bash
Expand Down Expand Up @@ -106,10 +131,11 @@ apm config unset copilot-cowork-skills-dir
- **Format:** JSON object, one entry per stored key.
- **Created on first read** with `{"default_client": "vscode"}`. Hand-editing is supported but `apm config set` is preferred -- it validates input and normalizes paths.

Internal JSON keys use snake_case (`auto_integrate`, `temp_dir`, `copilot_cowork_skills_dir`); CLI keys use kebab-case. The CLI translates between the two.
Internal JSON keys use snake_case (`auto_integrate`, `temp_dir`, `allow_protocol_fallback`, `prefer_ssh`, `copilot_cowork_skills_dir`); CLI keys use kebab-case. The CLI translates between the two.

## Related

- [`apm install`](../install/) -- consumes `temp-dir` for clone/download work.
- [`apm install`](../install/) -- consumes `temp-dir` for clone/download work and `allow-protocol-fallback` / `prefer-ssh` for transport selection.
- [`apm compile`](../compile/) -- affected by `auto-integrate`.
- [`apm experimental`](../experimental/) -- gates `copilot-cowork-skills-dir`.
- [Environment variables](../environment-variables/) -- `APM_ALLOW_PROTOCOL_FALLBACK`, `APM_GIT_PROTOCOL` are the env-var equivalents of the transport keys.
9 changes: 9 additions & 0 deletions docs/src/content/docs/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ PAT / bearer credentials APM reads when cloning packages, calling host APIs, or
| `GIT_SSH_COMMAND` | Standard git SSH command override. APM reads it before composing its own SSH env. | unset | If you set it, APM preserves your value. |
| `APM_GIT_CREDENTIAL_TIMEOUT` | Seconds to wait for a `git credential fill` response. | implementation default | Integer-like string; invalid values are ignored. |

## Transport and protocol

Controls how APM clones packages from Git hosts. These settings can also be persisted via [`apm config set`](./cli/config/) to avoid repeating flags or environment-variable exports.

| Variable | Purpose | Default | Notes |
|---|---|---|---|
| `APM_GIT_PROTOCOL` | Preferred clone protocol for shorthand (`owner/repo`) dependencies. Accepted values: `ssh`, `https`. | unset | Equivalent to `--ssh` / `--https` flag. Resolution: CLI flag → env var → `prefer-ssh` key in `~/.apm/config.json` → git `insteadOf` rules → HTTPS. |
| `APM_ALLOW_PROTOCOL_FALLBACK` | Set to `1` (or `true`/`yes`/`on`) to enable the legacy cross-protocol fallback chain. When enabled, a failed clone is retried with the opposite protocol. | unset | Equivalent to `--allow-protocol-fallback`. Resolution: CLI flag → env var → `allow-protocol-fallback` key in `~/.apm/config.json` → `false`. |
Comment thread
Aaryan-Dadu marked this conversation as resolved.
Comment on lines +36 to +41

## Registry (MCP and proxy)

| Variable | Purpose | Default | Notes |
Expand Down
79 changes: 70 additions & 9 deletions src/apm_cli/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"auto_integrate": "auto-integrate",
"temp_dir": "temp-dir",
"copilot_cowork_skills_dir": "copilot-cowork-skills-dir",
"allow_protocol_fallback": "allow-protocol-fallback",
"prefer_ssh": "prefer-ssh",
}


Expand All @@ -35,27 +37,31 @@ def _parse_bool_value(value: str) -> bool:

def _get_config_setters():
"""Return config setters keyed by CLI option name."""
from ..config import set_auto_integrate
from ..config import set_allow_protocol_fallback, set_auto_integrate, set_prefer_ssh

return {
"auto-integrate": (set_auto_integrate, "Auto-integration"),
"allow-protocol-fallback": (set_allow_protocol_fallback, "Protocol fallback"),
"prefer-ssh": (set_prefer_ssh, "SSH transport preference"),
}


def _get_config_getters():
"""Return config getters keyed by CLI option name."""
from ..config import get_auto_integrate
from ..config import get_allow_protocol_fallback, get_auto_integrate, get_prefer_ssh

return {
"auto-integrate": get_auto_integrate,
"allow-protocol-fallback": get_allow_protocol_fallback,
"prefer-ssh": get_prefer_ssh,
}


def _valid_config_keys() -> str:
"""Return valid config keys for messages."""
from ..core.experimental import is_enabled

keys = ["auto-integrate", "temp-dir"]
keys = ["auto-integrate", "temp-dir", "allow-protocol-fallback", "prefer-ssh"]
if is_enabled("copilot_cowork"):
keys.append("copilot-cowork-skills-dir")
return ", ".join(keys)
Comment thread
Aaryan-Dadu marked this conversation as resolved.
Comment on lines 62 to 73
Expand Down Expand Up @@ -120,12 +126,23 @@ def config(ctx):

config_table.add_row("Global", "APM CLI Version", get_version())

from ..config import get_allow_protocol_fallback as _get_apf
from ..config import get_prefer_ssh as _get_prefer_ssh_cfg
from ..config import get_temp_dir as _get_temp_dir

_temp_dir_val = _get_temp_dir()
if _temp_dir_val:
config_table.add_row("", "Temp Directory", _temp_dir_val)

# Only surface transport keys when they have been enabled -- the
# false-default rows add noise for users who never configured them.
_apf = _get_apf()
_prefer_ssh = _get_prefer_ssh_cfg()
if _apf:
config_table.add_row("", "Allow Protocol Fallback", "true")
if _prefer_ssh:
config_table.add_row("", "Prefer SSH Transport", "true")

from ..core.experimental import is_enabled as _is_enabled

if _is_enabled("copilot_cowork"):
Expand Down Expand Up @@ -159,12 +176,17 @@ def config(ctx):
click.echo(f"\n{HIGHLIGHT}Global:{RESET}")
click.echo(f" APM CLI Version: {get_version()}")

from ..config import get_allow_protocol_fallback as _get_apf_fb
from ..config import get_prefer_ssh as _get_prefer_ssh_fb
from ..config import get_temp_dir as _get_temp_dir_fb

_temp_dir_fb = _get_temp_dir_fb()
if _temp_dir_fb:
click.echo(f" Temp Directory: {_temp_dir_fb}")

click.echo(f" allow-protocol-fallback: {str(_get_apf_fb()).lower()}")
click.echo(f" prefer-ssh: {str(_get_prefer_ssh_fb()).lower()}")

from ..core.experimental import is_enabled as _is_enabled_fb

if _is_enabled_fb("copilot_cowork"):
Expand Down Expand Up @@ -236,10 +258,20 @@ def set(key, value): # noqa: F811

setter, label = config_entry
setter(enabled)
if enabled:
logger.success(f"{label} enabled")
else:
logger.success(f"{label} disabled")
logger.success(f"{label} set to {'true' if enabled else 'false'}")

# Warn when persisting allow-protocol-fallback=true in a CI environment where
# $HOME is often shared across jobs -- the persisted value will affect all
# subsequent apm install runs on that host. The env var is safer for CI.
import os as _os

if key == "allow-protocol-fallback" and enabled and _os.environ.get("CI"):
logger.warning(
"allow-protocol-fallback is now persisted to ~/.apm/config.json. "
"In CI environments with a shared $HOME this will affect all subsequent "
"apm install runs on this host. "
"Prefer APM_ALLOW_PROTOCOL_FALLBACK=1 as an invocation-scoped alternative."
)


@config.command(help="Get a configuration value")
Expand Down Expand Up @@ -283,17 +315,30 @@ def get(key):
)
sys.exit(1)
value = getter()
click.echo(f"{key}: {value}")
# Render booleans as lowercase true/false (npm convention).
if isinstance(value, bool):
click.echo(f"{key}: {str(value).lower()}")
else:
click.echo(f"{key}: {value}")
else:
# Show all user-settable keys with their effective values (including
# defaults). Iterating raw config keys would hide settings that
# have not been written yet (e.g. auto_integrate on a fresh install).
from ..config import get_allow_protocol_fallback, get_prefer_ssh

logger.progress("APM Configuration:")
click.echo(f" auto-integrate: {get_auto_integrate()}")
click.echo(f" auto-integrate: {str(get_auto_integrate()).lower()}")
temp_dir = get_temp_dir()
click.echo(
f" temp-dir: {temp_dir if temp_dir is not None else 'Not set (using system default)'}"
)
# Only show transport keys when non-default to reduce noise.
_apf_val = get_allow_protocol_fallback()
_ssh_val = get_prefer_ssh()
if _apf_val:
click.echo(" allow-protocol-fallback: true")
if _ssh_val:
click.echo(" prefer-ssh: true")

from ..core.experimental import is_enabled as _is_enabled_get

Expand All @@ -314,6 +359,8 @@ def unset(key):

Examples:
apm config unset temp-dir
apm config unset allow-protocol-fallback
apm config unset prefer-ssh
apm config unset copilot-cowork-skills-dir
"""
logger = CommandLogger("config unset")
Expand All @@ -325,6 +372,20 @@ def unset(key):
logger.success("Temporary directory configuration removed")
return

if key == "allow-protocol-fallback":
from ..config import unset_allow_protocol_fallback

unset_allow_protocol_fallback()
logger.success("Protocol fallback preference removed (will use env var or default)")
return

if key == "prefer-ssh":
from ..config import unset_prefer_ssh

unset_prefer_ssh()
logger.success("SSH transport preference removed (will use env var or default)")
return

if key == "copilot-cowork-skills-dir":
from ..config import unset_copilot_cowork_skills_dir

Expand Down
15 changes: 10 additions & 5 deletions src/apm_cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -1364,8 +1364,6 @@ def install( # noqa: PLR0913
# Resolve transport selection inputs.
from ..deps.transport_selection import (
ProtocolPreference,
is_fallback_allowed,
protocol_pref_from_env,
)

if use_ssh and use_https:
Expand All @@ -1376,9 +1374,16 @@ def install( # noqa: PLR0913
elif use_https:
protocol_pref = ProtocolPreference.HTTPS
else:
protocol_pref = protocol_pref_from_env()
# CLI flag OR env var enables fallback.
allow_protocol_fallback = allow_protocol_fallback or is_fallback_allowed()
# Precedence: APM_GIT_PROTOCOL env var > apm config ssh > git insteadOf
from ..config import get_apm_protocol_pref as _get_apm_protocol_pref

_pref_str = _get_apm_protocol_pref()
protocol_pref = ProtocolPreference.from_str(_pref_str)
# CLI flag > env var (APM_ALLOW_PROTOCOL_FALLBACK) > apm config > default.
# get_apm_allow_protocol_fallback() already encodes env > config > False.
from ..config import get_apm_allow_protocol_fallback as _get_apm_apf

allow_protocol_fallback = allow_protocol_fallback or _get_apm_apf()

# Resolve scope
from ..core.scope import (
Expand Down
Loading
Loading