gravity-cli 0.4.4__tar.gz → 0.5.2__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 (106) hide show
  1. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/AGENT_STANDARD.md +15 -1
  2. gravity_cli-0.4.4/README.md → gravity_cli-0.5.2/PKG-INFO +48 -5
  3. gravity_cli-0.4.4/PKG-INFO → gravity_cli-0.5.2/README.md +31 -22
  4. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/__init__.py +6 -0
  5. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/catalog.py +11 -2
  6. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/catalog.yaml +45 -0
  7. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/catalog_imported.yaml +9632 -4276
  8. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/errors.py +2 -0
  9. gravity_cli-0.5.2/packages/gravity-schema/gravity_schema/source_scan.py +83 -0
  10. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/validation.py +23 -2
  11. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/pyproject.toml +1 -1
  12. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_catalog_parity.py +11 -0
  13. gravity_cli-0.5.2/packages/gravity-schema/tests/test_source_scan.py +67 -0
  14. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_validation.py +25 -0
  15. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/pyproject.toml +2 -2
  16. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/BUILDER_GUIDE.md +27 -12
  17. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/_runner_script.py +44 -4
  18. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/api.py +2 -1
  19. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/cli.py +2 -0
  20. gravity_cli-0.5.2/src/gravity_cli/commands/connect.py +82 -0
  21. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/run.py +2 -0
  22. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/tools.py +21 -2
  23. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/validate.py +31 -37
  24. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/config.py +33 -8
  25. gravity_cli-0.5.2/src/gravity_cli/manifest.py +22 -0
  26. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/ui.py +3 -0
  27. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_api.py +2 -1
  28. gravity_cli-0.5.2/tests/test_connect.py +78 -0
  29. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_run.py +2 -1
  30. gravity_cli-0.5.2/tests/test_session.py +80 -0
  31. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_templates.py +68 -0
  32. gravity_cli-0.5.2/tests/test_tools.py +45 -0
  33. gravity_cli-0.5.2/tests/test_validate.py +128 -0
  34. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/uv.lock +2 -2
  35. gravity_cli-0.4.4/src/gravity_cli/manifest.py +0 -9
  36. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/.env.example +0 -0
  37. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/.github/workflows/ci.yml +0 -0
  38. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/.github/workflows/release.yml +0 -0
  39. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/.gitignore +0 -0
  40. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/LICENSE +0 -0
  41. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/NOTICE +0 -0
  42. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/RELEASING.md +0 -0
  43. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/TEMPLATES.md +0 -0
  44. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/LICENSE +0 -0
  45. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/NOTICE +0 -0
  46. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/README.md +0 -0
  47. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/__main__.py +0 -0
  48. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/contract.py +0 -0
  49. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/cron.py +0 -0
  50. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/export.py +0 -0
  51. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/gen_catalog.py +0 -0
  52. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/gravity_schema/manifest.py +0 -0
  53. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/schema/manifest.schema.json +0 -0
  54. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/conftest.py +0 -0
  55. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_cron.py +0 -0
  56. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_errors.py +0 -0
  57. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_json_schema.py +0 -0
  58. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_kind_outputs.py +0 -0
  59. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_manifest.py +0 -0
  60. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/packages/gravity-schema/tests/test_slots.py +0 -0
  61. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/__init__.py +0 -0
  62. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/__main__.py +0 -0
  63. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/__init__.py +0 -0
  64. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/adapt.py +0 -0
  65. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/build.py +0 -0
  66. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/guide.py +0 -0
  67. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/init.py +0 -0
  68. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/login.py +0 -0
  69. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/push.py +0 -0
  70. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/runs.py +0 -0
  71. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/commands/schedule.py +0 -0
  72. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/project.py +0 -0
  73. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/chat/.gitignore +0 -0
  74. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/chat/manifest.yaml +0 -0
  75. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/chat/requirements.txt +0 -0
  76. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/chat/src/agent.py +0 -0
  77. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/deepagent/.gitignore +0 -0
  78. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/deepagent/manifest.yaml +0 -0
  79. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/deepagent/requirements.txt +0 -0
  80. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/deepagent/src/agent.py +0 -0
  81. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/minimal/.gitignore +0 -0
  82. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/minimal/manifest.yaml +0 -0
  83. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/minimal/requirements.txt +0 -0
  84. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/minimal/src/agent.py +0 -0
  85. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/.gitignore +0 -0
  86. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/manifest.yaml +0 -0
  87. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/requirements.txt +0 -0
  88. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/src/agent.py +0 -0
  89. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/src/gateway.py +0 -0
  90. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/src/nodes/agent.py +0 -0
  91. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/src/prompts.py +0 -0
  92. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/templates/structured/src/state.py +0 -0
  93. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/src/gravity_cli/timeline.py +0 -0
  94. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/starter/manifest.yaml +0 -0
  95. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/starter/requirements.txt +0 -0
  96. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/starter/src/agent.py +0 -0
  97. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/starter/src/summary.py +0 -0
  98. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/starter/tests/test_summary.py +0 -0
  99. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/conftest.py +0 -0
  100. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_adapt.py +0 -0
  101. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_build_push.py +0 -0
  102. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_gateway_token.py +0 -0
  103. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_manifest_integration.py +0 -0
  104. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_timeline.py +0 -0
  105. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_ui.py +0 -0
  106. {gravity_cli-0.4.4 → gravity_cli-0.5.2}/tests/test_welcome.py +0 -0
