gravity-cli 0.5.0__tar.gz → 0.5.3__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 (111) hide show
  1. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/AGENT_STANDARD.md +45 -2
  2. gravity_cli-0.5.0/README.md → gravity_cli-0.5.3/PKG-INFO +43 -0
  3. gravity_cli-0.5.0/PKG-INFO → gravity_cli-0.5.3/README.md +26 -17
  4. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/catalog.py +11 -2
  5. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/catalog.yaml +29 -0
  6. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/catalog_imported.yaml +10119 -4285
  7. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/pyproject.toml +1 -1
  8. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_catalog_parity.py +11 -0
  9. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/pyproject.toml +2 -2
  10. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/BUILDER_GUIDE.md +2 -1
  11. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/_runner_script.py +45 -4
  12. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/api.py +2 -1
  13. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/cli.py +2 -0
  14. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/connect.py +11 -7
  15. gravity_cli-0.5.3/src/gravity_cli/commands/dev.py +40 -0
  16. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/run.py +159 -63
  17. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/tools.py +8 -2
  18. gravity_cli-0.5.3/src/gravity_cli/dev.py +309 -0
  19. gravity_cli-0.5.3/src/gravity_cli/dev_page/index.html +406 -0
  20. gravity_cli-0.5.3/src/gravity_cli/localrun.py +228 -0
  21. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_api.py +2 -1
  22. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_connect.py +20 -3
  23. gravity_cli-0.5.3/tests/test_dev.py +267 -0
  24. gravity_cli-0.5.3/tests/test_localrun.py +83 -0
  25. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_run.py +26 -1
  26. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_templates.py +68 -0
  27. gravity_cli-0.5.3/tests/test_tools.py +45 -0
  28. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/uv.lock +6 -6
  29. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/.env.example +0 -0
  30. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/.github/workflows/ci.yml +0 -0
  31. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/.github/workflows/release.yml +0 -0
  32. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/.gitignore +0 -0
  33. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/LICENSE +0 -0
  34. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/NOTICE +0 -0
  35. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/RELEASING.md +0 -0
  36. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/TEMPLATES.md +0 -0
  37. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/LICENSE +0 -0
  38. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/NOTICE +0 -0
  39. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/README.md +0 -0
  40. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/__init__.py +0 -0
  41. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/__main__.py +0 -0
  42. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/contract.py +0 -0
  43. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/cron.py +0 -0
  44. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/errors.py +0 -0
  45. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/export.py +0 -0
  46. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/gen_catalog.py +0 -0
  47. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/manifest.py +0 -0
  48. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/source_scan.py +0 -0
  49. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/gravity_schema/validation.py +0 -0
  50. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/schema/manifest.schema.json +0 -0
  51. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/conftest.py +0 -0
  52. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_cron.py +0 -0
  53. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_errors.py +0 -0
  54. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_json_schema.py +0 -0
  55. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_kind_outputs.py +0 -0
  56. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_manifest.py +0 -0
  57. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_slots.py +0 -0
  58. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_source_scan.py +0 -0
  59. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/packages/gravity-schema/tests/test_validation.py +0 -0
  60. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/__init__.py +0 -0
  61. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/__main__.py +0 -0
  62. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/__init__.py +0 -0
  63. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/adapt.py +0 -0
  64. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/build.py +0 -0
  65. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/guide.py +0 -0
  66. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/init.py +0 -0
  67. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/login.py +0 -0
  68. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/push.py +0 -0
  69. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/runs.py +0 -0
  70. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/schedule.py +0 -0
  71. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/commands/validate.py +0 -0
  72. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/config.py +0 -0
  73. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/manifest.py +0 -0
  74. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/project.py +0 -0
  75. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/chat/.gitignore +0 -0
  76. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/chat/manifest.yaml +0 -0
  77. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/chat/requirements.txt +0 -0
  78. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/chat/src/agent.py +0 -0
  79. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/deepagent/.gitignore +0 -0
  80. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/deepagent/manifest.yaml +0 -0
  81. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/deepagent/requirements.txt +0 -0
  82. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/deepagent/src/agent.py +0 -0
  83. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/minimal/.gitignore +0 -0
  84. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/minimal/manifest.yaml +0 -0
  85. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/minimal/requirements.txt +0 -0
  86. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/minimal/src/agent.py +0 -0
  87. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/.gitignore +0 -0
  88. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/manifest.yaml +0 -0
  89. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/requirements.txt +0 -0
  90. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/src/agent.py +0 -0
  91. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/src/gateway.py +0 -0
  92. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/src/nodes/agent.py +0 -0
  93. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/src/prompts.py +0 -0
  94. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/templates/structured/src/state.py +0 -0
  95. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/timeline.py +0 -0
  96. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/src/gravity_cli/ui.py +0 -0
  97. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/starter/manifest.yaml +0 -0
  98. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/starter/requirements.txt +0 -0
  99. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/starter/src/agent.py +0 -0
  100. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/starter/src/summary.py +0 -0
  101. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/starter/tests/test_summary.py +0 -0
  102. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/conftest.py +0 -0
  103. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_adapt.py +0 -0
  104. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_build_push.py +0 -0
  105. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_gateway_token.py +0 -0
  106. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_manifest_integration.py +0 -0
  107. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_session.py +0 -0
  108. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_timeline.py +0 -0
  109. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_ui.py +0 -0
  110. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_validate.py +0 -0
  111. {gravity_cli-0.5.0 → gravity_cli-0.5.3}/tests/test_welcome.py +0 -0
