gravity-cli 0.2.0__tar.gz → 0.3.1__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.
Files changed (96) hide show
  1. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/.env.example +7 -6
  2. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/PKG-INFO +12 -9
  3. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/README.md +10 -7
  4. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/pyproject.toml +1 -1
  5. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/pyproject.toml +2 -2
  6. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/api.py +38 -3
  7. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/run.py +17 -3
  8. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/validate.py +4 -2
  9. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/config.py +1 -2
  10. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/ui.py +1 -1
  11. gravity_cli-0.3.1/tests/conftest.py +8 -0
  12. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_api.py +35 -0
  13. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_run.py +12 -3
  14. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/uv.lock +2 -2
  15. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/.github/workflows/ci.yml +0 -0
  16. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/.github/workflows/release.yml +0 -0
  17. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/.gitignore +0 -0
  18. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/AGENT_STANDARD.md +0 -0
  19. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/LICENSE +0 -0
  20. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/NOTICE +0 -0
  21. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/TEMPLATES.md +0 -0
  22. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/LICENSE +0 -0
  23. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/NOTICE +0 -0
  24. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/README.md +0 -0
  25. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/__init__.py +0 -0
  26. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/__main__.py +0 -0
  27. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/catalog.py +0 -0
  28. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/catalog.yaml +0 -0
  29. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/catalog_imported.yaml +0 -0
  30. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/contract.py +0 -0
  31. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/cron.py +0 -0
  32. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/errors.py +0 -0
  33. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/export.py +0 -0
  34. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/gen_catalog.py +0 -0
  35. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/manifest.py +0 -0
  36. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/gravity_schema/validation.py +0 -0
  37. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/schema/manifest.schema.json +0 -0
  38. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/conftest.py +0 -0
  39. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_catalog_parity.py +0 -0
  40. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_cron.py +0 -0
  41. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_errors.py +0 -0
  42. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_json_schema.py +0 -0
  43. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_kind_outputs.py +0 -0
  44. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_manifest.py +0 -0
  45. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_slots.py +0 -0
  46. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/packages/gravity-schema/tests/test_validation.py +0 -0
  47. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/BUILDER_GUIDE.md +0 -0
  48. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/__init__.py +0 -0
  49. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/__main__.py +0 -0
  50. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/_runner_script.py +0 -0
  51. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/cli.py +0 -0
  52. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/__init__.py +0 -0
  53. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/adapt.py +0 -0
  54. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/build.py +0 -0
  55. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/guide.py +0 -0
  56. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/init.py +0 -0
  57. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/login.py +0 -0
  58. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/push.py +0 -0
  59. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/runs.py +0 -0
  60. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/schedule.py +0 -0
  61. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/commands/tools.py +0 -0
  62. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/manifest.py +0 -0
  63. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/project.py +0 -0
  64. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/chat/.gitignore +0 -0
  65. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/chat/manifest.yaml +0 -0
  66. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/chat/requirements.txt +0 -0
  67. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/chat/src/agent.py +0 -0
  68. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/deepagent/.gitignore +0 -0
  69. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/deepagent/manifest.yaml +0 -0
  70. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/deepagent/requirements.txt +0 -0
  71. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/deepagent/src/agent.py +0 -0
  72. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/minimal/.gitignore +0 -0
  73. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/minimal/manifest.yaml +0 -0
  74. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/minimal/requirements.txt +0 -0
  75. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/minimal/src/agent.py +0 -0
  76. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/.gitignore +0 -0
  77. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/manifest.yaml +0 -0
  78. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/requirements.txt +0 -0
  79. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/src/agent.py +0 -0
  80. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/src/gateway.py +0 -0
  81. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/src/nodes/agent.py +0 -0
  82. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/src/prompts.py +0 -0
  83. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/templates/structured/src/state.py +0 -0
  84. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/src/gravity_cli/timeline.py +0 -0
  85. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/starter/manifest.yaml +0 -0
  86. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/starter/requirements.txt +0 -0
  87. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/starter/src/agent.py +0 -0
  88. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/starter/src/summary.py +0 -0
  89. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/starter/tests/test_summary.py +0 -0
  90. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_adapt.py +0 -0
  91. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_build_push.py +0 -0
  92. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_manifest_integration.py +0 -0
  93. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_templates.py +0 -0
  94. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_timeline.py +0 -0
  95. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_ui.py +0 -0
  96. {gravity_cli-0.2.0 → gravity_cli-0.3.1}/tests/test_welcome.py +0 -0