@@ -191,6 +191,17 @@ model = ChatOpenAI(model="openai/gpt-6-luna",
191
191
  api_key=os.environ["GRAVITY_RUN_TOKEN"])
192
192
  ```
193
193
 
194
+ **Embeddings** use the same URL and token (`<GRAVITY_LLM_URL>/embeddings`), metered
195
+ and budgeted like chat calls:
196
+
197
+ ```python
198
+ emb = OpenAIEmbeddings(model="voyageai/voyage-4-lite", # or google/gemini-embedding-2
199
+ base_url=os.environ["GRAVITY_LLM_URL"],
200
+ api_key=os.environ["GRAVITY_RUN_TOKEN"],
201
+ check_embedding_ctx_length=False) # send text, not OpenAI token ids
202
+ vectors = await emb.aembed_documents(["first text", "second text"])
203
+ ```
204
+
194
205
  Those three env vars — `GRAVITY_GATEWAY_URL`, `GRAVITY_LLM_URL`,
195
206
  `GRAVITY_RUN_TOKEN` — are the entire Gravity surface, plus the `gravity`
196
207
  package for tools on the platform (§8). No `OPENAI_API_KEY`: locally and in
@@ -271,7 +282,10 @@ start**: everything before the call happens twice.
271
282
  replaced. State is kept per thread. A key the next run doesn't send keeps its
272
283
  value (a monitor's `seen`, a draft). For memory shared by one consumer's
273
284
  threads, use `langgraph.config.get_store()` (`aget` / `aput` / `asearch` by
274
- namespace and filter; no semantic search).
285
+ namespace and filter; no semantic search). It is kept per consumer and agent,
286
+ across runs and versions, until the agent deletes an item (`aput(ns, key, None)`);
287
+ store one item per record, not one large blob. `gravity run` gives you the same
288
+ store locally, saved in `.gravity/store.json` (delete it to start over).
275
289
 
276
290
  **Conversation.** `messages` must use the `add_messages` reducer:
277
291
  `messages: Annotated[list, add_messages]`. Each turn the platform sends only
@@ -1,3 +1,20 @@
1
+ Metadata-Version: 2.5
2
+ Name: gravity-cli
3
+ Version: 0.5.2
4
+ Summary: gravity cli — init, run, and push python agents to the Gravity marketplace
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ License-File: NOTICE
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: gravity-schema==0.5.2
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: keyring>=25.0.0
12
+ Requires-Dist: python-dotenv>=1.0.0
13
+ Requires-Dist: rich>=13.9.0
14
+ Requires-Dist: typer>=0.15.0
15
+ Requires-Dist: uv>=0.8.0
16
+ Description-Content-Type: text/markdown
17
+
1
18
  <h1 align="center">Gravity CLI</h1>
2
19
 
3
20
  <p align="center">
@@ -161,8 +178,8 @@ gravity push
161
178
 
162
179
  | Service | What it does | Used by |
163
180
  |---|---|---|
164
- | **Gateway** | Serves tools over MCP (`/mcp`) and models over an OpenAI-compatible API (`/v1`). Issues run tokens and refuses any tool a run was not granted. | `run`, `schedule`, `adapt`, and your agent |
165
- | **Control plane** | Accounts, the tool catalog, pushed agents, builds and hosted runs. | `login`, `logout`, `whoami`, `push`, `tools`, `agents`, `runs`, `logs` |
181
+ | **Gateway** | Serves tools over MCP (`/mcp`) and models over an OpenAI-compatible API (`/v1`). Lists its tools at `/tools` and your connected accounts at `/connections`. Issues run tokens and refuses any tool a run was not granted. | `run`, `schedule`, `adapt`, `tools`, `connect`, and your agent |
182
+ | **Control plane** | Accounts, pushed agents, builds and hosted runs. | `login`, `logout`, `whoami`, `push`, `agents`, `runs`, `logs` |
166
183
 
167
184
  On `gravity run`, the CLI trades your dev token for a run token scoped to the manifest's tools and starts the agent with only that token. Your own tokens are removed from the agent's environment, so agent code can never act as you.
168
185
 
@@ -190,6 +207,7 @@ Run `gravity help` for this list in the terminal, and add `-h` to any command fo
190
207
  | **Account** | [`gravity login`](#gravity-login) | Sign in to Gravity in your browser |
191
208
  | | [`gravity logout`](#gravity-logout) | Sign out |
192
209
  | | [`gravity whoami`](#gravity-whoami) | Show who you are signed in as |
210
+ | | [`gravity connect`](#gravity-connect) | Connect an account your agents act on (Gmail, Slack...) |
193
211
  | **Help** | [`gravity guide`](#gravity-guide) | Read the builder guide: how to build a good agent |
194
212
  | | [`gravity help`](#gravity-help) | Show the list of commands |
195
213
 
@@ -238,7 +256,10 @@ What it checks:
238
256
  - the entrypoint file exists and defines the object the manifest names
239
257
  - `requirements.txt` exists, and compiles into `requirements.lock` for the platform image (ARM64 Linux) and the manifest's Python version
240
258
  - every adapter a slot declares has a file, and every adapter file is declared
241
- - warnings, never failures, for tool names or network libraries in your code that the manifest does not declare
259
+ - every tool in the manifest exists in the catalog, the same list the platform's build uses, so a typo fails here instead of after a push
260
+ - every imported write tool is under `approvals.required_for`
261
+ - your code never names a write tool the manifest does not grant
262
+ - a warning, never a failure, for read tools your code names but the manifest does not declare
242
263
 
243
264
  ```bash
