gravity-cli 0.5.6__tar.gz → 0.6.0__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 (128) hide show
  1. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/AGENT_STANDARD.md +9 -7
  2. gravity_cli-0.5.6/README.md → gravity_cli-0.6.0/PKG-INFO +72 -6
  3. gravity_cli-0.5.6/PKG-INFO → gravity_cli-0.6.0/README.md +55 -23
  4. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/RELEASING.md +4 -0
  5. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/TEMPLATES.md +1 -1
  6. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/pyproject.toml +1 -1
  7. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/pyproject.toml +2 -2
  8. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/BUILDER_GUIDE.md +33 -12
  9. gravity_cli-0.6.0/src/gravity_cli/CHANGELOG.md +37 -0
  10. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/api.py +9 -1
  11. gravity_cli-0.6.0/src/gravity_cli/changelog.py +55 -0
  12. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/cli.py +66 -6
  13. gravity_cli-0.6.0/src/gravity_cli/commands/changelog.py +20 -0
  14. gravity_cli-0.6.0/src/gravity_cli/commands/docs.py +32 -0
  15. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/init.py +3 -1
  16. gravity_cli-0.6.0/src/gravity_cli/commands/update.py +38 -0
  17. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/config.py +23 -1
  18. gravity_cli-0.6.0/src/gravity_cli/docs/Gravity.agent.md +721 -0
  19. gravity_cli-0.6.0/src/gravity_cli/docs/problem-to-agent.md +933 -0
  20. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/ui.py +92 -15
  21. gravity_cli-0.6.0/src/gravity_cli/updater.py +206 -0
  22. gravity_cli-0.6.0/src/gravity_cli/workspace.py +152 -0
  23. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/starter/manifest.yaml +1 -1
  24. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/starter/src/agent.py +19 -6
  25. gravity_cli-0.6.0/tests/conftest.py +16 -0
  26. gravity_cli-0.6.0/tests/test_agent_guide.py +129 -0
  27. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_api.py +28 -0
  28. gravity_cli-0.6.0/tests/test_changelog.py +64 -0
  29. gravity_cli-0.6.0/tests/test_updater.py +352 -0
  30. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_welcome.py +23 -1
  31. gravity_cli-0.6.0/tests/test_workspace.py +215 -0
  32. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/uv.lock +27 -27
  33. gravity_cli-0.5.6/tests/conftest.py +0 -8
  34. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/.env.example +0 -0
  35. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/.github/workflows/ci.yml +0 -0
  36. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/.github/workflows/release.yml +0 -0
  37. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/.gitignore +0 -0
  38. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/LICENSE +0 -0
  39. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/NOTICE +0 -0
  40. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/LICENSE +0 -0
  41. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/NOTICE +0 -0
  42. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/README.md +0 -0
  43. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/__init__.py +0 -0
  44. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/__main__.py +0 -0
  45. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/catalog.py +0 -0
  46. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/catalog.yaml +0 -0
  47. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/catalog_imported.yaml +0 -0
  48. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/contract.py +0 -0
  49. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/cron.py +0 -0
  50. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/errors.py +0 -0
  51. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/export.py +0 -0
  52. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/gen_catalog.py +0 -0
  53. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/manifest.py +0 -0
  54. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/source_scan.py +0 -0
  55. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/gravity_schema/validation.py +0 -0
  56. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/schema/manifest.schema.json +0 -0
  57. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/conftest.py +0 -0
  58. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_catalog_parity.py +0 -0
  59. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_cron.py +0 -0
  60. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_errors.py +0 -0
  61. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_json_schema.py +0 -0
  62. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_kind_outputs.py +0 -0
  63. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_manifest.py +0 -0
  64. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_slots.py +0 -0
  65. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_source_scan.py +0 -0
  66. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/packages/gravity-schema/tests/test_validation.py +0 -0
  67. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/__init__.py +0 -0
  68. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/__main__.py +0 -0
  69. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/_runner_script.py +0 -0
  70. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/__init__.py +0 -0
  71. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/adapt.py +0 -0
  72. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/build.py +0 -0
  73. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/connect.py +0 -0
  74. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/dev.py +0 -0
  75. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/guide.py +0 -0
  76. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/login.py +0 -0
  77. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/push.py +0 -0
  78. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/run.py +0 -0
  79. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/runs.py +0 -0
  80. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/schedule.py +0 -0
  81. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/tools.py +0 -0
  82. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/commands/validate.py +0 -0
  83. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/dev.py +0 -0
  84. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/dev_page/index.html +0 -0
  85. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/localrun.py +0 -0
  86. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/manifest.py +0 -0
  87. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/project.py +0 -0
  88. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/chat/.gitignore +0 -0
  89. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/chat/manifest.yaml +0 -0
  90. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/chat/requirements.txt +0 -0
  91. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/chat/src/agent.py +0 -0
  92. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/deepagent/.gitignore +0 -0
  93. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/deepagent/manifest.yaml +0 -0
  94. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/deepagent/requirements.txt +0 -0
  95. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/deepagent/src/agent.py +0 -0
  96. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/minimal/.gitignore +0 -0
  97. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/minimal/manifest.yaml +0 -0
  98. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/minimal/requirements.txt +0 -0
  99. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/minimal/src/agent.py +0 -0
  100. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/.gitignore +0 -0
  101. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/manifest.yaml +0 -0
  102. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/requirements.txt +0 -0
  103. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/src/agent.py +0 -0
  104. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/src/gateway.py +0 -0
  105. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/src/nodes/agent.py +0 -0
  106. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/src/prompts.py +0 -0
  107. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/templates/structured/src/state.py +0 -0
  108. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/src/gravity_cli/timeline.py +0 -0
  109. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/starter/requirements.txt +0 -0
  110. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/starter/src/summary.py +0 -0
  111. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/starter/tests/test_summary.py +0 -0
  112. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_adapt.py +0 -0
  113. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_agents.py +0 -0
  114. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_build_push.py +0 -0
  115. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_connect.py +0 -0
  116. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_dev.py +0 -0
  117. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_gateway_token.py +0 -0
  118. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_localrun.py +0 -0
  119. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_manifest_integration.py +0 -0
  120. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_run.py +0 -0
  121. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_runs.py +0 -0
  122. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_schedule.py +0 -0
  123. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_session.py +0 -0
  124. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_templates.py +0 -0
  125. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_timeline.py +0 -0
  126. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_tools.py +0 -0
  127. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_ui.py +0 -0
  128. {gravity_cli-0.5.6 → gravity_cli-0.6.0}/tests/test_validate.py +0 -0
