bumblebee-cli 0.1.1 → 0.2.0
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/README.md +49 -47
- package/bin/bb.mjs +132 -132
- package/package.json +28 -28
- package/python/bb_cli/__main__.py +3 -0
- package/python/bb_cli/api_client.py +7 -0
- package/python/bb_cli/commands/agent.py +2287 -1030
- package/python/bb_cli/commands/auth.py +79 -79
- package/python/bb_cli/commands/board.py +47 -47
- package/python/bb_cli/commands/comment.py +34 -34
- package/python/bb_cli/commands/daemon.py +153 -0
- package/python/bb_cli/commands/init.py +83 -62
- package/python/bb_cli/commands/item.py +192 -192
- package/python/bb_cli/commands/project.py +175 -111
- package/python/bb_cli/config.py +136 -136
- package/python/bb_cli/main.py +44 -44
- package/python/bb_cli/progress.py +117 -0
- package/python/bb_cli/streaming.py +168 -0
- package/python/pyproject.toml +1 -1
- package/python/{bb_cli/bumblebee_cli.egg-info/requires.txt → requirements.txt} +4 -4
- package/scripts/build.sh +20 -20
- package/scripts/postinstall.mjs +146 -146
- package/python/bb_cli/bumblebee_cli.egg-info/PKG-INFO +0 -9
- package/python/bb_cli/bumblebee_cli.egg-info/SOURCES.txt +0 -21
- package/python/bb_cli/bumblebee_cli.egg-info/dependency_links.txt +0 -1
- package/python/bb_cli/bumblebee_cli.egg-info/entry_points.txt +0 -2
- package/python/bb_cli/bumblebee_cli.egg-info/top_level.txt +0 -5
- package/python/bb_cli/commands/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/agent.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/auth.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/board.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/comment.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/init.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/item.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/label.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/project.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/sprint.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/story.cpython-313.pyc +0 -0
- package/python/bb_cli/commands/__pycache__/task.cpython-313.pyc +0 -0
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
from rich import print as rprint
|
|
5
|
-
from rich.table import Table
|
|
6
|
-
|
|
7
|
-
from ..api_client import api_get, api_post, api_put
|
|
8
|
-
from ..config import get_current_project
|
|
9
|
-
|
|
10
|
-
app = typer.Typer(help="Work item management")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _require_project() -> str:
|
|
14
|
-
slug = get_current_project()
|
|
15
|
-
if not slug:
|
|
16
|
-
rprint("[red]No project selected. Run [bold]bb project switch <slug>[/bold] first.[/red]")
|
|
17
|
-
raise typer.Exit(1)
|
|
18
|
-
return slug
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _resolve_item(slug: str, id_or_number: str) -> dict:
|
|
22
|
-
"""Resolve a work item by UUID, number, or KEY-number format."""
|
|
23
|
-
# UUID format
|
|
24
|
-
if len(id_or_number) > 8 and "-" in id_or_number:
|
|
25
|
-
return api_get(f"/api/work-items/{id_or_number}")
|
|
26
|
-
|
|
27
|
-
# KEY-number format (e.g. BB-42) or plain integer
|
|
28
|
-
match = re.match(r"^(?:[A-Za-z]+-)?(\d+)$", id_or_number)
|
|
29
|
-
if match:
|
|
30
|
-
number = int(match.group(1))
|
|
31
|
-
return api_get(f"/api/projects/{slug}/work-items/by-number/{number}")
|
|
32
|
-
|
|
33
|
-
rprint(f"[red]Invalid identifier: {id_or_number}. Use UUID, number, or KEY-number (e.g. BB-42).[/red]")
|
|
34
|
-
raise typer.Exit(1)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@app.command(name="list")
|
|
38
|
-
def list_items(
|
|
39
|
-
type: str = typer.Option(None, "-t", "--type", help="Filter by type (story, task, epic, bug...)"),
|
|
40
|
-
status: str = typer.Option(None, "-s", "--status", help="Filter by status"),
|
|
41
|
-
parent: str = typer.Option(None, "--parent", help="Filter by parent ID"),
|
|
42
|
-
assignee: str = typer.Option(None, "-a", "--assignee", help="Filter by assignee"),
|
|
43
|
-
):
|
|
44
|
-
"""List work items in the current project."""
|
|
45
|
-
slug = _require_project()
|
|
46
|
-
params = {}
|
|
47
|
-
if type:
|
|
48
|
-
params["type"] = type
|
|
49
|
-
if status:
|
|
50
|
-
params["status"] = status
|
|
51
|
-
if parent:
|
|
52
|
-
params["parent_id"] = parent
|
|
53
|
-
if assignee:
|
|
54
|
-
params["assignee"] = assignee
|
|
55
|
-
items = api_get(f"/api/projects/{slug}/work-items", params=params)
|
|
56
|
-
table = Table(title=f"Work Items - {slug}")
|
|
57
|
-
table.add_column("#", style="cyan", justify="right")
|
|
58
|
-
table.add_column("Type", style="magenta")
|
|
59
|
-
table.add_column("Title")
|
|
60
|
-
table.add_column("Status", style="green")
|
|
61
|
-
table.add_column("Priority")
|
|
62
|
-
table.add_column("Assignee")
|
|
63
|
-
for item in items:
|
|
64
|
-
key = item.get("key") or str(item["number"])
|
|
65
|
-
table.add_row(
|
|
66
|
-
key,
|
|
67
|
-
item["type"],
|
|
68
|
-
item["title"],
|
|
69
|
-
item["status"],
|
|
70
|
-
item["priority"],
|
|
71
|
-
item.get("assignee") or "-",
|
|
72
|
-
)
|
|
73
|
-
rprint(table)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@app.command()
|
|
77
|
-
def create(
|
|
78
|
-
title: str = typer.Argument(...),
|
|
79
|
-
type: str = typer.Option("story", "-t", "--type", help="Item type: story, task, epic, bug, feature, chore, spike"),
|
|
80
|
-
description: str = typer.Option(None, "-d", "--description"),
|
|
81
|
-
priority: str = typer.Option("medium", "-p", "--priority"),
|
|
82
|
-
parent: str = typer.Option(None, "--parent", help="Parent item ID or number"),
|
|
83
|
-
):
|
|
84
|
-
"""Create a new work item."""
|
|
85
|
-
slug = _require_project()
|
|
86
|
-
data: dict = {"title": title, "type": type, "priority": priority}
|
|
87
|
-
if description:
|
|
88
|
-
data["description"] = description
|
|
89
|
-
if parent:
|
|
90
|
-
# Resolve parent to UUID
|
|
91
|
-
parent_item = _resolve_item(slug, parent)
|
|
92
|
-
data["parent_id"] = parent_item["id"]
|
|
93
|
-
item = api_post(f"/api/projects/{slug}/work-items", json=data)
|
|
94
|
-
key = item.get("key") or f"#{item['number']}"
|
|
95
|
-
rprint(f"[green]Created {item['type']} {key}: {item['title']}[/green]")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@app.command()
|
|
99
|
-
def show(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number (e.g. BB-42)")):
|
|
100
|
-
"""Show work item details."""
|
|
101
|
-
slug = _require_project()
|
|
102
|
-
item = _resolve_item(slug, id_or_number)
|
|
103
|
-
key = item.get("key") or f"#{item['number']}"
|
|
104
|
-
rprint(f"[bold]{key}[/bold] [{item['type']}] {item['title']}")
|
|
105
|
-
rprint(f"Status: {item['status']} | Priority: {item['priority']}")
|
|
106
|
-
if item.get("assignee"):
|
|
107
|
-
rprint(f"Assignee: {item['assignee']}")
|
|
108
|
-
if item.get("parent_id"):
|
|
109
|
-
rprint(f"Parent: {item['parent_id']}")
|
|
110
|
-
if item.get("sprint_id"):
|
|
111
|
-
rprint(f"Sprint: {item['sprint_id']}")
|
|
112
|
-
if item.get("story_points"):
|
|
113
|
-
rprint(f"Points: {item['story_points']}")
|
|
114
|
-
if item.get("description"):
|
|
115
|
-
rprint(f"\n{item['description']}")
|
|
116
|
-
if item.get("acceptance_criteria"):
|
|
117
|
-
rprint(f"\n[bold]Acceptance Criteria:[/bold]\n{item['acceptance_criteria']}")
|
|
118
|
-
if item.get("plan"):
|
|
119
|
-
rprint(f"\n[bold]Plan:[/bold]\n{item['plan']}")
|
|
120
|
-
if item.get("ai_summary"):
|
|
121
|
-
rprint(f"\n[bold]AI Summary:[/bold]\n{item['ai_summary']}")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@app.command()
|
|
125
|
-
def update(
|
|
126
|
-
id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
|
|
127
|
-
status: str = typer.Option(None, "-s", "--status"),
|
|
128
|
-
assignee: str = typer.Option(None, "-a", "--assignee"),
|
|
129
|
-
priority: str = typer.Option(None, "-p", "--priority"),
|
|
130
|
-
title: str = typer.Option(None, "--title"),
|
|
131
|
-
type: str = typer.Option(None, "-t", "--type"),
|
|
132
|
-
):
|
|
133
|
-
"""Update a work item."""
|
|
134
|
-
slug = _require_project()
|
|
135
|
-
item = _resolve_item(slug, id_or_number)
|
|
136
|
-
data = {}
|
|
137
|
-
if status:
|
|
138
|
-
data["status"] = status
|
|
139
|
-
if assignee:
|
|
140
|
-
data["assignee"] = assignee
|
|
141
|
-
if priority:
|
|
142
|
-
data["priority"] = priority
|
|
143
|
-
if title:
|
|
144
|
-
data["title"] = title
|
|
145
|
-
if type:
|
|
146
|
-
data["type"] = type
|
|
147
|
-
if not data:
|
|
148
|
-
rprint("[yellow]Nothing to update.[/yellow]")
|
|
149
|
-
raise typer.Exit()
|
|
150
|
-
updated = api_put(f"/api/work-items/{item['id']}", json=data)
|
|
151
|
-
key = updated.get("key") or f"#{updated['number']}"
|
|
152
|
-
rprint(f"[green]Updated {key}[/green]")
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
@app.command()
|
|
156
|
-
def assign(
|
|
157
|
-
id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
|
|
158
|
-
assignee: str = typer.Argument(...),
|
|
159
|
-
):
|
|
160
|
-
"""Assign a work item to someone."""
|
|
161
|
-
slug = _require_project()
|
|
162
|
-
item = _resolve_item(slug, id_or_number)
|
|
163
|
-
updated = api_put(f"/api/work-items/{item['id']}", json={"assignee": assignee})
|
|
164
|
-
key = updated.get("key") or f"#{updated['number']}"
|
|
165
|
-
rprint(f"[green]{key} assigned to {assignee}[/green]")
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@app.command()
|
|
169
|
-
def children(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number")):
|
|
170
|
-
"""List children of a work item."""
|
|
171
|
-
slug = _require_project()
|
|
172
|
-
item = _resolve_item(slug, id_or_number)
|
|
173
|
-
kids = api_get(f"/api/work-items/{item['id']}/children")
|
|
174
|
-
if not kids:
|
|
175
|
-
rprint("[dim]No children.[/dim]")
|
|
176
|
-
return
|
|
177
|
-
table = Table(title=f"Children of {item.get('key') or '#' + str(item['number'])}")
|
|
178
|
-
table.add_column("#", style="cyan", justify="right")
|
|
179
|
-
table.add_column("Type", style="magenta")
|
|
180
|
-
table.add_column("Title")
|
|
181
|
-
table.add_column("Status", style="green")
|
|
182
|
-
table.add_column("Assignee")
|
|
183
|
-
for kid in kids:
|
|
184
|
-
key = kid.get("key") or str(kid["number"])
|
|
185
|
-
table.add_row(
|
|
186
|
-
key,
|
|
187
|
-
kid["type"],
|
|
188
|
-
kid["title"],
|
|
189
|
-
kid["status"],
|
|
190
|
-
kid.get("assignee") or "-",
|
|
191
|
-
)
|
|
192
|
-
rprint(table)
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from rich import print as rprint
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from ..api_client import api_get, api_post, api_put
|
|
8
|
+
from ..config import get_current_project
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Work item management")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _require_project() -> str:
|
|
14
|
+
slug = get_current_project()
|
|
15
|
+
if not slug:
|
|
16
|
+
rprint("[red]No project selected. Run [bold]bb project switch <slug>[/bold] first.[/red]")
|
|
17
|
+
raise typer.Exit(1)
|
|
18
|
+
return slug
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve_item(slug: str, id_or_number: str) -> dict:
|
|
22
|
+
"""Resolve a work item by UUID, number, or KEY-number format."""
|
|
23
|
+
# UUID format
|
|
24
|
+
if len(id_or_number) > 8 and "-" in id_or_number:
|
|
25
|
+
return api_get(f"/api/work-items/{id_or_number}")
|
|
26
|
+
|
|
27
|
+
# KEY-number format (e.g. BB-42) or plain integer
|
|
28
|
+
match = re.match(r"^(?:[A-Za-z]+-)?(\d+)$", id_or_number)
|
|
29
|
+
if match:
|
|
30
|
+
number = int(match.group(1))
|
|
31
|
+
return api_get(f"/api/projects/{slug}/work-items/by-number/{number}")
|
|
32
|
+
|
|
33
|
+
rprint(f"[red]Invalid identifier: {id_or_number}. Use UUID, number, or KEY-number (e.g. BB-42).[/red]")
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@app.command(name="list")
|
|
38
|
+
def list_items(
|
|
39
|
+
type: str = typer.Option(None, "-t", "--type", help="Filter by type (story, task, epic, bug...)"),
|
|
40
|
+
status: str = typer.Option(None, "-s", "--status", help="Filter by status"),
|
|
41
|
+
parent: str = typer.Option(None, "--parent", help="Filter by parent ID"),
|
|
42
|
+
assignee: str = typer.Option(None, "-a", "--assignee", help="Filter by assignee"),
|
|
43
|
+
):
|
|
44
|
+
"""List work items in the current project."""
|
|
45
|
+
slug = _require_project()
|
|
46
|
+
params = {}
|
|
47
|
+
if type:
|
|
48
|
+
params["type"] = type
|
|
49
|
+
if status:
|
|
50
|
+
params["status"] = status
|
|
51
|
+
if parent:
|
|
52
|
+
params["parent_id"] = parent
|
|
53
|
+
if assignee:
|
|
54
|
+
params["assignee"] = assignee
|
|
55
|
+
items = api_get(f"/api/projects/{slug}/work-items", params=params)
|
|
56
|
+
table = Table(title=f"Work Items - {slug}")
|
|
57
|
+
table.add_column("#", style="cyan", justify="right")
|
|
58
|
+
table.add_column("Type", style="magenta")
|
|
59
|
+
table.add_column("Title")
|
|
60
|
+
table.add_column("Status", style="green")
|
|
61
|
+
table.add_column("Priority")
|
|
62
|
+
table.add_column("Assignee")
|
|
63
|
+
for item in items:
|
|
64
|
+
key = item.get("key") or str(item["number"])
|
|
65
|
+
table.add_row(
|
|
66
|
+
key,
|
|
67
|
+
item["type"],
|
|
68
|
+
item["title"],
|
|
69
|
+
item["status"],
|
|
70
|
+
item["priority"],
|
|
71
|
+
item.get("assignee") or "-",
|
|
72
|
+
)
|
|
73
|
+
rprint(table)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@app.command()
|
|
77
|
+
def create(
|
|
78
|
+
title: str = typer.Argument(...),
|
|
79
|
+
type: str = typer.Option("story", "-t", "--type", help="Item type: story, task, epic, bug, feature, chore, spike"),
|
|
80
|
+
description: str = typer.Option(None, "-d", "--description"),
|
|
81
|
+
priority: str = typer.Option("medium", "-p", "--priority"),
|
|
82
|
+
parent: str = typer.Option(None, "--parent", help="Parent item ID or number"),
|
|
83
|
+
):
|
|
84
|
+
"""Create a new work item."""
|
|
85
|
+
slug = _require_project()
|
|
86
|
+
data: dict = {"title": title, "type": type, "priority": priority}
|
|
87
|
+
if description:
|
|
88
|
+
data["description"] = description
|
|
89
|
+
if parent:
|
|
90
|
+
# Resolve parent to UUID
|
|
91
|
+
parent_item = _resolve_item(slug, parent)
|
|
92
|
+
data["parent_id"] = parent_item["id"]
|
|
93
|
+
item = api_post(f"/api/projects/{slug}/work-items", json=data)
|
|
94
|
+
key = item.get("key") or f"#{item['number']}"
|
|
95
|
+
rprint(f"[green]Created {item['type']} {key}: {item['title']}[/green]")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.command()
|
|
99
|
+
def show(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number (e.g. BB-42)")):
|
|
100
|
+
"""Show work item details."""
|
|
101
|
+
slug = _require_project()
|
|
102
|
+
item = _resolve_item(slug, id_or_number)
|
|
103
|
+
key = item.get("key") or f"#{item['number']}"
|
|
104
|
+
rprint(f"[bold]{key}[/bold] [{item['type']}] {item['title']}")
|
|
105
|
+
rprint(f"Status: {item['status']} | Priority: {item['priority']}")
|
|
106
|
+
if item.get("assignee"):
|
|
107
|
+
rprint(f"Assignee: {item['assignee']}")
|
|
108
|
+
if item.get("parent_id"):
|
|
109
|
+
rprint(f"Parent: {item['parent_id']}")
|
|
110
|
+
if item.get("sprint_id"):
|
|
111
|
+
rprint(f"Sprint: {item['sprint_id']}")
|
|
112
|
+
if item.get("story_points"):
|
|
113
|
+
rprint(f"Points: {item['story_points']}")
|
|
114
|
+
if item.get("description"):
|
|
115
|
+
rprint(f"\n{item['description']}")
|
|
116
|
+
if item.get("acceptance_criteria"):
|
|
117
|
+
rprint(f"\n[bold]Acceptance Criteria:[/bold]\n{item['acceptance_criteria']}")
|
|
118
|
+
if item.get("plan"):
|
|
119
|
+
rprint(f"\n[bold]Plan:[/bold]\n{item['plan']}")
|
|
120
|
+
if item.get("ai_summary"):
|
|
121
|
+
rprint(f"\n[bold]AI Summary:[/bold]\n{item['ai_summary']}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.command()
|
|
125
|
+
def update(
|
|
126
|
+
id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
|
|
127
|
+
status: str = typer.Option(None, "-s", "--status"),
|
|
128
|
+
assignee: str = typer.Option(None, "-a", "--assignee"),
|
|
129
|
+
priority: str = typer.Option(None, "-p", "--priority"),
|
|
130
|
+
title: str = typer.Option(None, "--title"),
|
|
131
|
+
type: str = typer.Option(None, "-t", "--type"),
|
|
132
|
+
):
|
|
133
|
+
"""Update a work item."""
|
|
134
|
+
slug = _require_project()
|
|
135
|
+
item = _resolve_item(slug, id_or_number)
|
|
136
|
+
data = {}
|
|
137
|
+
if status:
|
|
138
|
+
data["status"] = status
|
|
139
|
+
if assignee:
|
|
140
|
+
data["assignee"] = assignee
|
|
141
|
+
if priority:
|
|
142
|
+
data["priority"] = priority
|
|
143
|
+
if title:
|
|
144
|
+
data["title"] = title
|
|
145
|
+
if type:
|
|
146
|
+
data["type"] = type
|
|
147
|
+
if not data:
|
|
148
|
+
rprint("[yellow]Nothing to update.[/yellow]")
|
|
149
|
+
raise typer.Exit()
|
|
150
|
+
updated = api_put(f"/api/work-items/{item['id']}", json=data)
|
|
151
|
+
key = updated.get("key") or f"#{updated['number']}"
|
|
152
|
+
rprint(f"[green]Updated {key}[/green]")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command()
|
|
156
|
+
def assign(
|
|
157
|
+
id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number"),
|
|
158
|
+
assignee: str = typer.Argument(...),
|
|
159
|
+
):
|
|
160
|
+
"""Assign a work item to someone."""
|
|
161
|
+
slug = _require_project()
|
|
162
|
+
item = _resolve_item(slug, id_or_number)
|
|
163
|
+
updated = api_put(f"/api/work-items/{item['id']}", json={"assignee": assignee})
|
|
164
|
+
key = updated.get("key") or f"#{updated['number']}"
|
|
165
|
+
rprint(f"[green]{key} assigned to {assignee}[/green]")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@app.command()
|
|
169
|
+
def children(id_or_number: str = typer.Argument(..., help="UUID, number, or KEY-number")):
|
|
170
|
+
"""List children of a work item."""
|
|
171
|
+
slug = _require_project()
|
|
172
|
+
item = _resolve_item(slug, id_or_number)
|
|
173
|
+
kids = api_get(f"/api/work-items/{item['id']}/children")
|
|
174
|
+
if not kids:
|
|
175
|
+
rprint("[dim]No children.[/dim]")
|
|
176
|
+
return
|
|
177
|
+
table = Table(title=f"Children of {item.get('key') or '#' + str(item['number'])}")
|
|
178
|
+
table.add_column("#", style="cyan", justify="right")
|
|
179
|
+
table.add_column("Type", style="magenta")
|
|
180
|
+
table.add_column("Title")
|
|
181
|
+
table.add_column("Status", style="green")
|
|
182
|
+
table.add_column("Assignee")
|
|
183
|
+
for kid in kids:
|
|
184
|
+
key = kid.get("key") or str(kid["number"])
|
|
185
|
+
table.add_row(
|
|
186
|
+
key,
|
|
187
|
+
kid["type"],
|
|
188
|
+
kid["title"],
|
|
189
|
+
kid["status"],
|
|
190
|
+
kid.get("assignee") or "-",
|
|
191
|
+
)
|
|
192
|
+
rprint(table)
|