244
265
  gravity validate
@@ -376,7 +397,9 @@ gravity logs <run_id>
376
397
 
377
398
  ### `gravity tools`
378
399
 
379
- Browse the tool catalog: the names you can put under `permissions.tools`, with a description and the access each one needs.
400
+ Browse the tool catalog: the names you can put under `permissions.tools`, with what each one does. It is the gateway's own list, so it shows exactly what a run can call, and it needs no sign-in.
401
+
402
+ About 25 tools are curated, such as `gmail.read` and `web.search`. The other ~1,100 are imported from the same apps and unreviewed: any imported tool that changes something must also be under `approvals.required_for`, and `gravity validate` checks that.
380
403
 
381
404
  ```
382
405
  gravity tools [search]
@@ -412,6 +435,23 @@ gravity whoami
412
435
  gravity whoami --token # print the gateway token itself, for CI or a teammate's .env
413
436
  ```
414
437
 
438
+ ### `gravity connect`
439
+
440
+ Connect an account your agents' tools act on, through the app's own sign-in page in your browser. With no app, list what is connected.
441
+
442
+ ```
443
+ gravity connect [app]
444
+ ```
445
+
446
+ ```bash
447
+ gravity connect # what is connected, and whose accounts runs act on
448
+ gravity connect gmail # opens the sign-in page, then waits until it is done
449
+ gravity connect sheets # use the app name from tool names: sheets, calendar, drive, linear, slack, github...
450
+ ```
451
+
452
+ - Uses the gateway token from `gravity login`.
453
+ - `web` tools need no account.
454
+
415
455
  ### `gravity guide`
416
456
 
417
457
  Read the builder guide, which ships with the CLI: how an agent is put together, the rules your code must follow, formatting results, approvals, adapters, scheduling, memory, and a checklist.
@@ -472,7 +512,8 @@ Never commit a `.env` file. The `.gitignore` in every template keeps `.env` and
472
512
  |---|---|---|---|
473
513
  | Create, check and package an agent | `init`, `validate`, `build` | nothing | nothing |
474
514
  | Run an agent locally | `run`, `schedule`, `adapt` | `gravity login` (or `GRAVITY_DEV_TOKEN`) | nothing |