@@ -25,7 +25,7 @@ my-agent/
25
25
  ```
26
26
 
27
27
  **Only these are bundled.** `gravity build` zips exactly `manifest.yaml`,
28
- `requirements.txt`, `requirements.lock`, and the contents of `src/` and
28
+ `requirements.txt`, `requirements.lock`, `tools_needed.yaml` (if present), and the contents of `src/` and
29
29
  `tests/` (minus `__pycache__`). Anything else — `.venv/`, `build/`, notebooks,
30
30
  a `data/` folder — is not shipped, and code that reads it will fail at runtime.
31
31
 
@@ -41,14 +41,14 @@ runtime:
41
41
  framework: langgraph # only value accepted
42
42
  python: "3.12" # the platform runs 3.12; validate refuses any other
43
43
  entrypoint: src/agent.py:graph # file:object — see §3
44
- timeout_seconds: 300 # 10–900
44
+ timeout_seconds: 300 # 10–28800
45
45
 
46
46
  dependencies:
47
47
  lockfile: requirements.lock # leave as-is
48
48
 
49
49
  inputs: # becomes the consumer's form; arrives as state["inputs"]
50
50
  - name: query # the key in state["inputs"]
51
- type: text # text | textarea | number | boolean | static-dropdown | email | url | date | file
51
+ type: text # text | textarea | number | boolean | static-dropdown | email | url | date | secret | file
52
52
  label: "What should I do?" # what the consumer sees
53
53
  required: true
54
54
  # options: [a, b] # static-dropdown only
@@ -69,6 +69,7 @@ triggers: # optional — what starts a run
69
69
  # schedule:
70
70
  # cron: "0 9 * * 1" # 5 fields: digits, * / , - only (no @daily, no MON); every 5 minutes at the fastest; one day field at most; Asia/Kolkata
71
71
  # consumer_can_change: true
72
+ # webhook: [github.push] # run on a GitHub push; the only source today
72
73
 
73
74
  approvals: # optional — tools that pause the run for the consumer's yes
74
75
  required_for: [] # each must also be in permissions.tools
@@ -127,9 +128,10 @@ runner is too old, not your agent.
127
128
 
128
129
  **State.** The runtime invokes `graph.astream({"inputs": {...}}, stream_mode="updates")`
129
130
  — the keyword is passed only when your `astream` accepts it, so a hand-rolled
130
- object with a plain `astream(state)` works too (accepting `**kwargs` is the
131
- safe habit). The dict is the
132
- dict is the consumer's form answers keyed by `inputs[].name`. Your state
131
+ object with a plain `astream(state)` works in `gravity run` (accepting `**kwargs`
132
+ is the safe habit). The hosted platform needs a compiled LangGraph graph: it
133
+ injects its own checkpointer and store into it. The dict is the
134
+ consumer's form answers keyed by `inputs[].name`. Your state
133
135
  therefore needs:
134
136
 
135
137
  ```python
@@ -259,7 +261,7 @@ leave their record in `.gravity/runs/`; one run is live per folder).
259
261
 
260
262
  | Stage | Catches |
261
263
  |---|---|
262
- | `gravity validate` / `build` (local) | manifest schema · entrypoint file + object exist (static) · `requirements.txt` present · lockfile compiles for aarch64 · undeclared network imports (warning) |
264
+ | `gravity validate` / `build` (local) | manifest schema · entrypoint file + object exist (static) · `requirements.txt` present · lockfile compiles for aarch64 · undeclared tool names in source (an error for a write tool, a warning for a read tool) |
263
265
  | `gravity push` (server) | manifest re-validated with the same rules · `permissions.tools` all exist in the catalog · version not already published |
264
266
  | deploy | every pin in the lock installs on ARM64 |
265
267
  | first run | module imports · `graph` resolves to something with `astream()` · state has `inputs` · final state has `result` |
@@ -1,3 +1,20 @@
1
+ Metadata-Version: 2.5
2
+ Name: gravity-cli
3
+ Version: 0.6.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.6.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
+
1
18
  <h1 align="center">Gravity CLI</h1>
2
19
 
3
20
  <p align="center">
@@ -75,7 +92,7 @@ gravity init my-agent
75
92
 
76
93
  ## Declare it once, enforced on every call
77
94
 
78
- There is no SDK to learn. Your agent is plain LangGraph that reads three environment variables. Everything Gravity enforces comes from `manifest.yaml`:
95
+ There is almost no SDK to learn. Your agent is plain LangGraph that reads three environment variables; the one Gravity package, `gravity`, is preinstalled on the platform and is only needed to load approval-gated tools. Everything Gravity enforces comes from `manifest.yaml`:
79
96
 
80
97
  | You declare in `manifest.yaml` | What Gravity does with it |
81
98
  |---|---|
@@ -91,11 +108,11 @@ The same rules apply on your laptop and when the agent runs hosted, because both
91
108
 
92
109
  | | |
93
110
  |---|---|
94
- | Commands | 18, grouped into build an agent, publish, your agents, account and help |
111
+ | Commands | 21, grouped into build an agent, publish, your agents, account, help and Gravity CLI |
95
112
  | Starter templates | 4: `minimal`, `structured`, `deepagent`, `chat` |
96
- | Tool catalog | 1,402 tools across 15 apps (Gmail, Slack, Linear, GitHub, Calendar, Drive, Sheets, LinkedIn, Jira, Confluence, Notion, WhatsApp, web search, and two built-ins), 28 of them curated |
97
- | Test suite | 466 tests (214 CLI, 252 schema), run on Ubuntu and Windows with Python 3.12 and 3.13 |
98
- | Runtime contract | 3 environment variables, no Gravity import in your code |
113
+ | Tool catalog | 1,405 tools across 12 apps (Gmail, Slack, Linear, GitHub, Calendar, Drive, Sheets, LinkedIn, Jira, Confluence, Notion, WhatsApp), plus web search, Firecrawl and two built-ins; 31 of them curated |
114
+ | Test suite | 543 tests (285 CLI, 258 schema), run on Ubuntu and Windows with Python 3.12 and 3.13 |
115
+ | Runtime contract | 3 environment variables; an optional `import gravity` only for approval-gated tools |
99
116
 
100
117
  ## Five things to know before you start
101
118
 
