detached b99b1ae

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:
3636 | `packages/core` | Agent loop, messages, tool contracts, events, confirmation, sessions, compaction |
3737 | `packages/provider` | Provider clients, wire formats, streaming, retries, model metadata and discovery |
3838 | `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 |
4040 | `packages/agent/modes` | Interactive, print, JSON, dialog, bot, and Telegram interfaces |
4141 | `packages/agent/extensions` | Extension process lifecycle and host integration |
4242 | `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:
4242 - `packages/core`: provider-independent agent behavior and session structures
4343 - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior
4444 - `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
4646 - `packages/agent/modes`: interactive and non-interactive user modes
4747 - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK
4848 - `packages/agent/skills`: reusable instruction discovery
@@ -42,7 +42,7 @@ The major ownership boundaries are:
42 - `packages/core`: provider-independent agent behavior and session structures42 - `packages/core`: provider-independent agent behavior and session structures
43 - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior43 - `packages/provider`: provider requests, streaming responses, model discovery, retries, and wire-specific behavior
44 - `packages/provider/auth`: credential and login handling44 - `packages/provider/auth`: credential and login handling
45-- `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and Zotfiles45+- `packages/agent`: command-line orchestration, configuration, runtime assembly, RPC, and zotfiles
46 - `packages/agent/modes`: interactive and non-interactive user modes46 - `packages/agent/modes`: interactive and non-interactive user modes
47 - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK47 - `packages/agent/extensions`, `extproto`, and `ext`: extension hosting, protocol, and SDK
48 - `packages/agent/skills`: reusable instruction discovery48 - `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
215215
216216 ## zotfile agents
217217
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:
219219
220220 ```bash
221221 zot run ./my-agent
222222 zot run ./my-agent.zot
223+zot run zot-maintenance --cwd /path/to/zot
223224 zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot
224225 ```
225226
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.
227228
228229 ## Embedding
229230
@@ -215,15 +215,16 @@ When the sandbox is on (see `/jail`), all four tools refuse paths outside the se
215 215
216 ## zotfile agents216 ## 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 ```bash220 ```bash
221 zot run ./my-agent221 zot run ./my-agent
222 zot run ./my-agent.zot222 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/zot224 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 ## Embedding229 ## Embedding
229 230
M docs/zotfiles.md +28 -10
@@ -1,8 +1,8 @@
1-# Zotfile agents
1+# zotfile agents
22
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.
44
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.
66
77 ## Quick start
88
@@ -141,7 +141,7 @@ description: Diagnose a failing command from its output and relevant source file
141141 4. Propose the smallest correction and validation plan.
142142 ```
143143
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.
145145
146146 ## Manifest reference
147147
@@ -271,7 +271,7 @@ Use `requirements.bin` for commands that must already be on `PATH`:
271271 }
272272 ```
273273
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.
275275
276276 ### Entry fields
277277
@@ -465,7 +465,7 @@ The directory is created for every run, but the agent can access it only when `$
465465
466466 ## Agent-scoped sessions
467467
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:
469469
470470 ```text
471471 $ZOT_HOME/sessions/agents/<name>/
@@ -494,7 +494,24 @@ zot run ./my-agent.zot --json "Do the task"
494494
495495 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.
496496
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:
498515
499516 ```bash
500517 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
508525
509526 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`.
510527
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.
512529
513530 ### `zot pack`
514531
@@ -578,6 +595,7 @@ Archive extraction rejects absolute paths and parent traversal. Unsupported tar
578595 Implemented now:
579596
580597 - local directories and `.zot` archives
598+- local-first short-name resolution to the official GitHub agent collection
581599 - temporary execution of agent directories from public GitHub repositories
582600 - `zot pack`, `zot inspect`, `zot verify`, and `zot run`
583601 - canonical tar creation with zstd compression
@@ -595,7 +613,7 @@ Implemented now:
595613 Not implemented yet:
596614
597615 - `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
599617 - network allowlist enforcement
600618 - environment-variable filtering
601619 - safely confined bundled executable extensions
@@ -604,4 +622,4 @@ Not implemented yet:
604622 - per-agent Telegram, bot, or RPC selection
605623 - `--no-sandbox`, `--trust-bash`, or registry trust override flags
606624
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 agents1+# 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 start7 ## 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 reference146 ## 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 fields276 ### 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 sessions466 ## 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 ```text470 ```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 ```bash516 ```bash
500 zot run https://github.com/patriceckhart/agents/zot-maintenance --cwd /path/to/zot517 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` archives597 - 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 repositories599 - 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 compression601 - 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 resolution616+- installed-name, arbitrary URL, OCI, configurable registry, or zot.sh index resolution
599 - network allowlist enforcement617 - network allowlist enforcement
600 - environment-variable filtering618 - environment-variable filtering
601 - safely confined bundled executable extensions619 - safely confined bundled executable extensions
@@ -604,4 +622,4 @@ Not implemented yet:
604 - per-agent Telegram, bot, or RPC selection622 - per-agent Telegram, bot, or RPC selection
605 - `--no-sandbox`, `--trust-bash`, or registry trust override flags623 - `--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 {
103103 SwarmAgent string
104104
105105 // 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
107107 // manifest's declared file/bash permissions.
108108 AgentName string
109109 AgentDataDir string
@@ -103,7 +103,7 @@ type Args struct {
103 SwarmAgent string103 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 the106+ // 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 string108 AgentName string
109 AgentDataDir string109 AgentDataDir string
M packages/agent/zotfile.go +45 -3
@@ -77,12 +77,12 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
7777 return true, zotPack(dir, out)
7878 case "inspect":
7979 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")
8181 }
8282 return true, zotInspect(rawArgs[1])
8383 case "verify":
8484 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")
8686 }
8787 zf, cleanup, err := loadZotfile(rawArgs[1])
8888 if cleanup != nil {
@@ -95,7 +95,7 @@ func runZotfileCommand(rawArgs []string, version string) (bool, error) {
9595 return true, nil
9696 case "run":
9797 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")
9999 }
100100 ref := rawArgs[1]
101101 rest := rawArgs[2:]
@@ -318,6 +318,11 @@ func zotPack(dir, out string) error {
318318 }
319319
320320 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
321326 if u, err := url.Parse(ref); err == nil && (u.Scheme == "http" || u.Scheme == "https") {
322327 return loadRemoteZotfile(u)
323328 }
@@ -358,6 +363,43 @@ func loadZotfile(ref string) (zotfileLoaded, func(), error) {
358363 return zotfileLoaded{Dir: tmp, Temp: true, Digest: digest, Manifest: m}, cleanup, nil
359364 }
360365
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+
361403 var zotfileHTTPClient = &http.Client{Timeout: 60 * time.Second}
362404
363405 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, nil95 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, nil363 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) {
4848 }
4949 }
5050
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+
5186 func TestLoadZotfileRejectsBundledExecutableExtension(t *testing.T) {
5287 dir := writeTestZotfile(t, `{"zotfile":1,"name":"test"}`)
5388 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")

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close