475
- | Publish and inspect agents | `push`, `tools`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | nothing |
515
+ | Publish and inspect agents | `push`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | nothing |
516
+ | Browse tools, connect accounts | `tools`, `connect` | nothing for `tools`; `gravity login` for `connect` | nothing |
476
517
 
477
518
  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).
478
519
 
@@ -714,6 +755,8 @@ Real tools act on a connected account. Connect one with `gravity-gateway connect
714
755
 
715
756
  ## What's new
716
757
 
758
+ - **0.5.2** Jira, Confluence and Notion join the catalog: 1,351 tools across 11 apps. `get_store()` works in local runs, kept in `.gravity/store.json`. Renewing an expired sign-in keeps your gateway token, so `gravity run` keeps working after an hour. Commands start faster: the catalog loads in a quarter of the time.
759
+ - **0.5.0** `gravity connect` links your own accounts. `web.search` and `web.fetch` join the catalog. `gravity validate` now refuses a tool that isn't in the catalog, an imported write tool without an approval, and code that names a write tool the manifest doesn't grant. `gravity tools` reads the gateway's list and needs no sign-in. On Windows the session stays in the keychain instead of a file.
717
760
  - **0.4.4** Same code as 0.4.3, released from this repository so the GitHub release, the tag and PyPI line up again. Adds `RELEASING.md`.
718
761
  - **0.4.3** Documents the `secret` input type and the `supadata.get_transcript` tool.
719
762
  - **0.4.1** New input type `secret` for an API key, password or other confidential value. New built-in tool `supadata.get_transcript` (`gravity tools supadata`).
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: gravity-cli
3
- Version: 0.4.4
4
- Summary: gravity cli — init, run, and push python agents to the Gravity marketplace
5
- License-Expression: Apache-2.0
6
- License-File: LICENSE
7
- License-File: NOTICE
8
- Requires-Python: >=3.12
9
- Requires-Dist: gravity-schema==0.4.4
10
- Requires-Dist: httpx>=0.27.0
11
- Requires-Dist: keyring>=25.0.0
12
- Requires-Dist: python-dotenv>=1.0.0
13
- Requires-Dist: rich>=13.9.0
14
- Requires-Dist: typer>=0.15.0
15
- Requires-Dist: uv>=0.8.0
16
- Description-Content-Type: text/markdown
17
-
18
1
  <h1 align="center">Gravity CLI</h1>
19
2
 
20
3
  <p align="center">
@@ -178,8 +161,8 @@ gravity push
178
161
 
179
162
  | Service | What it does | Used by |
180
163
  |---|---|---|
181
- | **Gateway** | Serves tools over MCP (`/mcp`) and models over an OpenAI-compatible API (`/v1`). Issues run tokens and refuses any tool a run was not granted. | `run`, `schedule`, `adapt`, and your agent |
182
- | **Control plane** | Accounts, the tool catalog, pushed agents, builds and hosted runs. | `login`, `logout`, `whoami`, `push`, `tools`, `agents`, `runs`, `logs` |
164
+ | **Gateway** | Serves tools over MCP (`/mcp`) and models over an OpenAI-compatible API (`/v1`). Lists its tools at `/tools` and your connected accounts at `/connections`. Issues run tokens and refuses any tool a run was not granted. | `run`, `schedule`, `adapt`, `tools`, `connect`, and your agent |
165
+ | **Control plane** | Accounts, pushed agents, builds and hosted runs. | `login`, `logout`, `whoami`, `push`, `agents`, `runs`, `logs` |
183
166
 
184
167
  On `gravity run`, the CLI trades your dev token for a run token scoped to the manifest's tools and starts the agent with only that token. Your own tokens are removed from the agent's environment, so agent code can never act as you.
185
168
 
@@ -207,6 +190,7 @@ Run `gravity help` for this list in the terminal, and add `-h` to any command fo
207
190
  | **Account** | [`gravity login`](#gravity-login) | Sign in to Gravity in your browser |
208
191
  | | [`gravity logout`](#gravity-logout) | Sign out |
209
192
  | | [`gravity whoami`](#gravity-whoami) | Show who you are signed in as |
193
+ | | [`gravity connect`](#gravity-connect) | Connect an account your agents act on (Gmail, Slack...) |
210
194
  | **Help** | [`gravity guide`](#gravity-guide) | Read the builder guide: how to build a good agent |
211
195
  | | [`gravity help`](#gravity-help) | Show the list of commands |
212
196
 
@@ -255,7 +239,10 @@ What it checks:
255
239
  - the entrypoint file exists and defines the object the manifest names
256
240
  - `requirements.txt` exists, and compiles into `requirements.lock` for the platform image (ARM64 Linux) and the manifest's Python version
257
241
  - every adapter a slot declares has a file, and every adapter file is declared
258
- - warnings, never failures, for tool names or network libraries in your code that the manifest does not declare
242
+ - every tool in the manifest exists in the catalog, the same list the platform's build uses, so a typo fails here instead of after a push
243
+ - every imported write tool is under `approvals.required_for`
244
+ - your code never names a write tool the manifest does not grant
245
+ - a warning, never a failure, for read tools your code names but the manifest does not declare
259
246
 
260
247
  ```bash
