guild-cli 0.7.3__tar.gz → 0.8.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.
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/guild/scripts/create.sh +13 -5
- {guild_cli-0.7.3 → guild_cli-0.8.0}/CHANGELOG.md +32 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/CLAUDE.md +1 -1
- {guild_cli-0.7.3 → guild_cli-0.8.0}/PKG-INFO +1 -1
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/_provision_template.py +9 -1
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/create.py +90 -29
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/scaffold/__init__.py +3 -3
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/scaffold/instantiate.py +187 -52
- {guild_cli-0.7.3 → guild_cli-0.8.0}/pyproject.toml +1 -1
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_create.py +215 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_scaffold_instantiate.py +217 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/uv.lock +1 -1
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/agent-config/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/cicd/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/guild/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/guild/scripts/configure-repo.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/guild/scripts/overview.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/onboard/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/onboard/scripts/onboard.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/run-tests/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/teach/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/teach/scripts/teach.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/think/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/think/scripts/think.sh +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/version-bump/SKILL.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.claude/skills.local.yaml.example +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.devague/frames/guildmaster-ships-teach-and-onboard-two-agent-firs.json +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.devague/plans/guildmaster-ships-teach-and-onboard-two-agent-firs.json +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.flake8 +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.github/workflows/publish.yml +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.github/workflows/tests.yml +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.gitignore +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/.markdownlint-cli2.yaml +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/LICENSE +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/README.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/culture.yaml +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/docs/cutover.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/docs/plans/2026-05-24-guildmaster-ships-teach-and-onboard-two-agent-firs.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/docs/skill-sources.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/docs/specs/2026-05-24-guildmaster-ships-teach-and-onboard-two-agent-firs.md +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/__init__.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/__main__.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/__init__.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/__init__.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/_broadcast.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/explain.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/learn.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/onboard.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/overview.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/show.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/teach.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_commands/whoami.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_errors.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_output.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/cli/_repo.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/skills/__init__.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/skills/identity.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/skills/ledger.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/skills/render.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/guild/skills/sources.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/sonar-project.properties +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/__init__.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_broadcast_post.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_explain.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_learn.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_onboard.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_overview.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_show.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_teach.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_cli_whoami.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_skills_convention.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_skills_identity.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_skills_ledger.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_skills_render.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_skills_sources.py +0 -0
- {guild_cli-0.7.3 → guild_cli-0.8.0}/tests/test_version_fallback.py +0 -0
|
@@ -12,12 +12,20 @@ set -euo pipefail
|
|
|
12
12
|
# Usage:
|
|
13
13
|
# create.sh --agent OWNER/REPO --desc TEXT [--backend claude|acp]
|
|
14
14
|
# [--workspace-root DIR] [--template OWNER/REPO]
|
|
15
|
-
# [--org agentculture] [--
|
|
15
|
+
# [--org agentculture] [--command NAME] [--pkg NAME]
|
|
16
|
+
# [--dist NAME] [--apply] [--json]
|
|
16
17
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
18
|
+
# The repo name is the repo/agent identity (README, culture.yaml suffix,
|
|
19
|
+
# CLAUDE.md seed, repo URL). --command / --pkg / --dist each retarget one facet
|
|
20
|
+
# independently and default to the repo name (or, for --pkg, the underscore form
|
|
21
|
+
# of --command), so with no overrides the result is identical to before:
|
|
22
|
+
# --command NAME console-command name (the [project.scripts] key only); e.g.
|
|
23
|
+
# --command reachy for a repo reachy-mini-cli.
|
|
24
|
+
# --pkg NAME importable package; defaults to the underscore form of
|
|
25
|
+
# --command (pass only to decouple command from package).
|
|
26
|
+
# --dist NAME PyPI distribution name; e.g. --dist jetson-cli. Retargets
|
|
27
|
+
# [project].name, the importlib.metadata lookup, and the
|
|
28
|
+
# TestPyPI install pin.
|
|
21
29
|
#
|
|
22
30
|
# Exit codes:
|
|
23
31
|
# 0 success (delegates to `guild create`; its exit code propagates)
|
|
@@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-05-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`guild create`: repo≠command split via `--command` / `--pkg`**
|
|
13
|
+
([#43](https://github.com/agentculture/guildmaster/issues/43)). A new sibling
|
|
14
|
+
can now ship its CLI under a shorter name than the repo — the guildmaster
|
|
15
|
+
pattern (repo `guildmaster` → command/package `guild` → dist `guild-cli`) —
|
|
16
|
+
without a manual post-create edit.
|
|
17
|
+
- `--command NAME` sets the console-command (binary) name (default: the repo
|
|
18
|
+
token) and retargets **only** the `[project.scripts]` entry-point key.
|
|
19
|
+
- `--pkg NAME` sets the importable Python package (default: the underscore
|
|
20
|
+
form of `--command`, so command + import package stay in lock-step); pass it
|
|
21
|
+
only to decouple the two.
|
|
22
|
+
- The **repo token stays the repo/agent identity** (README heading,
|
|
23
|
+
`culture.yaml` suffix, CLAUDE.md seed, repo URL). `--command` / `--pkg` /
|
|
24
|
+
`--dist` are each independent; with no overrides the output is byte-identical
|
|
25
|
+
to before.
|
|
26
|
+
- Validation is fail-fast (before any external act): `--command` must be a PEP
|
|
27
|
+
503-ish name and the *effective* import package must be a valid, non-keyword
|
|
28
|
+
Python identifier (the error attributes the bad value to `--pkg`, else
|
|
29
|
+
`--command`, else the repo name).
|
|
30
|
+
- A `--dist`/`--command` rename still does **not** register a PyPI/TestPyPI
|
|
31
|
+
Trusted Publisher — `guild create` configures only the GitHub side.
|
|
32
|
+
- Pure transform: new `_resolve_identifiers` (single source of truth shared by
|
|
33
|
+
`transform_plan` / `transform_clone`) and `_retarget_command` (narrow
|
|
34
|
+
scripts-key rewrite, mirroring `_retarget_dist`).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
8
40
|
## [0.7.3] - 2026-05-29
|
|
9
41
|
|
|
10
42
|
### Added
|
|
@@ -108,7 +108,7 @@ two live broadcasters.
|
|
|
108
108
|
|
|
109
109
|
**Provisioning verb — `guild create` (template-instantiate a new sibling).**
|
|
110
110
|
|
|
111
|
-
- `guild create --agent OWNER/REPO --desc TEXT [--backend claude|acp] [--workspace-root DIR] [--template agentculture/culture-agent-template] [--dist NAME] [--apply] [--json]` — provision a brand-new AgentCulture sibling by instantiating the GitHub template, renaming identifiers throughout the clone, writing a self-init CLAUDE.md seed, configuring the GitHub repo (branch protection, environments, SONAR_TOKEN placeholder), pushing the genesis commit, and registering the agent in `docs/skill-sources.md`. Dry-run by default; `--apply` executes. The transform is **pure** (unit-testable against a fixture dir; no network): `culture_agent_template` → pkg (underscore form, e.g. `my_agent`), `culture-agent-template` → repo token (hyphen form, e.g. `my-agent`). `--dist NAME` sets the **PyPI distribution name** (default: the repo token); pass e.g. `--dist jetson-cli` to publish as `jetson-cli` while keeping the command and import package as the repo token (`jetson`) — this retargets only `[project].name`, the `importlib.metadata` lookup, and the TestPyPI install pin, matching the `guild-cli`/`guild` convention without a manual post-create edit. (Renaming the dist needs a matching Trusted Publisher registered for
|
|
111
|
+
- `guild create --agent OWNER/REPO --desc TEXT [--backend claude|acp] [--workspace-root DIR] [--template agentculture/culture-agent-template] [--command NAME] [--pkg NAME] [--dist NAME] [--apply] [--json]` — provision a brand-new AgentCulture sibling by instantiating the GitHub template, renaming identifiers throughout the clone, writing a self-init CLAUDE.md seed, configuring the GitHub repo (branch protection, environments, SONAR_TOKEN placeholder), pushing the genesis commit, and registering the agent in `docs/skill-sources.md`. Dry-run by default; `--apply` executes. The transform is **pure** (unit-testable against a fixture dir; no network): `culture_agent_template` → pkg (underscore form, e.g. `my_agent`), `culture-agent-template` → repo token (hyphen form, e.g. `my-agent`). **The repo token is the repo/agent identity only** — it always drives the README heading, `culture.yaml` suffix, CLAUDE.md seed, and repo URL. The **command**, **import package**, and **dist** are each independently retargetable and default to the repo token (or, for `--pkg`, the underscore form of `--command`); with no overrides the output is byte-identical to a plain rename. `--command NAME` sets the **console-command (binary) name** (default: the repo token) and retargets **only** the `[project.scripts]` entry-point key — e.g. `--command reachy` for repo `reachy-mini-cli` ships the command as `reachy`. `--pkg NAME` sets the **importable Python package** (default: the underscore form of `--command`, so command and import package stay in lock-step like `guild`/`guild`); pass it only to decouple the two. `--dist NAME` sets the **PyPI distribution name** (default: the repo token); pass e.g. `--dist jetson-cli` to publish as `jetson-cli` while keeping the command and import package as the repo token (`jetson`) — this retargets only `[project].name`, the `importlib.metadata` lookup, and the TestPyPI install pin, matching the `guild-cli`/`guild` convention without a manual post-create edit. (Renaming the dist or command does **not** register a Trusted Publisher — that needs a matching Trusted Publisher registered for the project name on PyPI/TestPyPI; `guild create` configures the GitHub side only.) The CLAUDE.md seed is a `/init`-style bootstrap placeholder — run `/init` in the new repo after provisioning to expand it into a full runtime prompt. Implementation: `guild/scaffold/instantiate.py` (pure transform: `_resolve_identifiers` + `_retarget_command` / `_retarget_dist`), `guild/cli/_commands/create.py` (CLI), `guild/cli/_commands/_provision_template.py` (injectable-runner executor), `.claude/skills/guild/scripts/create.sh` (thin wrapper).
|
|
112
112
|
|
|
113
113
|
**Inventory verbs — `overview` & `show` (the read-only surfaces).** guildmaster
|
|
114
114
|
owns the mesh's *inventory* surfaces per
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: guild-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: guildmaster — an agent and CLI that manages skills for the AgentCulture mesh.
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentculture/guildmaster
|
|
6
6
|
Project-URL: Issues, https://github.com/agentculture/guildmaster/issues
|
|
@@ -242,6 +242,8 @@ def apply(
|
|
|
242
242
|
runner: Runner | None = None,
|
|
243
243
|
template: str = DEFAULT_TEMPLATE,
|
|
244
244
|
dist: str | None = None,
|
|
245
|
+
command: str | None = None,
|
|
246
|
+
pkg: str | None = None,
|
|
245
247
|
poll_attempts: int = 30,
|
|
246
248
|
poll_delay: float = 2.0,
|
|
247
249
|
) -> dict:
|
|
@@ -269,6 +271,12 @@ def apply(
|
|
|
269
271
|
dist:
|
|
270
272
|
PyPI distribution name to set in the clone. ``None`` leaves it as the
|
|
271
273
|
repo name; a different value retargets the dist (see ``transform_clone``).
|
|
274
|
+
command:
|
|
275
|
+
Console-command name to set in the clone. ``None`` leaves it as the repo
|
|
276
|
+
name; a different value retargets the ``[project.scripts]`` key.
|
|
277
|
+
pkg:
|
|
278
|
+
Importable package name. ``None`` defaults to the underscore form of the
|
|
279
|
+
effective command (see ``transform_clone``).
|
|
272
280
|
poll_attempts / poll_delay:
|
|
273
281
|
``gh repo create --template`` copies the template content
|
|
274
282
|
**asynchronously**; cloning immediately yields an empty repo. After
|
|
@@ -319,7 +327,7 @@ def apply(
|
|
|
319
327
|
_check(clone, f"git clone {agent} failed", EXIT_ENV_ERROR)
|
|
320
328
|
|
|
321
329
|
# Step 4 — pure transform (no runner needed).
|
|
322
|
-
transform_clone(clone_dest, bare, desc, backend, dist=dist)
|
|
330
|
+
transform_clone(clone_dest, bare, desc, backend, dist=dist, command=command, pkg=pkg)
|
|
323
331
|
|
|
324
332
|
# Step 5 — stage everything.
|
|
325
333
|
add = runner(["git", "-C", str(clone_dest), "add", "-A"])
|
|
@@ -15,7 +15,8 @@ Usage
|
|
|
15
15
|
guild create --agent OWNER/REPO --desc TEXT [--org agentculture]
|
|
16
16
|
[--backend claude|acp] [--workspace-root DIR]
|
|
17
17
|
[--template agentculture/culture-agent-template]
|
|
18
|
-
[--
|
|
18
|
+
[--command NAME] [--pkg NAME] [--dist NAME]
|
|
19
|
+
[--apply] [--json]
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
from __future__ import annotations
|
|
@@ -35,6 +36,11 @@ from guild.cli._repo import repo_root
|
|
|
35
36
|
from guild.scaffold.instantiate import transform_plan as _transform_plan
|
|
36
37
|
from guild.skills import ledger as _ledger
|
|
37
38
|
|
|
39
|
+
# A PEP 503-ish name: a non-empty run of letters/digits/.-_ that starts and ends
|
|
40
|
+
# alphanumerically. PyPI normalises [-_.] runs, but the raw value must still look
|
|
41
|
+
# like a name. Shared by the --dist and --command validators.
|
|
42
|
+
_PEP503 = re.compile(r"^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$")
|
|
43
|
+
|
|
38
44
|
|
|
39
45
|
def register(sub: argparse._SubParsersAction) -> None:
|
|
40
46
|
parser = sub.add_parser(
|
|
@@ -87,6 +93,30 @@ def register(sub: argparse._SubParsersAction) -> None:
|
|
|
87
93
|
default=_provision.DEFAULT_TEMPLATE,
|
|
88
94
|
help=f"GitHub template repo to instantiate (default: {_provision.DEFAULT_TEMPLATE}).",
|
|
89
95
|
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"--command",
|
|
98
|
+
# Explicit dest: the bare "command" dest collides with the subparsers'
|
|
99
|
+
# ``dest="command"`` (which holds the chosen subcommand name).
|
|
100
|
+
dest="command_name",
|
|
101
|
+
metavar="NAME",
|
|
102
|
+
default=None,
|
|
103
|
+
help=(
|
|
104
|
+
"Console-command (binary) name. Defaults to the repo name; pass e.g. "
|
|
105
|
+
"'reachy' for a repo 'reachy-mini-cli' to ship the command as 'reachy'. "
|
|
106
|
+
"Retargets only the [project.scripts] entry-point key. The import "
|
|
107
|
+
"package follows it (underscore form) unless --pkg overrides."
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--pkg",
|
|
112
|
+
metavar="NAME",
|
|
113
|
+
default=None,
|
|
114
|
+
help=(
|
|
115
|
+
"Importable Python package name. Defaults to the underscore form of "
|
|
116
|
+
"--command, so command and import package stay in lock-step (like "
|
|
117
|
+
"guild/guild). Pass it only to decouple the two."
|
|
118
|
+
),
|
|
119
|
+
)
|
|
90
120
|
parser.add_argument(
|
|
91
121
|
"--dist",
|
|
92
122
|
metavar="NAME",
|
|
@@ -115,50 +145,77 @@ def register(sub: argparse._SubParsersAction) -> None:
|
|
|
115
145
|
|
|
116
146
|
|
|
117
147
|
# ---------------------------------------------------------------------------
|
|
118
|
-
#
|
|
148
|
+
# Validation (fail-fast, before any external act)
|
|
119
149
|
# ---------------------------------------------------------------------------
|
|
120
150
|
|
|
121
151
|
|
|
122
|
-
def
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
bare = agent.rsplit("/", 1)[-1]
|
|
126
|
-
|
|
127
|
-
# Fail fast (before any external act) if the repo name can't derive a valid
|
|
128
|
-
# Python package identifier — the derived pkg is both a directory name and a
|
|
129
|
-
# global rename token, so an invalid value (dots, leading digit) breaks the
|
|
130
|
-
# generated sibling.
|
|
131
|
-
pkg = bare.lower().replace("-", "_")
|
|
132
|
-
if not pkg.isidentifier() or keyword.iskeyword(pkg):
|
|
152
|
+
def _validate_pep503(value: str | None, flag: str, what: str, example: str) -> None:
|
|
153
|
+
"""Reject *value* (when given) if it is not a PEP 503-ish name."""
|
|
154
|
+
if value is not None and not _PEP503.match(value):
|
|
133
155
|
raise GuildError(
|
|
134
156
|
code=EXIT_USER_ERROR,
|
|
135
|
-
message=f"
|
|
157
|
+
message=f"{flag} {value!r} is not a valid {what}",
|
|
136
158
|
remediation=(
|
|
137
|
-
"use
|
|
138
|
-
"non-keyword Python identifier (e.g. not starting with a digit)"
|
|
159
|
+
"use letters/digits/.-_ starting and ending alphanumerically " f"(e.g. {example!r})"
|
|
139
160
|
),
|
|
140
161
|
)
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if args.
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
|
|
164
|
+
def _effective_pkg_for_validation(args: argparse.Namespace, repo_token: str) -> str:
|
|
165
|
+
"""Mirror the resolver: the import package the transform will actually use."""
|
|
166
|
+
effective_command = args.command_name if args.command_name is not None else repo_token
|
|
167
|
+
return args.pkg if args.pkg is not None else effective_command.replace("-", "_")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _validate_identifiers(args: argparse.Namespace, bare: str, repo_token: str) -> None:
|
|
171
|
+
"""Fail-fast validation of --command / --pkg / --dist before any external act.
|
|
172
|
+
|
|
173
|
+
The *effective* import package is both a directory name and a global rename
|
|
174
|
+
token, so an invalid value (dots, leading digit, keyword) breaks the
|
|
175
|
+
generated sibling. Attribute a bad value to --pkg, else --command, else the
|
|
176
|
+
repo name.
|
|
177
|
+
"""
|
|
178
|
+
_validate_pep503(args.command_name, "--command", "command name", "reachy")
|
|
179
|
+
|
|
180
|
+
effective_pkg = _effective_pkg_for_validation(args, repo_token)
|
|
181
|
+
if not effective_pkg.isidentifier() or keyword.iskeyword(effective_pkg):
|
|
182
|
+
if args.pkg is not None:
|
|
183
|
+
source = f"--pkg {args.pkg!r}"
|
|
184
|
+
elif args.command_name is not None:
|
|
185
|
+
source = f"--command {args.command_name!r}"
|
|
186
|
+
else:
|
|
187
|
+
source = f"repo name {bare!r}"
|
|
148
188
|
raise GuildError(
|
|
149
189
|
code=EXIT_USER_ERROR,
|
|
150
|
-
message=f"
|
|
190
|
+
message=f"{source} derives an invalid Python package name {effective_pkg!r}",
|
|
151
191
|
remediation=(
|
|
152
|
-
"
|
|
192
|
+
"the import package must be a valid, non-keyword Python identifier "
|
|
193
|
+
"(letters/digits/underscore, not starting with a digit); pass --pkg to set it"
|
|
153
194
|
),
|
|
154
195
|
)
|
|
155
196
|
|
|
197
|
+
_validate_pep503(args.dist, "--dist", "PyPI distribution name", "jetson-cli")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ---------------------------------------------------------------------------
|
|
201
|
+
# Handler
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _handle(args: argparse.Namespace) -> None:
|
|
206
|
+
root = repo_root()
|
|
207
|
+
agent = _broadcast.normalize_target(args.agent, args.org)
|
|
208
|
+
bare = agent.rsplit("/", 1)[-1]
|
|
209
|
+
repo_token = bare.lower()
|
|
210
|
+
|
|
211
|
+
_validate_identifiers(args, bare, repo_token)
|
|
212
|
+
|
|
156
213
|
# Resolve workspace root (parent of guildmaster if not supplied).
|
|
157
214
|
workspace_root = args.workspace_root if args.workspace_root is not None else root.parent
|
|
158
215
|
clone_dest = workspace_root / bare
|
|
159
216
|
|
|
160
217
|
# Build the dry-run plan (pure — no writes, no subprocess).
|
|
161
|
-
plan = _transform_plan(bare, args.desc, dist=args.dist)
|
|
218
|
+
plan = _transform_plan(bare, args.desc, dist=args.dist, command=args.command_name, pkg=args.pkg)
|
|
162
219
|
|
|
163
220
|
# Ledger diff (pure — reads only).
|
|
164
221
|
skills = _broadcast.canonical_skills(root)
|
|
@@ -198,6 +255,8 @@ def _handle(args: argparse.Namespace) -> None:
|
|
|
198
255
|
guildmaster_root=root,
|
|
199
256
|
template=args.template,
|
|
200
257
|
dist=args.dist,
|
|
258
|
+
command=args.command_name,
|
|
259
|
+
pkg=args.pkg,
|
|
201
260
|
)
|
|
202
261
|
|
|
203
262
|
# Register in the ledger idempotently.
|
|
@@ -213,6 +272,8 @@ def _handle(args: argparse.Namespace) -> None:
|
|
|
213
272
|
"repo": apply_result["repo"],
|
|
214
273
|
"clone_dest": apply_result["clone_dest"],
|
|
215
274
|
"pushed": apply_result["pushed"],
|
|
275
|
+
"command": plan["command"],
|
|
276
|
+
"pkg": plan["pkg"],
|
|
216
277
|
"dist": plan["dist"],
|
|
217
278
|
"ledger_written": ledger_written,
|
|
218
279
|
}
|
|
@@ -233,10 +294,10 @@ def _render_dry_run(result: dict) -> str:
|
|
|
233
294
|
f" agent : {result['agent']}",
|
|
234
295
|
f" backend : {result['backend']}",
|
|
235
296
|
f" clone dest : {result['clone_dest']}",
|
|
236
|
-
f"
|
|
237
|
-
f"
|
|
238
|
-
f"
|
|
239
|
-
|
|
297
|
+
f" repo token : {plan['repo_token']} (identity: README, culture.yaml, seed, URL)",
|
|
298
|
+
f" command : {plan['command']}",
|
|
299
|
+
f" pkg import : {plan['pkg']}",
|
|
300
|
+
f" dist (PyPI): {plan['dist']}",
|
|
240
301
|
"",
|
|
241
302
|
"── rename map ──",
|
|
242
303
|
]
|
|
@@ -8,7 +8,7 @@ new sibling agent.
|
|
|
8
8
|
Public surface
|
|
9
9
|
--------------
|
|
10
10
|
``instantiate`` — the transform module:
|
|
11
|
-
transform_clone(dest,
|
|
12
|
-
rename_map(bare) -> dict[str, str]
|
|
13
|
-
transform_plan(bare, desc) -> dict
|
|
11
|
+
transform_clone(dest, bare, desc, backend="claude", dist=None, command=None, pkg=None) -> None
|
|
12
|
+
rename_map(bare, pkg=None) -> dict[str, str]
|
|
13
|
+
transform_plan(bare, desc, dist=None, command=None, pkg=None) -> dict
|
|
14
14
|
"""
|