@@ -194,6 +211,9 @@ Run `gravity help` for this list in the terminal, and add `-h` to any command fo
194
211
  | | [`gravity connect`](#gravity-connect) | Connect an account your agents act on (Gmail, Slack...) |
195
212
  | **Help** | [`gravity guide`](#gravity-guide) | Read the builder guide: how to build a good agent |
196
213
  | | [`gravity help`](#gravity-help) | Show the list of commands |
214
+ | **Gravity CLI** | [`gravity docs`](#gravity-docs) | Write `Gravity.agent.md` for the coding agent in this folder |
215
+ | | [`gravity update`](#gravity-update) | Update to the latest release and see what is new |
216
+ | | [`gravity changelog`](#gravity-changelog) | What changed in each release |
197
217
 
198
218
  `gravity --api-url URL <command>` saves a different control-plane address to `~/.gravity/config.json`; it must start with `http://` or `https://`. The flag applies to the command it is given with; on later commands a `GRAVITY_API_URL` in `~/.gravity/.env` or the shell still wins over `config.json`, and the CLI says so when that is the case. `gravity --version` (or `-V`) prints the installed version. Wherever a command takes `[path]`, it is the agent's folder and defaults to the current one.
199
219
 
@@ -412,7 +432,7 @@ gravity logs <run_id>
412
432
 
413
433
  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.
414
434
 
415
- 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.
435
+ 31 tools are curated, such as `gmail.read` and `web.search`. The other ~1,370 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.
416
436
 
417
437
  ```
418
438
  gravity tools [search]
@@ -485,6 +505,46 @@ gravity guide --save
485
505
  gravity guide | less
486
506
  ```
487
507
 
508
+ ### `gravity docs`
509
+
510
+ Write or refresh the two files a coding agent needs to build for Gravity in this folder: `Gravity.agent.md` (how Gravity runs agents, the manifest, every tool this release knows, the output formats, the rules that fail a load) and `problem-to-agent.md` (the method for turning a problem statement into an agent that holds up).
511
+
512
+ ```
513
+ gravity docs [--force]
514
+ ```
515
+
516
+ | Option | Description |
517
+ |---|---|
518
+ | `--force` | Replace the files even where you edited them, or where a file Gravity did not write has the same name |
519
+
520
+ You rarely need this. Every command writes the two files into the folder it runs in if they are missing (never into your home folder or a drive root; `gravity init` writes them into the new agent), and rewrites them when a newer release changes them. The first line of a file Gravity wrote names the release and a hash of the text after it: if you edit the file the hash no longer matches and Gravity leaves it alone. Your own `agent.md`, `AGENTS.md` and `CLAUDE.md` are never touched. Point your coding agent at `Gravity.agent.md` from them if it does not look there by itself. Set `GRAVITY_AGENT_DOCS=0` to stop the automatic writes.
521
+
522
+ ### `gravity update`
523
+
524
+ Check PyPI for a newer release, install it, and show what is new.
525
+
526
+ ```
527
+ gravity update [--check]
528
+ ```
529
+
530
+ | Option | Description |
531
+ |---|---|
532
+ | `--check` | Only say whether a newer release exists |
533
+
534
+ You do not have to run it: once every six hours a command looks for a newer release, and when there is one it says `Update available 0.6.0 → 0.6.1`, updates, and runs your command again as the new version, which lists what changed. It upgrades the way you installed it (`uv tool` or `pipx`); a copy installed any other way, or from a source checkout, is told the command to run and left alone. Nothing here can stop a command: a failed update is one warning and the command runs on the version you have. Set `GRAVITY_AUTO_UPDATE=0` to turn the check and the install off; CI never updates.
535
+
536
+ ### `gravity changelog`
537
+
538
+ What changed in each release, written for builders.
539
+
540
+ ```
541
+ gravity changelog [--all]
542
+ ```
543
+
544
+ | Option | Description |
545
+ |---|---|
546
+ | `--all` | Every release, not only the one you are on |
547
+
488
548
  ### `gravity help`
489
549
 
490
550
  Show the list of commands. The same as `gravity -h`.
@@ -540,6 +600,9 @@ Set `GRAVITY_API_URL` and `GRAVITY_GATEWAY_URL` only to work against services yo
540
600
  | `GRAVITY_API_URL` | The control plane's address | `https://agents.dev.gravity.fast` |
541
601
  | `GRAVITY_API_TOKEN` | A control-plane dev token, used instead of `gravity login`. For development only. | none |
542
602
  | `GRAVITY_CONFIG_DIR` | Moves `~/.gravity` elsewhere. Useful for tests and CI. | `~/.gravity` |
603
+ | `GRAVITY_AUTO_UPDATE` | When `0`, commands neither check for a newer release nor install one. Always off when `CI` is set. | `1` |
604
+ | `GRAVITY_AGENT_DOCS` | When `0`, commands do not write `Gravity.agent.md` and `problem-to-agent.md` into the folder they run in. `gravity docs` still does. | `1` |
605
+ | `GRAVITY_UPDATE_URL` | Where the latest version is read from, a JSON document with `info.version`. For tests and mirrors. | PyPI |
543
606
  | `GRAVITY_UNATTENDED` | When `1`, calls that need approval are rejected without asking. `gravity schedule` sets it for you. | unset |
544
607
 
545
608
  ### What your agent receives
@@ -775,6 +838,7 @@ Real tools act on a connected account. Connect one with `gravity-gateway connect
775
838
 
776
839
  ## What's new
777
840
 
841
+ - **0.6.0** Gravity CLI keeps itself up to date and says what changed (`gravity update`, `gravity changelog`). `Gravity.agent.md` and `problem-to-agent.md` appear in the folder you work in, for your coding agent (`gravity docs`). `gravity` on its own shows your version, the agent in the folder, whether you are signed in, and any update. The notes shown after an update are `src/gravity_cli/CHANGELOG.md`.
778
842
  - **0.5.5** `manifest.yaml` takes an optional `display:` hint for how a run's result shows: `view` (document, report, table, chart, code or files), `field` (the result key to show) and `chart` (`type: line` or `bar`, `x`, and up to three `y` fields). It is a hint, never a rule: an unknown view or key is ignored, so it never fails a push. 0.5.4 and earlier refuse a manifest that has it.
779
843
  - **0.5.4** `gravity agents` shows each agent's review: a REVIEW column (not submitted, submitted, approved, rejected), and under "From the Gravity team" the reviewer's notes and what to do next. An agent the Gravity team turned off shows as `off` with the reason. UPDATED reads "3 min ago" instead of a timestamp, and status and review are coloured. Also, from a full audit of every command: `gravity --version`. Clear messages instead of tracebacks for: a proxy or sign-in page answering instead of Gravity, a refused `GRAVITY_API_TOKEN` or `GRAVITY_DEV_TOKEN`, an expired sign-in code, a non-UTF-8 manifest, an out-of-range `--port`, a missing agent folder, a malformed session file. `gravity validate` now refuses what the build would refuse: a `graph` that is not an async factory, `os.environ` or `asyncio.run` at import, relative imports, unquoted `python: 3.12`, schedules faster than every 5 minutes or with both day fields, out-of-range cron fields; it accepts a re-exported `graph`. `gravity agents` shows the review status and failure reasons; `gravity runs` never shortens an id; `gravity logs` never wraps. `gravity push` refuses a bundle over 30 MB or a manifest over 12 KB before uploading, and says why when a failed version is re-pushed. `gravity build` ships `tools_needed.yaml`, leaves dotfiles out, and refuses to build without `requirements.lock`. `gravity adapt` leaves the folder untouched when a model call fails. `gravity schedule` checks inputs before the first tick and runs on Asia/Kolkata like the hosted schedules. `gravity run` deletes `.gravity/payload.json` after the run, warns when no `.venv` is present, and keeps a plain agent's `result`. `gravity connect <app>` refuses on the shared test account instead of attaching your account to it. The builder guide and agent standard describe the real flow (init, validate, run or dev, build, push, review on /app).
780
844
  - **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,402 across 15 apps including the curated and built-in ones). 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.
@@ -858,6 +922,8 @@ uv run python -m gravity_schema.gen_catalog --root <platform repo>/code-first
858
922
  | Document | What it covers |
859
923
  |---|---|
860
924
  | [Builder guide](src/gravity_cli/BUILDER_GUIDE.md) | How to build a good agent, start to finish, with a complete example. Also `gravity guide` |
925
+ | [`Gravity.agent.md`](src/gravity_cli/docs/Gravity.agent.md) | The guide `gravity docs` writes for coding agents; its tool list is filled in from the catalog of the release that writes it |
926
+ | [`problem-to-agent.md`](src/gravity_cli/docs/problem-to-agent.md) | The method for turning a problem statement into an agent that holds up |
861
927
  | [Agent standard](AGENT_STANDARD.md) | The exact rules an agent must follow |
862
928
  | [Templates](TEMPLATES.md) | The four templates and when to pick each |
863
929
  | [`manifest.schema.json`](packages/gravity-schema/schema/manifest.schema.json) | The manifest as a machine-readable JSON Schema |
@@ -1,20 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: gravity-cli
3
- Version: 0.5.6
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.6
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">
@@ -92,7 +75,7 @@ gravity init my-agent
92
75
 
93
76
  ## Declare it once, enforced on every call
94
77
 
95
- There is no SDK to learn. Your agent is plain LangGraph that reads three environment variables. Everything Gravity enforces comes from `manifest.yaml`:
78
+ There is almost no SDK to learn. Your agent is plain LangGraph that reads three environment variables; the one Gravity package, `gravity`, is preinstalled on the platform and is only needed to load approval-gated tools. Everything Gravity enforces comes from `manifest.yaml`:
96
79
 
97
80
  | You declare in `manifest.yaml` | What Gravity does with it |
98
81
  |---|---|
@@ -108,11 +91,11 @@ The same rules apply on your laptop and when the agent runs hosted, because both
108
91
 
109
92
  | | |
110
93
  |---|---|
111
- | Commands | 18, grouped into build an agent, publish, your agents, account and help |
94
+ | Commands | 21, grouped into build an agent, publish, your agents, account, help and Gravity CLI |
112
95
  | Starter templates | 4: `minimal`, `structured`, `deepagent`, `chat` |
113
- | Tool catalog | 1,402 tools across 15 apps (Gmail, Slack, Linear, GitHub, Calendar, Drive, Sheets, LinkedIn, Jira, Confluence, Notion, WhatsApp, web search, and two built-ins), 28 of them curated |
114
- | Test suite | 466 tests (214 CLI, 252 schema), run on Ubuntu and Windows with Python 3.12 and 3.13 |
115
- | Runtime contract | 3 environment variables, no Gravity import in your code |
96
+ | Tool catalog | 1,405 tools across 12 apps (Gmail, Slack, Linear, GitHub, Calendar, Drive, Sheets, LinkedIn, Jira, Confluence, Notion, WhatsApp), plus web search, Firecrawl and two built-ins; 31 of them curated |
97
+ | Test suite | 543 tests (285 CLI, 258 schema), run on Ubuntu and Windows with Python 3.12 and 3.13 |
98
+ | Runtime contract | 3 environment variables; an optional `import gravity` only for approval-gated tools |
116
99
 
117
100
  ## Five things to know before you start
118
101
 
@@ -211,6 +194,9 @@ Run `gravity help` for this list in the terminal, and add `-h` to any command fo
211
194
  | | [`gravity connect`](#gravity-connect) | Connect an account your agents act on (Gmail, Slack...) |
212
195
  | **Help** | [`gravity guide`](#gravity-guide) | Read the builder guide: how to build a good agent |
213
196
  | | [`gravity help`](#gravity-help) | Show the list of commands |
197
+ | **Gravity CLI** | [`gravity docs`](#gravity-docs) | Write `Gravity.agent.md` for the coding agent in this folder |
198
+ | | [`gravity update`](#gravity-update) | Update to the latest release and see what is new |
199
+ | | [`gravity changelog`](#gravity-changelog) | What changed in each release |
214
200
 
215
201
  `gravity --api-url URL <command>` saves a different control-plane address to `~/.gravity/config.json`; it must start with `http://` or `https://`. The flag applies to the command it is given with; on later commands a `GRAVITY_API_URL` in `~/.gravity/.env` or the shell still wins over `config.json`, and the CLI says so when that is the case. `gravity --version` (or `-V`) prints the installed version. Wherever a command takes `[path]`, it is the agent's folder and defaults to the current one.
216
202
 
@@ -429,7 +415,7 @@ gravity logs <run_id>
429
415
 
430
416
  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.
431
417
 
432
- 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.
418
+ 31 tools are curated, such as `gmail.read` and `web.search`. The other ~1,370 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.
433
419
 
434
420
  ```
435
421
  gravity tools [search]
@@ -502,6 +488,46 @@ gravity guide --save
502
488
  gravity guide | less
503
489
  ```
504
490
 
491
+ ### `gravity docs`
492
+
493
+ Write or refresh the two files a coding agent needs to build for Gravity in this folder: `Gravity.agent.md` (how Gravity runs agents, the manifest, every tool this release knows, the output formats, the rules that fail a load) and `problem-to-agent.md` (the method for turning a problem statement into an agent that holds up).
494
+
495
+ ```
496
+ gravity docs [--force]
497
+ ```
498
+
499
+ | Option | Description |
500
+ |---|---|
501
+ | `--force` | Replace the files even where you edited them, or where a file Gravity did not write has the same name |
502
+
503
+ You rarely need this. Every command writes the two files into the folder it runs in if they are missing (never into your home folder or a drive root; `gravity init` writes them into the new agent), and rewrites them when a newer release changes them. The first line of a file Gravity wrote names the release and a hash of the text after it: if you edit the file the hash no longer matches and Gravity leaves it alone. Your own `agent.md`, `AGENTS.md` and `CLAUDE.md` are never touched. Point your coding agent at `Gravity.agent.md` from them if it does not look there by itself. Set `GRAVITY_AGENT_DOCS=0` to stop the automatic writes.
504
+
505
+ ### `gravity update`
506
+
507
+ Check PyPI for a newer release, install it, and show what is new.
508
+
509
+ ```
510
+ gravity update [--check]
511
+ ```
512
+
513
+ | Option | Description |
514
+ |---|---|
515
+ | `--check` | Only say whether a newer release exists |
516
+
517
+ You do not have to run it: once every six hours a command looks for a newer release, and when there is one it says `Update available 0.6.0 → 0.6.1`, updates, and runs your command again as the new version, which lists what changed. It upgrades the way you installed it (`uv tool` or `pipx`); a copy installed any other way, or from a source checkout, is told the command to run and left alone. Nothing here can stop a command: a failed update is one warning and the command runs on the version you have. Set `GRAVITY_AUTO_UPDATE=0` to turn the check and the install off; CI never updates.
518
+
519
+ ### `gravity changelog`
520
+
521
+ What changed in each release, written for builders.
522
+
523
+ ```
524
+ gravity changelog [--all]
525
+ ```
526
+
527
+ | Option | Description |
528
+ |---|---|
529
+ | `--all` | Every release, not only the one you are on |
530
+
505
531
  ### `gravity help`
506
532
 
507
533
  Show the list of commands. The same as `gravity -h`.
@@ -557,6 +583,9 @@ Set `GRAVITY_API_URL` and `GRAVITY_GATEWAY_URL` only to work against services yo
557
583
  | `GRAVITY_API_URL` | The control plane's address | `https://agents.dev.gravity.fast` |
558
584
  | `GRAVITY_API_TOKEN` | A control-plane dev token, used instead of `gravity login`. For development only. | none |
559
585
  | `GRAVITY_CONFIG_DIR` | Moves `~/.gravity` elsewhere. Useful for tests and CI. | `~/.gravity` |
586
+ | `GRAVITY_AUTO_UPDATE` | When `0`, commands neither check for a newer release nor install one. Always off when `CI` is set. | `1` |
587
+ | `GRAVITY_AGENT_DOCS` | When `0`, commands do not write `Gravity.agent.md` and `problem-to-agent.md` into the folder they run in. `gravity docs` still does. | `1` |
588
+ | `GRAVITY_UPDATE_URL` | Where the latest version is read from, a JSON document with `info.version`. For tests and mirrors. | PyPI |
560
589
  | `GRAVITY_UNATTENDED` | When `1`, calls that need approval are rejected without asking. `gravity schedule` sets it for you. | unset |
561
590
 
562
591
  ### What your agent receives
@@ -792,6 +821,7 @@ Real tools act on a connected account. Connect one with `gravity-gateway connect
792
821
 
793
822
  ## What's new
794
823
 
824
+ - **0.6.0** Gravity CLI keeps itself up to date and says what changed (`gravity update`, `gravity changelog`). `Gravity.agent.md` and `problem-to-agent.md` appear in the folder you work in, for your coding agent (`gravity docs`). `gravity` on its own shows your version, the agent in the folder, whether you are signed in, and any update. The notes shown after an update are `src/gravity_cli/CHANGELOG.md`.
795
825
  - **0.5.5** `manifest.yaml` takes an optional `display:` hint for how a run's result shows: `view` (document, report, table, chart, code or files), `field` (the result key to show) and `chart` (`type: line` or `bar`, `x`, and up to three `y` fields). It is a hint, never a rule: an unknown view or key is ignored, so it never fails a push. 0.5.4 and earlier refuse a manifest that has it.
796
826
  - **0.5.4** `gravity agents` shows each agent's review: a REVIEW column (not submitted, submitted, approved, rejected), and under "From the Gravity team" the reviewer's notes and what to do next. An agent the Gravity team turned off shows as `off` with the reason. UPDATED reads "3 min ago" instead of a timestamp, and status and review are coloured. Also, from a full audit of every command: `gravity --version`. Clear messages instead of tracebacks for: a proxy or sign-in page answering instead of Gravity, a refused `GRAVITY_API_TOKEN` or `GRAVITY_DEV_TOKEN`, an expired sign-in code, a non-UTF-8 manifest, an out-of-range `--port`, a missing agent folder, a malformed session file. `gravity validate` now refuses what the build would refuse: a `graph` that is not an async factory, `os.environ` or `asyncio.run` at import, relative imports, unquoted `python: 3.12`, schedules faster than every 5 minutes or with both day fields, out-of-range cron fields; it accepts a re-exported `graph`. `gravity agents` shows the review status and failure reasons; `gravity runs` never shortens an id; `gravity logs` never wraps. `gravity push` refuses a bundle over 30 MB or a manifest over 12 KB before uploading, and says why when a failed version is re-pushed. `gravity build` ships `tools_needed.yaml`, leaves dotfiles out, and refuses to build without `requirements.lock`. `gravity adapt` leaves the folder untouched when a model call fails. `gravity schedule` checks inputs before the first tick and runs on Asia/Kolkata like the hosted schedules. `gravity run` deletes `.gravity/payload.json` after the run, warns when no `.venv` is present, and keeps a plain agent's `result`. `gravity connect <app>` refuses on the shared test account instead of attaching your account to it. The builder guide and agent standard describe the real flow (init, validate, run or dev, build, push, review on /app).
797
827
  - **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,402 across 15 apps including the curated and built-in ones). 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.
@@ -875,6 +905,8 @@ uv run python -m gravity_schema.gen_catalog --root <platform repo>/code-first
875
905
  | Document | What it covers |
876
906
  |---|---|
877
907
  | [Builder guide](src/gravity_cli/BUILDER_GUIDE.md) | How to build a good agent, start to finish, with a complete example. Also `gravity guide` |
908
+ | [`Gravity.agent.md`](src/gravity_cli/docs/Gravity.agent.md) | The guide `gravity docs` writes for coding agents; its tool list is filled in from the catalog of the release that writes it |
909
+ | [`problem-to-agent.md`](src/gravity_cli/docs/problem-to-agent.md) | The method for turning a problem statement into an agent that holds up |
878
910
  | [Agent standard](AGENT_STANDARD.md) | The exact rules an agent must follow |
879
911
  | [Templates](TEMPLATES.md) | The four templates and when to pick each |
880
912
  | [`manifest.schema.json`](packages/gravity-schema/schema/manifest.schema.json) | The manifest as a machine-readable JSON Schema |
@@ -35,6 +35,8 @@ Three places must carry the same number:
35
35
  | `pyproject.toml` | `"gravity-schema==X.Y.Z"` under `dependencies` |
36
36
  | `packages/gravity-schema/pyproject.toml` | `version = "X.Y.Z"` |
37
37
 
38
+ Add the release to `src/gravity_cli/CHANGELOG.md`: a `## X.Y.Z - date` heading and one `- new|improved|fixed: Headline. Detail.` line per change a builder would care about. It is what `gravity update` shows after an update, and `tests/test_changelog.py` fails a version that has no entry. Set the date to the day you tag.
39
+
38
40
  Then refresh the lockfile:
39
41
 
40
42
  ```bash
@@ -116,6 +118,8 @@ gh run rerun <run-id> --failed
116
118
 
117
119
  ## Rules
118
120
 
121
+ - **A release reaches every installed CLI within about six hours.** Each command checks PyPI for a newer version and updates itself (`src/gravity_cli/updater.py`), so a bad release is out of your hands quickly: ship the fix as the next version, and yank on PyPI only if it is harmful (a yanked release is no longer offered). Verify step 6 before you walk away.
122
+
119
123
  - No copies of the source in other repositories. Pin the PyPI version in your requirements instead.
120
124
  - No PyPI API tokens. The trusted publisher configured for this repository is the only credential. If you hold a token for either package, say so and it gets revoked.
121
125
  - Every change is a pull request, however small. Review turnaround is same day.
@@ -169,5 +169,5 @@ src/agent.py exports `graph`
169
169
  .gitignore .venv and caches. requirements.lock is NOT ignored: it ships.
170
170
  ```
171
171
 
172
- Note: templates declare `python: "3.13"`; the AgentCore base image is 3.12.
172
+ Note: templates declare `python: "3.12"`, the version the platform runs.
173
173
  Set `runtime.python: "3.12"` in the manifest before `gravity push`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gravity-schema"
3
- version = "0.5.6"
3
+ version = "0.6.0"
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.5.6"
3
+ version = "0.6.0"
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.5.6", # released in lockstep: always the same version as this package
10
+ "gravity-schema==0.6.0", # 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",
@@ -7,7 +7,8 @@ and a sign-in (`gravity login`, with your Google account).
7
7
 
8
8
  You write an ordinary Python agent with LangGraph and declare in `manifest.yaml` what it may
9
9
  touch. When we run it, Gravity gives it tools (Gmail, Linear, Slack…) and models through a
10
- gateway, and only the tools you declared. There is no Gravity SDK to learn.
10
+ gateway, and only the tools you declared. The one Gravity package you may touch is `gravity`, which the
11
+ platform preinstalls and which you need only to call approval-gated tools (§6.1).
11
12
 
12
13
  **Contents:** 1 how it works · 2 the folder · 3 the files (copy these) · 4 rules your code must
13
14
  follow · 5 output: formatting, formats and the gold standard · 6 add-ons: approvals, platform
@@ -26,7 +27,7 @@ you (gravity CLI) Gravity (on our machines)
26
27
  ───────────────── ──────────────────────────
27
28
  gravity init, change the files (§3)
28
29
  gravity validate the same checks the build runs
29
- gravity run / gravity dev ──► our real gateway and models, on the shared test accounts
30
+ gravity run / gravity dev ──► our real gateway and models, on the accounts you connected
30
31
  gravity build, gravity push ──► validate, scan, build the image, start a runtime
31
32
  tell us it's ready we review it and list it on /app for users (§9, §10)
32
33
  ```
@@ -160,6 +161,11 @@ from langgraph.graph import END, StateGraph
160
161
  from pydantic import BaseModel, Field
161
162
  from summary import count_by_sender # absolute import from src/
162
163
 
164
+ try:
165
+ import gravity # preinstalled on the platform, not on PyPI; absent in gravity run and in tests
166
+ except ImportError:
167
+ gravity = None
168
+
163
169
 
164
170
  class Digest(BaseModel):
165
171
  headline: str = Field(description="the day's email in one sentence, from the given facts only")
@@ -189,16 +195,24 @@ def _payload(raw: Any) -> Any:
189
195
  return raw.get("data", raw) if isinstance(raw, dict) else raw
190
196
 
191
197
 
192
- async def graph():
193
- # Read the three environment variables here, inside the function, never at import time.
194
- token = os.environ["GRAVITY_RUN_TOKEN"]
195
- model = ChatOpenAI(model="openai/gpt-6-luna", base_url=os.environ["GRAVITY_LLM_URL"], api_key=token)
198
+ async def load_tools() -> dict:
199
+ """On the platform a tool under approvals.required_for must come from gravity.tools(): it pauses the
200
+ run until a person decides. Anywhere else the MCP client serves the same tools under the same names."""
201
+ if gravity:
202
+ return {t.name: t for t in await gravity.tools()}
196
203
  client = MultiServerMCPClient({"gravity": {
197
204
  "transport": "streamable_http",
198
205
  "url": os.environ["GRAVITY_GATEWAY_URL"],
199
- "headers": {"Authorization": f"Bearer {token}"},
206
+ "headers": {"Authorization": f"Bearer {os.environ['GRAVITY_RUN_TOKEN']}"},
200
207
  }})
201
- tools = {t.name: t for t in await client.get_tools()} # only the tools in manifest.yaml
208
+ return {t.name: t for t in await client.get_tools()}
209
+
210
+
211
+ async def graph():
212
+ # Read the three environment variables here, inside the function, never at import time.
213
+ token = os.environ["GRAVITY_RUN_TOKEN"]
214
+ model = ChatOpenAI(model="openai/gpt-6-luna", base_url=os.environ["GRAVITY_LLM_URL"], api_key=token)
215
+ tools = await load_tools() # only the tools in manifest.yaml
202
216
 
203
217
  async def read(state: State):
204
218
  # Tool names use _ instead of . : gmail.read -> tools["gmail_read"]
@@ -285,7 +299,7 @@ If any of these is broken, the agent fails when we load or run it.
285
299
  | Every tool you call is in the manifest | `gmail.send` listed under `permissions.tools` | calling a tool you didn't list |
286
300
  | Accounts are reached only through tools, models only through `GRAVITY_LLM_URL` | `tools["gmail_read"]`, `ChatOpenAI(base_url=...)` | calling Gmail's API with your own credentials, your own API keys, `OPENAI_API_KEY` |
287
301
  | The final state sets `result` | `return {"result": {...}}` | a last node that returns nothing |
288
- | No Gravity imports | only your code and your dependencies | there's nothing to import anyway |
302
+ | Gravity imports are optional | `import gravity` behind `try/except ImportError`, only to load approval-gated tools (§6.1) | listing `gravity` in `requirements.txt` (it is not on PyPI), or importing it unguarded |
289
303
  | Only declare `messages` in the state if the agent is a conversation | see §6.3 | adding it "just in case" (the agent is then run as a chat) |
290
304
 
291
305
  ## 5. Output: formatting, formats and the gold standard
@@ -438,6 +452,13 @@ the agent, that call is held until a human answers. If it isn't approved, the to
438
452
  containing `APPROVAL_REJECTED` or `APPROVAL_TIMEOUT` and does nothing, so **always have a
439
453
  fallback** (the example keeps a draft). A tool you gate must also be in `permissions.tools`.
440
454
 
455
+ On the platform the run **pauses** for the decision (up to 7 days) instead of holding a connection, and
456
+ that only works for tools loaded with `await gravity.tools()`: a gated call made through a plain MCP client
457
+ fails there with `APPROVAL_REQUIRED`. The example's `load_tools()` does this, and falls back to the MCP client
458
+ when `gravity` is not installed (`gravity run`, `gravity dev`, tests). After the decision the node that made
459
+ the call runs again from its start, so keep the gated call last in its node. If you wrap the call in a broad
460
+ `except`, re-raise `langgraph.errors.GraphBubbleUp` first (`except GraphBubbleUp: raise`), or the pause is swallowed.
461
+
441
462
  ### 6.2 Platform adapters: let the user choose where results go
442
463
 
443
464
  For agents whose output could go to different platforms (Gmail or Slack, WordPress or Webflow),
@@ -615,7 +636,7 @@ return {"result": result, "memory": {"seen": sorted(seen | delivered)[-1000:]}}
615
636
 
616
637
  Your agent can be for any field: sales, marketing, finance, support, engineering, operations,
617
638
  anything. **The table below is today's starting set, not the limit.** If your agent needs a tool
618
- that isn't here, run `gravity tools <app>` first: the gateway serves about 1,100 more tools for the
639
+ that isn't here, run `gravity tools <app>` first: the gateway serves more than 1,300 more tools for the
619
640
  same apps. If it still isn't there, ask for it as in §7.1.
620
641
 
621
642
  | App | Tools |
@@ -737,13 +758,13 @@ see, locally or hosted:
737
758
  | Message | It means | Fix |
738
759
  |---|---|---|
739
760
  | `TOOL_NOT_DECLARED` | Your code called a tool that isn't in the manifest | Add it to `permissions.tools` (or the adapter's list), or stop calling it |
740
- | `NEEDS_RECONNECT` | Our shared test account for that app isn't connected (or its sign-in lapsed) | Tell us and we reconnect it. `gravity connect <app>` refuses on the shared account on purpose: local runs share one test account until per-builder accounts ship, so anything you connected would be used by every builder's runs |
761
+ | `NEEDS_RECONNECT` | The account for that app isn't connected, or its sign-in lapsed | Locally: `gravity connect <app>` connects your own account (a `gravity login` run uses the accounts you connected). Only if you run with `GRAVITY_DEV_TOKEN` are you on the shared test account, where `gravity connect` refuses on purpose and you tell us to reconnect it. Hosted, the person running the agent connects it |
741
762
  | `TOOL_NOT_FOUND` | A tool in `manifest.yaml` isn't in the catalog | Check the spelling with `gravity tools`. For a new one, ask for it (§7.1) |
742
763
  | `APPROVAL_REQUIRED` | An imported write tool has no approval | List it under `approvals.required_for`, with a fallback (§6.1) |
743
764
  | `UNDECLARED_TOOL_CALL` | Your code names a write tool the manifest doesn't grant | Declare it (and gate it), or remove the call |
744
765
  | `TOOL_UNAVAILABLE` | That tool isn't available yet | Check the spelling against §7, or wait until we've added it (§7.1) |
745
766
  | `APPROVAL_REJECTED` / `APPROVAL_TIMEOUT` | A gated call wasn't approved | Expected; your fallback should have run |
746
- | "the final state has no result" | Your last node didn't set `result` | Return `{"result": ...}` |
767
+ | "finished without a result" (`gravity run`), or a `null` result on the platform | Your last node didn't set `result` | Return `{"result": ...}` |
747
768
  | "could not load the agent" | An import error, a relative import, or building the graph at import | Follow §4 |
748
769
  | Lockfile doesn't compile | A dependency has no Linux ARM64 wheel | Choose another version or package |
749
770
 
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ Shown by `gravity update` and `gravity changelog`, and after an update. Each release is `## X.Y.Z - date`,
4
+ then one bullet per change: `- new|improved|fixed: A one-line headline. The detail behind it.`
5
+ Write it for a builder, not for a maintainer. `tests/test_changelog.py` fails a release that has no entry.
6
+
7
+ ## 0.6.0 - 2026-10-22
8
+
9
+ - new: Gravity CLI keeps itself up to date. Once every six hours a command checks PyPI; when there is a newer release it says so, updates, and runs your command again as the new version. GRAVITY_AUTO_UPDATE=0 turns it off.
10
+ - new: gravity update and gravity changelog. Update on demand (--check only looks), and read what changed in any release.
11
+ - new: Gravity.agent.md and problem-to-agent.md appear in the folder you run gravity in. They tell your coding agent how Gravity runs agents, the manifest, the tools and the build method, and are rewritten on every update. A file you edited is left alone, and your own agent.md is never touched.
12
+ - new: gravity docs writes or refreshes those two files on demand; --force replaces ones you edited.
13
+ - improved: gravity on its own is now a short home screen. It shows your version, the agent in this folder, whether you are signed in and any update, the next step for where you are, and every command on one line per group. gravity -h still lists them all with descriptions.
14
+
15
+ ## 0.5.6 - 2026-10-15
16
+
17
+ - new: Firecrawl tools. firecrawl.search and firecrawl.scrape read pages that refuse plain HTTP clients, and web.render is the plain fallback for pages web.fetch returns empty. They run on your own connected Firecrawl account.
18
+
19
+ ## 0.5.5 - 2026-10-13
20
+
21
+ - new: manifest.yaml takes an optional display: hint. It tells the page how a run's result shows (document, report, table, chart, code or files). It is a hint, never a rule, so an unknown value never fails a push.
22
+
23
+ ## 0.5.4 - 2026-10-10
24
+
25
+ - improved: gravity agents shows the review. A REVIEW column (not submitted, submitted, approved, rejected), each message from the Gravity team with its decision and what to do next, and why an agent was turned off.
26
+
27
+ ## 0.5.3 - 2026-10-10
28
+
29
+ - new: WhatsApp in the tool catalog. 51 tools with Composio-managed sign-in.
30
+ - new: gravity dev shows images in outputs, from image data URLs.
31
+ - improved: the builder guide explains NEEDS_RECONNECT on the shared test account: ask Gravity, do not connect your own.
32
+
33
+ ## 0.5.2 - 2026-10-10
34
+
35
+ - new: Jira, Confluence and Notion in the tool catalog. 209 more tools, all with Composio-managed sign-in; imported writes need approvals.required_for.
36
+ - new: gravity run keeps get_store() locally in .gravity/store.json, so a monitor's history can be tried before it ships.
37
+ - improved: gravity starts faster.
@@ -18,6 +18,14 @@ CHECK_URL = "Check GRAVITY_API_URL in ~/.gravity/.env, or api_url in ~/.gravity/
18
18
  IN_THE_WAY = "Check GRAVITY_API_URL, or whether a proxy or login page is in the way."
19
19
 
20
20
 
21
+ def _where_the_address_came_from() -> str:
22
+ """Which setting chose the address and what it is hiding: a stale localhost line in .env beats the saved one."""
23
+ shadowed = config.api_url_shadowed()
24
+ if not shadowed:
25
+ return f"The address comes from {config.api_url_source()}. {CHECK_URL}"
26
+ return f"The address comes from {config.api_url_source()}, which overrides {shadowed} saved in config.json. Remove that setting to use the saved one, or point at another with gravity --api-url."
27
+
28
+
21
29
  def _token() -> str:
22
30
  # GRAVITY_API_TOKEN (the control plane's dev token) wins over a stored session.
23
31
  token = os.environ.get("GRAVITY_API_TOKEN", "").strip()
@@ -40,7 +48,7 @@ def _request(
40
48
  with httpx.Client(base_url=config.api_url(), timeout=timeout, headers=sent) as client:
41
49
  resp = client.request(method, path, **kw)
42
50
  except httpx.TransportError as e:
43
- raise Problem(f"Can't reach Gravity at {config.api_url()}", str(e), CHECK_URL) from e
51
+ raise Problem(f"Can't reach Gravity at {config.api_url()}", str(e), _where_the_address_came_from()) from e
44
52
  if resp.status_code == 401 and auth:
45
53
  if os.environ.get("GRAVITY_API_TOKEN", "").strip():
46
54
  raise Problem("Gravity refused GRAVITY_API_TOKEN", fix="Fix it in ~/.gravity/.env, or remove it and run gravity login.")
@@ -0,0 +1,55 @@
1
+ """CHANGELOG.md, shipped inside the package: what each release changed, in the words a builder reads.
2
+
3
+ A release is `## X.Y.Z - date`, followed by bullets `- new|improved|fixed: A sentence. More detail.`
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+
12
+ CHANGELOG = Path(__file__).parent / "CHANGELOG.md"
13
+ TAGS = ("new", "improved", "fixed")
14
+ _HEADING = re.compile(r"^##\s+(\d+(?:\.\d+)*)\s*(?:[-–—]\s*(.+))?$")
15
+ _BULLET = re.compile(rf"^-\s+({'|'.join(TAGS)}):\s+(.+)$", re.IGNORECASE)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Change:
20
+ tag: str
21
+ text: str
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class Release:
26
+ version: str
27
+ date: str
28
+ changes: tuple[Change, ...]
29
+
30
+
31
+ def version_key(version: str) -> tuple[int, ...] | None:
32
+ return tuple(int(part) for part in version.split(".")) if re.fullmatch(r"\d+(\.\d+)*", version or "") else None
33
+
34
+
35
+ def parse(text: str) -> list[Release]:
36
+ """Newest first, in the order the file lists them."""
37
+ releases: list[tuple[str, str, list[Change]]] = []
38
+ for line in text.splitlines():
39
+ if heading := _HEADING.match(line.strip()):
40
+ releases.append((heading[1], (heading[2] or "").strip(), []))
41
+ elif releases and (bullet := _BULLET.match(line.strip())):
42
+ releases[-1][2].append(Change(bullet[1].lower(), bullet[2].strip()))
43
+ return [Release(version, date, tuple(changes)) for version, date, changes in releases]
44
+
45
+
46
+ def releases() -> list[Release]:
47
+ return parse(CHANGELOG.read_text(encoding="utf-8"))
48
+
49
+
50
+ def since(previous: str, current: str) -> list[Release]:
51
+ """Releases after `previous`, up to and including `current`, newest first."""
52
+ low, high = version_key(previous), version_key(current)
53
+ if low is None or high is None:
54
+ return []
55
+ return [r for r in releases() if (key := version_key(r.version)) and low < key <= high]