261
248
  gravity validate
@@ -393,7 +380,9 @@ gravity logs <run_id>
393
380
 
394
381
  ### `gravity tools`
395
382
 
396
- Browse the tool catalog: the names you can put under `permissions.tools`, with a description and the access each one needs.
383
+ Browse the tool catalog: the names you can put under `permissions.tools`, with what each one does. It is the gateway's own list, so it shows exactly what a run can call, and it needs no sign-in.
384
+
385
+ About 25 tools are curated, such as `gmail.read` and `web.search`. The other ~1,100 are imported from the same apps and unreviewed: any imported tool that changes something must also be under `approvals.required_for`, and `gravity validate` checks that.
397
386
 
398
387
  ```
399
388
  gravity tools [search]
@@ -429,6 +418,23 @@ gravity whoami
429
418
  gravity whoami --token # print the gateway token itself, for CI or a teammate's .env
430
419
  ```
431
420
 
421
+ ### `gravity connect`
422
+
423
+ Connect an account your agents' tools act on, through the app's own sign-in page in your browser. With no app, list what is connected.
424
+
425
+ ```
426
+ gravity connect [app]
427
+ ```
428
+
429
+ ```bash
430
+ gravity connect # what is connected, and whose accounts runs act on
431
+ gravity connect gmail # opens the sign-in page, then waits until it is done
432
+ gravity connect sheets # use the app name from tool names: sheets, calendar, drive, linear, slack, github...
433
+ ```
434
+
435
+ - Uses the gateway token from `gravity login`.
436
+ - `web` tools need no account.
437
+
432
438
  ### `gravity guide`
433
439
 
434
440
  Read the builder guide, which ships with the CLI: how an agent is put together, the rules your code must follow, formatting results, approvals, adapters, scheduling, memory, and a checklist.
@@ -489,7 +495,8 @@ Never commit a `.env` file. The `.gitignore` in every template keeps `.env` and
489
495
  |---|---|---|---|
490
496
  | Create, check and package an agent | `init`, `validate`, `build` | nothing | nothing |
491
497
  | Run an agent locally | `run`, `schedule`, `adapt` | `gravity login` (or `GRAVITY_DEV_TOKEN`) | nothing |
492
- | Publish and inspect agents | `push`, `tools`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | nothing |
498
+ | Publish and inspect agents | `push`, `agents`, `runs`, `logs`, `whoami` | `gravity login` | nothing |
499
+ | Browse tools, connect accounts | `tools`, `connect` | nothing for `tools`; `gravity login` for `connect` | nothing |
493
500
 
494
501
  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).
495
502
 
