agentnet-cli 0.2.3__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/.github/workflows/ci.yml +0 -0
  2. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/.github/workflows/publish.yml +0 -0
  3. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/.gitignore +0 -0
  4. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/ARCHITECTURE.md +0 -0
  5. agentnet_cli-0.3.0/CHANGELOG.md +9 -0
  6. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/CLAUDE.md +2 -1
  7. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/PKG-INFO +6 -14
  8. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/README.md +5 -13
  9. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/plans/2026-05-09-hermes-native-plugin.md +0 -0
  10. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/plans/2026-05-09-marketplace-cli-and-skill.md +0 -0
  11. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/plans/2026-05-10-claude-native-plugin.md +0 -0
  12. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/plans/2026-05-10-openclaw-native-plugin.md +0 -0
  13. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/specs/2026-05-09-hermes-native-plugin-design.md +0 -0
  14. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/specs/2026-05-09-marketplace-cli-and-skill-design.md +0 -0
  15. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/specs/2026-05-10-claude-native-plugin-design.md +0 -0
  16. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/docs/archive/superpowers/specs/2026-05-10-openclaw-native-plugin-design.md +0 -0
  17. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/pyproject.toml +1 -1
  18. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/__init__.py +0 -0
  19. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/__init__.py +0 -0
  20. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/__init__.py +0 -0
  21. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/connect.py +0 -0
  22. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/detect.py +0 -0
  23. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/disconnect.py +0 -0
  24. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/register.py +0 -0
  25. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/setup_wizard.py +19 -0
  26. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/status.py +0 -0
  27. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/core/updater.py +0 -0
  28. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/main.py +53 -4
  29. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/cli/marketplace/__init__.py +0 -0
  30. agentnet_cli-0.3.0/src/agentnet_cli/cli/marketplace/agent.py +23 -0
  31. agentnet_cli-0.3.0/src/agentnet_cli/cli/marketplace/discover.py +19 -0
  32. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/README.md +0 -0
  33. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/__init__.py +0 -0
  34. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/base.py +0 -0
  35. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/claude.py +50 -20
  36. agentnet_cli-0.3.0/src/agentnet_cli/connectors/claude_search_hook.py +150 -0
  37. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/codex.py +0 -0
  38. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/copilot.py +0 -0
  39. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/cursor.py +0 -0
  40. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/hermes.py +0 -0
  41. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/openclaw.py +0 -0
  42. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/registry.py +0 -0
  43. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/shims.py +0 -0
  44. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/templates/README.md +0 -0
  45. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/templates/SKILL.hosted.md +26 -37
  46. agentnet_cli-0.3.0/src/agentnet_cli/connectors/templates/codex/skill.md +10 -0
  47. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/templates/copilot/agentnet.agent.md +0 -0
  48. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/templates/cursor/agent.md +0 -0
  49. agentnet_cli-0.3.0/src/agentnet_cli/connectors/templates/cursor/agentnet.mdc +6 -0
  50. agentnet_cli-0.3.0/src/agentnet_cli/connectors/templates/shared/context.md +48 -0
  51. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/templates/shared/default-chat.md +0 -0
  52. agentnet_cli-0.3.0/src/agentnet_cli/connectors/templates/vscode/instructions.md +1 -0
  53. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/connectors/vscode.py +0 -0
  54. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/infra/__init__.py +0 -0
  55. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/infra/config.py +0 -0
  56. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/infra/manifest.py +0 -0
  57. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/infra/package_paths.py +0 -0
  58. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/infra/paths.py +0 -0
  59. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/infra/platform.py +0 -0
  60. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/claude/.claude-plugin/marketplace.json +0 -0
  61. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/claude/plugin/.claude-plugin/plugin.json +0 -0
  62. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/claude/plugin/.mcp.json +0 -0
  63. agentnet_cli-0.3.0/src/agentnet_cli/integrations/claude/plugin/agents/marketplace.md +23 -0
  64. agentnet_cli-0.3.0/src/agentnet_cli/integrations/claude/plugin/hooks/hooks.json +49 -0
  65. agentnet_cli-0.3.0/src/agentnet_cli/integrations/claude/plugin/skills/agentnet/SKILL.md +33 -0
  66. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/openclaw/.mcp.json +0 -0
  67. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/openclaw/index.js +0 -0
  68. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/openclaw/openclaw.plugin.json +1 -1
  69. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/integrations/openclaw/package.json +1 -1
  70. agentnet_cli-0.3.0/src/agentnet_cli/integrations/openclaw/skills/agentnet/SKILL.md +32 -0
  71. agentnet_cli-0.3.0/src/agentnet_cli/integrations/shared/discovery-skill.base.md +48 -0
  72. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/__init__.py +0 -0
  73. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/auth.py +0 -0
  74. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/catalogs/__init__.py +0 -0
  75. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/catalogs/claude_marketplace.py +0 -0
  76. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/catalogs/clawhub.py +0 -0
  77. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/client.py +37 -12
  78. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/skills/__init__.py +0 -0
  79. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/skills/client.py +0 -0
  80. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/skills/discovery.py +0 -0
  81. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/marketplace/skills/skillsmp.py +0 -0
  82. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/__init__.py +0 -0
  83. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/handlers.py +0 -12
  84. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/hermes/__init__.py +0 -0
  85. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/hermes/handlers.py +0 -0
  86. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/hermes/plugin.yaml +1 -1
  87. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/hermes/schemas.py +0 -0
  88. agentnet_cli-0.3.0/src/agentnet_cli/tools/hermes/skills/agentnet/SKILL.md +39 -0
  89. agentnet_cli-0.3.0/src/agentnet_cli/tools/hook.py +674 -0
  90. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/src/agentnet_cli/tools/mcp_server.py +0 -0
  91. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/__init__.py +0 -0
  92. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/conftest.py +0 -0
  93. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_agent_cmd.py +26 -0
  94. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_claude.py +28 -2
  95. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_claude_marketplace.py +0 -0
  96. agentnet_cli-0.3.0/tests/test_claude_search_hook.py +142 -0
  97. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_clawhub_client.py +0 -0
  98. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_cli.py +0 -0
  99. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_codex.py +0 -0
  100. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_config.py +0 -0
  101. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_copilot.py +0 -0
  102. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_cursor.py +0 -0
  103. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_detect.py +0 -0
  104. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_discover_cmd.py +22 -40
  105. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_discovery.py +0 -0
  106. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_discovery_skill.py +1 -2
  107. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_e2e.py +0 -0
  108. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_hermes.py +0 -0
  109. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_hermes_plugin.py +0 -0
  110. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_manifest.py +0 -0
  111. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_marketplace.py +0 -0
  112. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_mcp_tools.py +0 -65
  113. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_openclaw.py +0 -0
  114. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_paths.py +0 -0
  115. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_platform_client.py +78 -30
  116. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_platform_config.py +0 -0
  117. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_register.py +0 -0
  118. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_server.py +0 -2
  119. agentnet_cli-0.3.0/tests/test_skill_hook.py +583 -0
  120. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_skills_client.py +0 -0
  121. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_skillsmp_client.py +0 -0
  122. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_updater.py +0 -0
  123. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/tests/test_vscode.py +0 -0
  124. {agentnet_cli-0.2.3 → agentnet_cli-0.3.0}/uv.lock +1 -1
  125. agentnet_cli-0.2.3/src/agentnet_cli/cli/marketplace/agent.py +0 -18
  126. agentnet_cli-0.2.3/src/agentnet_cli/cli/marketplace/discover.py +0 -34
  127. agentnet_cli-0.2.3/src/agentnet_cli/cli/marketplace/search.py +0 -122
  128. agentnet_cli-0.2.3/src/agentnet_cli/connectors/templates/codex/skill.md +0 -12
  129. agentnet_cli-0.2.3/src/agentnet_cli/connectors/templates/cursor/agentnet.mdc +0 -8
  130. agentnet_cli-0.2.3/src/agentnet_cli/connectors/templates/shared/context.md +0 -82
  131. agentnet_cli-0.2.3/src/agentnet_cli/connectors/templates/vscode/instructions.md +0 -5
  132. agentnet_cli-0.2.3/src/agentnet_cli/integrations/claude/plugin/agents/marketplace.md +0 -23
  133. agentnet_cli-0.2.3/src/agentnet_cli/integrations/claude/plugin/hooks/hooks.json +0 -22
  134. agentnet_cli-0.2.3/src/agentnet_cli/integrations/claude/plugin/skills/agentnet/SKILL.md +0 -35
  135. agentnet_cli-0.2.3/src/agentnet_cli/integrations/openclaw/skills/agentnet/SKILL.md +0 -32
  136. agentnet_cli-0.2.3/src/agentnet_cli/integrations/shared/discovery-skill.base.md +0 -82
  137. agentnet_cli-0.2.3/src/agentnet_cli/tools/hermes/skills/agentnet/SKILL.md +0 -45
  138. agentnet_cli-0.2.3/tests/test_search_cmd.py +0 -76