@@ -3,19 +3,20 @@
3
3
  # What each variable does: README.md, "Environment variables".
4
4
 
5
5
  # --- gravity run / schedule / adapt -------------------------------------
6
- # Your gateway dev token. Required to run an agent. A local gateway prints
7
- # one at startup, or uses its LOCAL_DEV_TOKEN.
6
+ # Your gateway dev token. Required to run an agent. Issued by the Gravity team;
7
+ # a local gateway prints one at startup, or uses its LOCAL_DEV_TOKEN.
8
8
  GRAVITY_DEV_TOKEN=
9
9
 
10
- # The gateway's MCP endpoint. Leave unset for a local gateway on port 8000.
11
- # GRAVITY_GATEWAY_URL=https://<hosted gateway host>/mcp
10
+ # The gateway's MCP endpoint. Leave unset: the CLI asks Gravity where the gateway is.
11
+ # Set it only for a local gateway.
12
+ # GRAVITY_GATEWAY_URL=http://127.0.0.1:8000/mcp
12
13
 
13
14
  # The model endpoint. Leave unset: it follows the gateway (<gateway>/v1).
14
15
  # GRAVITY_LLM_URL=
15
16
 
16
17
  # --- push / tools / agents / runs / logs / whoami ------------------------
17
- # The control plane. Leave unset for a local one at http://localhost:3010.
18
- # GRAVITY_API_URL=https://<control plane host>
18
+ # The control plane. Leave unset for Gravity's; set it only for a local one.
19
+ # GRAVITY_API_URL=http://localhost:3010
19
20
 
20
21
  # Development only: the control plane's dev token, instead of `gravity login`.
21
22
  # GRAVITY_API_TOKEN=
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gravity-cli
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: gravity cli — init, run, and push python agents to the Gravity marketplace
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
7
7
  License-File: NOTICE
8
8
  Requires-Python: >=3.12
9
- Requires-Dist: gravity-schema==0.2.0
9
+ Requires-Dist: gravity-schema==0.3.1
10
10
  Requires-Dist: httpx>=0.27.0
11
11
  Requires-Dist: keyring>=25.0.0
12
12
  Requires-Dist: python-dotenv>=1.0.0
@@ -69,7 +69,7 @@ pipx install gravity-cli
69
69
  gravity init my-agent
70
70
  ```
71
71
 
72
- > **Status: 0.1.0 preview.** `init`, `validate` and `build` work anywhere. `run`, `login` and `push` talk to Gravity services, and the built-in addresses still point at a local development setup. Until the hosted addresses ship as defaults, set them yourself as shown in [Configuration](#configuration).
72
+ > **Status.** `login`, `push` and the tool catalog work out of the box against Gravity's hosted services. `gravity run` also needs a gateway dev token, issued by the Gravity team until per-builder tokens ship.
73
73
 
74
74
  ## Contents
75
75
 
@@ -469,10 +469,10 @@ Put them in `~/.gravity/.env` (on Windows, `%USERPROFILE%\.gravity\.env`). Every
469
469
  ```bash
470
470
  # ~/.gravity/.env
471
471
  GRAVITY_DEV_TOKEN=<your gateway dev token>