@@ -191,6 +191,37 @@ 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
+
205
+ **Images** come from an image model through the same URL and token: no tool to
206
+ declare. Ask for image output; the picture comes back as a data URL, which is
207
+ exactly what the corpus takes (§8):
208
+
209
+ ```python
210
+ async with httpx.AsyncClient(timeout=120) as http:
211
+ resp = await http.post(f"{os.environ['GRAVITY_LLM_URL']}/chat/completions",
212
+ headers={"Authorization": f"Bearer {os.environ['GRAVITY_RUN_TOKEN']}"},
213
+ json={"model": "google/gemini-2.5-flash-image", # or openai/gpt-5-image-mini
214
+ "modalities": ["image", "text"],
215
+ "messages": [{"role": "user", "content": "A flat illustration of ..., no text"}]})
216
+ resp.raise_for_status()
217
+ src = resp.json()["choices"][0]["message"]["images"][0]["image_url"]["url"] # data:image/png;base64,...
218
+ ```
219
+
220
+ An image is a few MB and the whole state is checkpointed after every step, so
221
+ make pictures in the **last** node (or the one that builds the corpus) and keep
222
+ them out of state until then. Each one is metered and counts toward the run's
223
+ budget like any model call.
224
+
194
225
  Those three env vars — `GRAVITY_GATEWAY_URL`, `GRAVITY_LLM_URL`,
195
226
  `GRAVITY_RUN_TOKEN` — are the entire Gravity surface, plus the `gravity`
196
227
  package for tools on the platform (§8). No `OPENAI_API_KEY`: locally and in
@@ -217,6 +248,11 @@ undeclared tool is refused locally exactly as it will be when hosted. Your
217
248
  agent only ever sees the run token. `GRAVITY_GATEWAY_URL` defaults to
218
249
  `http://127.0.0.1:8000/mcp` and `GRAVITY_LLM_URL` to the same host's `/v1`.
219
250
 
251
+ `gravity dev` opens a local dashboard over the same runs: a form from your
252
+ manifest, a live timeline, approvals, the result and a history. A run from
253
+ `gravity run` in the same folder shows up in it, and the other way round (both
254
+ leave their record in `.gravity/runs/`; one run is live per folder).
255
+
220
256
  ## 7. What's checked where
221
257
 
222
258
  | Stage | Catches |