File without changes
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 - 2026-07-18
4
+
5
+ - Add Claude Code search-fire hook support so AgentNet can surface relevant marketplace skills during search workflows.
6
+ - Add `agentnet enable-search-fire` for one-command Claude hook installation.
7
+ - Preserve malformed Claude settings files instead of overwriting them during connector setup.
8
+ - Add setup telemetry hooks and a platform client telemetry helper.
9
+ - Bump bundled Hermes, hosted skill, and OpenClaw integration metadata to match the CLI release.
@@ -8,7 +8,7 @@ CLI tool that detects AI coding agents on your system and connects them to the [
8
8
  - **Package manager:** uv
9
9
  - **CLI framework:** Typer + Rich
10
10
  - **HTTP client:** httpx
11
- - **Testing:** pytest (354 tests), pytest-cov
11
+ - **Testing:** pytest (406 tests), pytest-cov
12
12
  - **CI:** GitHub Actions (lint + test matrix on 3.11/3.12/3.13)
13
13
  - **Publish:** PyPI via trusted publisher (tag `v*`)
14
14
 
@@ -44,6 +44,7 @@ uv run agentnet --help # Run locally
44
44
  - **Hermes Plugin:** `agentnet connect hermes` copies the plugin to `~/.hermes/plugins/agentnet/` and skills to `~/.hermes/skills/agentnet/`, using Hermes's native plugin system.
45
45
  - **OpenClaw Plugin:** `agentnet connect openclaw` delegates to `openclaw plugins install` + `openclaw plugins uninstall` instead of writing files directly. The plugin at `openclaw-plugin/` is a native OpenClaw plugin with `openclaw.plugin.json` manifest, publishable to ClawHub.
46
46
  - **Plugin hint:** The CLI emits a `<claude-code-hint>` tag on stderr when `CLAUDECODE=1` is set, prompting Claude Code users to install the plugin.
47
+ - **Every-prompt skill hook (Claude Code):** `agentnet enable-skill-fire` (and `connect claude`) install three hooks in `~/.claude/settings.json` (also bundled in the plugin's `hooks/hooks.json`). `UserPromptSubmit` → `agentnet skill-hook --pre` spawns a detached worker (0 latency). The worker discovers **installable skill candidates** (`discover_skills` → skills.sh, each carrying a `<repo>@<slug>`) + runs a cheap no-tool `claude -p` **classifier** (Haiku) as the reliable relevance **gate** (a bare yes/no gate isn't — the model just answers). Two-phase so the outcome reaches the hooks fast: **Phase 1** caches the **recommendation list** (relevant skills by name + why + skills.sh link) the moment the gate opens (~12s); **Phase 2** *appends* the top match fetched via `npx skills use <repo>@<slug>` (which downloads the skill — SKILL.md + references — to a temp dir; single-file skills are materialized to a temp `SKILL.md`) as a **concise header (name + description) + the on-disk path**, not a full SKILL.md dump. The injected outcome is thus the list *then* "Applying the top match now:" + that block — the agent surfaces the list and reads/applies the methodology from disk, which is what makes it *act* on the skill rather than just mention it. No files are written to the repo. If content is unavailable (no `npx`/all fetches miss), Phase 2 falls back — behind the same open gate — to the live **Skills Agent** over **brokered A2A**: `PlatformClient.use_agent(agent_id="agentnet-skills-agent", task=…)` with the user's `setup` identity; the platform relays A2A and returns `{status:"settled", agent_response}` in one call. **No skills-agent token is ever held client-side.** Only a `settled` response is used. Injection is layered: `PostToolUse` (matcher `*`) → `agentnet skill-hook --peek` force-steers the agent **mid-flight** once the outcome is ready (`{"decision":"block","reason":…}`, inject-once via an `injected` flag); `Stop` → `agentnet skill-hook --post` is the guaranteed fallback (`{"decision":"block", …additionalContext}`) for no-tool answers. Because the hooks are registered in **both** `settings.json` (via `connect`/`enable-skill-fire`) and the plugin's `hooks.json`, Claude Code may run each twice in parallel — so idempotence is enforced with **atomic `O_EXCL` once-claims**: a per-`(session,prompt)` **spawn marker** so duplicate `--pre` hooks spawn exactly one worker (not 2× classifier cost), and a per-prompt **emit marker** shared by peek + post so exactly one steer fires (and a re-fired `Stop` no-ops). Session-keyed JSON cache `$TMPDIR/agentnet-skill/<session>.json` (`{outcome}`) + sibling `.emitted`/`.<hash>.spawn` markers; `AGENTNET_SKILL_SUBAGENT=1` guards recursion. All best-effort — no token/binary/candidates/unreachable-platform/timeout injects nothing. `agent_id` overridable via `AGENTNET_SKILLS_AGENT_ID`/config. Implemented in `tools/hook.py` + `connectors/claude_search_hook.py`.
47
48
 
48
49
  ## Testing Patterns
49
50
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnet-cli
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Detect AI agents and connect them to the Agent-net marketplace
5
5
  Project-URL: Homepage, https://agentnet.market
6
6
  Project-URL: Repository, https://github.com/TheAgent-net/agentnet-cli
@@ -65,6 +65,9 @@ Requires Python 3.11+.
65
65
  # Install from PyPI
66
66
  pip install agentnet-cli
67
67
 
68
+ # Install this release exactly
69
+ pip install agentnet-cli==0.3.0
70
+
68
71
  # Or run without installing
69
72
  uvx agentnet
70
73
 
@@ -151,19 +154,8 @@ All marketplace commands output JSON to stdout. Errors output `{"error": "..."}`
151
154
 
152
155
  | Command | Description |
153
156
  |---------|-------------|
154
- | `agentnet discover <query>` | Search the marketplace by capability |
155
- | `agentnet agents <query>` | Search for agents by name or capability |
156
- | `agentnet agent <id>` | Get full details about an agent |
157
-
158
- ### Unified Search (JSON output)
159
-
160
- | Command | Description |
161
- |---------|-------------|
162
- | `agentnet search "<query>"` | Search listings, agents, skills, and plugins |
163
- | `agentnet search "<query>" --type listings` | Search marketplace listings |
164
- | `agentnet search "<query>" --type agents` | Search AI agents |
165
- | `agentnet search "<query>" --type skills` | Discover ranked skills/plugins |
166
- | `agentnet search "<query>" --type plugins` | Search plugin sources |
157
+ | `agentnet discover <query>` | Discover agents and community skills by capability |
158
+ | `agentnet agent <id>` | Get full details about an agent, or the full content of a skill (`agent skill:<id>`) |
167
159
 
168
160
  ### MCP Server (internal)
169
161
 
@@ -38,6 +38,9 @@ Requires Python 3.11+.
38
38
  # Install from PyPI
39
39
  pip install agentnet-cli
40
40
 
41
+ # Install this release exactly
42
+ pip install agentnet-cli==0.3.0
43
+
41
44
  # Or run without installing
42
45
  uvx agentnet
43
46
 
@@ -124,19 +127,8 @@ All marketplace commands output JSON to stdout. Errors output `{"error": "..."}`
124
127
 
125
128
  | Command | Description |
126
129
  |---------|-------------|
127
- | `agentnet discover <query>` | Search the marketplace by capability |
128
- | `agentnet agents <query>` | Search for agents by name or capability |
129
- | `agentnet agent <id>` | Get full details about an agent |
130
-
131
- ### Unified Search (JSON output)
132
-
133
- | Command | Description |
134
- |---------|-------------|
135
- | `agentnet search "<query>"` | Search listings, agents, skills, and plugins |
136
- | `agentnet search "<query>" --type listings` | Search marketplace listings |
137
- | `agentnet search "<query>" --type agents` | Search AI agents |
138
- | `agentnet search "<query>" --type skills` | Discover ranked skills/plugins |
139
- | `agentnet search "<query>" --type plugins` | Search plugin sources |
130
+ | `agentnet discover <query>` | Discover agents and community skills by capability |
131
+ | `agentnet agent <id>` | Get full details about an agent, or the full content of a skill (`agent skill:<id>`) |
140
132
 
141
133
  ### MCP Server (internal)
142
134
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentnet-cli"
3
- version = "0.2.3"
3
+ version = "0.3.0"
4
4
  description = "Detect AI agents and connect them to the Agent-net marketplace"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -11,6 +11,8 @@ from rich.console import Console
11
11
  from rich.table import Table
12
12
 
13
13
  from ...infra.config import load_config
14
+ from ...infra.platform import resolve_platform_url
15
+ from ...marketplace.client import PlatformClient
14
16
  from .connect import connect_command
15
17
  from .detect import detect_all
16
18
  from ...infra.paths import AgentName, agent_display_name
@@ -19,7 +21,23 @@ from .register import default_agent_name, register_command
19
21
  console = Console()
20
22
 
21
23
 
24
+ def _send_telemetry(event_type: str, **metadata: str) -> None:
25
+ """Best-effort telemetry to platform; never blocks setup."""
26
+ try:
27
+ import httpx
28
+
29
+ config = load_config() or {}
30
+ base = resolve_platform_url(config=config)
31
+ token = config.get("api_token", "")
32
+ http_client = httpx.Client(timeout=5.0)
33
+ with PlatformClient(base_url=base, api_token=token, http_client=http_client) as client:
34
+ client.send_telemetry(event_type=event_type, metadata=metadata or None)
35
+ except Exception:
36
+ pass
37
+
38
+
22
39
  def setup_command(platform_url: str | None = None, *, choose: bool = False) -> None:
40
+ _send_telemetry("cli_setup")
23
41
  config = load_config()
24
42
  if not config or not config.get("api_token"):
25
43
  console.print()
@@ -62,6 +80,7 @@ def setup_command(platform_url: str | None = None, *, choose: bool = False) -> N
62
80
  f" [bold]Step 3:[/bold] Configuring all detected agents ({len(targets)})"
63
81
  )
