goodeye 0.1.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.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: goodeye
3
+ Version: 0.1.0
4
+ Summary: Public CLI for Goodeye - manage AI workflow skills from the terminal.
5
+ Project-URL: Repository, https://github.com/Goodeye-Labs/goodeye-cli
6
+ Project-URL: Issues, https://github.com/Goodeye-Labs/goodeye-cli/issues
7
+ Author: Goodeye Labs
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Goodeye Labs
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: ai,cli,goodeye,mcp,workflows
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Environment :: Console
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Topic :: Software Development
39
+ Classifier: Topic :: Utilities
40
+ Requires-Python: >=3.12
41
+ Requires-Dist: httpx>=0.27
42
+ Requires-Dist: pydantic-settings>=2.4
43
+ Requires-Dist: pydantic>=2.7
44
+ Requires-Dist: pyyaml>=6
45
+ Requires-Dist: rich>=13
46
+ Requires-Dist: typer>=0.12
47
+ Description-Content-Type: text/markdown
48
+
49
+ # goodeye-cli
50
+
51
+ Command-line client for Goodeye - manage AI workflow skills from the terminal.
52
+
53
+ Goodeye is an outcome-aligned AI workflow registry: you author skills (markdown
54
+ bodies + manifests) and verifiers that score an AI agent against a measurable
55
+ business outcome. This CLI is wired to the public `/v1/` REST API.
56
+
57
+ ## Install
58
+
59
+ Requires Python 3.12+.
60
+
61
+ ```sh
62
+ uv tool install goodeye
63
+ # or
64
+ pipx install goodeye
65
+ # or
66
+ pip install goodeye
67
+ ```
68
+
69
+ Once installed, the `goodeye` command is available on your `PATH`.
70
+
71
+ ## Quickstart
72
+
73
+ ```sh
74
+ # Browse the public registry without an account
75
+ goodeye skills list --filter public
76
+
77
+ # Create an account (emails a one-time code)
78
+ goodeye signup --email you@example.com
79
+
80
+ # Or log in on a machine with a browser
81
+ goodeye login
82
+
83
+ # Confirm who you are
84
+ goodeye whoami
85
+
86
+ # Fetch a public skill as markdown
87
+ goodeye skills get brand-voice > brand-voice.md
88
+
89
+ # Push a local skill
90
+ goodeye skills push ./my-skill.md --public
91
+ ```
92
+
93
+ ### Skill files
94
+
95
+ `goodeye skills push` reads a markdown file with optional YAML front-matter:
96
+
97
+ ```markdown
98
+ ---
99
+ slug: my-skill
100
+ visibility: private
101
+ manifest:
102
+ title: My skill
103
+ tags: [data, cleanup]
104
+ ---
105
+
106
+ # Body
107
+
108
+ The rest of the file is the skill body rendered to the agent at runtime.
109
+ ```
110
+
111
+ `--public` on the command line overrides `visibility`. `--slug` on the command
112
+ line overrides the front-matter `slug`.
113
+
114
+ ## Command reference
115
+
116
+ ```
117
+ goodeye login [--email EMAIL]
118
+ Without --email: opens the browser for WorkOS device-code approval.
119
+ With --email: sends a one-time code to your inbox.
120
+
121
+ goodeye signup --email EMAIL
122
+ Creates an account and mints your initial API key.
123
+
124
+ goodeye logout
125
+ Deletes local credentials. Does not revoke the key server-side; see
126
+ `goodeye auth list-keys` and `goodeye auth revoke-key`.
127
+
128
+ goodeye whoami
129
+ Shows the current user identified by your credentials.
130
+
131
+ goodeye auth create-key --name NAME [--copy]
132
+ Mints a new API key. The secret is printed once.
133
+
134
+ goodeye auth list-keys
135
+ Table of your API keys (secrets are never returned).
136
+
137
+ goodeye auth revoke-key <id>
138
+ Revokes (soft-deletes) a key.
139
+
140
+ goodeye skills list [--filter all|public|own] [--tag TAG] [--search QUERY] [--json]
141
+ Paginated listing; auto-follows cursor.
142
+
143
+ goodeye skills get <id-or-slug> [--version N] [--output PATH] [--json]
144
+ Emits raw markdown by default; --json returns the envelope.
145
+
146
+ goodeye skills push <file.md> [--id ID] [--public] [--slug SLUG]
147
+ Creates or appends a skill version.
148
+
149
+ goodeye skills set-visibility <id> <private|public>
150
+ goodeye skills delete <id> [--yes]
151
+
152
+ goodeye design
153
+ Prints the workflow-designer prompt pack to stdout.
154
+ ```
155
+
156
+ ## Configuration
157
+
158
+ ### Credentials
159
+
160
+ - `GOODEYE_API_KEY` env var (highest precedence).
161
+ - `~/.config/goodeye/credentials.json` (or `$XDG_CONFIG_HOME/goodeye/`).
162
+
163
+ Credential files are created with mode `0600`.
164
+
165
+ ### Server
166
+
167
+ - `GOODEYE_SERVER` env var.
168
+ - `server` field inside `credentials.json`.
169
+ - Default: `https://mcp.goodeyelabs.com`.
170
+
171
+ ## REST API, not the CLI
172
+
173
+ This CLI is pinned to the `/v1/` REST API contract. If you are integrating
174
+ programmatically and want a stable contract, prefer the REST API directly;
175
+ the CLI is a convenience layer over it.
176
+
177
+ ## Contributing
178
+
179
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for local-dev setup and the PR
180
+ process. Issues and PRs welcome.
181
+
182
+ ## License
183
+
184
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,21 @@
1
+ goodeye_cli/__init__.py,sha256=RSSlXudxAflV6gv0bxSl7812iWOeg5CE9rVH4kkRqIA,53
2
+ goodeye_cli/__main__.py,sha256=mi3sTSnf-KIOp1YSjj7rd4bdIh6uZ014YmqkIeMwQi8,238
3
+ goodeye_cli/app.py,sha256=PFdsdxja4TQM-eGyJ5WM1f4hlNRjP5OBndjeFqSTwlA,2056
4
+ goodeye_cli/auth_flows.py,sha256=iXjNAOaepYXh57BWjHTD7bd-P_V8Pjclg42MYw07lSg,6367
5
+ goodeye_cli/client.py,sha256=xATishplzeJGxKoiOpwaolXLaRyH25mTQit3pjJwRcI,9927
6
+ goodeye_cli/config.py,sha256=QD6klJQ3iy07IDtuSJjwA3gECwglcjzzVj7aHoVg-tY,4832
7
+ goodeye_cli/errors.py,sha256=reJpX5gSYa5SKAPtUoA3ZQVQzvlORh2tq0SQ_9ioN2U,2849
8
+ goodeye_cli/wire.py,sha256=GFbdZFfqolcB-Za2OBZ9JmvPCnD7KWTr_0lKH5-rmvo,2256
9
+ goodeye_cli/commands/__init__.py,sha256=lt7T0WVXVuhXPgzvaCmzBnCB21N1d0G-u2iJ5unxuCA,32
10
+ goodeye_cli/commands/auth.py,sha256=jBfj2QSRskCuT1lYdb8RCoudI2Mowd-Qfs2jWmEcbDM,3827
11
+ goodeye_cli/commands/design.py,sha256=otAVFVk87wl9YyaxiGhdUVZEs_lr2EMlu5yxQX-1S8U,1478
12
+ goodeye_cli/commands/login.py,sha256=IapCLBccg4_MbwGnf5Ard2-pLLcJbI2n2tl9rhdE3to,1747
13
+ goodeye_cli/commands/logout.py,sha256=MIhapOGWrsoaa-Z0GrIZTezP7OeUsssY5Iu1J059gOI,902
14
+ goodeye_cli/commands/signup.py,sha256=m3qa2kGl223AP2k_zF9laG9IuDgXfnePe0QWq2FJnSI,843
15
+ goodeye_cli/commands/skills.py,sha256=wPZuztOZLYp0Iu85C1CCaM8yH20Iuj1MrKaBy1sLN7U,8439
16
+ goodeye_cli/commands/whoami.py,sha256=agN_cTuk8yL5rbVqd5y_FgzyUcxhtDXxFM0Le_mTUIY,994
17
+ goodeye-0.1.0.dist-info/METADATA,sha256=-wUGoQjNO3Lhev7QXvLxR0zVi8eECkaren8GRLtUT_A,5564
18
+ goodeye-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
19
+ goodeye-0.1.0.dist-info/entry_points.txt,sha256=is74w4li0ittV5GT7DagyP_qS5MHHGyA6qZZw3qtzhI,54
20
+ goodeye-0.1.0.dist-info/licenses/LICENSE,sha256=elL9v1FdiFNSU5YJe1YWYjlJ0EqHbvUxINlVOOZytr4,1069
21
+ goodeye-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ goodeye = goodeye_cli.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Goodeye Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ """Public CLI for Goodeye."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,16 @@
1
+ """Module entrypoint.
2
+
3
+ Enables ``python -m goodeye_cli`` and the ``goodeye`` console script.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from goodeye_cli.app import app
9
+
10
+
11
+ def main() -> None:
12
+ app()
13
+
14
+
15
+ if __name__ == "__main__":
16
+ main()
goodeye_cli/app.py ADDED
@@ -0,0 +1,69 @@
1
+ """Typer app root. Wires subcommands and a global error handler."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ import typer
8
+ from rich.console import Console
9
+
10
+ from goodeye_cli import __version__
11
+ from goodeye_cli.commands import auth as auth_cmds
12
+ from goodeye_cli.commands import design as design_cmd
13
+ from goodeye_cli.commands import login as login_cmd
14
+ from goodeye_cli.commands import logout as logout_cmd
15
+ from goodeye_cli.commands import signup as signup_cmd
16
+ from goodeye_cli.commands import skills as skills_cmds
17
+ from goodeye_cli.commands import whoami as whoami_cmd
18
+ from goodeye_cli.errors import GoodeyeError
19
+
20
+ app = typer.Typer(
21
+ name="goodeye",
22
+ help="Public CLI for Goodeye - manage AI workflow skills from the terminal.",
23
+ no_args_is_help=True,
24
+ add_completion=False,
25
+ )
26
+
27
+ # Top-level commands.
28
+ app.command("login")(login_cmd.login)
29
+ app.command("signup")(signup_cmd.signup)
30
+ app.command("logout")(logout_cmd.logout)
31
+ app.command("whoami")(whoami_cmd.whoami)
32
+ app.command("design")(design_cmd.design)
33
+
34
+ # Command groups.
35
+ app.add_typer(auth_cmds.app, name="auth", help="Manage API keys.")
36
+ app.add_typer(skills_cmds.app, name="skills", help="Browse and manage skills.")
37
+
38
+
39
+ def _version_callback(value: bool) -> None:
40
+ if value:
41
+ typer.echo(f"goodeye {__version__}")
42
+ raise typer.Exit()
43
+
44
+
45
+ @app.callback()
46
+ def _root(
47
+ version: bool = typer.Option(
48
+ False,
49
+ "--version",
50
+ callback=_version_callback,
51
+ is_eager=True,
52
+ help="Show the CLI version and exit.",
53
+ ),
54
+ ) -> None:
55
+ """Global options processed before any subcommand."""
56
+ # Body intentionally empty; the callback fires only to register the option.
57
+ _ = version
58
+
59
+
60
+ def main() -> None:
61
+ """Console-script entrypoint with a structured-error-friendly wrapper."""
62
+ console = Console(stderr=True)
63
+ try:
64
+ app()
65
+ except GoodeyeError as exc:
66
+ console.print(f"[bold red]{exc.slug}[/bold red]: {exc.message}")
67
+ if exc.hint:
68
+ console.print(f"[dim]hint: {exc.hint}[/dim]")
69
+ sys.exit(1)
@@ -0,0 +1,168 @@
1
+ """Browser-assisted and headless auth flows.
2
+
3
+ Two flows are implemented:
4
+
5
+ * **Device code flow** (``device_code_login``): used for ``goodeye login`` with no
6
+ email. Requests a user_code from WorkOS, opens the verification URL in the
7
+ default browser, polls until the user approves, then exchanges the resulting
8
+ WorkOS JWT for a Goodeye API key via ``POST /v1/auth/exchange``.
9
+
10
+ * **Magic-auth flow** (``magic_auth_flow``): used for ``goodeye login --email``
11
+ and ``goodeye signup --email``. Posts the email to ``/v1/{intent}``, prompts
12
+ for the emailed code, and posts to ``/v1/{intent}/verify`` to retrieve the
13
+ initial API key.
14
+
15
+ The flows are deliberately injection-friendly: every external dependency
16
+ (``httpx`` transport, browser opener, code prompt, clock) can be overridden so
17
+ the flows can be unit-tested with ``respx`` without any real I/O.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import contextlib
23
+ import time
24
+ import webbrowser
25
+ from collections.abc import Callable
26
+
27
+ from rich.console import Console
28
+
29
+ from goodeye_cli.client import (
30
+ GoodeyeClient,
31
+ poll_device_token,
32
+ request_device_authorization,
33
+ )
34
+ from goodeye_cli.errors import GoodeyeError, InvalidCredentials
35
+
36
+
37
+ def device_code_login(
38
+ server: str,
39
+ workos_client_id: str,
40
+ workos_device_authorization_uri: str,
41
+ workos_token_uri: str,
42
+ *,
43
+ hostname: str | None = None,
44
+ console: Console | None = None,
45
+ open_browser: Callable[[str], bool] | None = None,
46
+ sleep: Callable[[float], None] = time.sleep,
47
+ clock: Callable[[], float] = time.monotonic,
48
+ max_wait_s: float | None = None,
49
+ ) -> str:
50
+ """Run the device-code flow and return the minted API key.
51
+
52
+ Args:
53
+ server: Goodeye server base URL (used for ``/v1/auth/exchange``).
54
+ workos_client_id: The WorkOS application client id, fetched from
55
+ ``/.well-known/goodeye-client-config``.
56
+ workos_device_authorization_uri: WorkOS device-authorization endpoint URL.
57
+ workos_token_uri: WorkOS token endpoint URL.
58
+ hostname: Optional host label to embed in the minted key's name.
59
+ console: Rich console for UX output. A default is created when None.
60
+ open_browser: Overridable function to open the verification URL.
61
+ Returns False if no browser could be opened.
62
+ sleep: Overridable sleep for tests.
63
+ clock: Overridable monotonic clock for tests.
64
+ max_wait_s: Optional hard cap on total poll duration. Defaults to the
65
+ ``expires_in`` returned by WorkOS.
66
+
67
+ Returns:
68
+ The newly minted Goodeye API key.
69
+ """
70
+ out = console or Console()
71
+ opener = open_browser or webbrowser.open
72
+
73
+ auth = request_device_authorization(workos_device_authorization_uri, workos_client_id)
74
+ out.print(
75
+ f"\nVisit this URL to approve the sign-in:\n [bold]{auth.verification_uri_complete}[/bold]"
76
+ )
77
+ out.print(f"User code: [bold]{auth.user_code}[/bold]\n")
78
+ # Best-effort browser open; failure is non-fatal because the URL is already printed.
79
+ with contextlib.suppress(Exception):
80
+ opener(auth.verification_uri_complete)
81
+
82
+ deadline = clock() + (max_wait_s if max_wait_s is not None else auth.expires_in)
83
+ interval = max(1, int(auth.interval))
84
+
85
+ access_token: str | None = None
86
+ while clock() < deadline:
87
+ status, body = poll_device_token(workos_token_uri, workos_client_id, auth.device_code)
88
+ if status == 200 and isinstance(body.get("access_token"), str):
89
+ access_token = str(body["access_token"])
90
+ break
91
+ # WorkOS/OAuth pending-states: continue polling. Everything else: fail fast.
92
+ error = body.get("error") if isinstance(body, dict) else None
93
+ if status == 400 and error in ("authorization_pending", "slow_down"):
94
+ if error == "slow_down":
95
+ interval += 5
96
+ sleep(interval)
97
+ continue
98
+ description = body.get("error_description") if isinstance(body, dict) else None
99
+ message = description if isinstance(description, str) else "Device authorization failed."
100
+ raise InvalidCredentials(
101
+ slug=str(error) if isinstance(error, str) else "invalid_credentials",
102
+ message=message,
103
+ status_code=status,
104
+ )
105
+
106
+ if access_token is None:
107
+ raise GoodeyeError(
108
+ slug="auth_required",
109
+ message="Timed out waiting for device approval.",
110
+ hint="Re-run `goodeye login` and complete approval in the browser.",
111
+ )
112
+
113
+ with GoodeyeClient(server, api_key=access_token) as client:
114
+ result = client.exchange(hostname=hostname)
115
+ return result.api_key
116
+
117
+
118
+ def magic_auth_flow(
119
+ server: str,
120
+ email: str,
121
+ *,
122
+ intent: str = "login",
123
+ prompt_code: Callable[[], str] | None = None,
124
+ console: Console | None = None,
125
+ ) -> str:
126
+ """Run the magic-auth flow and return the newly minted API key.
127
+
128
+ Args:
129
+ server: Goodeye server base URL.
130
+ email: The user's email address.
131
+ intent: ``"login"`` or ``"signup"``. Both call the same underlying WorkOS
132
+ magic-auth endpoints but are kept separate for logging/legibility.
133
+ prompt_code: Callable that returns the 6-digit code the user received.
134
+ Defaults to a Rich input prompt.
135
+ console: Rich console for UX output.
136
+
137
+ Returns:
138
+ The newly minted Goodeye API key.
139
+ """
140
+ if intent not in ("login", "signup"):
141
+ raise ValueError(f"intent must be 'login' or 'signup', got {intent!r}")
142
+
143
+ out = console or Console()
144
+
145
+ def _default_prompt() -> str:
146
+ return out.input("Enter the 6-digit code sent to your email: ").strip()
147
+
148
+ prompt = prompt_code or _default_prompt
149
+
150
+ with GoodeyeClient(server) as client:
151
+ if intent == "signup":
152
+ client.signup(email)
153
+ else:
154
+ client.login(email)
155
+ out.print(f"A sign-in code was sent to [bold]{email}[/bold].")
156
+ code = prompt()
157
+ if not code:
158
+ raise InvalidCredentials(
159
+ slug="invalid_credentials",
160
+ message="No code provided.",
161
+ hint="Check your email and re-run the command.",
162
+ )
163
+ result = (
164
+ client.signup_verify(email, code)
165
+ if intent == "signup"
166
+ else client.login_verify(email, code)
167
+ )
168
+ return result.api_key