@@ -271,7 +307,10 @@ start**: everything before the call happens twice.
271
307
  replaced. State is kept per thread. A key the next run doesn't send keeps its
272
308
  value (a monitor's `seen`, a draft). For memory shared by one consumer's
273
309
  threads, use `langgraph.config.get_store()` (`aget` / `aput` / `asearch` by
274
- namespace and filter; no semantic search).
310
+ namespace and filter; no semantic search). It is kept per consumer and agent,
311
+ across runs and versions, until the agent deletes an item (`aput(ns, key, None)`);
312
+ store one item per record, not one large blob. `gravity run` gives you the same
313
+ store locally, saved in `.gravity/store.json` (delete it to start over).
275
314
 
276
315
  **Conversation.** `messages` must use the `add_messages` reducer:
277
316
  `messages: Annotated[list, add_messages]`. Each turn the platform sends only
@@ -314,6 +353,7 @@ corpus = gravity.Corpus(
314
353
  facts=[gravity.Fact(label="TAM", value="$18B", source="s1")], # value: text or a number
315
354
  table=gravity.Table(columns=["Segment", "Teams"], rows=[["Founders", "1.2M"]]),
316
355
  notes="speaker notes / read-out text",
356
+ images=[gravity.Image(src=src, alt="server rack", caption="Option A")], # data URL, see §4
317
357
  sections=[gravity.Section(heading="one level of nesting, same shape")],
318
358
  )],
319
359
  sources=[gravity.Source(id="s1", title="Report", url="https://example.com")],
@@ -322,7 +362,10 @@ return {**corpus.to_state(), "result": {"title": corpus.title}}
322
362
  ```
323
363
 
324
364
  A plain dict of the same shape works too. The models catch a typo when the
325
- corpus is built rather than when it's rendered. Set `result` as well: it's
365
+ corpus is built rather than when it's rendered. Images (`png`, `jpeg`, `webp`
366
+ or `gif` data URLs, at most 8 MB each and 12 per corpus) are embedded in the
367
+ Word, PowerPoint and Markdown downloads and shown on `/app` and in `gravity
368
+ dev`; an emailed output names each one as `[image: alt — caption]` instead. Set `result` as well: it's
326
369
  what the run returns.
327
370
 
328
371
  ## 9. Checklist
@@ -1,3 +1,20 @@
1
+ Metadata-Version: 2.5
2
+ Name: gravity-cli
3
+ Version: 0.5.3
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.3
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">
@@ -179,6 +196,7 @@ Run `gravity help` for this list in the terminal, and add `-h` to any command fo
179
196
  | **Build an agent** | [`gravity init`](#gravity-init) | Create a new agent from a template |
180
197
  | | [`gravity validate`](#gravity-validate) | Check your agent for mistakes before it runs |
181
198
  | | [`gravity run`](#gravity-run) | Run your agent locally against real tools |
199
+ | | [`gravity dev`](#gravity-dev) | Open a local dashboard to run and watch your agent |
182
200
  | | [`gravity schedule`](#gravity-schedule) | Run your agent on its schedule, locally |
183
201
  | | [`gravity adapt`](#gravity-adapt) | Write a new slot adapter with AI, kept only if its tests pass |
184
202
  | **Publish** | [`gravity build`](#gravity-build) | Package your agent into `build/agent.zip` |
@@ -274,6 +292,29 @@ gravity run --reply "make it shorter"
274
292
  - Requires a gateway token: the one `gravity login` stores, or `GRAVITY_DEV_TOKEN`.
275
293
  - A call to a tool in `approvals.required_for` pauses, shows what it would do, and waits for `y` or `n`. With no terminal to ask in, it is rejected.
276
294
  - Exits with code 0 when the run finishes and 1 when it fails.
295
+ - Leaves a record in `.gravity/runs/<id>/` (`run.json` and `events.jsonl`), which is what `gravity dev` reads. One run is live per folder at a time, because runs share the folder's memory, store and thread files.
296
+
297
+ ### `gravity dev`
298
+
299
+ Open a local dashboard for the agent in this folder: a form built from the manifest's inputs, a live timeline of each step, model call and tool call, approval buttons for held calls, the result, and a history of runs.
300
+
301
+ ```
302
+ gravity dev [path] [--port 4747] [--no-open] [--llm-url URL]
303
+ ```
304
+
305
+ | Option | Description |
306
+ |---|---|
307
+ | `--port N` | Port to listen on (default 4747). If it is taken, a free one is used and printed. |
308
+ | `--no-open` | Print the address without opening a browser (SSH, WSL, CI) |
309
+ | `--llm-url URL` | Model endpoint to use instead of `GRAVITY_LLM_URL` |
310
+
311
+ - A run from `gravity run` in the same folder shows up in the dashboard live, and a run started in the dashboard is the same run (same engine, same memory, store and thread files), so `gravity run --reply` continues it.
312
+ - It listens on `127.0.0.1` only, behind a random token in the printed address, for as long as the command runs. Ctrl+C stops it and any run it started. Nothing keeps running in the background.
313
+ - Connected accounts: it shows the accounts your tools need and a Connect button for each one that is missing.
314
+ - Approvals: a call held for approval can be decided in the terminal or in the dashboard; whichever answers first wins.
315
+ - Cancel a run from the dashboard only if the dashboard started it; stop a terminal run with Ctrl+C in that terminal.
316
+ - Results show as text. Word, PowerPoint and CSV files are produced by Gravity after you push. A file input is read as text here.
317
+ - Same requirements as `gravity run`: a gateway token from `gravity login`, or `GRAVITY_DEV_TOKEN`.
277
318
 
278
319
  ### `gravity schedule`
279
320
 
@@ -738,6 +779,8 @@ Real tools act on a connected account. Connect one with `gravity-gateway connect
738
779
 