64
82
  connect_command(connect_all=True)
83
+ _send_telemetry("cli_setup_complete", connectors=",".join(targets))
65
84
 
66
85
 
67
86
  def _available_targets(results) -> list[str]:
@@ -301,13 +301,62 @@ def mcp_serve() -> None:
301
301
  serve()
302
302
 
303
303
 
304
+ @app.command(name="skill-hook", hidden=True)
305
+ def skill_hook(
306
+ pre: bool = typer.Option(False, "--pre", help="UserPromptSubmit: spawn the discovery worker"),
307
+ peek: bool = typer.Option(False, "--peek", help="PostToolUse: steer the agent mid-flight"),
308
+ post: bool = typer.Option(False, "--post", help="Stop: fold in relevant AgentNet skills"),
309
+ fetch: bool = typer.Option(False, "--fetch", help="Detached worker: discover + cache (internal)"),
310
+ session: str = typer.Option("", "--session", help="Session id (worker)"),
311
+ query: str = typer.Option("", "--query", help="Prompt text (worker)"),
312
+ limit: int = typer.Option(5, "--limit", help="Max skills to suggest"),
313
+ hook_timeout: float = typer.Option(
314
+ 3.0, "--timeout", help="Max seconds a hook waits for the subagent's result",
315
+ ),
316
+ ) -> None:
317
+ """Claude Code every-prompt hooks — surface relevant AgentNet skills (internal).
318
+
319
+ ``--pre`` (UserPromptSubmit) spawns a detached skill-scout worker; ``--peek`` (PostToolUse)
320
+ steers the agent mid-flight once the outcome is ready; ``--post`` (Stop) is the guaranteed
321
+ fallback. Best-effort: nothing/exit 0 on error.
322
+ """
323
+ from ..tools.hook import run_fetch, run_peek, run_post, run_pre
324
+
325
+ if fetch:
326
+ run_fetch(session=session, query=query, limit=limit, timeout=hook_timeout)
327
+ elif pre:
328
+ run_pre(limit=limit, timeout=hook_timeout)
329
+ elif peek:
330
+ run_peek(limit=limit, timeout=hook_timeout)
331
+ else: # default and --post
332
+ run_post(limit=limit, timeout=hook_timeout)
333
+
334
+
335
+ @app.command(name="enable-skill-fire")
336
+ def enable_skill_fire(
337
+ remove: bool = typer.Option(False, "--remove", help="Remove the hook instead of installing"),
338
+ ) -> None:
339
+ """Fire AgentNet on every Claude Code prompt (writes ~/.claude/settings.json)."""
340
+ from ..connectors.claude_search_hook import SettingsHookError, install, uninstall
341
+
342
+ try:
343
+ changed, path = uninstall() if remove else install()
344
+ except SettingsHookError as exc:
345
+ console.print(f"[red]✗[/red] {exc}")
346
+ raise typer.Exit(1) from exc
347
+ action = "removed" if remove else "installed"
348
+ if changed:
349
+ console.print(f"[green]✓[/green] AgentNet skill hook {action} in [bold]{path}[/bold]")
350
+ if not remove:
351
+ console.print(" [dim]Restart Claude Code — every prompt now fires AgentNet.[/dim]")
352
+ else:
353
+ state = "not present" if remove else "already installed"
354
+ console.print(f"[dim]No change ({state}): {path}[/dim]")
355
+
356
+
304
357
  # -- Marketplace commands --
