@jahanxu/trellis 0.5.0 → 0.5.5
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.
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +98 -5
- package/dist/commands/init.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +8 -58
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +0 -17
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +0 -19
- package/dist/constants/paths.js.map +1 -1
- package/dist/templates/claude/commands/trellis/handoff.md +56 -122
- package/dist/templates/claude/hooks/enforce-output-dir.py +115 -0
- package/dist/templates/claude/hooks/session-start.py +87 -166
- package/dist/templates/claude/settings.json +10 -0
- package/dist/templates/iflow/hooks/session-start.py +0 -171
- package/dist/templates/markdown/index.d.ts +0 -9
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +0 -10
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/trellis/index.d.ts +9 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +17 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/__init__.py +11 -0
- package/dist/templates/trellis/scripts/common/paths.py +1 -49
- package/dist/templates/trellis/scripts/common/roles.py +252 -0
- package/dist/templates/trellis/spec/roles/designer/index.md +49 -0
- package/dist/templates/trellis/spec/roles/frontend-impl/index.md +52 -0
- package/dist/templates/trellis/spec/roles/pm/index.md +44 -0
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +2 -0
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +2 -2
- package/dist/templates/claude/commands/trellis/pick-task.md +0 -145
- package/dist/templates/iflow/commands/trellis/handoff.md +0 -148
- package/dist/templates/iflow/commands/trellis/pick-task.md +0 -145
- package/dist/templates/markdown/spec/roles/designer/index.md.txt +0 -57
- package/dist/templates/markdown/spec/roles/designer/mock-data-standards.md.txt +0 -63
- package/dist/templates/markdown/spec/roles/designer/prototype-guidelines.md.txt +0 -49
- package/dist/templates/markdown/spec/roles/frontend-impl/api-integration.md.txt +0 -63
- package/dist/templates/markdown/spec/roles/frontend-impl/index.md.txt +0 -57
- package/dist/templates/markdown/spec/roles/frontend-impl/prototype-to-production.md.txt +0 -57
- package/dist/templates/markdown/spec/roles/pm/index.md.txt +0 -45
- package/dist/templates/markdown/spec/roles/pm/prd-template.md.txt +0 -64
- package/dist/templates/markdown/spec/roles/pm/requirement-checklist.md.txt +0 -43
- package/dist/templates/trellis/scripts/pool.py +0 -322
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# PRD Template
|
|
2
|
-
|
|
3
|
-
> Standard structure for Product Requirements Documents.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Template
|
|
8
|
-
|
|
9
|
-
```markdown
|
|
10
|
-
# Feature: {Feature Name}
|
|
11
|
-
|
|
12
|
-
## Background
|
|
13
|
-
Why this feature is needed. Business context and user pain points.
|
|
14
|
-
|
|
15
|
-
## Goals
|
|
16
|
-
- Primary goal
|
|
17
|
-
- Secondary goals
|
|
18
|
-
|
|
19
|
-
## User Stories
|
|
20
|
-
As a [user type], I want to [action] so that [benefit].
|
|
21
|
-
|
|
22
|
-
## Requirements
|
|
23
|
-
|
|
24
|
-
### Functional Requirements
|
|
25
|
-
1. ...
|
|
26
|
-
2. ...
|
|
27
|
-
|
|
28
|
-
### Non-Functional Requirements
|
|
29
|
-
- Performance: ...
|
|
30
|
-
- Security: ...
|
|
31
|
-
- Accessibility: ...
|
|
32
|
-
|
|
33
|
-
## UI/UX Requirements
|
|
34
|
-
- Layout description
|
|
35
|
-
- Key interactions
|
|
36
|
-
- Error states
|
|
37
|
-
|
|
38
|
-
## API Requirements (if applicable)
|
|
39
|
-
- Endpoints needed
|
|
40
|
-
- Data format expectations
|
|
41
|
-
|
|
42
|
-
## Acceptance Criteria
|
|
43
|
-
- [ ] Criterion 1
|
|
44
|
-
- [ ] Criterion 2
|
|
45
|
-
|
|
46
|
-
## Out of Scope
|
|
47
|
-
- Items explicitly excluded from this feature
|
|
48
|
-
|
|
49
|
-
## Open Questions
|
|
50
|
-
- Questions that need resolution
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Guidelines
|
|
56
|
-
|
|
57
|
-
1. **Be specific**: Avoid vague language like "should be fast" - specify measurable criteria
|
|
58
|
-
2. **Include edge cases**: Document what happens with empty data, errors, permission issues
|
|
59
|
-
3. **Reference designs**: Link to Figma/mockups when available
|
|
60
|
-
4. **Scope clearly**: Explicitly state what is NOT included
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
**Language**: All documentation should be written in **English**.
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Requirement Checklist
|
|
2
|
-
|
|
3
|
-
> Quality checklist before handing off requirements to the Designer.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Pre-Handoff Checklist
|
|
8
|
-
|
|
9
|
-
### Completeness
|
|
10
|
-
- [ ] All user stories are written with clear acceptance criteria
|
|
11
|
-
- [ ] Edge cases are documented (empty states, error states, loading states)
|
|
12
|
-
- [ ] UI/UX requirements include layout descriptions and key interactions
|
|
13
|
-
- [ ] API requirements are specified (if applicable)
|
|
14
|
-
- [ ] Non-functional requirements are defined (performance, security, accessibility)
|
|
15
|
-
|
|
16
|
-
### Clarity
|
|
17
|
-
- [ ] No ambiguous language (e.g., "fast", "nice", "user-friendly" without metrics)
|
|
18
|
-
- [ ] Technical constraints are explicitly stated
|
|
19
|
-
- [ ] Out-of-scope items are listed
|
|
20
|
-
- [ ] Open questions are resolved or explicitly deferred
|
|
21
|
-
|
|
22
|
-
### Consistency
|
|
23
|
-
- [ ] Feature aligns with existing product patterns
|
|
24
|
-
- [ ] Naming conventions match existing features
|
|
25
|
-
- [ ] No contradictions between requirements sections
|
|
26
|
-
|
|
27
|
-
### Downstream Readiness
|
|
28
|
-
- [ ] Designer can start prototyping without needing clarification meetings
|
|
29
|
-
- [ ] All referenced resources (Figma links, API docs) are accessible
|
|
30
|
-
- [ ] Priority and scope are clear
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Common Issues to Avoid
|
|
35
|
-
|
|
36
|
-
1. **Missing error states**: Every user action should have a defined failure path
|
|
37
|
-
2. **Implicit assumptions**: State all assumptions explicitly
|
|
38
|
-
3. **Feature creep**: Keep scope tight - add "future considerations" for nice-to-haves
|
|
39
|
-
4. **Missing data requirements**: Specify what data the UI needs and where it comes from
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
**Language**: All documentation should be written in **English**.
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
Pool Management Script for Three-Role Collaboration Pipeline.
|
|
5
|
-
|
|
6
|
-
Usage:
|
|
7
|
-
python3 pool.py init [pool-name...] # Create pool JSON files
|
|
8
|
-
python3 pool.py add <pool> <id> <title> <path> [--handoff <path>] # Add deliverable
|
|
9
|
-
python3 pool.py list [pool] # List available items
|
|
10
|
-
python3 pool.py status <pool> <id> # Get item status
|
|
11
|
-
python3 pool.py consume <pool> <id> <consumed-by> # Mark as consumed
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import sys
|
|
17
|
-
|
|
18
|
-
# IMPORTANT: Force stdout to use UTF-8 on Windows
|
|
19
|
-
# This fixes UnicodeEncodeError when outputting non-ASCII characters
|
|
20
|
-
if sys.platform == "win32":
|
|
21
|
-
import io as _io
|
|
22
|
-
if hasattr(sys.stdout, "reconfigure"):
|
|
23
|
-
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
24
|
-
elif hasattr(sys.stdout, "detach"):
|
|
25
|
-
sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
26
|
-
|
|
27
|
-
import argparse
|
|
28
|
-
import json
|
|
29
|
-
import os
|
|
30
|
-
import sys
|
|
31
|
-
from datetime import datetime, timezone
|
|
32
|
-
from pathlib import Path
|
|
33
|
-
|
|
34
|
-
from common.paths import (
|
|
35
|
-
get_repo_root,
|
|
36
|
-
get_developer,
|
|
37
|
-
get_pool_dir,
|
|
38
|
-
get_pool_file,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# =============================================================================
|
|
43
|
-
# Colors
|
|
44
|
-
# =============================================================================
|
|
45
|
-
|
|
46
|
-
class Colors:
|
|
47
|
-
RED = "\033[0;31m"
|
|
48
|
-
GREEN = "\033[0;32m"
|
|
49
|
-
YELLOW = "\033[1;33m"
|
|
50
|
-
BLUE = "\033[0;34m"
|
|
51
|
-
CYAN = "\033[0;36m"
|
|
52
|
-
NC = "\033[0m"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def colored(text: str, color: str) -> str:
|
|
56
|
-
"""Apply color to text."""
|
|
57
|
-
return f"{color}{text}{Colors.NC}"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# =============================================================================
|
|
61
|
-
# Constants
|
|
62
|
-
# =============================================================================
|
|
63
|
-
|
|
64
|
-
DEFAULT_POOLS = ["requirements", "prototypes", "implementations"]
|
|
65
|
-
|
|
66
|
-
EMPTY_POOL = {"available": []}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# =============================================================================
|
|
70
|
-
# Pool I/O (atomic writes)
|
|
71
|
-
# =============================================================================
|
|
72
|
-
|
|
73
|
-
def _read_pool(pool_path: Path) -> dict:
|
|
74
|
-
"""Read pool JSON file. Auto-create if missing, backup if corrupted."""
|
|
75
|
-
if not pool_path.is_file():
|
|
76
|
-
return {"available": []}
|
|
77
|
-
|
|
78
|
-
try:
|
|
79
|
-
data = json.loads(pool_path.read_text(encoding="utf-8"))
|
|
80
|
-
if not isinstance(data, dict) or "available" not in data:
|
|
81
|
-
raise ValueError("Invalid pool schema")
|
|
82
|
-
return data
|
|
83
|
-
except (json.JSONDecodeError, ValueError):
|
|
84
|
-
# Backup corrupted file
|
|
85
|
-
bak_path = pool_path.with_suffix(".json.bak")
|
|
86
|
-
try:
|
|
87
|
-
pool_path.rename(bak_path)
|
|
88
|
-
print(colored(f" Warning: Corrupted pool backed up to {bak_path.name}", Colors.YELLOW))
|
|
89
|
-
except OSError:
|
|
90
|
-
pass
|
|
91
|
-
return {"available": []}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def _write_pool(pool_path: Path, data: dict) -> bool:
|
|
95
|
-
"""Write pool JSON atomically (write .tmp then os.replace)."""
|
|
96
|
-
tmp_path = pool_path.with_suffix(".json.tmp")
|
|
97
|
-
try:
|
|
98
|
-
pool_path.parent.mkdir(parents=True, exist_ok=True)
|
|
99
|
-
tmp_path.write_text(
|
|
100
|
-
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
|
|
101
|
-
encoding="utf-8",
|
|
102
|
-
)
|
|
103
|
-
os.replace(str(tmp_path), str(pool_path))
|
|
104
|
-
return True
|
|
105
|
-
except (OSError, IOError) as e:
|
|
106
|
-
print(colored(f" Error writing pool: {e}", Colors.RED))
|
|
107
|
-
# Clean up tmp if it exists
|
|
108
|
-
try:
|
|
109
|
-
tmp_path.unlink(missing_ok=True)
|
|
110
|
-
except OSError:
|
|
111
|
-
pass
|
|
112
|
-
return False
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# =============================================================================
|
|
116
|
-
# Subcommands
|
|
117
|
-
# =============================================================================
|
|
118
|
-
|
|
119
|
-
def cmd_init(args: argparse.Namespace) -> int:
|
|
120
|
-
"""Create pool JSON files."""
|
|
121
|
-
repo_root = get_repo_root()
|
|
122
|
-
pool_dir = get_pool_dir(repo_root)
|
|
123
|
-
pool_dir.mkdir(parents=True, exist_ok=True)
|
|
124
|
-
|
|
125
|
-
pool_names = args.pools if args.pools else DEFAULT_POOLS
|
|
126
|
-
|
|
127
|
-
for name in pool_names:
|
|
128
|
-
pool_path = pool_dir / f"{name}.json"
|
|
129
|
-
if pool_path.is_file():
|
|
130
|
-
print(colored(f" Pool '{name}' already exists, skipping", Colors.YELLOW))
|
|
131
|
-
continue
|
|
132
|
-
if _write_pool(pool_path, EMPTY_POOL):
|
|
133
|
-
print(colored(f" Created pool: {name}", Colors.GREEN))
|
|
134
|
-
else:
|
|
135
|
-
print(colored(f" Failed to create pool: {name}", Colors.RED))
|
|
136
|
-
return 1
|
|
137
|
-
|
|
138
|
-
print(colored("Pool initialization complete.", Colors.GREEN))
|
|
139
|
-
return 0
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def cmd_add(args: argparse.Namespace) -> int:
|
|
143
|
-
"""Add a deliverable to a pool."""
|
|
144
|
-
repo_root = get_repo_root()
|
|
145
|
-
pool_path = get_pool_file(args.pool, repo_root)
|
|
146
|
-
|
|
147
|
-
data = _read_pool(pool_path)
|
|
148
|
-
|
|
149
|
-
# Check for duplicate ID
|
|
150
|
-
for item in data["available"]:
|
|
151
|
-
if item["id"] == args.id:
|
|
152
|
-
print(colored(f" Warning: ID '{args.id}' already exists in pool '{args.pool}', skipping", Colors.YELLOW))
|
|
153
|
-
return 0
|
|
154
|
-
|
|
155
|
-
developer = get_developer(repo_root) or "unknown"
|
|
156
|
-
|
|
157
|
-
entry = {
|
|
158
|
-
"id": args.id,
|
|
159
|
-
"title": args.title,
|
|
160
|
-
"path": args.path,
|
|
161
|
-
"completed_by": developer,
|
|
162
|
-
"completed_at": datetime.now(timezone.utc).isoformat(),
|
|
163
|
-
"handoff_doc": args.handoff if args.handoff else f"{args.path}/HANDOFF.md",
|
|
164
|
-
"status": "available",
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
data["available"].append(entry)
|
|
168
|
-
|
|
169
|
-
if _write_pool(pool_path, data):
|
|
170
|
-
print(colored(f" Added '{args.id}' to pool '{args.pool}'", Colors.GREEN))
|
|
171
|
-
return 0
|
|
172
|
-
return 1
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def cmd_list(args: argparse.Namespace) -> int:
|
|
176
|
-
"""List available items in pools."""
|
|
177
|
-
repo_root = get_repo_root()
|
|
178
|
-
pool_dir = get_pool_dir(repo_root)
|
|
179
|
-
|
|
180
|
-
if not pool_dir.is_dir():
|
|
181
|
-
print(colored("No pool directory found. Run 'pool.py init' first.", Colors.YELLOW))
|
|
182
|
-
return 0
|
|
183
|
-
|
|
184
|
-
pools_to_list = [args.pool] if args.pool else [
|
|
185
|
-
p.stem for p in sorted(pool_dir.glob("*.json"))
|
|
186
|
-
]
|
|
187
|
-
|
|
188
|
-
if not pools_to_list:
|
|
189
|
-
print(colored("No pools found.", Colors.YELLOW))
|
|
190
|
-
return 0
|
|
191
|
-
|
|
192
|
-
for pool_name in pools_to_list:
|
|
193
|
-
pool_path = get_pool_file(pool_name, repo_root)
|
|
194
|
-
data = _read_pool(pool_path)
|
|
195
|
-
|
|
196
|
-
available = [i for i in data["available"] if i.get("status") == "available"]
|
|
197
|
-
consumed = [i for i in data["available"] if i.get("status") == "consumed"]
|
|
198
|
-
|
|
199
|
-
print(colored(f"\n=== {pool_name} ===", Colors.CYAN))
|
|
200
|
-
print(f" Available: {len(available)} | Consumed: {len(consumed)}")
|
|
201
|
-
|
|
202
|
-
if available:
|
|
203
|
-
print(colored(" Available items:", Colors.GREEN))
|
|
204
|
-
for item in available:
|
|
205
|
-
print(f" - {item['id']}: {item['title']} (by {item.get('completed_by', '?')})")
|
|
206
|
-
if consumed:
|
|
207
|
-
print(colored(" Consumed items:", Colors.YELLOW))
|
|
208
|
-
for item in consumed:
|
|
209
|
-
consumed_by = item.get("consumed_by", "?")
|
|
210
|
-
print(f" - {item['id']}: {item['title']} (consumed by {consumed_by})")
|
|
211
|
-
|
|
212
|
-
print()
|
|
213
|
-
return 0
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def cmd_status(args: argparse.Namespace) -> int:
|
|
217
|
-
"""Get status of a specific item."""
|
|
218
|
-
repo_root = get_repo_root()
|
|
219
|
-
pool_path = get_pool_file(args.pool, repo_root)
|
|
220
|
-
data = _read_pool(pool_path)
|
|
221
|
-
|
|
222
|
-
for item in data["available"]:
|
|
223
|
-
if item["id"] == args.id:
|
|
224
|
-
print(json.dumps(item, indent=2, ensure_ascii=False))
|
|
225
|
-
return 0
|
|
226
|
-
|
|
227
|
-
print(colored(f" Item '{args.id}' not found in pool '{args.pool}'", Colors.RED))
|
|
228
|
-
return 1
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def cmd_consume(args: argparse.Namespace) -> int:
|
|
232
|
-
"""Mark an item as consumed."""
|
|
233
|
-
repo_root = get_repo_root()
|
|
234
|
-
pool_path = get_pool_file(args.pool, repo_root)
|
|
235
|
-
data = _read_pool(pool_path)
|
|
236
|
-
|
|
237
|
-
for item in data["available"]:
|
|
238
|
-
if item["id"] == args.id:
|
|
239
|
-
if item.get("status") == "consumed":
|
|
240
|
-
consumed_by = item.get("consumed_by", "?")
|
|
241
|
-
print(colored(f" Item '{args.id}' already consumed by {consumed_by}", Colors.YELLOW))
|
|
242
|
-
return 1
|
|
243
|
-
|
|
244
|
-
item["status"] = "consumed"
|
|
245
|
-
item["consumed_by"] = args.consumed_by
|
|
246
|
-
item["consumed_at"] = datetime.now(timezone.utc).isoformat()
|
|
247
|
-
|
|
248
|
-
if _write_pool(pool_path, data):
|
|
249
|
-
print(colored(f" Marked '{args.id}' as consumed by {args.consumed_by}", Colors.GREEN))
|
|
250
|
-
return 0
|
|
251
|
-
return 1
|
|
252
|
-
|
|
253
|
-
print(colored(f" Item '{args.id}' not found in pool '{args.pool}'", Colors.RED))
|
|
254
|
-
return 1
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
# =============================================================================
|
|
258
|
-
# CLI Parser
|
|
259
|
-
# =============================================================================
|
|
260
|
-
|
|
261
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
262
|
-
"""Build argument parser."""
|
|
263
|
-
parser = argparse.ArgumentParser(
|
|
264
|
-
description="Pool management for three-role collaboration pipeline",
|
|
265
|
-
)
|
|
266
|
-
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
267
|
-
|
|
268
|
-
# init
|
|
269
|
-
p_init = subparsers.add_parser("init", help="Create pool JSON files")
|
|
270
|
-
p_init.add_argument("pools", nargs="*", help="Pool names (default: requirements, prototypes, implementations)")
|
|
271
|
-
|
|
272
|
-
# add
|
|
273
|
-
p_add = subparsers.add_parser("add", help="Add deliverable to pool")
|
|
274
|
-
p_add.add_argument("pool", help="Pool name (e.g., requirements)")
|
|
275
|
-
p_add.add_argument("id", help="Deliverable ID (e.g., user-login)")
|
|
276
|
-
p_add.add_argument("title", help="Deliverable title")
|
|
277
|
-
p_add.add_argument("path", help="Path to deliverable directory")
|
|
278
|
-
p_add.add_argument("--handoff", help="Path to HANDOFF.md (default: <path>/HANDOFF.md)")
|
|
279
|
-
|
|
280
|
-
# list
|
|
281
|
-
p_list = subparsers.add_parser("list", help="List available items")
|
|
282
|
-
p_list.add_argument("pool", nargs="?", help="Pool name (omit for all)")
|
|
283
|
-
|
|
284
|
-
# status
|
|
285
|
-
p_status = subparsers.add_parser("status", help="Get item status")
|
|
286
|
-
p_status.add_argument("pool", help="Pool name")
|
|
287
|
-
p_status.add_argument("id", help="Item ID")
|
|
288
|
-
|
|
289
|
-
# consume
|
|
290
|
-
p_consume = subparsers.add_parser("consume", help="Mark item as consumed")
|
|
291
|
-
p_consume.add_argument("pool", help="Pool name")
|
|
292
|
-
p_consume.add_argument("id", help="Item ID")
|
|
293
|
-
p_consume.add_argument("consumed_by", help="Consumer identity (e.g., designer-bob)")
|
|
294
|
-
|
|
295
|
-
return parser
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# =============================================================================
|
|
299
|
-
# Main
|
|
300
|
-
# =============================================================================
|
|
301
|
-
|
|
302
|
-
def main() -> int:
|
|
303
|
-
parser = build_parser()
|
|
304
|
-
args = parser.parse_args()
|
|
305
|
-
|
|
306
|
-
if not args.command:
|
|
307
|
-
parser.print_help()
|
|
308
|
-
return 1
|
|
309
|
-
|
|
310
|
-
commands = {
|
|
311
|
-
"init": cmd_init,
|
|
312
|
-
"add": cmd_add,
|
|
313
|
-
"list": cmd_list,
|
|
314
|
-
"status": cmd_status,
|
|
315
|
-
"consume": cmd_consume,
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return commands[args.command](args)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if __name__ == "__main__":
|
|
322
|
-
sys.exit(main())
|