472
- GRAVITY_GATEWAY_URL=https://<gateway host>/mcp
473
- GRAVITY_API_URL=https://<control plane host>
474
472
  ```
475
473
 
474
+ That is the only setting most builders ever need. The CLI knows where Gravity's control plane is, and asks it where the gateway is.
475
+
476
476
  When a setting is given in more than one place, the first match wins:
477
477
 
478
478
  1. a variable exported in your shell
@@ -487,17 +487,19 @@ Never commit a `.env` file. The `.gitignore` in every template keeps `.env` and
487
487
  | You want to | Commands | Required | Usually also set |
488
488
  |---|---|---|---|
489
489
  | Create, check and package an agent | `init`, `validate`, `build` | nothing | nothing |
490
- | Run an agent locally | `run`, `schedule`, `adapt` | `GRAVITY_DEV_TOKEN` | `GRAVITY_GATEWAY_URL` |
491
- | Publish and inspect agents | `push`, `tools`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | `GRAVITY_API_URL` |
490
+ | Run an agent locally | `run`, `schedule`, `adapt` | `GRAVITY_DEV_TOKEN` | nothing |
491
+ | Publish and inspect agents | `push`, `tools`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | nothing |
492
+
493
+ Set `GRAVITY_API_URL` and `GRAVITY_GATEWAY_URL` only to work against services you run yourself; see [Running a local gateway](#running-a-local-gateway).
492
494
 
493
495
  ### Environment variables
494
496
 
495
497
  | Variable | What it is | Default |
496
498
  |---|---|---|
497
499
  | `GRAVITY_DEV_TOKEN` | Your gateway dev token. `gravity run` trades it for a run token scoped to the manifest's tools. Required for `run`, `schedule` and `adapt`. | none |
498
- | `GRAVITY_GATEWAY_URL` | The gateway's MCP endpoint, ending in `/mcp` | `http://127.0.0.1:8000/mcp` |
500
+ | `GRAVITY_GATEWAY_URL` | The gateway's MCP endpoint, ending in `/mcp` | the gateway the control plane names in `GET /api/cli/config` |
499
501
  | `GRAVITY_LLM_URL` | The OpenAI-compatible model endpoint | the gateway's address + `/v1` |
500
- | `GRAVITY_API_URL` | The control plane's address | `http://localhost:3010` |
502
+ | `GRAVITY_API_URL` | The control plane's address | `https://agents.dev.gravity.fast` |
501
503
  | `GRAVITY_API_TOKEN` | A control-plane dev token, used instead of `gravity login`. For development only. | none |
502
504
  | `GRAVITY_CONFIG_DIR` | Moves `~/.gravity` elsewhere. Useful for tests and CI. | `~/.gravity` |
503
505
  | `GRAVITY_UNATTENDED` | When `1`, calls that need approval are rejected without asking. `gravity schedule` sets it for you. | unset |
@@ -701,6 +703,7 @@ A fully local setup, in `~/.gravity/.env`:
701
703
 
702
704
  ```bash
703
705
  GRAVITY_DEV_TOKEN=<the gateway's LOCAL_DEV_TOKEN>
706
+ GRAVITY_GATEWAY_URL=http://127.0.0.1:8000/mcp
704
707
  GRAVITY_API_URL=http://localhost:3010
705
708
  GRAVITY_API_TOKEN=<the control plane's PLATFORM_DEV_TOKEN>
706
709
  ```
@@ -52,7 +52,7 @@ pipx install gravity-cli
52
52
  gravity init my-agent
53
53
  ```
54
54
 
55
- > **Status: 0.1.0 preview.** `init`, `validate` and `build` work anywhere. `run`, `login` and `push` talk to Gravity services, and the built-in addresses still point at a local development setup. Until the hosted addresses ship as defaults, set them yourself as shown in [Configuration](#configuration).
55
+ > **Status.** `login`, `push` and the tool catalog work out of the box against Gravity's hosted services. `gravity run` also needs a gateway dev token, issued by the Gravity team until per-builder tokens ship.
56
56
 
57
57
  ## Contents
58
58
 
@@ -452,10 +452,10 @@ Put them in `~/.gravity/.env` (on Windows, `%USERPROFILE%\.gravity\.env`). Every
452
452
  ```bash
453
453
  # ~/.gravity/.env
454
454
  GRAVITY_DEV_TOKEN=<your gateway dev token>
