Add local-first zotfile name resolution
- commit
- b99b1aebbc19f4798b76323ab32a5bed41babb9d
- author
- patriceckhart · · 2026-07-19 10:28 UTC
- parent
- 4d6e4be
- signature
- unsigned
- refs
- tag: v0.2.92
7 files · +114 -18
M AGENTS.md +1 -1
| @@ -36,7 +36,7 @@ Put behavior in the package that owns the concern: | |||
| 36 | 36 | | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction | | |
| 37 | 37 | | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery | | |
| 38 | 38 | | `packages/provider/auth` | Login flows, credential lookup, refresh, and storage | | |
| 39 | - | | `packages/agent` | CLI configuration, model selection, runtime assembly, RPC, and Zotfiles | | |
| 39 | + | | `packages/agent` | CLI configuration, model selection, runtime assembly, RPC, and zotfiles | | |
| 40 | 40 | | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces | | |
| 41 | 41 | | `packages/agent/extensions` | Extension process lifecycle and host integration | | |
| 42 | 42 | | `packages/agent/extproto` | Extension wire protocol | | |
| @@ -36,7 +36,7 @@ Put behavior in the package that owns the concern: | |||||
| 36 | | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction | | 36 | | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction | | ||
| 37 | | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery | | 37 | | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery | | ||
| 38 | | `packages/provider/auth` | Login flows, credential lookup, refresh, and storage | | 38 | | `packages/provider/auth` | Login flows, credential lookup, refresh, and storage | | ||
| 39 | - | | `packages/agent` | CLI configuration, model selection, runtime assembly, RPC, and Zotfiles | | 39 | + | | `packages/agent` | CLI configuration, model selection, runtime assembly, RPC, and zotfiles | |
| 40 | | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces | | 40 | | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces | | ||
| 41 | | `packages/agent/extensions` | Extension process lifecycle and host integration | | 41 | | `packages/agent/extensions` | Extension process lifecycle and host integration | | ||
| 42 | | `packages/agent/extproto` | Extension wire protocol | | 42 | | `packages/agent/extproto` | Extension wire protocol | | ||
M CONTRIBUTING.md +1 -1
| @@ -42,7 +42,7 @@ The major ownership boundaries are: | |||
| 42 | 42 | - `packages/core`: provider-independent agent behavior and session structures | |
| 43 | 43 | - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior | |
| 44 | 44 | - `packages/provider/auth`: credential and login handling | |
| 45 | - | - `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and Zotfiles | |
| 45 | + | - `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zotfiles | |
| 46 | 46 | - `packages/agent/modes`: interactive and non-interactive user modes | |
| 47 | 47 | - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK | |
| 48 | 48 | - `packages/agent/skills`: reusable instruction discovery | |
| @@ -42,7 +42,7 @@ The major ownership boundaries are: | |||||
| 42 | - `packages/core`: provider-independent agent behavior and session structures | 42 | - `packages/core`: provider-independent agent behavior and session structures | ||
| 43 | - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior | 43 | - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior | ||
| 44 | - `packages/provider/auth`: credential and login handling | 44 | - `packages/provider/auth`: credential and login handling | ||
| 45 | - | - `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and Zotfiles | 45 | + | - `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zotfiles |
| 46 | - `packages/agent/modes`: interactive and non-interactive user modes | 46 | - `packages/agent/modes`: interactive and non-interactive user modes | ||
| 47 | - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK | 47 | - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK | ||
| 48 | - `packages/agent/skills`: reusable instruction discovery | 48 | - `packages/agent/skills`: reusable instruction discovery | ||
M README.md +3 -2
| @@ -215,15 +215,16 @@ When the sandbox is on (see `/jail`), all four tools refuse paths outside the se | |||
| 215 | 215 | ||
| 216 | 216 | ## zotfile agents | |
| 217 | 217 | ||
| 218 | - | A zotfile packages an agent's instructions, skills, requirements, and enforced tool permissions as a shareable agent. Run one from a local directory, a packed `.zot` artifact, or directly from a public GitHub repository: | |
| 218 | + | A zotfile packages an agent's instructions, skills, requirements, and enforced tool permissions as a shareable agent. Run one from a local directory, a packed `.zot` artifact, a short name, or directly from a public GitHub repository: | |
| 219 | 219 | ||
| 220 | 220 | ```bash | |
| 221 | 221 | zot run ./my-agent | |
| 222 | 222 | zot run ./my-agent.zot | |
| 223 | + | zot run zot-maintenance --cwd /path/to/zot | |
| 223 | 224 | zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot | |
| 224 | 225 | ``` | |
| 225 | 226 | ||
| 226 | - | For a GitHub URL, zot downloads the repository archive into a temporary directory, validates and runs the selected agent, then removes the downloaded source when the command exits. Agent data, consent receipts, and sessions still persist under `$ZOT_HOME`. See [docs/zotfiles.md](docs/zotfiles.md) for authoring, permissions, packaging, and current limitations. | |
| 227 | + | Short names resolve local-first: zot checks the matching directory and `.zot` archive before falling back to the official GitHub agent collection. For GitHub sources, zot downloads the repository archive into a temporary directory, validates and runs the selected agent, then removes the downloaded source when the command exits. Agent data, consent receipts, and sessions still persist under `$ZOT_HOME`. See [docs/zotfiles.md](docs/zotfiles.md) for authoring, permissions, packaging, and current limitations. | |
| 227 | 228 | ||
| 228 | 229 | ## Embedding | |
| 229 | 230 | ||
| @@ -215,15 +215,16 @@ When the sandbox is on (see `/jail`), all four tools refuse paths outside the se | |||||
| 215 | 215 | ||||
| 216 | ## zotfile agents | 216 | ## zotfile agents | ||
| 217 | 217 | ||||
| 218 | - | A zotfile packages an agent's instructions, skills, requirements, and enforced tool permissions as a shareable agent. Run one from a local directory, a packed `.zot` artifact, or directly from a public GitHub repository: | 218 | + | A zotfile packages an agent's instructions, skills, requirements, and enforced tool permissions as a shareable agent. Run one from a local directory, a packed `.zot` artifact, a short name, or directly from a public GitHub repository: |
| 219 | 219 | ||||
| 220 | ```bash | 220 | ```bash | ||
| 221 | zot run ./my-agent | 221 | zot run ./my-agent | ||
| 222 | zot run ./my-agent.zot | 222 | zot run ./my-agent.zot | ||
| 223 | + | zot run zot-maintenance --cwd /path/to/zot | |||
| 223 | zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot | 224 | zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot | ||
| 224 | ``` | 225 | ``` | ||
| 225 | 226 | ||||
| 226 | - | For a GitHub URL, zot downloads the repository archive into a temporary directory, validates and runs the selected agent, then removes the downloaded source when the command exits. Agent data, consent receipts, and sessions still persist under `$ZOT_HOME`. See [docs/zotfiles.md](docs/zotfiles.md) for authoring, permissions, packaging, and current limitations. | 227 | + | Short names resolve local-first: zot checks the matching directory and `.zot` archive before falling back to the official GitHub agent collection. For GitHub sources, zot downloads the repository archive into a temporary directory, validates and runs the selected agent, then removes the downloaded source when the command exits. Agent data, consent receipts, and sessions still persist under `$ZOT_HOME`. See [docs/zotfiles.md](docs/zotfiles.md) for authoring, permissions, packaging, and current limitations. |
| 227 | 228 | ||||
| 228 | ## Embedding | 229 | ## Embedding | ||
| 229 | 230 | ||||
M docs/zotfiles.md +28 -10
| @@ -1,8 +1,8 @@ | |||
| 1 | - | # Zotfile agents | |
| 1 | + | # zotfile agents | |
| 2 | 2 | ||
| 3 | - | A Zotfile packages an agent's behavior into one portable `.zot` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs. | |
| 3 | + | A zotfile packages an agent's behavior into one portable `.zot` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs. | |
| 4 | 4 | ||
| 5 | - | The current implementation supports creating, inspecting, verifying, and running local directories and `.zot` archives. It can also run an agent directory directly from a public GitHub repository without keeping a clone. Registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet. | |
| 5 | + | The current implementation supports creating, inspecting, verifying, and running local directories and `.zot` archives. It can also run an agent directory directly from a public GitHub repository without keeping a clone. Short names resolve local-first and then fall back to zot's official GitHub agent collection. Indexed registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet. | |
| 6 | 6 | ||
| 7 | 7 | ## Quick start | |
| 8 | 8 | ||
| @@ -141,7 +141,7 @@ description: Diagnose a failing command from its output and relevant source file | |||
| 141 | 141 | 4. Propose the smallest correction and validation plan. | |
| 142 | 142 | ``` | |
| 143 | 143 | ||
| 144 | - | Bundled skills are added to normal skill discovery while the Zotfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format. | |
| 144 | + | Bundled skills are added to normal skill discovery while the zotfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format. | |
| 145 | 145 | ||
| 146 | 146 | ## Manifest reference | |
| 147 | 147 | ||
| @@ -271,7 +271,7 @@ Use `requirements.bin` for commands that must already be on `PATH`: | |||
| 271 | 271 | } | |
| 272 | 272 | ``` | |
| 273 | 273 | ||
| 274 | - | Zot checks these requirements before requesting consent. Zotfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs. | |
| 274 | + | Zot checks these requirements before requesting consent. These zotfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs. | |
| 275 | 275 | ||
| 276 | 276 | ### Entry fields | |
| 277 | 277 | ||
| @@ -465,7 +465,7 @@ The directory is created for every run, but the agent can access it only when `$ | |||
| 465 | 465 | ||
| 466 | 466 | ## Agent-scoped sessions | |
| 467 | 467 | ||
| 468 | - | Zotfile sessions are isolated from ordinary zot sessions and from other agents: | |
| 468 | + | Sessions created by a zotfile are isolated from ordinary zot sessions and from other agents: | |
| 469 | 469 | ||
| 470 | 470 | ```text | |
| 471 | 471 | $ZOT_HOME/sessions/agents/<name>/ | |
| @@ -494,7 +494,24 @@ zot run ./my-agent.zot --json "Do the task" | |||
| 494 | 494 | ||
| 495 | 495 | Arguments after the reference use zot's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. | |
| 496 | 496 | ||
| 497 | - | Local filesystem directories, local archive paths, and public GitHub agent-directory URLs are accepted. For GitHub, zot downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits: | |
| 497 | + | Local filesystem directories, local archive paths, short names, and public GitHub agent-directory URLs are accepted. A short name uses local-first resolution: | |
| 498 | + | ||
| 499 | + | ```bash | |
| 500 | + | zot run zot-maintenance | |
| 501 | + | ``` | |
| 502 | + | ||
| 503 | + | Zot checks `./zot-maintenance` and `./zot-maintenance.zot` before falling back to `https://github.com/patriceckhart/agents/zot-maintenance`. This makes local development override the remote collection without an install or cache step. | |
| 504 | + | ||
| 505 | + | A two-part name selects a GitHub repository under the `patriceckhart` organization: | |
| 506 | + | ||
| 507 | + | ```bash | |
| 508 | + | zot run agents/zot-maintenance | |
| 509 | + | zot run agents\zot-maintenance # also accepted on Windows | |
| 510 | + | ``` | |
| 511 | + | ||
| 512 | + | The first part is the repository and the second part is the agent directory. Both parts must use lowercase letters, digits, dots, hyphens, or underscores. Prefix a reference with `./`, use an absolute path, retain the `.zot` suffix, or provide a complete GitHub URL when remote fallback is not wanted. | |
| 513 | + | ||
| 514 | + | For GitHub, zot downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits: | |
| 498 | 515 | ||
| 499 | 516 | ```bash | |
| 500 | 517 | zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot | |
| @@ -508,7 +525,7 @@ zot run https://github.com/patriceckhart/agents/tree/main/zot-maintenance | |||
| 508 | 525 | ||
| 509 | 526 | The short form reads the repository's default branch through GitHub's `HEAD` archive. A tree URL uses the branch or tag in the URL. Private repositories and GitHub references containing `/` are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under `$ZOT_HOME`. | |
| 510 | 527 | ||
| 511 | - | Installed names, arbitrary URLs, registry references, and OCI references are not resolved yet. | |
| 528 | + | Short-name fallback is a fixed GitHub mapping, not an indexed or signed registry. Installed names, arbitrary URLs, OCI references, and third-party registry configuration are not resolved yet. | |
| 512 | 529 | ||
| 513 | 530 | ### `zot pack` | |
| 514 | 531 | ||
| @@ -578,6 +595,7 @@ Archive extraction rejects absolute paths and parent traversal. Unsupported tar | |||
| 578 | 595 | Implemented now: | |
| 579 | 596 | ||
| 580 | 597 | - local directories and `.zot` archives | |
| 598 | + | - local-first short-name resolution to the official GitHub agent collection | |
| 581 | 599 | - temporary execution of agent directories from public GitHub repositories | |
| 582 | 600 | - `zot pack`, `zot inspect`, `zot verify`, and `zot run` | |
| 583 | 601 | - canonical tar creation with zstd compression | |
| @@ -595,7 +613,7 @@ Implemented now: | |||
| 595 | 613 | Not implemented yet: | |
| 596 | 614 | ||
| 597 | 615 | - `zot install`, `zot agents`, `zot use`, `zot update`, or `zot publish` | |
| 598 | - | - installed-name, arbitrary URL, OCI, or zot.sh index resolution | |
| 616 | + | - installed-name, arbitrary URL, OCI, configurable registry, or zot.sh index resolution | |
| 599 | 617 | - network allowlist enforcement | |
| 600 | 618 | - environment-variable filtering | |
| 601 | 619 | - safely confined bundled executable extensions | |
| @@ -604,4 +622,4 @@ Not implemented yet: | |||
| 604 | 622 | - per-agent Telegram, bot, or RPC selection | |
| 605 | 623 | - `--no-sandbox`, `--trust-bash`, or registry trust override flags | |
| 606 | 624 | ||
| 607 | - | Treat the unsupported fields and commands in the broader Zotfile proposal as forward-looking design, not as current runtime behavior. | |
| 625 | + | Treat the unsupported fields and commands in the broader zotfile proposal as forward-looking design, not as current runtime behavior. | |
| @@ -1,8 +1,8 @@ | |||||
| 1 | - | # Zotfile agents | 1 | + | # zotfile agents |
| 2 | 2 | ||||
| 3 | - | A Zotfile packages an agent's behavior into one portable `.zot` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs. | 3 | + | A zotfile packages an agent's behavior into one portable `.zot` file. It can contain the agent's instructions, reusable skills, static assets, and metadata describing the runtime, model, operating-system, binary, and tool permissions it needs. |
| 4 | 4 | ||||
| 5 | - | The current implementation supports creating, inspecting, verifying, and running local directories and `.zot` archives. It can also run an agent directory directly from a public GitHub repository without keeping a clone. Registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet. | 5 | + | The current implementation supports creating, inspecting, verifying, and running local directories and `.zot` archives. It can also run an agent directory directly from a public GitHub repository without keeping a clone. Short names resolve local-first and then fall back to zot's official GitHub agent collection. Indexed registry distribution, installation, signatures, bundled executable extensions, network permissions, and environment permissions are not implemented yet. |
| 6 | 6 | ||||
| 7 | ## Quick start | 7 | ## Quick start | ||
| 8 | 8 | ||||
| @@ -141,7 +141,7 @@ description: Diagnose a failing command from its output and relevant source file | |||||
| 141 | 4. Propose the smallest correction and validation plan. | 141 | 4. Propose the smallest correction and validation plan. | ||
| 142 | ``` | 142 | ``` | ||
| 143 | 143 | ||||
| 144 | - | Bundled skills are added to normal skill discovery while the Zotfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format. | 144 | + | Bundled skills are added to normal skill discovery while the zotfile is running. The model sees their name and description in the skill manifest and can load the full body through the `skill` tool. See [skills.md](skills.md) for the complete skill format. |
| 145 | 145 | ||||
| 146 | ## Manifest reference | 146 | ## Manifest reference | ||
| 147 | 147 | ||||
| @@ -271,7 +271,7 @@ Use `requirements.bin` for commands that must already be on `PATH`: | |||||
| 271 | } | 271 | } | ||
| 272 | ``` | 272 | ``` | ||
| 273 | 273 | ||||
| 274 | - | Zot checks these requirements before requesting consent. Zotfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs. | 274 | + | Zot checks these requirements before requesting consent. These zotfiles do not have install or postinstall hooks, so authors must document how users can obtain missing programs. |
| 275 | 275 | ||||
| 276 | ### Entry fields | 276 | ### Entry fields | ||
| 277 | 277 | ||||
| @@ -465,7 +465,7 @@ The directory is created for every run, but the agent can access it only when `$ | |||||
| 465 | 465 | ||||
| 466 | ## Agent-scoped sessions | 466 | ## Agent-scoped sessions | ||
| 467 | 467 | ||||
| 468 | - | Zotfile sessions are isolated from ordinary zot sessions and from other agents: | 468 | + | Sessions created by a zotfile are isolated from ordinary zot sessions and from other agents: |
| 469 | 469 | ||||
| 470 | ```text | 470 | ```text | ||
| 471 | $ZOT_HOME/sessions/agents/<name>/ | 471 | $ZOT_HOME/sessions/agents/<name>/ | ||
| @@ -494,7 +494,24 @@ zot run ./my-agent.zot --json "Do the task" | |||||
| 494 | 494 | ||||
| 495 | Arguments after the reference use zot's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. | 495 | Arguments after the reference use zot's normal CLI parser, so model, provider, reasoning, cwd, session, tool, and output-mode flags remain available. The manifest still imposes its permission and compatibility ceiling. | ||
| 496 | 496 | ||||
| 497 | - | Local filesystem directories, local archive paths, and public GitHub agent-directory URLs are accepted. For GitHub, zot downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits: | 497 | + | Local filesystem directories, local archive paths, short names, and public GitHub agent-directory URLs are accepted. A short name uses local-first resolution: |
| 498 | + | ||||
| 499 | + | ```bash | |||
| 500 | + | zot run zot-maintenance | |||
| 501 | + | ``` | |||
| 502 | + | ||||
| 503 | + | Zot checks `./zot-maintenance` and `./zot-maintenance.zot` before falling back to `https://github.com/patriceckhart/agents/zot-maintenance`. This makes local development override the remote collection without an install or cache step. | |||
| 504 | + | ||||
| 505 | + | A two-part name selects a GitHub repository under the `patriceckhart` organization: | |||
| 506 | + | ||||
| 507 | + | ```bash | |||
| 508 | + | zot run agents/zot-maintenance | |||
| 509 | + | zot run agents\zot-maintenance # also accepted on Windows | |||
| 510 | + | ``` | |||
| 511 | + | ||||
| 512 | + | The first part is the repository and the second part is the agent directory. Both parts must use lowercase letters, digits, dots, hyphens, or underscores. Prefix a reference with `./`, use an absolute path, retain the `.zot` suffix, or provide a complete GitHub URL when remote fallback is not wanted. | |||
| 513 | + | ||||
| 514 | + | For GitHub, zot downloads the repository archive into a temporary directory, selects the requested agent subdirectory, validates it, runs it, and removes the downloaded files when the command exits: | |||
| 498 | 515 | ||||
| 499 | ```bash | 516 | ```bash | ||
| 500 | zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot | 517 | zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot | ||
| @@ -508,7 +525,7 @@ zot run https://github.com/patriceckhart/agents/tree/main/zot-maintenance | |||||
| 508 | 525 | ||||
| 509 | The short form reads the repository's default branch through GitHub's `HEAD` archive. A tree URL uses the branch or tag in the URL. Private repositories and GitHub references containing `/` are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under `$ZOT_HOME`. | 526 | The short form reads the repository's default branch through GitHub's `HEAD` archive. A tree URL uses the branch or tag in the URL. Private repositories and GitHub references containing `/` are not currently supported. The downloaded source is temporary, but normal agent data, consent receipts, and session transcripts remain under `$ZOT_HOME`. | ||
| 510 | 527 | ||||
| 511 | - | Installed names, arbitrary URLs, registry references, and OCI references are not resolved yet. | 528 | + | Short-name fallback is a fixed GitHub mapping, not an indexed or signed registry. Installed names, arbitrary URLs, OCI references, and third-party registry configuration are not resolved yet. |
| 512 | 529 | ||||
| 513 | ### `zot pack` | 530 | ### `zot pack` | ||
| 514 | 531 | ||||
| @@ -578,6 +595,7 @@ Archive extraction rejects absolute paths and parent traversal. Unsupported tar | |||||
| 578 | Implemented now: | 595 | Implemented now: | ||
| 579 | 596 | ||||
| 580 | - local directories and `.zot` archives | 597 | - local directories and `.zot` archives | ||
| 598 | + | - local-first short-name resolution to the official GitHub agent collection | |||
| 581 | - temporary execution of agent directories from public GitHub repositories | 599 | - temporary execution of agent directories from public GitHub repositories | ||
| 582 | - `zot pack`, `zot inspect`, `zot verify`, and `zot run` | 600 | - `zot pack`, `zot inspect`, `zot verify`, and `zot run` | ||
| 583 | - canonical tar creation with zstd compression | 601 | - canonical tar creation with zstd compression | ||
| @@ -595,7 +613,7 @@ Implemented now: | |||||
| 595 | Not implemented yet: | 613 | Not implemented yet: | ||
| 596 | 614 | ||||
| 597 | - `zot install`, `zot agents`, `zot use`, `zot update`, or `zot publish` | 615 | - `zot install`, `zot agents`, `zot use`, `zot update`, or `zot publish` | ||
| 598 | - | - installed-name, arbitrary URL, OCI, or zot.sh index resolution | 616 | + | - installed-name, arbitrary URL, OCI, configurable registry, or zot.sh index resolution |
| 599 | - network allowlist enforcement | 617 | - network allowlist enforcement | ||
| 600 | - environment-variable filtering | 618 | - environment-variable filtering | ||
| 601 | - safely confined bundled executable extensions | 619 | - safely confined bundled executable extensions | ||
| @@ -604,4 +622,4 @@ Not implemented yet: | |||||
| 604 | - per-agent Telegram, bot, or RPC selection | 622 | - per-agent Telegram, bot, or RPC selection | ||
| 605 | - `--no-sandbox`, `--trust-bash`, or registry trust override flags | 623 | - `--no-sandbox`, `--trust-bash`, or registry trust override flags | ||
| 606 | 624 | ||||
| 607 | - | Treat the unsupported fields and commands in the broader Zotfile proposal as forward-looking design, not as current runtime behavior. | 625 | + | Treat the unsupported fields and commands in the broader zotfile proposal as forward-looking design, not as current runtime behavior. |
M packages/agent/args.go +1 -1
| @@ -103,7 +103,7 @@ type Args struct { | |||
| 103 | 103 | SwarmAgent string | |
| 104 | 104 | ||
| 105 | 105 | // AgentName/AgentDataDir/PermissionSet are populated by `zot run` | |
| 106 | - | // for local Zotfile agents. They scope sessions and enforce the | |
| 106 | + | // for local zotfile agents. They scope sessions and enforce the | |
| 107 | 107 | // manifest's declared file/bash permissions. | |
| 108 | 108 | AgentName string | |
| 109 | 109 | AgentDataDir string | |
| @@ -103,7 +103,7 @@ type Args struct { | |||||
| 103 | SwarmAgent string | 103 | SwarmAgent string | ||
| 104 | 104 | ||||
| 105 | // AgentName/AgentDataDir/PermissionSet are populated by `zot run` | 105 | // AgentName/AgentDataDir/PermissionSet are populated by `zot run` | ||
| 106 | - | // for local Zotfile agents. They scope sessions and enforce the | 106 | + | // for local zotfile agents. They scope sessions and enforce the |
| 107 | // manifest's declared file/bash permissions. | 107 | // manifest's declared file/bash permissions. | ||
| 108 | AgentName string | 108 | AgentName string | ||
| 109 | AgentDataDir string | 109 | AgentDataDir string | ||
M packages/agent/zotfile.go +45 -3
| @@ -77,12 +77,12 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) { | |||
| 77 | 77 | return true, zotPack(dir, out) | |
| 78 | 78 | case "inspect": | |
| 79 | 79 | if len(rawArgs) < 2 { | |
| 80 | - | return true, fmt.Errorf("zot inspect requires a .zot file, directory, or GitHub URL") | |
| 80 | + | return true, fmt.Errorf("zot inspect requires a name, .zot file, directory, or GitHub URL") | |
| 81 | 81 | } | |
| 82 | 82 | return true, zotInspect(rawArgs[1]) | |
| 83 | 83 | case "verify": | |
| 84 | 84 | if len(rawArgs) < 2 { | |
| 85 | - | return true, fmt.Errorf("zot verify requires a .zot file, directory, or GitHub URL") | |
| 85 | + | return true, fmt.Errorf("zot verify requires a name, .zot file, directory, or GitHub URL") | |
| 86 | 86 | } | |
| 87 | 87 | zf, cleanup, err := loadZotfile(rawArgs[1]) | |
| 88 | 88 | if cleanup != nil { | |
| @@ -95,7 +95,7 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) { | |||
| 95 | 95 | return true, nil | |
| 96 | 96 | case "run": | |
| 97 | 97 | if len(rawArgs) < 2 { | |
| 98 | - | return true, fmt.Errorf("zot run requires a .zot file, directory, or GitHub URL") | |
| 98 | + | return true, fmt.Errorf("zot run requires a name, .zot file, directory, or GitHub URL") | |
| 99 | 99 | } | |
| 100 | 100 | ref := rawArgs[1] | |
| 101 | 101 | rest := rawArgs[2:] | |
| @@ -318,6 +318,11 @@ func zotPack(dir, out string) error { | |||
| 318 | 318 | } | |
| 319 | 319 | ||
| 320 | 320 | func loadZotfile(ref string) (zotfileLoaded, func(), error) { | |
| 321 | + | resolved, err := resolveZotfileRef(ref) | |
| 322 | + | if err != nil { | |
| 323 | + | return zotfileLoaded{}, nil, err | |
| 324 | + | } | |
| 325 | + | ref = resolved | |
| 321 | 326 | if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") { | |
| 322 | 327 | return loadRemoteZotfile(u) | |
| 323 | 328 | } | |
| @@ -358,6 +363,43 @@ func loadZotfile(ref string) (zotfileLoaded, func(), error) { | |||
| 358 | 363 | return zotfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil | |
| 359 | 364 | } | |
| 360 | 365 | ||
| 366 | + | const officialZotfileCollection = "https://github.com/patriceckhart/agents" | |
| 367 | + | ||
| 368 | + | func resolveZotfileRef(ref string) (string, error) { | |
| 369 | + | if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") { | |
| 370 | + | return ref, nil | |
| 371 | + | } | |
| 372 | + | if _, err := os.Stat(ref); err == nil { | |
| 373 | + | return ref, nil | |
| 374 | + | } else if !os.IsNotExist(err) { | |
| 375 | + | return "", err | |
| 376 | + | } | |
| 377 | + | if filepath.Ext(ref) == "" { | |
| 378 | + | archive := ref + ".zot" | |
| 379 | + | if _, err := os.Stat(archive); err == nil { | |
| 380 | + | return archive, nil | |
| 381 | + | } else if !os.IsNotExist(err) { | |
| 382 | + | return "", err | |
| 383 | + | } | |
| 384 | + | } | |
| 385 | + | if strings.HasSuffix(strings.ToLower(ref), ".zot") { | |
| 386 | + | return ref, nil | |
| 387 | + | } | |
| 388 | + | collectionRef := strings.ReplaceAll(ref, "\\", "/") | |
| 389 | + | parts := strings.Split(collectionRef, "/") | |
| 390 | + | if len(parts) == 1 && validZotfileCollectionSegment(parts[0]) { | |
| 391 | + | return officialZotfileCollection + "/" + parts[0], nil | |
| 392 | + | } | |
| 393 | + | if len(parts) == 2 && validZotfileCollectionSegment(parts[0]) && validZotfileCollectionSegment(parts[1]) { | |
| 394 | + | return "https://github.com/patriceckhart/" + parts[0] + "/" + parts[1], nil | |
| 395 | + | } | |
| 396 | + | return ref, nil | |
| 397 | + | } | |
| 398 | + | ||
| 399 | + | func validZotfileCollectionSegment(s string) bool { | |
| 400 | + | return s != "" && s == strings.ToLower(s) && safeAgentName(s) == s | |
| 401 | + | } | |
| 402 | + | ||
| 361 | 403 | var zotfileHTTPClient = &http.Client{Timeout: 60 * time.Second} | |
| 362 | 404 | ||
| 363 | 405 | var githubArchiveURL = func(owner, repo, ref string) string { | |
| @@ -77,12 +77,12 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) { | |||||
| 77 | return true, zotPack(dir, out) | 77 | return true, zotPack(dir, out) | ||
| 78 | case "inspect": | 78 | case "inspect": | ||
| 79 | if len(rawArgs) < 2 { | 79 | if len(rawArgs) < 2 { | ||
| 80 | - | return true, fmt.Errorf("zot inspect requires a .zot file, directory, or GitHub URL") | 80 | + | return true, fmt.Errorf("zot inspect requires a name, .zot file, directory, or GitHub URL") |
| 81 | } | 81 | } | ||
| 82 | return true, zotInspect(rawArgs[1]) | 82 | return true, zotInspect(rawArgs[1]) | ||
| 83 | case "verify": | 83 | case "verify": | ||
| 84 | if len(rawArgs) < 2 { | 84 | if len(rawArgs) < 2 { | ||
| 85 | - | return true, fmt.Errorf("zot verify requires a .zot file, directory, or GitHub URL") | 85 | + | return true, fmt.Errorf("zot verify requires a name, .zot file, directory, or GitHub URL") |
| 86 | } | 86 | } | ||
| 87 | zf, cleanup, err := loadZotfile(rawArgs[1]) | 87 | zf, cleanup, err := loadZotfile(rawArgs[1]) | ||
| 88 | if cleanup != nil { | 88 | if cleanup != nil { | ||
| @@ -95,7 +95,7 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) { | |||||
| 95 | return true, nil | 95 | return true, nil | ||
| 96 | case "run": | 96 | case "run": | ||
| 97 | if len(rawArgs) < 2 { | 97 | if len(rawArgs) < 2 { | ||
| 98 | - | return true, fmt.Errorf("zot run requires a .zot file, directory, or GitHub URL") | 98 | + | return true, fmt.Errorf("zot run requires a name, .zot file, directory, or GitHub URL") |
| 99 | } | 99 | } | ||
| 100 | ref := rawArgs[1] | 100 | ref := rawArgs[1] | ||
| 101 | rest := rawArgs[2:] | 101 | rest := rawArgs[2:] | ||
| @@ -318,6 +318,11 @@ func zotPack(dir, out string) error { | |||||
| 318 | } | 318 | } | ||
| 319 | 319 | ||||
| 320 | func loadZotfile(ref string) (zotfileLoaded, func(), error) { | 320 | func loadZotfile(ref string) (zotfileLoaded, func(), error) { | ||
| 321 | + | resolved, err := resolveZotfileRef(ref) | |||
| 322 | + | if err != nil { | |||
| 323 | + | return zotfileLoaded{}, nil, err | |||
| 324 | + | } | |||
| 325 | + | ref = resolved | |||
| 321 | if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") { | 326 | if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") { | ||
| 322 | return loadRemoteZotfile(u) | 327 | return loadRemoteZotfile(u) | ||
| 323 | } | 328 | } | ||
| @@ -358,6 +363,43 @@ func loadZotfile(ref string) (zotfileLoaded, func(), error) { | |||||
| 358 | return zotfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil | 363 | return zotfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil | ||
| 359 | } | 364 | } | ||
| 360 | 365 | ||||
| 366 | + | const officialZotfileCollection = "https://github.com/patriceckhart/agents" | |||
| 367 | + | ||||
| 368 | + | func resolveZotfileRef(ref string) (string, error) { | |||
| 369 | + | if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") { | |||
| 370 | + | return ref, nil | |||
| 371 | + | } | |||
| 372 | + | if _, err := os.Stat(ref); err == nil { | |||
| 373 | + | return ref, nil | |||
| 374 | + | } else if !os.IsNotExist(err) { | |||
| 375 | + | return "", err | |||
| 376 | + | } | |||
| 377 | + | if filepath.Ext(ref) == "" { | |||
| 378 | + | archive := ref + ".zot" | |||
| 379 | + | if _, err := os.Stat(archive); err == nil { | |||
| 380 | + | return archive, nil | |||
| 381 | + | } else if !os.IsNotExist(err) { | |||
| 382 | + | return "", err | |||
| 383 | + | } | |||
| 384 | + | } | |||
| 385 | + | if strings.HasSuffix(strings.ToLower(ref), ".zot") { | |||
| 386 | + | return ref, nil | |||
| 387 | + | } | |||
| 388 | + | collectionRef := strings.ReplaceAll(ref, "\\", "/") | |||
| 389 | + | parts := strings.Split(collectionRef, "/") | |||
| 390 | + | if len(parts) == 1 && validZotfileCollectionSegment(parts[0]) { | |||
| 391 | + | return officialZotfileCollection + "/" + parts[0], nil | |||
| 392 | + | } | |||
| 393 | + | if len(parts) == 2 && validZotfileCollectionSegment(parts[0]) && validZotfileCollectionSegment(parts[1]) { | |||
| 394 | + | return "https://github.com/patriceckhart/" + parts[0] + "/" + parts[1], nil | |||
| 395 | + | } | |||
| 396 | + | return ref, nil | |||
| 397 | + | } | |||
| 398 | + | ||||
| 399 | + | func validZotfileCollectionSegment(s string) bool { | |||
| 400 | + | return s != "" && s == strings.ToLower(s) && safeAgentName(s) == s | |||
| 401 | + | } | |||
| 402 | + | ||||
| 361 | var zotfileHTTPClient = &http.Client{Timeout: 60 * time.Second} | 403 | var zotfileHTTPClient = &http.Client{Timeout: 60 * time.Second} | ||
| 362 | 404 | ||||
| 363 | var githubArchiveURL = func(owner, repo, ref string) string { | 405 | var githubArchiveURL = func(owner, repo, ref string) string { | ||
M packages/agent/zotfile_test.go +35 -0
| @@ -48,6 +48,41 @@ func TestLoadZotfileRejectsUnsafeOrCollidingNames(t *testing.T) { | |||
| 48 | 48 | } | |
| 49 | 49 | } | |
| 50 | 50 | ||
| 51 | + | func TestResolveZotfileRefUsesLocalFirstThenOfficialCollection(t *testing.T) { | |
| 52 | + | dir := t.TempDir() | |
| 53 | + | t.Chdir(dir) | |
| 54 | + | if err := os.Mkdir("local-agent", 0o700); err != nil { | |
| 55 | + | t.Fatal(err) | |
| 56 | + | } | |
| 57 | + | if err := os.WriteFile("packed-agent.zot", []byte("test"), 0o600); err != nil { | |
| 58 | + | t.Fatal(err) | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | tests := []struct { | |
| 62 | + | ref string | |
| 63 | + | want string | |
| 64 | + | }{ | |
| 65 | + | {"local-agent", "local-agent"}, | |
| 66 | + | {"packed-agent", "packed-agent.zot"}, | |
| 67 | + | {"remote-agent", officialZotfileCollection + "/remote-agent"}, | |
| 68 | + | {"missing.zot", "missing.zot"}, | |
| 69 | + | {"agents/remote-agent", "https://github.com/patriceckhart/agents/remote-agent"}, | |
| 70 | + | {`agents\remote-agent`, "https://github.com/patriceckhart/agents/remote-agent"}, | |
| 71 | + | {"./missing-agent", "./missing-agent"}, | |
| 72 | + | {"Remote-Agent", "Remote-Agent"}, | |
| 73 | + | {"https://github.com/acme/agents/example", "https://github.com/acme/agents/example"}, | |
| 74 | + | } | |
| 75 | + | for _, tt := range tests { | |
| 76 | + | got, err := resolveZotfileRef(tt.ref) | |
| 77 | + | if err != nil { | |
| 78 | + | t.Fatalf("resolve %q: %v", tt.ref, err) | |
| 79 | + | } | |
| 80 | + | if got != tt.want { | |
| 81 | + | t.Errorf("resolve %q = %q, want %q", tt.ref, got, tt.want) | |
| 82 | + | } | |
| 83 | + | } | |
| 84 | + | } | |
| 85 | + | ||
| 51 | 86 | func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) { | |
| 52 | 87 | dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`) | |
| 53 | 88 | ext := filepath.Join(dir, "extensions", "bad") | |
| @@ -48,6 +48,41 @@ func TestLoadZotfileRejectsUnsafeOrCollidingNames(t *testing.T) { | |||||
| 48 | } | 48 | } | ||
| 49 | } | 49 | } | ||
| 50 | 50 | ||||
| 51 | + | func TestResolveZotfileRefUsesLocalFirstThenOfficialCollection(t *testing.T) { | |||
| 52 | + | dir := t.TempDir() | |||
| 53 | + | t.Chdir(dir) | |||
| 54 | + | if err := os.Mkdir("local-agent", 0o700); err != nil { | |||
| 55 | + | t.Fatal(err) | |||
| 56 | + | } | |||
| 57 | + | if err := os.WriteFile("packed-agent.zot", []byte("test"), 0o600); err != nil { | |||
| 58 | + | t.Fatal(err) | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | tests := []struct { | |||
| 62 | + | ref string | |||
| 63 | + | want string | |||
| 64 | + | }{ | |||
| 65 | + | {"local-agent", "local-agent"}, | |||
| 66 | + | {"packed-agent", "packed-agent.zot"}, | |||
| 67 | + | {"remote-agent", officialZotfileCollection + "/remote-agent"}, | |||
| 68 | + | {"missing.zot", "missing.zot"}, | |||
| 69 | + | {"agents/remote-agent", "https://github.com/patriceckhart/agents/remote-agent"}, | |||
| 70 | + | {`agents\remote-agent`, "https://github.com/patriceckhart/agents/remote-agent"}, | |||
| 71 | + | {"./missing-agent", "./missing-agent"}, | |||
| 72 | + | {"Remote-Agent", "Remote-Agent"}, | |||
| 73 | + | {"https://github.com/acme/agents/example", "https://github.com/acme/agents/example"}, | |||
| 74 | + | } | |||
| 75 | + | for _, tt := range tests { | |||
| 76 | + | got, err := resolveZotfileRef(tt.ref) | |||
| 77 | + | if err != nil { | |||
| 78 | + | t.Fatalf("resolve %q: %v", tt.ref, err) | |||
| 79 | + | } | |||
| 80 | + | if got != tt.want { | |||
| 81 | + | t.Errorf("resolve %q = %q, want %q", tt.ref, got, tt.want) | |||
| 82 | + | } | |||
| 83 | + | } | |||
| 84 | + | } | |||
| 85 | + | ||||
| 51 | func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) { | 86 | func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) { | ||
| 52 | dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`) | 87 | dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`) | ||
| 53 | ext := filepath.Join(dir, "extensions", "bad") | 88 | ext := filepath.Join(dir, "extensions", "bad") | ||