739
780
  ## What's new
740
781
 
782
+ - **0.5.3** `gravity dev` opens a local dashboard for your agent: a form from your manifest, a live timeline, approvals, the result and run history; runs from `gravity run` in the same folder show up in it live, and the other way round. WhatsApp joins the catalog (51 tools; 1,374 across 12 apps). Agents can return generated images: put them in a corpus section's `images` and they land in the Word, PowerPoint and Markdown outputs. The agent standard now shows how to generate images and embeddings through the gateway. `NEEDS_RECONNECT` on the shared test account tells you to ask Gravity, not to connect your own account.
783
+ - **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.
741
784
  - **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.
742
785
  - **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`.
743
786
  - **0.4.3** Documents the `secret` input type and the `supadata.get_transcript` tool.
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: gravity-cli
3
- Version: 0.5.0
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.0
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">
@@ -196,6 +179,7 @@ Run `gravity help` for this list in the terminal, and add `-h` to any command fo
196
179
  | **Build an agent** | [`gravity init`](#gravity-init) | Create a new agent from a template |
197
180
  | | [`gravity validate`](#gravity-validate) | Check your agent for mistakes before it runs |
198
181
  | | [`gravity run`](#gravity-run) | Run your agent locally against real tools |
182
+ | | [`gravity dev`](#gravity-dev) | Open a local dashboard to run and watch your agent |
199
183
  | | [`gravity schedule`](#gravity-schedule) | Run your agent on its schedule, locally |
200
184
  | | [`gravity adapt`](#gravity-adapt) | Write a new slot adapter with AI, kept only if its tests pass |
201
185
  | **Publish** | [`gravity build`](#gravity-build) | Package your agent into `build/agent.zip` |
@@ -291,6 +275,29 @@ gravity run --reply "make it shorter"
291
275
  - Requires a gateway token: the one `gravity login` stores, or `GRAVITY_DEV_TOKEN`.
292
276
  - A call to a tool in `approvals.required_for` pauses, shows what it would do, and waits for `y` or `n`. With no terminal to ask in, it is rejected.
293
277
  - Exits with code 0 when the run finishes and 1 when it fails.
278
+ - Leaves a record in `.gravity/runs/<id>/` (`run.json` and `events.jsonl`), which is what `gravity dev` reads. One run is live per folder at a time, because runs share the folder's memory, store and thread files.
279
+
280
+ ### `gravity dev`
281
+
282
+ Open a local dashboard for the agent in this folder: a form built from the manifest's inputs, a live timeline of each step, model call and tool call, approval buttons for held calls, the result, and a history of runs.
283
+
284
+ ```
285
+ gravity dev [path] [--port 4747] [--no-open] [--llm-url URL]
286
+ ```
287
+
288
+ | Option | Description |
289
+ |---|---|
290
+ | `--port N` | Port to listen on (default 4747). If it is taken, a free one is used and printed. |
291
+ | `--no-open` | Print the address without opening a browser (SSH, WSL, CI) |
292
+ | `--llm-url URL` | Model endpoint to use instead of `GRAVITY_LLM_URL` |
293
+
294
+ - A run from `gravity run` in the same folder shows up in the dashboard live, and a run started in the dashboard is the same run (same engine, same memory, store and thread files), so `gravity run --reply` continues it.
295
+ - It listens on `127.0.0.1` only, behind a random token in the printed address, for as long as the command runs. Ctrl+C stops it and any run it started. Nothing keeps running in the background.
296
+ - Connected accounts: it shows the accounts your tools need and a Connect button for each one that is missing.
297
+ - Approvals: a call held for approval can be decided in the terminal or in the dashboard; whichever answers first wins.
298
+ - Cancel a run from the dashboard only if the dashboard started it; stop a terminal run with Ctrl+C in that terminal.
299
+ - Results show as text. Word, PowerPoint and CSV files are produced by Gravity after you push. A file input is read as text here.
300
+ - Same requirements as `gravity run`: a gateway token from `gravity login`, or `GRAVITY_DEV_TOKEN`.
294
301
 
295
302
  ### `gravity schedule`
296
303
 
@@ -755,6 +762,8 @@ Real tools act on a connected account. Connect one with `gravity-gateway connect
755
762
 