455
- GRAVITY_GATEWAY_URL=https://<gateway host>/mcp
456
- GRAVITY_API_URL=https://<control plane host>
457
455
  ```
458
456
 
457
+ That is the only setting most builders ever need. The CLI knows where Gravity's control plane is, and asks it where the gateway is.
458
+
459
459
  When a setting is given in more than one place, the first match wins:
460
460
 
461
461
  1. a variable exported in your shell
@@ -470,17 +470,19 @@ Never commit a `.env` file. The `.gitignore` in every template keeps `.env` and
470
470
  | You want to | Commands | Required | Usually also set |
471
471
  |---|---|---|---|
472
472
  | Create, check and package an agent | `init`, `validate`, `build` | nothing | nothing |
473
- | Run an agent locally | `run`, `schedule`, `adapt` | `GRAVITY_DEV_TOKEN` | `GRAVITY_GATEWAY_URL` |
474
- | Publish and inspect agents | `push`, `tools`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | `GRAVITY_API_URL` |
473
+ | Run an agent locally | `run`, `schedule`, `adapt` | `GRAVITY_DEV_TOKEN` | nothing |
474
+ | Publish and inspect agents | `push`, `tools`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | nothing |
475
+
476
+ Set `GRAVITY_API_URL` and `GRAVITY_GATEWAY_URL` only to work against services you run yourself; see [Running a local gateway](#running-a-local-gateway).
475
477
 
476
478
  ### Environment variables
477
479
 
478
480
  | Variable | What it is | Default |
479
481
  |---|---|---|
480
482
  | `GRAVITY_DEV_TOKEN` | Your gateway dev token. `gravity run` trades it for a run token scoped to the manifest's tools. Required for `run`, `schedule` and `adapt`. | none |
481
- | `GRAVITY_GATEWAY_URL` | The gateway's MCP endpoint, ending in `/mcp` | `http://127.0.0.1:8000/mcp` |
483
+ | `GRAVITY_GATEWAY_URL` | The gateway's MCP endpoint, ending in `/mcp` | the gateway the control plane names in `GET /api/cli/config` |
482
484
  | `GRAVITY_LLM_URL` | The OpenAI-compatible model endpoint | the gateway's address + `/v1` |
483
- | `GRAVITY_API_URL` | The control plane's address | `http://localhost:3010` |
485
+ | `GRAVITY_API_URL` | The control plane's address | `https://agents.dev.gravity.fast` |
484
486
  | `GRAVITY_API_TOKEN` | A control-plane dev token, used instead of `gravity login`. For development only. | none |
485
487
  | `GRAVITY_CONFIG_DIR` | Moves `~/.gravity` elsewhere. Useful for tests and CI. | `~/.gravity` |
486
488
  | `GRAVITY_UNATTENDED` | When `1`, calls that need approval are rejected without asking. `gravity schedule` sets it for you. | unset |
@@ -684,6 +686,7 @@ A fully local setup, in `~/.gravity/.env`:
684
686
 