305
358
  from .marketplace.agent import agent as _agent_fn # noqa: E402
306
- from .marketplace.discover import agents as _agents_fn # noqa: E402
307
359
  from .marketplace.discover import discover as _discover_fn # noqa: E402
308
- from .marketplace.search import search as _search_fn # noqa: E402
309
360
 
310
- app.command(name="search")(_search_fn)
311
361
  app.command(name="discover")(_discover_fn)
312
- app.command(name="agents")(_agents_fn)
313
362
  app.command(name="agent")(_agent_fn)
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from ...marketplace.auth import die, get_client, output
6
+ from ...marketplace.client import PlatformError
7
+
8
+
9
+ def agent(
10
+ agent_id: str = typer.Argument(
11
+ help="Agent or skill ID from discover results (skill IDs are prefixed skill:)",
12
+ ),
13
+ ) -> None:
14
+ """Get full details about an agent or a community skill's full content."""
15
+ client = get_client()
16
+ try:
17
+ if agent_id.startswith("skill:"):
18
+ result = client.get_skill(skill_id=agent_id)
19
+ else:
20
+ result = client.get_agent(agent_id=agent_id)
21
+ output(result)
22
+ except PlatformError as e:
23
+ die(str(e))
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from ...marketplace.auth import die, get_client, output
6
+ from ...marketplace.client import PlatformError
7
+
8
+
9
+ def discover(
10
+ query: str = typer.Argument(help="What you need — an agent capability or a skill"),
11
+ limit: int = typer.Option(20, "--limit", "-l", help="Max results"),
12
+ ) -> None:
13
+ """Discover agents and community skills matching a query."""
14
+ client = get_client()
15
+ try:
16
+ result = client.discover_agents(query=query, limit=limit)
17
+ output(result)
18
+ except PlatformError as e:
19
+ die(str(e))
@@ -45,34 +45,64 @@ class ClaudeConnector(AgentConnector):
45
45
  errors=["Claude Code not found. Install it from https://code.claude.com"],