756
763
  ## What's new
757
764
 
765
+ - **0.5.3** `gravity dev` opens a local dashboard for your agent: a form from your manifest, a live timeline, approvals, the result and run history; runs from `gravity run` in the same folder show up in it live, and the other way round. WhatsApp joins the catalog (51 tools; 1,374 across 12 apps). Agents can return generated images: put them in a corpus section's `images` and they land in the Word, PowerPoint and Markdown outputs. The agent standard now shows how to generate images and embeddings through the gateway. `NEEDS_RECONNECT` on the shared test account tells you to ask Gravity, not to connect your own account.
766
+ - **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.
758
767
  - **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.
759
768
  - **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`.
760
769
  - **0.4.3** Documents the `secret` input type and the `supadata.get_transcript` tool.
@@ -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,6 +22,7 @@
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
@@ -29,6 +31,7 @@
29
31
 
30
32
  - name: web.search
31
33
  slug: COMPOSIO_SEARCH_WEB
34
+ toolkit: composio_search
32
35
  description: search the web; returns results with titles, links and snippets. No account needed
33
36
  scopes: []
34
37
  breadth: generic
@@ -37,6 +40,7 @@
37
40
 
38
41
  - name: web.fetch
39
42
  slug: COMPOSIO_SEARCH_FETCH_URL_CONTENT
43
+ toolkit: composio_search
40
44
  description: read the text content of a web page by its URL. No account needed
41
45
  scopes: []
42
46
  breadth: generic
@@ -45,6 +49,7 @@
45
49
 
46
50
  - name: supadata.get_transcript
47
51
  slug: null
52
+ toolkit: null
48
53
  description: fetch the transcript of a YouTube video with the caller's own Supadata API key (api_key argument)
49
54
  scopes: []
50
55
  breadth: niche
@@ -53,6 +58,7 @@
53
58
 
54
59
  - name: gmail.read
55
60
  slug: GMAIL_FETCH_EMAILS
61
+ toolkit: gmail
56
62
  description: read the connected Gmail inbox
57
63
  scopes: [gmail.readonly]
58
64
  breadth: generic
@@ -61,6 +67,7 @@
61
67
 
62
68
  - name: gmail.draft
63
69
  slug: GMAIL_CREATE_EMAIL_DRAFT
70
+ toolkit: gmail
64
71
  description: create a Gmail draft
65
72
  scopes: [gmail.compose]
66
73
  breadth: generic
@@ -69,6 +76,7 @@
69
76
 
70
77
  - name: gmail.send
71
78
  slug: GMAIL_SEND_EMAIL
79
+ toolkit: gmail
72
80
  description: send an email from the connected Gmail
73
81
  scopes: [gmail.send]
74
82
  breadth: generic
@@ -77,6 +85,7 @@
77
85
 
78
86
  - name: sheets.read
79
87
  slug: GOOGLESHEETS_BATCH_GET
88
+ toolkit: googlesheets
80
89
  description: read values from a connected Google Sheet
81
90
  scopes: [sheets]
82
91
  breadth: generic
@@ -85,6 +94,7 @@
85
94
 
86
95
  - name: sheets.append
87
96
  slug: GOOGLESHEETS_BATCH_UPDATE
97
+ toolkit: googlesheets
88
98
  description: append a row to a connected Google Sheet
89
99
  scopes: [sheets]
90
100
  breadth: generic
@@ -93,6 +103,7 @@
93
103
 
94
104
  - name: slack.send
95
105
  slug: SLACK_SEND_MESSAGE
106
+ toolkit: slack
96
107
  description: post a message to a connected Slack channel
97
108
  scopes: [slack.chat:write]
98
109
  breadth: generic
@@ -101,6 +112,7 @@
101
112
 
102
113
  - name: slack.list_channels
103
114
  slug: SLACK_LIST_ALL_CHANNELS
115
+ toolkit: slack
104
116
  description: list channels in the connected Slack workspace
105
117
  scopes: [slack.channels:read]
106
118
  breadth: generic
@@ -109,6 +121,7 @@
109
121
 
110
122
  - name: slack.read_history
111
123
  slug: SLACK_FETCH_CONVERSATION_HISTORY
124
+ toolkit: slack
112
125
  description: read message history of a Slack channel
113
126
  scopes: [slack.channels:history]
114
127
  breadth: generic
@@ -117,6 +130,7 @@
117
130
 
118
131
  - name: slack.find_user
119
132
  slug: SLACK_FIND_USERS
133
+ toolkit: slack
120
134
  description: find a Slack user by name, handle or email, for mentions
121
135
  scopes: [slack.users:read]
122
136
  breadth: generic
@@ -125,6 +139,7 @@
125
139
 
126
140
  - name: calendar.read
127
141
  slug: GOOGLECALENDAR_EVENTS_LIST
142
+ toolkit: googlecalendar
128
143
  description: list events on the connected Google Calendar
129
144
  scopes: [calendar.readonly]
130
145
  breadth: generic
@@ -133,6 +148,7 @@
133
148
 
134
149
  - name: drive.read
135
150
  slug: GOOGLEDRIVE_DOWNLOAD_FILE
151
+ toolkit: googledrive
136
152
  description: download a file from the connected Google Drive
137
153
  scopes: [drive.readonly]
138
154
  breadth: generic
@@ -141,6 +157,7 @@
141
157
 
142
158
  - name: linear.create_issue
143
159
  slug: LINEAR_CREATE_LINEAR_ISSUE
160
+ toolkit: linear
144
161
  description: create an issue in a connected Linear team
145
162
  scopes: [linear.write]
146
163
  breadth: generic
@@ -149,6 +166,7 @@
149
166
 
150
167
  - name: linear.list_teams
151
168
  slug: LINEAR_LIST_LINEAR_TEAMS
169
+ toolkit: linear
152
170
  description: list the connected Linear workspace teams
153
171
  scopes: [linear.read]
154
172
  breadth: generic
@@ -157,6 +175,7 @@
157
175
 
158
176
  - name: linear.list_issues
159
177
  slug: LINEAR_LIST_LINEAR_ISSUES
178
+ toolkit: linear
160
179
  description: list open issues in the connected Linear workspace
161
180
  scopes: [linear.read]
162
181
  breadth: generic
@@ -165,6 +184,7 @@
165
184
 
166
185
  - name: linear.get_issue
167
186
  slug: LINEAR_GET_LINEAR_ISSUE
187
+ toolkit: linear
168
188
  description: read one Linear issue by id
169
189
  scopes: [linear.read]
170
190
  breadth: generic
@@ -174,6 +194,7 @@
174
194
  # GitHub: read-only, for change summaries and contribution profiles.
175
195
  - name: github.list_commits
176
196
  slug: GITHUB_LIST_COMMITS
197
+ toolkit: github
177
198
  description: list commits on a repository branch, filterable by date range, author or path
178
199
  scopes: [repo]
179
200
  breadth: generic
@@ -182,6 +203,7 @@
182
203
 
183
204
  - name: github.get_commit
184
205
  slug: GITHUB_GET_A_COMMIT
206
+ toolkit: github
185
207
  description: read one commit with its changed files, line counts and patches
186
208
  scopes: [repo]
187
209
  breadth: generic
@@ -190,6 +212,7 @@
190
212
 
191
213
  - name: github.compare
192
214
  slug: GITHUB_COMPARE_TWO_COMMITS
215
+ toolkit: github
193
216
  description: compare two refs (base...head) with the commits and changed files between them
194
217
  scopes: [repo]
195
218
  breadth: generic
@@ -198,6 +221,7 @@
198
221
 
199
222
  - name: github.list_pull_requests
200
223
  slug: GITHUB_LIST_PULL_REQUESTS
224
+ toolkit: github
201
225
  description: list pull requests on a repository, filterable by state and branch
202
226
  scopes: [repo]
203
227
  breadth: generic
@@ -206,6 +230,7 @@
206
230
 
207
231
  - name: github.list_pr_files
208
232
  slug: GITHUB_LIST_PULL_REQUESTS_FILES
233
+ toolkit: github
209
234
  description: list the files a pull request changes, with line counts and patches
210
235
  scopes: [repo]
211
236
  breadth: generic
@@ -214,6 +239,7 @@
214
239
 
215
240
  - name: github.list_pr_reviews
216
241
  slug: GITHUB_LIST_REVIEWS_FOR_A_PULL_REQUEST
242
+ toolkit: github
217
243
  description: list the reviews submitted on a pull request
218
244
  scopes: [repo]
219
245
  breadth: generic
@@ -222,6 +248,7 @@
222
248
 
223
249
  - name: github.search_issues
224
250
  slug: GITHUB_SEARCH_ISSUES_AND_PULL_REQUESTS
251
+ toolkit: github
225
252
  description: search issues and pull requests with GitHub qualifiers (repo:, author:, reviewed-by:, is:pr, created:)
226
253
  scopes: [repo]
227
254
  breadth: generic
@@ -230,6 +257,7 @@
230
257
 
231
258
  - name: linkedin.whoami
232
259
  slug: LINKEDIN_GET_MY_INFO
260
+ toolkit: linkedin
233
261
  description: read the connected LinkedIn member's id, name and headline (the author of their posts)
234
262
  scopes: [linkedin.profile]
235
263
  breadth: generic
@@ -238,6 +266,7 @@
238
266
 
239
267
  - name: linkedin.create_post
240
268
  slug: LINKEDIN_CREATE_LINKED_IN_POST
269
+ toolkit: linkedin
241
270
  description: publish a post on LinkedIn as the connected member or an organisation they manage
242
271
  scopes: [linkedin.w_member_social]
243
272
  breadth: generic