substack-cli 0.9.0__py3-none-any.whl
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.
- substack_cli/__init__.py +13 -0
- substack_cli/__main__.py +10 -0
- substack_cli/cli/__init__.py +136 -0
- substack_cli/cli/_commands/__init__.py +1 -0
- substack_cli/cli/_commands/cli.py +43 -0
- substack_cli/cli/_commands/doctor.py +194 -0
- substack_cli/cli/_commands/explain.py +38 -0
- substack_cli/cli/_commands/learn.py +88 -0
- substack_cli/cli/_commands/overview.py +112 -0
- substack_cli/cli/_commands/whoami.py +106 -0
- substack_cli/cli/_errors.py +42 -0
- substack_cli/cli/_output.py +53 -0
- substack_cli/explain/__init__.py +24 -0
- substack_cli/explain/catalog.py +136 -0
- substack_cli-0.9.0.dist-info/METADATA +137 -0
- substack_cli-0.9.0.dist-info/RECORD +19 -0
- substack_cli-0.9.0.dist-info/WHEEL +4 -0
- substack_cli-0.9.0.dist-info/entry_points.txt +2 -0
- substack_cli-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""``substack-cli whoami`` — the smallest identity probe.
|
|
2
|
+
|
|
3
|
+
Reports the agent's identity as declared in ``culture.yaml``: its nick
|
|
4
|
+
(``suffix``), the backend it runs on, and the served model (if any) — plus the
|
|
5
|
+
package version. Read-only; touches nothing but its own ``culture.yaml``.
|
|
6
|
+
|
|
7
|
+
When you clone this template, rename the package and update ``culture.yaml`` —
|
|
8
|
+
``whoami`` then reflects your new agent's identity with no code change.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from substack_cli import __version__
|
|
17
|
+
from substack_cli.cli._output import emit_result
|
|
18
|
+
|
|
19
|
+
_FALLBACK_NICK = "substack-cli"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def find_culture_yaml() -> Path | None:
|
|
23
|
+
"""Locate this agent's own ``culture.yaml`` by walking up from this module.
|
|
24
|
+
|
|
25
|
+
The identity must be the agent's own, not whatever ``culture.yaml`` happens
|
|
26
|
+
to sit in the caller's current working directory. In an editable / source
|
|
27
|
+
install, walking up from ``__file__`` finds the repo root; in a wheel
|
|
28
|
+
install no ``culture.yaml`` ships alongside the package and the caller falls
|
|
29
|
+
back to the literal defaults.
|
|
30
|
+
"""
|
|
31
|
+
here = Path(__file__).resolve()
|
|
32
|
+
for parent in here.parents:
|
|
33
|
+
candidate = parent / "culture.yaml"
|
|
34
|
+
if candidate.is_file():
|
|
35
|
+
return candidate
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def read_agent_fields() -> dict[str, str]:
|
|
40
|
+
"""Return ``suffix``/``backend``/``model`` from the first agent block.
|
|
41
|
+
|
|
42
|
+
Parsed without a YAML dependency to keep the runtime deps empty. Reads
|
|
43
|
+
top-level ``key: value`` lines within the first agent entry; anything
|
|
44
|
+
fancier than the documented shape falls back to the defaults below.
|
|
45
|
+
"""
|
|
46
|
+
fields = {"nick": _FALLBACK_NICK, "backend": "unknown", "model": "unknown"}
|
|
47
|
+
cfg = find_culture_yaml()
|
|
48
|
+
if cfg is None:
|
|
49
|
+
return fields
|
|
50
|
+
try:
|
|
51
|
+
text = cfg.read_text(encoding="utf-8")
|
|
52
|
+
except OSError:
|
|
53
|
+
return fields
|
|
54
|
+
seen_agent = False
|
|
55
|
+
for line in text.splitlines():
|
|
56
|
+
stripped = line.strip()
|
|
57
|
+
if stripped.startswith(("- suffix:", "suffix:")):
|
|
58
|
+
if seen_agent: # second agent block — stop at the first
|
|
59
|
+
break
|
|
60
|
+
seen_agent = True
|
|
61
|
+
fields["nick"] = _scalar(stripped, "suffix")
|
|
62
|
+
elif seen_agent and stripped.startswith("backend:"):
|
|
63
|
+
fields["backend"] = _scalar(stripped, "backend")
|
|
64
|
+
elif seen_agent and stripped.startswith("model:"):
|
|
65
|
+
fields["model"] = _scalar(stripped, "model")
|
|
66
|
+
return fields
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _scalar(line: str, key: str) -> str:
|
|
70
|
+
"""Extract the scalar after ``key:`` from a ``culture.yaml`` line."""
|
|
71
|
+
_, _, value = line.partition(f"{key}:")
|
|
72
|
+
return value.strip().strip("'\"") or "unknown"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def report() -> dict[str, object]:
|
|
76
|
+
fields = read_agent_fields()
|
|
77
|
+
return {
|
|
78
|
+
"nick": fields["nick"],
|
|
79
|
+
"version": __version__,
|
|
80
|
+
"backend": fields["backend"],
|
|
81
|
+
"model": fields["model"],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cmd_whoami(args: argparse.Namespace) -> None:
|
|
86
|
+
identity = report()
|
|
87
|
+
json_mode = bool(getattr(args, "json", False))
|
|
88
|
+
if json_mode:
|
|
89
|
+
emit_result(identity, json_mode=True)
|
|
90
|
+
return
|
|
91
|
+
text = (
|
|
92
|
+
f"nick: {identity['nick']}\n"
|
|
93
|
+
f"version: {identity['version']}\n"
|
|
94
|
+
f"backend: {identity['backend']}\n"
|
|
95
|
+
f"model: {identity['model']}"
|
|
96
|
+
)
|
|
97
|
+
emit_result(text, json_mode=False)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
101
|
+
p = sub.add_parser(
|
|
102
|
+
"whoami",
|
|
103
|
+
help="Report this agent's nick, version, backend, and served model.",
|
|
104
|
+
)
|
|
105
|
+
p.add_argument("--json", action="store_true", help="Emit structured JSON.")
|
|
106
|
+
p.set_defaults(func=cmd_whoami)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""CliError and exit-code policy (stable-contract).
|
|
2
|
+
|
|
3
|
+
Every failure inside substack-cli raises :class:`CliError`. The
|
|
4
|
+
top-level ``main()`` catches it, formats via :mod:`substack_cli.cli._output`,
|
|
5
|
+
and exits with :attr:`CliError.code`. This guarantees:
|
|
6
|
+
|
|
7
|
+
* no Python traceback leaks to stderr (the agent-first error contract);
|
|
8
|
+
* every error has a structured shape ``{code, message, remediation}``;
|
|
9
|
+
* the exit-code policy is centralised in one place.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
# Exit-code policy. Documented in ``substack-cli learn`` output.
|
|
17
|
+
# 0 = success
|
|
18
|
+
# 1 = user-input error (bad flag, missing required arg, unknown path)
|
|
19
|
+
# 2 = environment / setup error (tool not installed, file unreadable)
|
|
20
|
+
# 3+ = reserved for future categorisation
|
|
21
|
+
EXIT_SUCCESS = 0
|
|
22
|
+
EXIT_USER_ERROR = 1
|
|
23
|
+
EXIT_ENV_ERROR = 2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class CliError(Exception):
|
|
28
|
+
"""Structured error raised within the CLI; carries a remediation hint for agents."""
|
|
29
|
+
|
|
30
|
+
code: int
|
|
31
|
+
message: str
|
|
32
|
+
remediation: str = ""
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
super().__init__(self.message)
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict[str, object]:
|
|
38
|
+
return {
|
|
39
|
+
"code": self.code,
|
|
40
|
+
"message": self.message,
|
|
41
|
+
"remediation": self.remediation,
|
|
42
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""stdout / stderr helpers with a strict split (stable-contract).
|
|
2
|
+
|
|
3
|
+
Rule: **results go to stdout, diagnostics and errors go to stderr.** Agents
|
|
4
|
+
parsing output can rely on this invariant. JSON mode routes structured
|
|
5
|
+
payloads to the same streams — never mixes them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Any, TextIO
|
|
13
|
+
|
|
14
|
+
from substack_cli.cli._errors import CliError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def emit_result(data: Any, *, json_mode: bool, stream: TextIO | None = None) -> None:
|
|
18
|
+
"""Write a command result to stdout (or ``stream``)."""
|
|
19
|
+
s = stream if stream is not None else sys.stdout
|
|
20
|
+
if json_mode:
|
|
21
|
+
json.dump(data, s, ensure_ascii=False)
|
|
22
|
+
s.write("\n")
|
|
23
|
+
return
|
|
24
|
+
text = data if isinstance(data, str) else str(data)
|
|
25
|
+
s.write(text)
|
|
26
|
+
if not text.endswith("\n"):
|
|
27
|
+
s.write("\n")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def emit_error(err: CliError, *, json_mode: bool, stream: TextIO | None = None) -> None:
|
|
31
|
+
"""Write a :class:`CliError` to stderr.
|
|
32
|
+
|
|
33
|
+
Text mode renders as two lines when a remediation is present::
|
|
34
|
+
|
|
35
|
+
error: <message>
|
|
36
|
+
hint: <remediation>
|
|
37
|
+
|
|
38
|
+
The ``hint:`` prefix is required by the agent-first error rubric.
|
|
39
|
+
"""
|
|
40
|
+
s = stream if stream is not None else sys.stderr
|
|
41
|
+
if json_mode:
|
|
42
|
+
json.dump(err.to_dict(), s, ensure_ascii=False)
|
|
43
|
+
s.write("\n")
|
|
44
|
+
return
|
|
45
|
+
s.write(f"error: {err.message}\n")
|
|
46
|
+
if err.remediation:
|
|
47
|
+
s.write(f"hint: {err.remediation}\n")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def emit_diagnostic(message: str, *, stream: TextIO | None = None) -> None:
|
|
51
|
+
"""Write a human diagnostic (progress, summary) to stderr."""
|
|
52
|
+
s = stream if stream is not None else sys.stderr
|
|
53
|
+
s.write(message if message.endswith("\n") else message + "\n")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Explain catalog — markdown keyed by command-path tuples (stable-contract).
|
|
2
|
+
|
|
3
|
+
Every noun/verb registered in the CLI should have a catalog entry.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from substack_cli.cli._errors import EXIT_USER_ERROR, CliError
|
|
9
|
+
from substack_cli.explain.catalog import ENTRIES
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def resolve(path: tuple[str, ...]) -> str:
|
|
13
|
+
if path in ENTRIES:
|
|
14
|
+
return ENTRIES[path]
|
|
15
|
+
display = " ".join(path) if path else "<root>"
|
|
16
|
+
raise CliError(
|
|
17
|
+
code=EXIT_USER_ERROR,
|
|
18
|
+
message=f"no explain entry for: {display}",
|
|
19
|
+
remediation="list entries with: substack-cli explain substack-cli",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def known_paths() -> list[tuple[str, ...]]:
|
|
24
|
+
return list(ENTRIES.keys())
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Markdown catalog for ``substack-cli explain <path>``.
|
|
2
|
+
|
|
3
|
+
Each entry is verbatim markdown. Keys are command-path tuples. The empty tuple
|
|
4
|
+
and ``("substack-cli",)`` both resolve to the root entry.
|
|
5
|
+
|
|
6
|
+
Keep bodies self-contained: an agent reading one entry should get enough
|
|
7
|
+
context without chaining reads.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
_ROOT = """\
|
|
13
|
+
# substack-cli
|
|
14
|
+
|
|
15
|
+
A clonable template for AgentCulture mesh agents. It carries an agent-first CLI
|
|
16
|
+
(cited from the teken `python-cli` reference), a mesh identity (`culture.yaml` +
|
|
17
|
+
`CLAUDE.md`), the canonical guildmaster skill kit under `.claude/skills/`, and a
|
|
18
|
+
buildable/deployable package baseline. Clone it, rename the package, edit
|
|
19
|
+
`culture.yaml`, and you have a new agent.
|
|
20
|
+
|
|
21
|
+
## Verbs
|
|
22
|
+
|
|
23
|
+
- `substack-cli whoami` — identity probe from `culture.yaml`.
|
|
24
|
+
- `substack-cli learn` — structured self-teaching prompt.
|
|
25
|
+
- `substack-cli explain <path>` — markdown docs for any noun/verb.
|
|
26
|
+
- `substack-cli overview` — descriptive snapshot of the agent.
|
|
27
|
+
- `substack-cli doctor` — check the agent-identity invariants.
|
|
28
|
+
- `substack-cli cli overview` — describe the CLI surface.
|
|
29
|
+
|
|
30
|
+
## Exit-code policy
|
|
31
|
+
|
|
32
|
+
- `0` success
|
|
33
|
+
- `1` user-input error
|
|
34
|
+
- `2` environment / setup error
|
|
35
|
+
- `3+` reserved
|
|
36
|
+
|
|
37
|
+
## See also
|
|
38
|
+
|
|
39
|
+
- `substack-cli explain whoami`
|
|
40
|
+
- `substack-cli explain doctor`
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
_WHOAMI = """\
|
|
44
|
+
# substack-cli whoami
|
|
45
|
+
|
|
46
|
+
Reports the agent's identity from `culture.yaml`: nick (`suffix`), backend,
|
|
47
|
+
served model, and the package version. Read-only.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
substack-cli whoami
|
|
52
|
+
substack-cli whoami --json
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
_LEARN = """\
|
|
56
|
+
# substack-cli learn
|
|
57
|
+
|
|
58
|
+
Prints a structured self-teaching prompt covering purpose, command map,
|
|
59
|
+
exit-code policy, `--json` support, and the `explain` pointer.
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
substack-cli learn
|
|
64
|
+
substack-cli learn --json
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
_EXPLAIN = """\
|
|
68
|
+
# substack-cli explain <path>
|
|
69
|
+
|
|
70
|
+
Prints markdown documentation for any noun/verb path. Unlike `--help` (terse,
|
|
71
|
+
positional), `explain` is global and addressable by path.
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
substack-cli explain substack-cli
|
|
76
|
+
substack-cli explain whoami
|
|
77
|
+
substack-cli explain --json <path>
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_OVERVIEW = """\
|
|
81
|
+
# substack-cli overview
|
|
82
|
+
|
|
83
|
+
Read-only descriptive snapshot of the agent: identity (from `culture.yaml`), the
|
|
84
|
+
verb surface, and the sibling-pattern artifacts the template carries. Accepts an
|
|
85
|
+
ignored `target` so a stray path never hard-fails.
|
|
86
|
+
|
|
87
|
+
## Usage
|
|
88
|
+
|
|
89
|
+
substack-cli overview
|
|
90
|
+
substack-cli overview --json
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
_DOCTOR = """\
|
|
94
|
+
# substack-cli doctor
|
|
95
|
+
|
|
96
|
+
Checks the agent-identity invariants `steward doctor` verifies:
|
|
97
|
+
prompt-file-present and backend-consistency (`claude` → `CLAUDE.md`), plus a
|
|
98
|
+
skills-present check. Exits 1 when unhealthy.
|
|
99
|
+
|
|
100
|
+
prompt-file-present requires the *resident* prompt the declared backend
|
|
101
|
+
actually reads. Other harness prompt files recognized under the same backend
|
|
102
|
+
name (`AGENTS.override.md`, `.pi/SYSTEM.md`, `QWEN.md`) belong to
|
|
103
|
+
interactively available harnesses the mesh daemon never loads; they are
|
|
104
|
+
reported by the informational harness-prompts check and never substituted.
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
substack-cli doctor
|
|
109
|
+
substack-cli doctor --json
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
_CLI = """\
|
|
113
|
+
# substack-cli cli
|
|
114
|
+
|
|
115
|
+
Noun group for CLI-surface introspection. `cli overview` describes the CLI
|
|
116
|
+
itself (distinct from the global `overview`, which describes the agent).
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
substack-cli cli overview
|
|
121
|
+
substack-cli cli overview --json
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
ENTRIES: dict[tuple[str, ...], str] = {
|
|
126
|
+
(): _ROOT,
|
|
127
|
+
("substack-cli",): _ROOT,
|
|
128
|
+
("substack",): _ROOT,
|
|
129
|
+
("whoami",): _WHOAMI,
|
|
130
|
+
("learn",): _LEARN,
|
|
131
|
+
("explain",): _EXPLAIN,
|
|
132
|
+
("overview",): _OVERVIEW,
|
|
133
|
+
("doctor",): _DOCTOR,
|
|
134
|
+
("cli",): _CLI,
|
|
135
|
+
("cli", "overview"): _CLI,
|
|
136
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: substack-cli
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Agent-first CLI to manage a Substack publication and account: publish and schedule posts, read posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack.
|
|
5
|
+
Project-URL: Homepage, https://github.com/agentculture/substack-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/agentculture/substack-cli/issues
|
|
7
|
+
Author: AgentCulture
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# substack-cli
|
|
19
|
+
|
|
20
|
+
Agent-first CLI to manage a Substack publication and account: publish and schedule posts, read posts and comments, run audience and post statistics, and manage subscribers. Unofficial community tool, not affiliated with Substack.
|
|
21
|
+
|
|
22
|
+
## What you get
|
|
23
|
+
|
|
24
|
+
- **An agent-first CLI** cited from [teken](https://github.com/agentculture/teken)
|
|
25
|
+
(`afi-cli`) — the runtime package has no third-party dependencies.
|
|
26
|
+
- **A mesh identity** — `culture.yaml` (`suffix` + `backend`) and the matching
|
|
27
|
+
resident prompt file (`CLAUDE.md`, since this template runs
|
|
28
|
+
`backend: claude`). The mesh resident is one of **two separate
|
|
29
|
+
selections** over this clone — see
|
|
30
|
+
[Two selections, not one](#two-selections-not-one) below.
|
|
31
|
+
- **Four harness prompt files**, one per agent harness, each read by exactly
|
|
32
|
+
one of them (see [Prompt files by harness](#prompt-files-by-harness) below).
|
|
33
|
+
All four harnesses are usable interactively regardless of which one
|
|
34
|
+
`culture.yaml` names as the mesh resident.
|
|
35
|
+
- **The canonical guildmaster skill kit** (11 skills) under `.claude/skills/`,
|
|
36
|
+
vendored cite-don't-import. See [`docs/skill-sources.md`](docs/skill-sources.md).
|
|
37
|
+
- **A build + deploy baseline** — pytest, lint, the agent-first rubric gate, and
|
|
38
|
+
PyPI Trusted Publishing wired into GitHub Actions.
|
|
39
|
+
|
|
40
|
+
## Prompt files by harness
|
|
41
|
+
|
|
42
|
+
Four harnesses, four root files, no shared base — each file is read by
|
|
43
|
+
exactly one harness:
|
|
44
|
+
|
|
45
|
+
| Harness | File(s) |
|
|
46
|
+
|---------|---------|
|
|
47
|
+
| Claude Code | [`CLAUDE.md`](CLAUDE.md) |
|
|
48
|
+
| Pi / associate | [`AGENTS.override.md`](AGENTS.override.md) + [`.pi/SYSTEM.md`](.pi/SYSTEM.md) |
|
|
49
|
+
| colleague | [`AGENTS.colleague.md`](AGENTS.colleague.md) |
|
|
50
|
+
| Qwen Code | [`QWEN.md`](QWEN.md) |
|
|
51
|
+
|
|
52
|
+
**Claude Code** — `CLAUDE.md` is the fullest write-up of the repo's
|
|
53
|
+
conventions; read it first.
|
|
54
|
+
|
|
55
|
+
**Pi / associate** — `AGENTS.override.md` replaces this directory's
|
|
56
|
+
`AGENTS.md`/`CLAUDE.md` in Pi's context layer, so Pi does not inherit
|
|
57
|
+
`CLAUDE.md`. `.pi/SYSTEM.md` replaces Pi's default system prompt with the
|
|
58
|
+
non-coding `associate` identity (read/find/summarize only).
|
|
59
|
+
|
|
60
|
+
**colleague** — colleague's prompt cascade is `AGENTS.md` →
|
|
61
|
+
`AGENTS.colleague.md` → `AGENTS.colleague.<model>.md`. This repo ships only
|
|
62
|
+
the middle layer: there is no `AGENTS.md` (a shared base across harnesses was
|
|
63
|
+
considered and rejected) and no per-model override file.
|
|
64
|
+
|
|
65
|
+
**Qwen Code** — Qwen Code reads `QWEN.md` and `AGENTS.md`; since there is no
|
|
66
|
+
`AGENTS.md`, `QWEN.md` is its sole source of guidance.
|
|
67
|
+
|
|
68
|
+
There is intentionally **no `AGENTS.md`** at the root — each harness gets an
|
|
69
|
+
unrelated file rather than cascading from a shared base.
|
|
70
|
+
|
|
71
|
+
## Two selections, not one
|
|
72
|
+
|
|
73
|
+
It is tempting to read "switch harness" as one decision. It is actually two,
|
|
74
|
+
and this template exists partly to keep them separate:
|
|
75
|
+
|
|
76
|
+
1. **The interactive harness** — which binary you run (`claude`, `pi`,
|
|
77
|
+
`colleague`, `qwen`). `cd` into the clone and run any of them; all four
|
|
78
|
+
are live simultaneously, and none of them requires editing a file or
|
|
79
|
+
flipping a switch. A harness can be force-selected for one invocation
|
|
80
|
+
(e.g. a CI smoke check) without ever touching `culture.yaml` — see
|
|
81
|
+
[`docs/automation-contract.md`](docs/automation-contract.md).
|
|
82
|
+
2. **The mesh resident** — the single `backend` `culture.yaml` declares,
|
|
83
|
+
which is what the Culture daemon starts and what `steward doctor`
|
|
84
|
+
checks. `guild harness use <name>` changes only this.
|
|
85
|
+
|
|
86
|
+
`culture.yaml`'s `backend` affects (2) only. It never affects which harness
|
|
87
|
+
you can invoke interactively in (1). See
|
|
88
|
+
[`docs/harness-selection.md`](docs/harness-selection.md) for the full
|
|
89
|
+
writeup, including who reads this config and why existing siblings are not
|
|
90
|
+
retrofitted by this arc.
|
|
91
|
+
|
|
92
|
+
## Quickstart
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv sync
|
|
96
|
+
uv run pytest -n auto # run the test suite
|
|
97
|
+
uv run substack-cli whoami # identity from culture.yaml
|
|
98
|
+
uv run substack-cli learn # self-teaching prompt (add --json)
|
|
99
|
+
uv run teken cli doctor . --strict # the agent-first rubric gate CI runs
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## CLI
|
|
103
|
+
|
|
104
|
+
| Verb | What it does |
|
|
105
|
+
|------|--------------|
|
|
106
|
+
| `whoami` | Report this agent's nick, version, backend, and model from `culture.yaml`. |
|
|
107
|
+
| `learn` | Print a structured self-teaching prompt. |
|
|
108
|
+
| `explain <path>` | Markdown docs for any noun/verb path. |
|
|
109
|
+
| `overview` | Read-only descriptive snapshot of the agent. |
|
|
110
|
+
| `doctor` | Check the agent-identity invariants (prompt-file-present, backend-consistency). |
|
|
111
|
+
| `cli overview` | Describe the CLI surface itself. |
|
|
112
|
+
|
|
113
|
+
Every command supports `--json`. Results go to stdout, errors/diagnostics to
|
|
114
|
+
stderr (never mixed). Exit codes: `0` success, `1` user error, `2` environment
|
|
115
|
+
error, `3+` reserved.
|
|
116
|
+
|
|
117
|
+
## Make it your own
|
|
118
|
+
|
|
119
|
+
1. Rename the package `substack_cli/` and the `substack-cli`
|
|
120
|
+
CLI/dist name throughout `pyproject.toml`, the package, `tests/`,
|
|
121
|
+
`sonar-project.properties`, and this `README.md`. The name is hard-coded in
|
|
122
|
+
~100 places, so list every occurrence first — see the `git grep` discovery
|
|
123
|
+
command in [`CLAUDE.md`](CLAUDE.md), the authoritative rename procedure.
|
|
124
|
+
2. Edit `culture.yaml` with your `suffix` and `backend`.
|
|
125
|
+
3. Rewrite `CLAUDE.md` for your agent and run `/init`. Rewrite the other three
|
|
126
|
+
harness files (`AGENTS.override.md` + `.pi/SYSTEM.md`, `AGENTS.colleague.md`,
|
|
127
|
+
`QWEN.md`) too if your agent uses those harnesses — don't let them drift out
|
|
128
|
+
of sync with `CLAUDE.md`.
|
|
129
|
+
4. Re-vendor only the skills you need from guildmaster (see
|
|
130
|
+
[`docs/skill-sources.md`](docs/skill-sources.md)).
|
|
131
|
+
|
|
132
|
+
See [`CLAUDE.md`](CLAUDE.md) for the full conventions (version-bump-every-PR,
|
|
133
|
+
the `cicd` PR lane, deploy setup).
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
Apache 2.0 — see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
substack_cli/__init__.py,sha256=mVRyVa6yeX2aqdyi7NrlPJA3V01BkJve5vkkuuWrk2c,406
|
|
2
|
+
substack_cli/__main__.py,sha256=bYagIxRrQAwmYxYTY6sN1weaqhJwH60XMyNQCcPgd78,182
|
|
3
|
+
substack_cli/cli/__init__.py,sha256=9B9ThdCOcBVXVc-deqXUG_Edcw9vm3gYA11stXLKiIs,5016
|
|
4
|
+
substack_cli/cli/_errors.py,sha256=VFCJak9qVwkGzusUmBn6s0HNfyhozHvjkJxU7swEPRE,1307
|
|
5
|
+
substack_cli/cli/_output.py,sha256=6cabSCl26xx7N0SE3-mgyd0MitvoJ4fXaUnx8SuKE24,1711
|
|
6
|
+
substack_cli/cli/_commands/__init__.py,sha256=DqpfVkhBo7cj6eWOpSlwx1ag0nTbrYJkV8pnPgl2gyU,70
|
|
7
|
+
substack_cli/cli/_commands/cli.py,sha256=frg0r-uiDzifLZG_4dhNpZSbe1bfG0-z85_9FkOhNWY,1689
|
|
8
|
+
substack_cli/cli/_commands/doctor.py,sha256=YUep70WZFiaXKDVBkr3gCxRlgZSs4KlgNqoxKORGWho,7864
|
|
9
|
+
substack_cli/cli/_commands/explain.py,sha256=RSjbTocY8EDtC4p9hX7-BDyw_CkklpwAVz84snu54qQ,1237
|
|
10
|
+
substack_cli/cli/_commands/learn.py,sha256=aHBK3RjMUFtEF9HNrEs_yT8QbGR-n2w9G46-OcfJJnc,3027
|
|
11
|
+
substack_cli/cli/_commands/overview.py,sha256=xy9fIpRbBmObcgchCngHtzdxGr22WtpLqbRELyYRlgw,3971
|
|
12
|
+
substack_cli/cli/_commands/whoami.py,sha256=Q8Zmh_0Gwg7umgV_DSWgqovpVAiOsHry3h3OJ0Erl3w,3658
|
|
13
|
+
substack_cli/explain/__init__.py,sha256=elAzIX4yqKIDUHo9VsTotcbOO_jVp9faB_ZHQZM_zes,712
|
|
14
|
+
substack_cli/explain/catalog.py,sha256=v6cMCeGr3tSDCwj1V1FwzMUEiiZUTd-JD28Gt68GTZU,3649
|
|
15
|
+
substack_cli-0.9.0.dist-info/METADATA,sha256=e7vz2eylrKNZU1eEpKOEXHHmmwqw7JvHFjjfeZwvQxo,6469
|
|
16
|
+
substack_cli-0.9.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
17
|
+
substack_cli-0.9.0.dist-info/entry_points.txt,sha256=p39AC48B8VjDZseqOUIww6BQomRyYXoAwbAzhSXNDeM,51
|
|
18
|
+
substack_cli-0.9.0.dist-info/licenses/LICENSE,sha256=UTsio4-AMxNDoMcES3iMDOVbdrnTy1rQD7Be7zWjqcA,11340
|
|
19
|
+
substack_cli-0.9.0.dist-info/RECORD,,
|