685
687
  ```bash
686
688
  GRAVITY_DEV_TOKEN=<the gateway's LOCAL_DEV_TOKEN>
689
+ GRAVITY_GATEWAY_URL=http://127.0.0.1:8000/mcp
687
690
  GRAVITY_API_URL=http://localhost:3010
688
691
  GRAVITY_API_TOKEN=<the control plane's PLATFORM_DEV_TOKEN>
689
692
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gravity-schema"
3
- version = "0.2.0"
3
+ version = "0.3.1"
4
4
  description = "Gravity agent manifest contract — models, validation, and JSON Schema export"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "gravity-cli"
3
- version = "0.2.0"
3
+ version = "0.3.1"
4
4
  description = "gravity cli — init, run, and push python agents to the Gravity marketplace"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
7
7
  license-files = ["LICENSE", "NOTICE"]
8
8
  requires-python = ">=3.12"
9
9
  dependencies = [
10
- "gravity-schema==0.2.0", # released in lockstep: always the same version as this package
10
+ "gravity-schema==0.3.1", # released in lockstep: always the same version as this package
11
11
  "typer>=0.15.0",
12
12
  "httpx>=0.27.0",
13
13
  "rich>=13.9.0",
@@ -23,16 +23,22 @@ def _token() -> str:
23
23
  return token
24
24
 
25
25
 
26
- def _request(method: str, path: str, *, auth: bool = True, timeout: int = 60, headers: dict | None = None, **kw) -> Any:
26
+ def _request(method: str, path: str, *, auth: bool = True, timeout: int = 60, headers: dict | None = None, retry: bool = True, **kw) -> Any:
27
27
  """The JSON body. A refused connection or an error status becomes a Problem."""
28
- headers = {**({"Authorization": f"Bearer {_token()}"} if auth else {}), **(headers or {})}
28
+ extra = headers or {}
29
+ sent = {**({"Authorization": f"Bearer {_token()}"} if auth else {}), **extra}
29
30
  try:
30
- with httpx.Client(base_url=config.api_url(), timeout=timeout, headers=headers) as client:
31
+ with httpx.Client(base_url=config.api_url(), timeout=timeout, headers=sent) as client:
31
32
  resp = client.request(method, path, **kw)
32
33
  except httpx.TransportError as e:
33
34
  raise Problem(
34
35
  f"Can't reach Gravity at {config.api_url()}", str(e), "Check GRAVITY_API_URL in ~/.gravity/.env"
35
36
  ) from e
37
+ if resp.status_code == 401 and auth and not os.environ.get("GRAVITY_API_TOKEN"):
38
+ # A session's access token lasts an hour. Renew it once; if that can't be done, say so plainly.
39
+ if retry and _refresh():
40
+ return _request(method, path, auth=auth, timeout=timeout, headers=extra, retry=False, **kw)
41
+ raise Problem("Your sign-in has expired", fix="Run gravity login again.")
36
42
  if resp.status_code >= 400:
37
43
  try:
38
44
  body = resp.json()
@@ -47,6 +53,35 @@ def _request(method: str, path: str, *, auth: bool = True, timeout: int = 60, he
47
53
  return resp.json()
48
54
 
49
55
 
56
+ def _refresh() -> bool:
57
+ """A new session from the stored refresh token. Needs Gravity's config to name Supabase and its anon key."""
58
+ session = config.load_session()
59
+ settings = service_config()
60
+ url, key = settings.get("supabase_url"), settings.get("supabase_anon_key")
61
+ if not (session and url and key):
62
+ return False
63
+ try:
64
+ resp = httpx.post(
65
+ f"{url.rstrip('/')}/auth/v1/token",
66
+ params={"grant_type": "refresh_token"},
67
+ json={"refresh_token": session.refresh_token},
68
+ headers={"apikey": key},
69
+ timeout=15,
70
+ )
71
+ except httpx.TransportError:
72
+ return False
73
+ if resp.status_code != 200:
74
+ return False
75
+ data = resp.json()
76
+ config.save_session(config.Session(data["access_token"], data["refresh_token"]))
77
+ return True
78
+
79
+
80
+ def service_config() -> dict:
81
+ """Where Gravity's services are: api_url, gateway_url, min_cli_version. No sign-in needed."""
82
+ return _request("GET", "/api/cli/config", auth=False, timeout=15)
83
+
84
+
50
85
  def device_start() -> dict:
51
86
  return _request("POST", "/api/cli/device/start", auth=False, timeout=15)
52
87
 
@@ -2,15 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ import re
5
6
  import subprocess
6
7
  import sys
7
8
  import time
9
+ from importlib.metadata import version
8
10
  from pathlib import Path
9
11
 
10
12
  import httpx
11
13
  import typer
12
14
 
13
- from .. import Problem, project, ui
15
+ from .. import Problem, api, project, ui
14
16
  from ..manifest import load_manifest
15
17
  from ..timeline import EVENT_PREFIX, Timeline
16
18
 
@@ -20,11 +22,23 @@ BUILDER_TOKENS = ("GRAVITY_DEV_TOKEN", "GRAVITY_API_TOKEN")
20
22
 
21
23
 
22
24
  def gateway_urls() -> tuple[str, str]:
23
- """(MCP url, host base). Defaults to `gravity-gateway serve`'s own port."""
24
- url = os.environ.get("GRAVITY_GATEWAY_URL") or "http://127.0.0.1:8000/mcp"
25
+ """(MCP url, host base): GRAVITY_GATEWAY_URL, else the gateway Gravity's control plane names."""
26
+ url = os.environ.get("GRAVITY_GATEWAY_URL")
27
+ if not url:
28
+ settings = api.service_config()
29
+ _check_version(settings.get("min_cli_version"))
30
+ url = settings["gateway_url"].rstrip("/") + "/mcp"
25
31
  return url, url.rstrip("/").removesuffix("/mcp")
26
32
 
27
33
 
34
+ def _check_version(minimum: str | None) -> None:
35
+ """Gravity says which CLI it still supports; an older one gets a warning, not a refusal."""
36
+ mine = version("gravity-cli")
37
+ parts = lambda v: tuple(int(x) for x in re.findall(r"\d+", v)[:3])
38
+ if minimum and parts(minimum) > parts(mine):
39
+ ui.warn("Update gravity", f"Gravity needs {minimum} or newer; this is {mine}.", "uv tool install --force --refresh gravity-cli")
40
+
41
+
28
42
  def _mint_run_token(gateway_base: str, tools: list[str], approvals: list[str] = ()) -> dict:
29
43
  """Dev token -> a run token scoped to the manifest's tools, as a hosted run gets."""
30
44
  dev_token = os.environ.get("GRAVITY_DEV_TOKEN")
@@ -8,7 +8,7 @@ import time
8
8
  from pathlib import Path
9
9
 
10
10
  import typer
11
- from gravity_schema import LOCKFILE_PLATFORM
11
+ from gravity_schema import DEV_TOOL_NAMES, LOCKFILE_PLATFORM
12
12
  from gravity_schema.contract import TOOL_NAME_RE as _TOOL_NAME_RE
13
13
 
14
14
  from .. import ui
@@ -111,7 +111,9 @@ def _scan_drift(project_dir: Path, declared_tools: list[str]) -> list[tuple[str,
111
111
  f"{', '.join(sorted(imported_network_libs))} imported, but permissions.tools is empty: hosted agents can only reach the gateway.",
112
112
  "Declare the tools you need under permissions.tools, or drop the import.",
113
113
  ))
114
- if undeclared := sorted(referenced_tool_names - set(declared_tools)):
114
+ # Only names under an app the catalog knows: "package.json" is a file, not a tool.
115
+ apps = {name.split(".")[0] for name in DEV_TOOL_NAMES}
116
+ if undeclared := sorted(n for n in referenced_tool_names - set(declared_tools) if n.split(".")[0] in apps):
115
117
  warnings.append((
116
118
  "Undeclared tool",
117
119
  f"{', '.join(undeclared)} appears in your code",
@@ -24,8 +24,7 @@ ENV_FILE = CONFIG_DIR / ".env"
24
24
  CONFIG_DIR.mkdir(parents=True, exist_ok=True)
25
25
  load_dotenv(ENV_FILE) # no-op if the file doesn't exist yet
26
26
 
27
- # The local control plane; the published CLI needs the production URL.
28
- DEFAULT_API_URL = "http://localhost:3010"
27
+ DEFAULT_API_URL = "https://agents.dev.gravity.fast"
29
28
 
30
29
 
31
30
  def _read_config() -> dict:
@@ -156,7 +156,7 @@ def table(headers: list[str], rows: list[list[str]], footer: str = "") -> None:
156
156
  if len(row) == 1:
157
157
  grid.add_row(Text(f"\n{row[0]}", style="bold"))
158
158
  else:
159
- grid.add_row(*(Text(str(cell)) for cell in row))
159
+ grid.add_row(*(Text("" if cell is None else str(cell)) for cell in row))
160
160
  if any(headers):
161
161
  say()
162
162
  say(Padding(grid, (0, 0, 0, 2)))
@@ -0,0 +1,8 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture(autouse=True)
5
+ def no_network_for_addresses(monkeypatch):
6
+ """Without GRAVITY_GATEWAY_URL the CLI asks the real control plane where the gateway is.
7
+ Tests point at a local address instead; a test that wants the lookup deletes it."""
8
+ monkeypatch.setenv("GRAVITY_GATEWAY_URL", "http://127.0.0.1:8000/mcp")
@@ -63,3 +63,38 @@ def test_whoami_works_with_only_the_env_token(control_plane, monkeypatch, capsys
63
63
  whoami_command()
64
64
  assert control_plane["auth"] == "Bearer cp_dev"
65
65
  assert "local-builder (dev token)" in capsys.readouterr().out
66
+
67
+
68
+ def test_an_expired_session_is_renewed_once_when_gravity_names_supabase(monkeypatch):
69
+ """A 401 with a stored session: refresh against Supabase, then retry with the new token."""
70
+ saved: list = []
71
+ monkeypatch.delenv("GRAVITY_API_TOKEN", raising=False)
72
+ monkeypatch.setattr(config, "load_session", lambda: saved[-1] if saved else SESSION)
73
+ monkeypatch.setattr(config, "save_session", saved.append)
74
+ monkeypatch.setattr(api, "service_config", lambda: {"supabase_url": "https://sb.example", "supabase_anon_key": "anon"})
75
+ calls: list = []
76
+
77
+ def handler(request: httpx.Request) -> httpx.Response:
78
+ calls.append((request.url.host, request.headers.get("Authorization") or request.headers.get("apikey")))
79
+ if request.url.host == "sb.example":
80
+ return httpx.Response(200, json={"access_token": "fresh", "refresh_token": "r2"})
81
+ if request.headers["Authorization"] == "Bearer fresh":
82
+ return httpx.Response(200, json=[{"name": "demo"}])
83
+ return httpx.Response(401, json={"message": "Unauthorized"})
84
+
85
+ real = httpx.Client
86
+ monkeypatch.setattr(api.httpx, "Client", lambda **kw: real(transport=httpx.MockTransport(handler), **kw))
87
+ monkeypatch.setattr(api.httpx, "post", lambda url, **kw: real(transport=httpx.MockTransport(handler)).post(url, **kw))
88
+ assert api.get("/api/cli/agents") == [{"name": "demo"}]
89
+ assert saved[-1].access_token == "fresh" and [c[0] for c in calls].count("sb.example") == 1
90
+
91
+
92
+ def test_an_expired_session_without_refresh_says_sign_in_again(monkeypatch):
93
+ monkeypatch.delenv("GRAVITY_API_TOKEN", raising=False)
94
+ monkeypatch.setattr(config, "load_session", lambda: SESSION)
95
+ monkeypatch.setattr(api, "service_config", dict) # no Supabase details yet
96
+ real = httpx.Client
97
+ monkeypatch.setattr(api.httpx, "Client", lambda **kw: real(transport=httpx.MockTransport(lambda r: httpx.Response(401)), **kw))
98
+ with pytest.raises(typer.Exit) as e:
99
+ api.get("/api/cli/agents")
100
+ assert "sign-in has expired" in str(e.value) and "gravity login" in str(e.value)
@@ -87,18 +87,27 @@ captured_popen: dict = {}
87
87
 
88
88
  def test_run_mints_a_token_for_the_manifests_tools(project, gateway, child, monkeypatch):
89
89
  monkeypatch.setenv("GRAVITY_DEV_TOKEN", "grt_dev")
90
- monkeypatch.delenv("GRAVITY_GATEWAY_URL", raising=False)
90
+ monkeypatch.delenv("GRAVITY_GATEWAY_URL", raising=False) # so the gateway comes from Gravity's config
91
+ monkeypatch.setattr(run_mod.api, "service_config", lambda: {"gateway_url": "https://gw.example", "min_cli_version": "0.1.0"})
91
92
  with pytest.raises(typer.Exit) as e:
92
93
  run_mod.run_command(project, input=[], llm_url=None, reply=None)
93
94
  assert e.value.exit_code == 0
94
95
 
95
96
  assert gateway == {
96
- "url": "http://127.0.0.1:8000/runs",
97
+ "url": "https://gw.example/runs",
97
98
  "json": {"tools": ["example.echo"], "approvals": []},
98
99
  "auth": "Bearer grt_dev",
99
100
  }
101
+ assert child["GRAVITY_GATEWAY_URL"] == "https://gw.example/mcp"
100
102
  assert child["GRAVITY_RUN_TOKEN"] == "grt_run"
101
- assert child["GRAVITY_LLM_URL"] == "http://127.0.0.1:8000/v1"
103
+ assert child["GRAVITY_LLM_URL"] == "https://gw.example/v1"
104
+
105
+
106
+ def test_an_old_cli_is_told_to_update(capsys):
107
+ run_mod._check_version("99.0.0")
108
+ assert "Update gravity" in capsys.readouterr().out
109
+ run_mod._check_version("0.1.0")
110
+ assert capsys.readouterr().out == ""
102
111
 
103
112
 
104
113
  def test_the_builders_tokens_never_reach_the_agent(project, gateway, child, monkeypatch):
@@ -164,7 +164,7 @@ wheels = [
164
164
 
165
165
  [[package]]
166
166
  name = "gravity-cli"
167
- version = "0.2.0"
167
+ version = "0.3.1"
168
168
  source = { editable = "." }
169
169
  dependencies = [
170
170
  { name = "gravity-schema" },
@@ -201,7 +201,7 @@ dev = [
201
201
 
202
202
  [[package]]
203
203
  name = "gravity-schema"
204
- version = "0.2.0"
204
+ version = "0.3.1"
205
205
  source = { editable = "packages/gravity-schema" }
206
206
  dependencies = [
207
207
  { name = "pydantic" },
File without changes
File without changes
File without changes
File without changes