46
46
  )
47
47
 
48
- marketplace_src = _marketplace_source()
49
-
50
- proc = subprocess.run(
51
- ["claude", "plugin", "marketplace", "add", marketplace_src, "--scope", "user"],
52
- capture_output=True,
53
- timeout=_SUBPROCESS_TIMEOUT,
54
- )
55
- if proc.returncode != 0:
56
- msg = proc.stderr.decode(errors="replace").strip()
57
- return ConnectionResult(success=False, errors=[f"marketplace add failed: {msg}"])
58
-
59
- proc = subprocess.run(
60
- ["claude", "plugin", "install", _PLUGIN_ID, "--scope", "user"],
61
- capture_output=True,
62
- timeout=_SUBPROCESS_TIMEOUT,
63
- )
64
- if proc.returncode != 0:
65
- msg = proc.stderr.decode(errors="replace").strip()
66
- return ConnectionResult(success=False, errors=[f"plugin install failed: {msg}"])
48
+ # 1. Install the AgentNet every-prompt hook straight into settings.json.
49
+ # This is the reliable path: every prompt fires AgentNet (discover +
50
+ # fold in relevant skills). It does NOT depend on the plugin marketplace
51
+ # flow (which errors on some Claude Code versions), so connect succeeds
52
+ # even if that fails.
53
+ from .claude_search_hook import SettingsHookError
54
+ from .claude_search_hook import install as install_search_hook
55
+
56
+ errors: list[str] = []
57
+ try:
58
+ install_search_hook()
59
+ except SettingsHookError as exc:
60
+ # A malformed settings.json must not be overwritten; report and preserve it, but let
61
+ # the rest of connect (MCP + plugin) still run.
62
+ errors.append(str(exc))
63
+
64
+ # 2. Best-effort: install the plugin for the discovery MCP tools and
65
+ # session hooks. `marketplace add` takes only <source> (no --scope).
66
+ # Failures here are non-fatal the prompt hook above is already live.
67
+ try:
68
+ marketplace_src = _marketplace_source()
69
+ proc = subprocess.run(
70
+ ["claude", "plugin", "marketplace", "add", marketplace_src],
71
+ capture_output=True,
72
+ timeout=_SUBPROCESS_TIMEOUT,
73
+ )
74
+ if proc.returncode == 0:
75
+ proc = subprocess.run(
76
+ ["claude", "plugin", "install", _PLUGIN_ID, "--scope", "user"],
77
+ capture_output=True,
78
+ timeout=_SUBPROCESS_TIMEOUT,
79
+ )
80
+ if proc.returncode != 0:
81
+ errors.append(
82
+ "plugin install (discovery tools) failed: "
83
+ + proc.stderr.decode(errors="replace").strip()
84
+ )
85
+ else:
86
+ errors.append(
87
+ "plugin marketplace add (discovery tools) failed: "
88
+ + proc.stderr.decode(errors="replace").strip()
89
+ )
90
+ except Exception as exc: # noqa: BLE001 — plugin step is best-effort
91
+ errors.append(f"plugin step skipped: {exc}")
67
92
 
