forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgoal.py
More file actions
172 lines (147 loc) · 6.23 KB
/
Copy pathgoal.py
File metadata and controls
172 lines (147 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""``omi goal`` — tracked progress metrics."""
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import typer
from omi_cli.errors import UsageError
from omi_cli.models import GoalType
from omi_cli.output import shorten
if TYPE_CHECKING:
from omi_cli.main import AppContext
app = typer.Typer(no_args_is_help=True)
def _ctx(typer_ctx: typer.Context) -> "AppContext":
obj = typer_ctx.obj
if obj is None: # pragma: no cover
raise RuntimeError("AppContext not initialized")
return obj # type: ignore[no-any-return]
_LIST_COLUMNS = ["id", "title", "goal_type", "current_value", "target_value", "unit", "is_active"]
@app.command("list", help="List goals.")
def list_goals(
typer_ctx: typer.Context,
limit: int = typer.Option(10, "--limit", min=1, max=100),
include_inactive: bool = typer.Option(False, "--include-inactive", help="Include inactive/completed goals."),
) -> None:
ctx = _ctx(typer_ctx)
with ctx.make_client() as client:
items = client.get(
"/v1/dev/user/goals",
params={"limit": limit, "include_inactive": include_inactive},
)
if ctx.renderer.json_mode:
ctx.renderer.emit(items)
return
rows = []
for g in items or []:
rows.append(
{
"id": shorten(g.get("id"), 14),
"title": shorten(g.get("title"), 40),
"goal_type": g.get("goal_type"),
"current_value": g.get("current_value"),
"target_value": g.get("target_value"),
"unit": g.get("unit"),
"is_active": g.get("is_active"),
}
)
ctx.renderer.emit(rows, columns=_LIST_COLUMNS, title="goals")
@app.command("get", help="Fetch a single goal by ID.")
def get_goal(
typer_ctx: typer.Context,
goal_id: str = typer.Argument(..., help="Goal ID."),
) -> None:
ctx = _ctx(typer_ctx)
with ctx.make_client() as client:
result = client.get(f"/v1/dev/user/goals/{goal_id}")
ctx.renderer.emit(result, title="goal")
@app.command("create", help="Create a new goal. Up to 3 active goals per user.")
def create_goal(
typer_ctx: typer.Context,
title: str = typer.Argument(..., help="Goal title (1-500 chars)."),
target_value: float = typer.Option(..., "--target", help="Target value to achieve."),
goal_type: GoalType = typer.Option(GoalType.scale, "--type", help="boolean, scale, or numeric."),
current_value: float = typer.Option(0, "--current", help="Current progress value."),
min_value: float = typer.Option(0, "--min", help="Minimum scale value."),
max_value: float = typer.Option(10, "--max", help="Maximum scale value."),
unit: Optional[str] = typer.Option(None, "--unit", help="Unit label (e.g. 'users', 'points')."),
) -> None:
ctx = _ctx(typer_ctx)
body: dict[str, object] = {
"title": title,
"goal_type": goal_type.value,
"target_value": target_value,
"current_value": current_value,
"min_value": min_value,
"max_value": max_value,
}
if unit is not None:
body["unit"] = unit
with ctx.make_client() as client:
result = client.post("/v1/dev/user/goals", json_body=body)
ctx.renderer.success(f"Goal created: [bold]{result.get('id')}[/bold]")
ctx.renderer.emit(result)
@app.command("update", help="Update a goal's metadata.")
def update_goal(
typer_ctx: typer.Context,
goal_id: str = typer.Argument(..., help="Goal ID."),
title: Optional[str] = typer.Option(None, "--title"),
target_value: Optional[float] = typer.Option(None, "--target"),
current_value: Optional[float] = typer.Option(None, "--current"),
min_value: Optional[float] = typer.Option(None, "--min"),
max_value: Optional[float] = typer.Option(None, "--max"),
unit: Optional[str] = typer.Option(None, "--unit"),
) -> None:
ctx = _ctx(typer_ctx)
body: dict[str, object] = {}
if title is not None:
body["title"] = title
if target_value is not None:
body["target_value"] = target_value
if current_value is not None:
body["current_value"] = current_value
if min_value is not None:
body["min_value"] = min_value
if max_value is not None:
body["max_value"] = max_value
if unit is not None:
body["unit"] = unit
if not body:
raise UsageError(
message="No fields to update", detail="Provide one of --title/--target/--current/--min/--max/--unit."
)
with ctx.make_client() as client:
result = client.patch(f"/v1/dev/user/goals/{goal_id}", json_body=body)
ctx.renderer.success(f"Updated goal [bold]{goal_id}[/bold].")
ctx.renderer.emit(result)
@app.command("progress", help="Update only the current_value of a goal (shortcut).")
def update_progress(
typer_ctx: typer.Context,
goal_id: str = typer.Argument(..., help="Goal ID."),
current_value: float = typer.Argument(..., help="New progress value."),
) -> None:
ctx = _ctx(typer_ctx)
with ctx.make_client() as client:
# The progress endpoint takes current_value as a query param.
result = client.patch(f"/v1/dev/user/goals/{goal_id}/progress", params={"current_value": current_value})
ctx.renderer.success(f"Updated progress on [bold]{goal_id}[/bold] → {current_value}.")
ctx.renderer.emit(result)
@app.command("history", help="Fetch progress history for a goal.")
def goal_history(
typer_ctx: typer.Context,
goal_id: str = typer.Argument(..., help="Goal ID."),
days: int = typer.Option(30, "--days", min=1, max=365),
) -> None:
ctx = _ctx(typer_ctx)
with ctx.make_client() as client:
result = client.get(f"/v1/dev/user/goals/{goal_id}/history", params={"days": days})
ctx.renderer.emit(result, title=f"goal history (last {days}d)")
@app.command("delete", help="Delete a goal by ID.")
def delete_goal(
typer_ctx: typer.Context,
goal_id: str = typer.Argument(..., help="Goal ID."),
confirm: bool = typer.Option(False, "--yes", "-y"),
) -> None:
ctx = _ctx(typer_ctx)
if not confirm:
typer.confirm(f"Delete goal {goal_id}?", abort=True)
with ctx.make_client() as client:
client.delete(f"/v1/dev/user/goals/{goal_id}")
ctx.renderer.success(f"Deleted goal [bold]{goal_id}[/bold].")