Skip to content
Merged
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Artifactory archive entry download for virtual file packages (#525)
- `apm view <package> [field]` command for viewing package metadata and remote refs (#613)
- `apm view <package> versions` field selector lists remote tags and branches via `git ls-remote` (#613)
- `apm outdated` command compares locked dependencies against remote refs (#613)
- `--parallel-checks` (`-j`) option on `apm outdated` for concurrent remote checks (default: 4) (#613)
- Rich progress feedback during `apm outdated` dependency checking (#613)
- `--global` flag on `apm view` for inspecting user-scope packages (#613)

### Changed

- Rename `apm info` to `apm view` for npm convention alignment; `apm info` kept as hidden alias (#613)
- Scope resolution now happens once via `TargetProfile.for_scope()` and `resolve_targets()` -- integrators no longer need scope-aware parameters (#562)
- Unified integration dispatch table in `dispatch.py` -- both install and uninstall import from one source of truth (#562)
- Hook merge logic deduplicated: three copy-pasted JSON-merge methods replaced with `_integrate_merged_hooks()` + config dict (#562)
- `apm outdated` uses SHA comparison for branch-pinned deps instead of reporting them as `unknown` (#613)

### Fixed

Expand Down
6 changes: 3 additions & 3 deletions docs/src/content/docs/guides/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ apm deps list
apm deps tree

# Get package details
apm deps info apm-sample-package
apm view apm-sample-package
```

### 4. Use Dependencies in Compilation
Expand Down Expand Up @@ -801,7 +801,7 @@ curl -H "Authorization: token $GITHUB_CLI_PAT" https://api.github.com/user

```bash
# Show detailed package information
apm deps info package-name
apm view package-name

# Show full dependency tree
apm deps tree
Expand Down Expand Up @@ -856,4 +856,4 @@ apm compile
- **[Context Guide](../../introduction/how-it-works/)** - Understanding the AI-Native Development framework
- **[Creating Packages](../../introduction/key-concepts/)** - Build your own APM packages

Ready to create your own APM packages? See the [Context Guide](../../introduction/key-concepts/) for detailed instructions on building reusable context collections and agent workflows.
Ready to create your own APM packages? See the [Context Guide](../../introduction/key-concepts/) for detailed instructions on building reusable context collections and agent workflows.
96 changes: 83 additions & 13 deletions docs/src/content/docs/reference/cli-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,81 @@ curl -sSL https://aka.ms/apm-unix | sh
powershell -ExecutionPolicy Bypass -c "irm https://aka.ms/apm-windows | iex"
```

### `apm view` - View package metadata or list remote versions

Show local metadata for an installed package, or query remote refs with a field selector.

> **Note:** `apm info` is accepted as a hidden alias for backward compatibility.

```bash
apm view PACKAGE [FIELD] [OPTIONS]
```

**Arguments:**
- `PACKAGE` - Package name, usually `owner/repo` or a short repo name
- `FIELD` - Optional field selector. Supported value: `versions`

**Options:**
- `-g, --global` - Inspect package from user scope (`~/.apm/`)

**Examples:**
```bash
# Show installed package metadata
apm view microsoft/apm-sample-package

# Short-name lookup for an installed package
apm view apm-sample-package

# List remote tags and branches without cloning
apm view microsoft/apm-sample-package versions

# Inspect a package from user scope
apm view microsoft/apm-sample-package -g
```

**Behavior:**
- Without `FIELD`, reads installed package metadata from `apm_modules/`
- Shows package name, version, description, source, install path, context files, workflows, and hooks
- `versions` lists remote tags and branches without cloning the repository
- `versions` does not require the package to be installed locally

### `apm outdated` - Check locked dependencies for updates

Compare locked dependencies against remote refs to detect staleness.

```bash
apm outdated [OPTIONS]
```

**Options:**
- `-g, --global` - Check user-scope dependencies from `~/.apm/`
- `-v, --verbose` - Show extra detail for outdated packages, including available tags
- `-j, --parallel-checks N` - Max concurrent remote checks (default: 4, 0 = sequential)

**Examples:**
```bash
# Check project dependencies
apm outdated

# Check user-scope dependencies
apm outdated --global

# Show available tags for outdated packages
apm outdated --verbose

# Use 8 parallel checks for large dependency sets
apm outdated -j 8
```

**Behavior:**
- Reads the current lockfile (`apm.lock.yaml`; legacy `apm.lock` is migrated automatically)
- For tag-pinned deps: compares the locked semver tag against the latest available remote tag
- For branch-pinned deps: compares the locked commit SHA against the remote branch tip SHA
- For deps with no ref: compares against the default branch (main/master) tip SHA
- Displays `Package`, `Current`, `Latest`, and `Status` columns
- Status values are `up-to-date`, `outdated`, and `unknown`
- Local dependencies and Artifactory dependencies are skipped

### `apm deps` - Manage APM package dependencies

Manage APM package dependencies with installation status, tree visualization, and package information.
Expand Down Expand Up @@ -680,32 +755,27 @@ company-website (local)
- Version numbers from dependency package metadata
- Version information for each dependency

#### `apm deps info` - Show detailed package information
#### `apm deps info` - Alias for `apm view`

Display comprehensive information about a specific installed package.
Backward-compatible alias for `apm view PACKAGE_NAME`.

```bash
apm deps info PACKAGE_NAME
```

**Arguments:**
- `PACKAGE_NAME` - Name of the package to show information about
- `PACKAGE_NAME` - Installed package name to inspect

**Examples:**
```bash
# Show details for compliance rules package
# Show installed package metadata
apm deps info compliance-rules

# Show info for design guidelines package
apm deps info design-guidelines
```

**Output includes:**
- Complete package metadata (name, version, description, author)
- Source repository and installation details
- Detailed context file counts by type
- Agent workflow descriptions and counts
- Installation path and status
**Notes:**
- Produces the same local metadata output as `apm view PACKAGE_NAME`
- Use `apm view` in new docs and scripts
- For remote refs, use `apm view PACKAGE_NAME versions`

#### `apm deps clean` - Remove all APM dependencies

Expand Down
4 changes: 3 additions & 1 deletion packages/apm-guide/.apm/skills/apm-usage/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
| `apm prune` | Remove orphaned packages | `--dry-run` |
| `apm deps list` | List installed packages | `-g` global, `--all` both scopes |
| `apm deps tree` | Show dependency tree | -- |
| `apm deps info PKG` | Show package details | -- |
| `apm view PKG [FIELD]` | View package details or remote refs | `-g` global, `FIELD=versions` |
| `apm outdated` | Check locked deps via SHA/semver comparison | `-g` global, `-v` verbose, `-j N` parallel checks |
| `apm deps info PKG` | Alias for `apm view PKG` local metadata | -- |
| `apm deps clean` | Clean dependency cache | `--dry-run`, `-y` skip confirm |
| `apm deps update [PKGS...]` | Update specific packages | `--verbose`, `--force`, `--target`, `--parallel-downloads N` |

Expand Down
12 changes: 12 additions & 0 deletions src/apm_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
from apm_cli.commands.compile import compile as compile_cmd
from apm_cli.commands.config import config
from apm_cli.commands.deps import deps
from apm_cli.commands.view import view as view_cmd
from apm_cli.commands.init import init
from apm_cli.commands.install import install
from apm_cli.commands.list_cmd import list as list_cmd
from apm_cli.commands.marketplace import marketplace, search as marketplace_search
from apm_cli.commands.mcp import mcp
from apm_cli.commands.outdated import outdated as outdated_cmd
from apm_cli.commands.pack import pack_cmd, unpack_cmd
from apm_cli.commands.prune import prune
from apm_cli.commands.run import preview, run
Expand Down Expand Up @@ -57,6 +59,15 @@ def cli(ctx):
# Register command groups
cli.add_command(audit)
cli.add_command(deps)
cli.add_command(view_cmd)
# Hidden backward-compatible alias: ``apm info`` → ``apm view``
cli.add_command(click.Command(
name="info",
callback=view_cmd.callback,
params=list(view_cmd.params),
help=view_cmd.help,
hidden=True,
))
cli.add_command(pack_cmd, name="pack")
cli.add_command(unpack_cmd, name="unpack")
cli.add_command(init)
Expand All @@ -71,6 +82,7 @@ def cli(ctx):
cli.add_command(config)
cli.add_command(runtime)
cli.add_command(mcp)
cli.add_command(outdated_cmd, name="outdated")
cli.add_command(marketplace)
cli.add_command(marketplace_search, name="search")

Expand Down
114 changes: 4 additions & 110 deletions src/apm_cli/commands/deps/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,8 @@ def update(packages, verbose, force, target, parallel_downloads, global_):
@click.argument('package', required=True)
def info(package: str):
"""Show detailed information about a specific package including context files and workflows."""
from ..view import resolve_package_path, display_package_info

logger = CommandLogger("deps-info")

project_root = Path(".")
Expand All @@ -672,113 +674,5 @@ def info(package: str):
logger.progress("Run 'apm install' to install dependencies first")
sys.exit(1)

# Find the package directory - handle org/repo and deep sub-path structures
package_path = None
# First try direct path match (handles any depth: org/repo, org/repo/subdir/pkg)
direct_match = apm_modules_path / package
if direct_match.is_dir() and (
(direct_match / APM_YML_FILENAME).exists() or (direct_match / SKILL_MD_FILENAME).exists()
):
package_path = direct_match
else:
# Fallback: scan org/repo structure (2-level) for short package names
for org_dir in apm_modules_path.iterdir():
if org_dir.is_dir() and not org_dir.name.startswith('.'):
for package_dir in org_dir.iterdir():
if package_dir.is_dir() and not package_dir.name.startswith('.'):
if package_dir.name == package or f"{org_dir.name}/{package_dir.name}" == package:
package_path = package_dir
break
if package_path:
break

if not package_path:
logger.error(f"Package '{package}' not found in apm_modules/")
logger.progress("Available packages:")

for org_dir in apm_modules_path.iterdir():
if org_dir.is_dir() and not org_dir.name.startswith('.'):
for package_dir in org_dir.iterdir():
if package_dir.is_dir() and not package_dir.name.startswith('.'):
click.echo(f" - {org_dir.name}/{package_dir.name}")
sys.exit(1)

try:
# Load package information
package_info = _get_detailed_package_info(package_path)

# Display with Rich panel if available
try:
from rich.panel import Panel
from rich.console import Console
from rich.text import Text
console = Console()

content_lines = []
content_lines.append(f"[bold]Name:[/bold] {package_info['name']}")
content_lines.append(f"[bold]Version:[/bold] {package_info['version']}")
content_lines.append(f"[bold]Description:[/bold] {package_info['description']}")
content_lines.append(f"[bold]Author:[/bold] {package_info['author']}")
content_lines.append(f"[bold]Source:[/bold] {package_info['source']}")
content_lines.append(f"[bold]Install Path:[/bold] {package_info['install_path']}")
content_lines.append("")
content_lines.append("[bold]Context Files:[/bold]")

for context_type, count in package_info['context_files'].items():
if count > 0:
content_lines.append(f" * {count} {context_type}")

if not any(count > 0 for count in package_info['context_files'].values()):
content_lines.append(" * No context files found")

content_lines.append("")
content_lines.append("[bold]Agent Workflows:[/bold]")
if package_info['workflows'] > 0:
content_lines.append(f" * {package_info['workflows']} executable workflows")
else:
content_lines.append(" * No agent workflows found")

if package_info.get('hooks', 0) > 0:
content_lines.append("")
content_lines.append("[bold]Hooks:[/bold]")
content_lines.append(f" * {package_info['hooks']} hook file(s)")

content = "\n".join(content_lines)
panel = Panel(content, title=f"[i] Package Info: {package}", border_style="cyan")
console.print(panel)

except ImportError:
# Fallback text display
click.echo(f"[i] Package Info: {package}")
click.echo("=" * 40)
click.echo(f"Name: {package_info['name']}")
click.echo(f"Version: {package_info['version']}")
click.echo(f"Description: {package_info['description']}")
click.echo(f"Author: {package_info['author']}")
click.echo(f"Source: {package_info['source']}")
click.echo(f"Install Path: {package_info['install_path']}")
click.echo("")
click.echo("Context Files:")

for context_type, count in package_info['context_files'].items():
if count > 0:
click.echo(f" * {count} {context_type}")

if not any(count > 0 for count in package_info['context_files'].values()):
click.echo(" * No context files found")

click.echo("")
click.echo("Agent Workflows:")
if package_info['workflows'] > 0:
click.echo(f" * {package_info['workflows']} executable workflows")
else:
click.echo(" * No agent workflows found")

if package_info.get('hooks', 0) > 0:
click.echo("")
click.echo("Hooks:")
click.echo(f" * {package_info['hooks']} hook file(s)")

except Exception as e:
logger.error(f"Error reading package information: {e}")
sys.exit(1)
package_path = resolve_package_path(package, apm_modules_path, logger)
display_package_info(package, package_path, logger)
Loading
Loading