68
93
  self._cleanup_legacy()
69
94
 
70
95
  return ConnectionResult(
71
96
  success=True,
72
- mcp_entry={"scope": "plugin", "plugin_name": _PLUGIN_ID},
97
+ mcp_entry={"scope": "settings-hook", "search_fire": True},
98
+ errors=errors,
73
99
  )
74
100
 
75
101
  def disconnect(self, connection_manifest: dict[str, Any]) -> bool:
102
+ from .claude_search_hook import uninstall as uninstall_search_hook
103
+
104
+ uninstall_search_hook()
105
+
76
106
  claude_bin = shutil.which("claude")
77
107
  if not claude_bin:
78
108
  return True
@@ -0,0 +1,150 @@
1
+ """Install the AgentNet every-prompt hooks directly into Claude Code's settings.json.
2
+
3
+ Registers three hooks: ``UserPromptSubmit`` (spawn a background discovery worker),
4
+ ``PostToolUse`` (steer the agent mid-flight once the outcome is ready), and ``Stop``
5
+ (guaranteed fallback that folds the outcome in). Same effect as the bundled plugin, but
6
+ written straight into ``~/.claude/settings.json`` so it works in one command regardless of
7
+ Claude Code version.
8
+
9
+ ``PostToolUse`` is tool-scoped so its block carries a ``*`` matcher (all tools);
10
+ ``UserPromptSubmit`` and ``Stop`` are not tool-scoped, so their blocks carry no matcher.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from typing import Any
17
+
18
+ from ..infra.paths import AgentName, agent_config_root
19
+
20
+
21
+ class SettingsHookError(Exception):
22
+ """Raised when ~/.claude/settings.json exists but is malformed, so it must not be overwritten."""
23
+
24
+
25
+ # event -> (command, matcher). matcher=None for non-tool-scoped events.
26
+ _HOOKS: dict[str, tuple[str, str | None]] = {
27
+ "UserPromptSubmit": ("agentnet skill-hook --pre", None),
28
+ "PostToolUse": ("agentnet skill-hook --peek", "*"),
29
+ "Stop": ("agentnet skill-hook --post", None),
30
+ }
31
+
32
+
33
+ def _settings_path():
34
+ return agent_config_root(AgentName.CLAUDE) / "settings.json"
35
+
36
+
37
+ def _load(path) -> dict[str, Any]:
38
+ """Parse an existing settings.json to a dict.
39
+
40
+ Absent file -> ``{}`` (a fresh config). A file that *exists* but is unparseable or not a JSON
41
+ object is NOT the same as empty — overwriting it would erase the user's real config — so raise
42
+ ``SettingsHookError`` and let the caller preserve the file untouched.
43
+ """
44
+ if not path.exists():
45
+ return {}
46
+ try:
47
+ data = json.loads(path.read_text())
48
+ except (json.JSONDecodeError, OSError) as exc:
49
+ raise SettingsHookError(f"{path} is not valid JSON — fix it and re-run") from exc
50
+ if not isinstance(data, dict):
51
+ raise SettingsHookError(f"{path} is not a JSON object — fix it and re-run")
52
+ return data
53
+
54
+
55
+ def _is_agentnet_cmd(cmd: Any) -> bool:
56
+ return isinstance(cmd, str) and cmd.startswith("agentnet skill-hook")
57
+
58
+
59
+ def _block(command: str, matcher: str | None) -> dict[str, Any]:
60
+ block: dict[str, Any] = {}
61
+ if matcher is not None:
62
+ block["matcher"] = matcher
63
+ block["hooks"] = [{"type": "command", "command": command}]
64
+ return block
65
+
66
+
67
+ def _event_has_agentnet(blocks: list[Any]) -> bool:
68
+ for b in blocks:
69
+ if isinstance(b, dict) and any(
70
+ isinstance(h, dict) and _is_agentnet_cmd(h.get("command")) for h in b.get("hooks", [])
71
+ ):
72
+ return True
73
+ return False
74
+
75
+
76
+ def install() -> tuple[bool, str]:
77
+ """Add the UserPromptSubmit/Stop hooks to settings.json. Returns (changed, path)."""
78
+ path = _settings_path()
79
+ path.parent.mkdir(parents=True, exist_ok=True)
80
+ data = _load(path)
81
+ hooks = data.setdefault("hooks", {})
82
+ if not isinstance(hooks, dict):
83
+ hooks = {}
84
+ data["hooks"] = hooks
85
+
86
+ changed = False
87
+ for event, (command, matcher) in _HOOKS.items():
88
+ blocks = hooks.get(event)
89
+ if blocks is None:
90
+ blocks = []
91
+ hooks[event] = blocks
92
+ elif isinstance(blocks, dict):
93
+ # A single hook block stored as an object — preserve it by wrapping, don't discard it.
94
+ blocks = [blocks]
95
+ hooks[event] = blocks
96
+ elif not isinstance(blocks, list):
97
+ # Some other scalar we don't understand — leave the user's value untouched.
98
+ continue
99
+ if not _event_has_agentnet(blocks):
100
+ blocks.append(_block(command, matcher))
101
+ changed = True
102
+
103
+ if changed:
104
+ path.write_text(json.dumps(data, indent=2) + "\n")
105
+ return changed, str(path)
106
+
107
+
108
+ def uninstall() -> tuple[bool, str]:
109
+ """Remove the AgentNet every-prompt hooks from settings.json. Returns (changed, path).
110
+
111
+ A malformed settings file has nothing removable and must not be overwritten, so treat it as
112
+ a no-op rather than raising into the (best-effort) disconnect path.
113
+ """
114
+ path = _settings_path()
115
+ try:
116
+ data = _load(path)
117
+ except SettingsHookError:
118
+ return False, str(path)
119
+ hooks = data.get("hooks")
120
+ if not isinstance(hooks, dict):
121
+ return False, str(path)
122
+
123
+ changed = False
124
+ for event in list(_HOOKS):
125
+ blocks = hooks.get(event)
126
+ if not isinstance(blocks, list):
127
+ continue
128
+ kept = [
129
+ b
130
+ for b in blocks
131
+ if not (
132
+ isinstance(b, dict)
133
+ and any(
134
+ isinstance(h, dict) and _is_agentnet_cmd(h.get("command"))
135
+ for h in b.get("hooks", [])
136
+ )
137
+ )
138
+ ]
139
+ if len(kept) != len(blocks):
140
+ changed = True
141
+ if kept:
142
+ hooks[event] = kept
143
+ else:
144
+ hooks.pop(event, None)
145
+
146
+ if changed:
147
+ if not hooks:
148
+ data.pop("hooks", None)
149
+ path.write_text(json.dumps(data, indent=2) + "\n")
150
+ return changed, str(path)