@@ -731,6 +738,8 @@ Real tools act on a connected account. Connect one with `gravity-gateway connect
731
738
 
732
739
  ## What's new
733
740
 
741
+ - **0.5.2** Jira, Confluence and Notion join the catalog: 1,351 tools across 11 apps. `get_store()` works in local runs, kept in `.gravity/store.json`. Renewing an expired sign-in keeps your gateway token, so `gravity run` keeps working after an hour. Commands start faster: the catalog loads in a quarter of the time.
742
+ - **0.5.0** `gravity connect` links your own accounts. `web.search` and `web.fetch` join the catalog. `gravity validate` now refuses a tool that isn't in the catalog, an imported write tool without an approval, and code that names a write tool the manifest doesn't grant. `gravity tools` reads the gateway's list and needs no sign-in. On Windows the session stays in the keychain instead of a file.
734
743
  - **0.4.4** Same code as 0.4.3, released from this repository so the GitHub release, the tag and PyPI line up again. Adds `RELEASING.md`.
735
744
  - **0.4.3** Documents the `secret` input type and the `supadata.get_transcript` tool.
736
745
  - **0.4.1** New input type `secret` for an API key, password or other confidential value. New built-in tool `supadata.get_transcript` (`gravity tools supadata`).
@@ -36,7 +36,9 @@ from .manifest import (
36
36
  StateSpec,
37
37
  TriggersSpec,
38
38
  )
39
+ from .source_scan import is_agent_source, undeclared_write_tools
39
40
  from .validation import (
41
+ check_imported_write_approvals,
40
42
  check_tool_references,
41
43
  load_manifest_file,
42
44
  load_manifest_text,
@@ -73,6 +75,10 @@ __all__ = [ # noqa: RUF022 - grouped by concern, not alphabetical
73
75
  "load_manifest_text",
74
76
  "load_manifest_file",
75
77
  "check_tool_references",
78
+ "check_imported_write_approvals",
79
+ # source scan
80
+ "is_agent_source",
81
+ "undeclared_write_tools",
76
82
  # catalog
77
83
  "ToolCatalog",
78
84
  "StaticToolCatalog",
@@ -10,6 +10,13 @@ from typing import Protocol
10
10
 
11
11
  import yaml
12
12
 
13
+ # libyaml's parser, about 8x faster: every CLI command loads the whole catalog at import.
14
+ _LOADER = getattr(yaml, "CSafeLoader", yaml.SafeLoader)
15
+
16
+
17
+ def _load(text: str):
18
+ return yaml.load(text, Loader=_LOADER)
19
+
13
20
 
14
21
  class ToolCatalog(Protocol):
15
22
  def exists(self, tool_name: str) -> bool: ...
@@ -39,6 +46,7 @@ class ToolEntry:
39
46
  breadth: str # generic | niche
40
47
  guarantee: str # certified | community | experimental
41
48
  side_effect: str # read | write
49
+ toolkit: str | None = None # the account a user connects for it; None when none is needed
42
50
 
43
51
 
44
52
  _BREADTH = {"generic", "niche"}
@@ -48,12 +56,12 @@ _SIDE_EFFECT = {"read", "write"}
48
56
 
49
57
  def load_catalog(path: Path | None = None) -> tuple[ToolEntry, ...]:
50
58
  """Parse and check catalog.yaml; a malformed entry fails at import."""
51
- raw = yaml.safe_load((path or Path(__file__).with_name("catalog.yaml")).read_text(encoding="utf-8"))
59
+ raw = _load((path or Path(__file__).with_name("catalog.yaml")).read_text(encoding="utf-8"))
52
60
  # catalog_imported.yaml (`gravity-gateway sync-tools`): the same apps' other tools,
53
61
  # experimental. Absent until the first sync; never read for an explicit path.
54
62
  imported = Path(__file__).with_name("catalog_imported.yaml")
55
63
  if path is None and imported.is_file():
56
- raw = [*raw, *(yaml.safe_load(imported.read_text(encoding="utf-8")) or [])]
64
+ raw = [*raw, *(_load(imported.read_text(encoding="utf-8")) or [])]
57
65
  entries: list[ToolEntry] = []
58
66
  for item in raw:
59
67
  entry = ToolEntry(
@@ -64,6 +72,7 @@ def load_catalog(path: Path | None = None) -> tuple[ToolEntry, ...]:
64
72
  breadth=item["breadth"],
65
73
  guarantee=item["guarantee"],
66
74
  side_effect=item["side_effect"],
75
+ toolkit=item.get("toolkit"),
67
76
  )
68
77
  problems = []
69
78
  if entry.breadth not in _BREADTH:
@@ -10,6 +10,7 @@
10
10
  # Fields
11
11
  # name the Gravity name builders write in permissions.tools
12
12
  # slug the vendor action it routes to; null for a gateway built-in
13
+ # toolkit the account a user connects for it (gravity connect); null when none is needed
13
14
  # description what a builder or a model reads to choose it
14
15
  # scopes the consent a consumer grants when connecting
15
16
  # breadth generic: ordinary plumbing any agent might use
@@ -21,14 +22,34 @@
21
22
 
22
23
  - name: example.echo
23
24
  slug: null
25
+ toolkit: null
24
26
  description: echoes back its input, for templates and plumbing tests
25
27
  scopes: []
26
28
  breadth: generic
27
29
  guarantee: certified
28
30
  side_effect: read
29
31
 
32
+ - name: web.search
33
+ slug: COMPOSIO_SEARCH_WEB
34
+ toolkit: composio_search
35
+ description: search the web; returns results with titles, links and snippets. No account needed
36
+ scopes: []
37
+ breadth: generic
38
+ guarantee: certified
39
+ side_effect: read
40
+
41
+ - name: web.fetch
42
+ slug: COMPOSIO_SEARCH_FETCH_URL_CONTENT
43
+ toolkit: composio_search
44
+ description: read the text content of a web page by its URL. No account needed
45
+ scopes: []
46
+ breadth: generic
47
+ guarantee: certified
48
+ side_effect: read
49
+
30
50
  - name: supadata.get_transcript
31
51
  slug: null
52
+ toolkit: null
32
53
  description: fetch the transcript of a YouTube video with the caller's own Supadata API key (api_key argument)
33
54
  scopes: []
34
55
  breadth: niche
@@ -37,6 +58,7 @@
37
58
 
38
59
  - name: gmail.read
39
60
  slug: GMAIL_FETCH_EMAILS
61
+ toolkit: gmail
40
62
  description: read the connected Gmail inbox
41
63
  scopes: [gmail.readonly]
42
64
  breadth: generic
@@ -45,6 +67,7 @@
45
67
 
46
68
  - name: gmail.draft
47
69
  slug: GMAIL_CREATE_EMAIL_DRAFT
70
+ toolkit: gmail
48
71
  description: create a Gmail draft
49
72
  scopes: [gmail.compose]
50
73
  breadth: generic
@@ -53,6 +76,7 @@
53
76
 
54
77
  - name: gmail.send
55
78
  slug: GMAIL_SEND_EMAIL
79
+ toolkit: gmail
56
80
  description: send an email from the connected Gmail
57
81
  scopes: [gmail.send]
58
82
  breadth: generic
@@ -61,6 +85,7 @@
61
85
 
62
86
  - name: sheets.read
63
87
  slug: GOOGLESHEETS_BATCH_GET
88
+ toolkit: googlesheets
64
89
  description: read values from a connected Google Sheet
65
90
  scopes: [sheets]
66
91
  breadth: generic
@@ -69,6 +94,7 @@
69
94
 
70
95
  - name: sheets.append
71
96
  slug: GOOGLESHEETS_BATCH_UPDATE
97
+ toolkit: googlesheets
72
98
  description: append a row to a connected Google Sheet
73
99
  scopes: [sheets]
74
100
  breadth: generic
@@ -77,6 +103,7 @@
77
103
 
78
104
  - name: slack.send
79
105
  slug: SLACK_SEND_MESSAGE
106
+ toolkit: slack
80
107
  description: post a message to a connected Slack channel
81
108
  scopes: [slack.chat:write]
82
109
  breadth: generic
@@ -85,6 +112,7 @@
85
112
 
86
113
  - name: slack.list_channels
87
114
  slug: SLACK_LIST_ALL_CHANNELS
115
+ toolkit: slack
88
116
  description: list channels in the connected Slack workspace
89
117
  scopes: [slack.channels:read]
90
118
  breadth: generic
@@ -93,6 +121,7 @@
93
121
 
94
122
  - name: slack.read_history
95
123
  slug: SLACK_FETCH_CONVERSATION_HISTORY
124
+ toolkit: slack
96
125
  description: read message history of a Slack channel
97
126
  scopes: [slack.channels:history]
98
127
  breadth: generic
@@ -101,6 +130,7 @@
101
130
 
102
131
  - name: slack.find_user
103
132
  slug: SLACK_FIND_USERS
133
+ toolkit: slack
104
134
  description: find a Slack user by name, handle or email, for mentions
105
135
  scopes: [slack.users:read]
106
136
  breadth: generic
@@ -109,6 +139,7 @@
109
139
 
110
140
  - name: calendar.read
111
141
  slug: GOOGLECALENDAR_EVENTS_LIST
142
+ toolkit: googlecalendar
112
143
  description: list events on the connected Google Calendar
113
144
  scopes: [calendar.readonly]
114
145
  breadth: generic
@@ -117,6 +148,7 @@
117
148
 
118
149
  - name: drive.read
119
150
  slug: GOOGLEDRIVE_DOWNLOAD_FILE
151
+ toolkit: googledrive
120
152
  description: download a file from the connected Google Drive
121
153
  scopes: [drive.readonly]
122
154
  breadth: generic
@@ -125,6 +157,7 @@
125
157
 
126
158
  - name: linear.create_issue
127
159
  slug: LINEAR_CREATE_LINEAR_ISSUE
160
+ toolkit: linear
128
161
  description: create an issue in a connected Linear team
129
162
  scopes: [linear.write]
130
163
  breadth: generic
@@ -133,6 +166,7 @@
133
166
 
134
167
  - name: linear.list_teams
135
168
  slug: LINEAR_LIST_LINEAR_TEAMS
169
+ toolkit: linear
136
170
  description: list the connected Linear workspace teams
137
171
  scopes: [linear.read]
138
172
  breadth: generic
@@ -141,6 +175,7 @@
141
175
 
142
176
  - name: linear.list_issues
143
177
  slug: LINEAR_LIST_LINEAR_ISSUES
178
+ toolkit: linear
144
179
  description: list open issues in the connected Linear workspace
145
180
  scopes: [linear.read]
146
181
  breadth: generic
@@ -149,6 +184,7 @@
149
184
 
150
185
  - name: linear.get_issue
151
186
  slug: LINEAR_GET_LINEAR_ISSUE
187
+ toolkit: linear
152
188
  description: read one Linear issue by id
153
189
  scopes: [linear.read]
154
190
  breadth: generic
@@ -158,6 +194,7 @@
158
194
  # GitHub: read-only, for change summaries and contribution profiles.
159
195
  - name: github.list_commits
160
196
  slug: GITHUB_LIST_COMMITS
197
+ toolkit: github
161
198
  description: list commits on a repository branch, filterable by date range, author or path
162
199
  scopes: [repo]
163
200
  breadth: generic
@@ -166,6 +203,7 @@
166
203
 
167
204
  - name: github.get_commit
168
205
  slug: GITHUB_GET_A_COMMIT
206
+ toolkit: github
169
207
  description: read one commit with its changed files, line counts and patches
170
208
  scopes: [repo]
171
209
  breadth: generic
@@ -174,6 +212,7 @@
174
212
 
175
213
  - name: github.compare
176
214
  slug: GITHUB_COMPARE_TWO_COMMITS
215
+ toolkit: github
177
216
  description: compare two refs (base...head) with the commits and changed files between them
178
217
  scopes: [repo]
179
218
  breadth: generic
@@ -182,6 +221,7 @@
182
221
 
183
222
  - name: github.list_pull_requests
184
223
  slug: GITHUB_LIST_PULL_REQUESTS
224
+ toolkit: github
185
225
  description: list pull requests on a repository, filterable by state and branch
186
226
  scopes: [repo]
187
227
  breadth: generic
@@ -190,6 +230,7 @@
190
230
 
191
231
  - name: github.list_pr_files
192
232
  slug: GITHUB_LIST_PULL_REQUESTS_FILES
233
+ toolkit: github
193
234
  description: list the files a pull request changes, with line counts and patches
194
235
  scopes: [repo]
195
236
  breadth: generic
@@ -198,6 +239,7 @@
198
239
 
199
240
  - name: github.list_pr_reviews
200
241
  slug: GITHUB_LIST_REVIEWS_FOR_A_PULL_REQUEST
242
+ toolkit: github
201
243
  description: list the reviews submitted on a pull request
202
244
  scopes: [repo]
203
245
  breadth: generic
@@ -206,6 +248,7 @@
206
248
 
207
249
  - name: github.search_issues
208
250
  slug: GITHUB_SEARCH_ISSUES_AND_PULL_REQUESTS
251
+ toolkit: github
209
252
  description: search issues and pull requests with GitHub qualifiers (repo:, author:, reviewed-by:, is:pr, created:)
210
253
  scopes: [repo]
211
254
  breadth: generic
@@ -214,6 +257,7 @@
214
257
 
215
258
  - name: linkedin.whoami
216
259
  slug: LINKEDIN_GET_MY_INFO
260
+ toolkit: linkedin
217
261
  description: read the connected LinkedIn member's id, name and headline (the author of their posts)
218
262
  scopes: [linkedin.profile]
219
263
  breadth: generic
@@ -222,6 +266,7 @@
222
266
 
223
267
  - name: linkedin.create_post
224
268
  slug: LINKEDIN_CREATE_LINKED_IN_POST
269
+ toolkit: linkedin
225
270
  description: publish a post on LinkedIn as the connected member or an organisation they manage
226
271
  scopes: [linkedin.w_member_social]
227
272
  breadth: generic