outcome-engineering 0.1.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.
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.3
2
+ Name: outcome-engineering
3
+ Version: 0.1.0
4
+ Summary: Repo-native product graph tooling for Outcome Engineering.
5
+ Requires-Dist: typer>=0.16.0
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+
9
+ # Outcome Engineering
10
+
11
+ Outcome Engineering is a framework for helping humans continuously challenge product thinking and invent valuable solutions that users and customers actually want, use, and pay for, in ways that work for the business.
12
+
13
+ The core claim:
14
+
15
+ > Product development should be modeled as a living intent graph where every product bet can be traced upward to its strategic purpose, downward to implementation, and sideways to the evidence and learning that shaped it.
16
+
17
+ Traceability is the mechanism. Better product judgment and better product outcomes are the point.
18
+
19
+ The graph provides structure for product work and gives agents a way to understand, organize, and challenge what is happening.
20
+
21
+ The framework connects product vision, strategy, OKRs, outcomes, opportunity solution trees, assumptions, experiments, PRDs, user stories, acceptance criteria, code, tests, and evidence into one coherent system.
22
+
23
+ It is not a replacement for human product judgment. Humans still set direction, talk to users, interpret nuance, and make decisions. Agentic engineering can help maintain structure, reframe opportunities, analyze assumptions, challenge output-thinking, expose what is known and unknown, generate options, implement code, run tests, and preserve traceability across the system.
24
+
25
+ Agents can generate plausible opportunities, solutions, assumptions, stories, and analyses, but plausibility is not grounding. Synthetic artifacts remain hypotheses until supported by real customer, user, market, business, or technical evidence.
26
+
27
+ ## The Problem
28
+
29
+ Most organizations lose intent as work moves through the product development process.
30
+
31
+ A compelling product vision becomes a strategy deck. Strategy becomes OKRs. OKRs become roadmap items. Roadmap items become tickets. Tickets become code. Code ships. Somewhere along the way, the original customer need, business intent, assumptions, and evidence are often lost.
32
+
33
+ The result is delivery without memory:
34
+
35
+ - Teams ship outputs without knowing which outcome they serve.
36
+ - Discovery evidence lives separately from delivery work.
37
+ - User stories lose their connection to customer opportunities.
38
+ - Code becomes hard to explain in product terms.
39
+ - Learning arrives too late, or never updates the underlying product model.
40
+
41
+ Outcome Engineering treats this as a structural problem.
42
+
43
+ ## The Model
44
+
45
+ At the highest level:
46
+
47
+ ```text
48
+ Vision
49
+ -> Strategy
50
+ -> OKRs
51
+ -> Outcomes
52
+ -> Opportunity Solution Trees
53
+ -> Opportunities
54
+ -> Solutions
55
+ -> Assumptions
56
+ -> Experiments
57
+ -> Decisions
58
+ -> PRDs
59
+ -> User Stories
60
+ -> Acceptance Criteria
61
+ -> Code
62
+ -> Tests
63
+ -> Released Product
64
+ ```
65
+
66
+ But this is not a one-way waterfall.
67
+
68
+ Evidence and learning can happen throughout the graph:
69
+
70
+ ```text
71
+ Any product belief
72
+ -> Evidence
73
+ -> Learning
74
+ -> Update the graph
75
+ ```
76
+
77
+ Evidence can come from user interviews, customer conversations, sales calls, support conversations, analytics, prototype tests, assumption tests, usability sessions, technical spikes, experiments, production telemetry, and market observation.
78
+
79
+ Quantitative data can show what is happening. Human discovery is needed to understand why it is happening.
80
+
81
+ ## Repository Structure
82
+
83
+ - [docs/framework.md](docs/framework.md) defines the framework.
84
+ - [docs/graph.md](docs/graph.md) describes the concept graph.
85
+ - [docs/glossary.md](docs/glossary.md) defines the core terms.
86
+ - [docs/example-structure.md](docs/example-structure.md) explains the example filesystem graph.
87
+
88
+ ## CLI
89
+
90
+ The Python package is `outcome-engineering`. The command is `oe`.
91
+
92
+ Real product repositories should store the graph at:
93
+
94
+ ```text
95
+ product/
96
+ ```
97
+
98
+ Install the bundled agent skill with Playwright-style project-local commands:
99
+
100
+ ```sh
101
+ uv run oe install --skills --force
102
+ uv run oe install --skills=agents --force
103
+ ```
104
+
105
+ Inspect a product graph:
106
+
107
+ ```sh
108
+ uv run oe validate product
109
+ uv run oe tree product
110
+ uv run oe list outcomes --root product
111
+ uv run oe list opportunities --root product
112
+ uv run oe list solutions --root product
113
+ ```
114
+
115
+ Trace product intent before editing a product artifact or implementing from a PRD:
116
+
117
+ ```sh
118
+ uv run oe trace solution.agent-central --root product
119
+ uv run oe context solution.agent-central --root product
120
+ ```
121
+
122
+ Read a node's canonical marker file:
123
+
124
+ ```sh
125
+ uv run oe show outcome.delegation-confidence --root product
126
+ uv run oe show opportunity.agents-lack-safe-access --root product
127
+ uv run oe show prd.agent-central-mvp --root product
128
+ ```
129
+
130
+ Create nodes deterministically:
131
+
132
+ ```sh
133
+ uv run oe new outcome delegation-confidence --root product
134
+ uv run oe new opportunity agents-lack-safe-access --root product --under outcome.delegation-confidence
135
+ uv run oe new solution agent-central --root product --under opportunity.agents-lack-safe-access
136
+ uv run oe new assumption operation-discovery-reduces-tool-overload --root product --under solution.agent-central
137
+ uv run oe new experiment fake-connector-prototype --root product --under assumption.operation-discovery-reduces-tool-overload
138
+ uv run oe new prd agent-central-mvp --root product --under solution.agent-central
139
+ ```
140
+
141
+ Experiments can only live under assumptions.
142
+
143
+ Try the example graph:
144
+
145
+ ```sh
146
+ uv run oe create-example --force
147
+ uv run oe validate examples/delegation-product-graph
148
+ uv run oe tree examples/delegation-product-graph
149
+ uv run oe context solution.agent-central --root examples/delegation-product-graph
150
+ ```
151
+
152
+ Install the skill into explicit global agent-tool locations:
153
+
154
+ ```sh
155
+ uv run oe install-skill --agent codex --force
156
+ uv run oe install-skill --agent claude --force
157
+ uv run oe install-skill --agent all --force
158
+ ```
@@ -0,0 +1,150 @@
1
+ # Outcome Engineering
2
+
3
+ Outcome Engineering is a framework for helping humans continuously challenge product thinking and invent valuable solutions that users and customers actually want, use, and pay for, in ways that work for the business.
4
+
5
+ The core claim:
6
+
7
+ > Product development should be modeled as a living intent graph where every product bet can be traced upward to its strategic purpose, downward to implementation, and sideways to the evidence and learning that shaped it.
8
+
9
+ Traceability is the mechanism. Better product judgment and better product outcomes are the point.
10
+
11
+ The graph provides structure for product work and gives agents a way to understand, organize, and challenge what is happening.
12
+
13
+ The framework connects product vision, strategy, OKRs, outcomes, opportunity solution trees, assumptions, experiments, PRDs, user stories, acceptance criteria, code, tests, and evidence into one coherent system.
14
+
15
+ It is not a replacement for human product judgment. Humans still set direction, talk to users, interpret nuance, and make decisions. Agentic engineering can help maintain structure, reframe opportunities, analyze assumptions, challenge output-thinking, expose what is known and unknown, generate options, implement code, run tests, and preserve traceability across the system.
16
+
17
+ Agents can generate plausible opportunities, solutions, assumptions, stories, and analyses, but plausibility is not grounding. Synthetic artifacts remain hypotheses until supported by real customer, user, market, business, or technical evidence.
18
+
19
+ ## The Problem
20
+
21
+ Most organizations lose intent as work moves through the product development process.
22
+
23
+ A compelling product vision becomes a strategy deck. Strategy becomes OKRs. OKRs become roadmap items. Roadmap items become tickets. Tickets become code. Code ships. Somewhere along the way, the original customer need, business intent, assumptions, and evidence are often lost.
24
+
25
+ The result is delivery without memory:
26
+
27
+ - Teams ship outputs without knowing which outcome they serve.
28
+ - Discovery evidence lives separately from delivery work.
29
+ - User stories lose their connection to customer opportunities.
30
+ - Code becomes hard to explain in product terms.
31
+ - Learning arrives too late, or never updates the underlying product model.
32
+
33
+ Outcome Engineering treats this as a structural problem.
34
+
35
+ ## The Model
36
+
37
+ At the highest level:
38
+
39
+ ```text
40
+ Vision
41
+ -> Strategy
42
+ -> OKRs
43
+ -> Outcomes
44
+ -> Opportunity Solution Trees
45
+ -> Opportunities
46
+ -> Solutions
47
+ -> Assumptions
48
+ -> Experiments
49
+ -> Decisions
50
+ -> PRDs
51
+ -> User Stories
52
+ -> Acceptance Criteria
53
+ -> Code
54
+ -> Tests
55
+ -> Released Product
56
+ ```
57
+
58
+ But this is not a one-way waterfall.
59
+
60
+ Evidence and learning can happen throughout the graph:
61
+
62
+ ```text
63
+ Any product belief
64
+ -> Evidence
65
+ -> Learning
66
+ -> Update the graph
67
+ ```
68
+
69
+ Evidence can come from user interviews, customer conversations, sales calls, support conversations, analytics, prototype tests, assumption tests, usability sessions, technical spikes, experiments, production telemetry, and market observation.
70
+
71
+ Quantitative data can show what is happening. Human discovery is needed to understand why it is happening.
72
+
73
+ ## Repository Structure
74
+
75
+ - [docs/framework.md](docs/framework.md) defines the framework.
76
+ - [docs/graph.md](docs/graph.md) describes the concept graph.
77
+ - [docs/glossary.md](docs/glossary.md) defines the core terms.
78
+ - [docs/example-structure.md](docs/example-structure.md) explains the example filesystem graph.
79
+
80
+ ## CLI
81
+
82
+ The Python package is `outcome-engineering`. The command is `oe`.
83
+
84
+ Real product repositories should store the graph at:
85
+
86
+ ```text
87
+ product/
88
+ ```
89
+
90
+ Install the bundled agent skill with Playwright-style project-local commands:
91
+
92
+ ```sh
93
+ uv run oe install --skills --force
94
+ uv run oe install --skills=agents --force
95
+ ```
96
+
97
+ Inspect a product graph:
98
+
99
+ ```sh
100
+ uv run oe validate product
101
+ uv run oe tree product
102
+ uv run oe list outcomes --root product
103
+ uv run oe list opportunities --root product
104
+ uv run oe list solutions --root product
105
+ ```
106
+
107
+ Trace product intent before editing a product artifact or implementing from a PRD:
108
+
109
+ ```sh
110
+ uv run oe trace solution.agent-central --root product
111
+ uv run oe context solution.agent-central --root product
112
+ ```
113
+
114
+ Read a node's canonical marker file:
115
+
116
+ ```sh
117
+ uv run oe show outcome.delegation-confidence --root product
118
+ uv run oe show opportunity.agents-lack-safe-access --root product
119
+ uv run oe show prd.agent-central-mvp --root product
120
+ ```
121
+
122
+ Create nodes deterministically:
123
+
124
+ ```sh
125
+ uv run oe new outcome delegation-confidence --root product
126
+ uv run oe new opportunity agents-lack-safe-access --root product --under outcome.delegation-confidence
127
+ uv run oe new solution agent-central --root product --under opportunity.agents-lack-safe-access
128
+ uv run oe new assumption operation-discovery-reduces-tool-overload --root product --under solution.agent-central
129
+ uv run oe new experiment fake-connector-prototype --root product --under assumption.operation-discovery-reduces-tool-overload
130
+ uv run oe new prd agent-central-mvp --root product --under solution.agent-central
131
+ ```
132
+
133
+ Experiments can only live under assumptions.
134
+
135
+ Try the example graph:
136
+
137
+ ```sh
138
+ uv run oe create-example --force
139
+ uv run oe validate examples/delegation-product-graph
140
+ uv run oe tree examples/delegation-product-graph
141
+ uv run oe context solution.agent-central --root examples/delegation-product-graph
142
+ ```
143
+
144
+ Install the skill into explicit global agent-tool locations:
145
+
146
+ ```sh
147
+ uv run oe install-skill --agent codex --force
148
+ uv run oe install-skill --agent claude --force
149
+ uv run oe install-skill --agent all --force
150
+ ```
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "outcome-engineering"
3
+ version = "0.1.0"
4
+ description = "Repo-native product graph tooling for Outcome Engineering."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "typer>=0.16.0",
9
+ ]
10
+
11
+ [project.scripts]
12
+ oe = "outcome_engineering.cli:app"
13
+
14
+ [dependency-groups]
15
+ dev = [
16
+ "pytest>=8.0.0",
17
+ ]
18
+
19
+ [build-system]
20
+ requires = ["uv_build>=0.8.0,<0.9.0"]
21
+ build-backend = "uv_build"
22
+
@@ -0,0 +1,4 @@
1
+ """Outcome Engineering CLI package."""
2
+
3
+ __version__ = "0.1.0"
4
+
@@ -0,0 +1,310 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from outcome_engineering.example import create_example
8
+ from outcome_engineering.graph import (
9
+ create_node,
10
+ discover_nodes,
11
+ find_node,
12
+ find_nodes_by_kind,
13
+ marker_content,
14
+ node_ancestors,
15
+ supporting_files,
16
+ validate as validate_graph,
17
+ )
18
+ from outcome_engineering.model import KIND_TO_RELATIONSHIP
19
+ from outcome_engineering.skill_installer import install_project_skill, install_skill, install_skill_for_agent
20
+
21
+ app = typer.Typer(help="Outcome Engineering product graph tooling.")
22
+
23
+
24
+ @app.command(
25
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
26
+ )
27
+ def install(
28
+ ctx: typer.Context,
29
+ force: bool = typer.Option(False, "--force", help="Replace the target skill directory if it already exists."),
30
+ ) -> None:
31
+ """Install bundled assets, including the oe-cli skill."""
32
+ try:
33
+ skill_value = parse_skills_option(ctx.args)
34
+ installed_at = install_project_skill(skill_value, force=force)
35
+ except ValueError as error:
36
+ typer.echo(str(error))
37
+ raise typer.Exit(code=1) from error
38
+ except FileExistsError as error:
39
+ typer.echo(str(error))
40
+ raise typer.Exit(code=1) from error
41
+
42
+ typer.echo(f"Installed oe-cli skill at {installed_at}")
43
+
44
+
45
+ @app.command()
46
+ def validate(
47
+ path: Path = typer.Argument(Path("product"), help="Product graph root to validate."),
48
+ ) -> None:
49
+ """Validate a product graph directory."""
50
+ issues = validate_graph(path)
51
+ if not issues:
52
+ typer.echo(f"OK: {path} is a valid product graph")
53
+ return
54
+
55
+ typer.echo(f"Invalid product graph: {path}")
56
+ for issue in issues:
57
+ typer.echo(f"- {issue.path}: {issue.message}")
58
+ raise typer.Exit(code=1)
59
+
60
+
61
+ @app.command("tree")
62
+ def tree_command(
63
+ path: Path = typer.Argument(Path("product"), help="Product graph root to print."),
64
+ ) -> None:
65
+ """Print the product graph tree."""
66
+ issues = validate_graph(path)
67
+ if issues:
68
+ typer.echo(f"Invalid product graph: {path}")
69
+ for issue in issues:
70
+ typer.echo(f"- {issue.path}: {issue.message}")
71
+ raise typer.Exit(code=1)
72
+
73
+ root = path.resolve()
74
+ typer.echo(str(path))
75
+ top_level = [node for node in discover_nodes(root) if node.parent is None and node.kind not in {"vision", "strategy"}]
76
+ for index, node in enumerate(top_level):
77
+ print_node(node, prefix="", is_last=index == len(top_level) - 1)
78
+
79
+
80
+ @app.command("create-example")
81
+ def create_example_command(
82
+ output: Path = typer.Option(
83
+ Path("examples/delegation-product-graph"),
84
+ "--output",
85
+ "-o",
86
+ help="Directory to create.",
87
+ ),
88
+ force: bool = typer.Option(False, "--force", help="Replace output directory if it already exists."),
89
+ ) -> None:
90
+ """Create an example product graph."""
91
+ try:
92
+ create_example(output, force=force)
93
+ except FileExistsError as error:
94
+ typer.echo(str(error))
95
+ raise typer.Exit(code=1) from error
96
+ typer.echo(f"Created example product graph at {output}")
97
+
98
+
99
+ @app.command("install-skill")
100
+ def install_skill_command(
101
+ agent: str = typer.Option("codex", "--agent", "-a", help="Agent tool target: codex, claude, or all."),
102
+ target: Path | None = typer.Option(None, "--target", "-t", help="Exact skill install directory. Overrides --agent."),
103
+ force: bool = typer.Option(False, "--force", help="Replace the target skill directory if it already exists."),
104
+ ) -> None:
105
+ """Install the bundled oe-cli agent skill."""
106
+ try:
107
+ installed_paths = [install_skill(target=target, force=force)] if target is not None else install_skill_for_agent(agent, force=force)
108
+ except (FileExistsError, ValueError) as error:
109
+ typer.echo(str(error))
110
+ raise typer.Exit(code=1) from error
111
+ for installed_at in installed_paths:
112
+ typer.echo(f"Installed oe-cli skill at {installed_at}")
113
+
114
+
115
+ @app.command()
116
+ def trace(
117
+ selector: str = typer.Argument(..., help="Node id, slug, node directory, or marker file path."),
118
+ root: Path = typer.Option(Path("product"), "--root", "-r", help="Product graph root."),
119
+ ) -> None:
120
+ """Show where a node sits in the product graph."""
121
+ issues = validate_graph(root)
122
+ if issues:
123
+ typer.echo(f"Invalid product graph: {root}")
124
+ for issue in issues:
125
+ typer.echo(f"- {issue.path}: {issue.message}")
126
+ raise typer.Exit(code=1)
127
+
128
+ node = find_node(root, selector)
129
+ if node is None:
130
+ typer.echo(f"Node not found or ambiguous: {selector}")
131
+ raise typer.Exit(code=1)
132
+
133
+ typer.echo(f"{node.kind}: {node.slug}")
134
+ typer.echo(f"id: {node.id}")
135
+ typer.echo(f"path: {node.path}")
136
+ typer.echo(f"marker: {node.marker_file}")
137
+ if node.parent is not None:
138
+ typer.echo(f"parent: {node.parent.id}")
139
+ typer.echo(f"relationship: {node.relationship}")
140
+ else:
141
+ typer.echo("parent: <root>")
142
+
143
+ ancestors = node_ancestors(node)
144
+ if ancestors:
145
+ typer.echo("")
146
+ typer.echo("Trace:")
147
+ for ancestor in ancestors:
148
+ typer.echo(f"- {ancestor.kind}: {ancestor.slug}")
149
+ typer.echo(f"- {node.kind}: {node.slug}")
150
+
151
+ if node.children:
152
+ typer.echo("")
153
+ typer.echo("Children:")
154
+ for child in node.children:
155
+ typer.echo(f"- {child.kind}: {child.slug}")
156
+
157
+
158
+ @app.command("list")
159
+ def list_command(
160
+ kind: str | None = typer.Argument(None, help="Optional node kind to list."),
161
+ root: Path = typer.Option(Path("product"), "--root", "-r", help="Product graph root."),
162
+ ) -> None:
163
+ """List graph nodes."""
164
+ issues = validate_graph(root)
165
+ if issues:
166
+ typer.echo(f"Invalid product graph: {root}")
167
+ for issue in issues:
168
+ typer.echo(f"- {issue.path}: {issue.message}")
169
+ raise typer.Exit(code=1)
170
+
171
+ if kind is not None and kind.endswith("s"):
172
+ kind = kind[:-1]
173
+ if kind is not None and kind not in KIND_TO_RELATIONSHIP:
174
+ supported = ", ".join(sorted(KIND_TO_RELATIONSHIP))
175
+ typer.echo(f"unsupported node kind {kind!r}; expected one of: {supported}")
176
+ raise typer.Exit(code=1)
177
+
178
+ nodes = find_nodes_by_kind(root, kind)
179
+ for node in nodes:
180
+ typer.echo(f"{node.id}\t{node.path}")
181
+
182
+
183
+ @app.command()
184
+ def show(
185
+ selector: str = typer.Argument(..., help="Node id, slug, node directory, or marker file path."),
186
+ root: Path = typer.Option(Path("product"), "--root", "-r", help="Product graph root."),
187
+ ) -> None:
188
+ """Print a node's marker file."""
189
+ node = load_valid_node(root, selector)
190
+ typer.echo(marker_content(node).rstrip())
191
+
192
+
193
+ @app.command()
194
+ def context(
195
+ selector: str = typer.Argument(..., help="Node id, slug, node directory, or marker file path."),
196
+ root: Path = typer.Option(Path("product"), "--root", "-r", help="Product graph root."),
197
+ ) -> None:
198
+ """Print deterministic context around a node for an agent."""
199
+ node = load_valid_node(root, selector)
200
+ ancestors = node_ancestors(node)
201
+
202
+ typer.echo(f"# Context: {node.id}")
203
+ typer.echo("")
204
+ typer.echo("## Trace")
205
+ for ancestor in ancestors:
206
+ typer.echo(f"- {ancestor.id} ({ancestor.marker_file})")
207
+ typer.echo(f"- {node.id} ({node.marker_file})")
208
+
209
+ if node.children:
210
+ typer.echo("")
211
+ typer.echo("## Children")
212
+ for child in node.children:
213
+ typer.echo(f"- {child.id} ({child.marker_file})")
214
+
215
+ files = supporting_files(node)
216
+ if files:
217
+ typer.echo("")
218
+ typer.echo("## Supporting Files")
219
+ for path in files:
220
+ typer.echo(f"- {path}")
221
+
222
+ if ancestors:
223
+ typer.echo("")
224
+ typer.echo("## Ancestor Content")
225
+ for ancestor in ancestors:
226
+ typer.echo("")
227
+ typer.echo(f"### {ancestor.id}")
228
+ typer.echo("")
229
+ typer.echo(marker_content(ancestor).rstrip())
230
+
231
+ typer.echo("")
232
+ typer.echo("## Node Content")
233
+ typer.echo("")
234
+ typer.echo(marker_content(node).rstrip())
235
+
236
+
237
+ @app.command("new")
238
+ def new_command(
239
+ kind: str = typer.Argument(..., help=f"Node kind: {', '.join(sorted(KIND_TO_RELATIONSHIP))}."),
240
+ slug: str = typer.Argument(..., help="Filesystem slug for the node."),
241
+ root: Path = typer.Option(Path("product"), "--root", "-r", help="Product graph root."),
242
+ under: str | None = typer.Option(None, "--under", "-u", help="Parent node id, slug, path, or marker file."),
243
+ title: str | None = typer.Option(None, "--title", "-t", help="Human-readable title."),
244
+ ) -> None:
245
+ """Create a product graph node in the valid location."""
246
+ root.mkdir(parents=True, exist_ok=True)
247
+ issues = validate_graph(root)
248
+ if issues:
249
+ typer.echo(f"Invalid product graph: {root}")
250
+ for issue in issues:
251
+ typer.echo(f"- {issue.path}: {issue.message}")
252
+ raise typer.Exit(code=1)
253
+
254
+ try:
255
+ node = create_node(root, kind=kind, slug=slug, title=title, under=under)
256
+ except (ValueError, FileExistsError) as error:
257
+ typer.echo(str(error))
258
+ raise typer.Exit(code=1) from error
259
+
260
+ typer.echo(f"Created {node.id}")
261
+ typer.echo(f"path: {node.path}")
262
+ typer.echo(f"marker: {node.marker_file}")
263
+
264
+
265
+ def load_valid_node(root: Path, selector: str):
266
+ issues = validate_graph(root)
267
+ if issues:
268
+ typer.echo(f"Invalid product graph: {root}")
269
+ for issue in issues:
270
+ typer.echo(f"- {issue.path}: {issue.message}")
271
+ raise typer.Exit(code=1)
272
+
273
+ node = find_node(root, selector)
274
+ if node is None:
275
+ typer.echo(f"Node not found or ambiguous: {selector}")
276
+ raise typer.Exit(code=1)
277
+ return node
278
+
279
+
280
+ def parse_skills_option(args: list[str]) -> str:
281
+ if not args:
282
+ raise ValueError("nothing to install; use --skills or --skills=agents")
283
+
284
+ value: str | None = None
285
+ index = 0
286
+ while index < len(args):
287
+ arg = args[index]
288
+ if arg == "--skills":
289
+ value = "claude"
290
+ elif arg.startswith("--skills="):
291
+ value = arg.split("=", 1)[1] or "claude"
292
+ else:
293
+ raise ValueError(f"unknown install option: {arg}")
294
+ index += 1
295
+
296
+ if value is None:
297
+ raise ValueError("nothing to install; use --skills or --skills=agents")
298
+ return value
299
+
300
+
301
+ def print_node(node, prefix: str, is_last: bool) -> None:
302
+ branch = "`-- " if is_last else "|-- "
303
+ typer.echo(f"{prefix}{branch}{node.kind}: {node.slug}")
304
+ child_prefix = prefix + (" " if is_last else "| ")
305
+ for index, child in enumerate(node.children):
306
+ print_node(child, child_prefix, index == len(node.children) - 1)
307
+
308
+
309
+ if __name__ == "__main__":
310
+ app()