agentgraph-server 0.5.4__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. {agentgraph_server-0.5.4/.agents/skills/AgentGraph → agentgraph_server-0.6.1/.agents/skills/agentgraph}/SKILL.md +1 -1
  2. {agentgraph_server-0.5.4/.agents/skills/AgentGraph → agentgraph_server-0.6.1/.agents/skills/agentgraph}/references/data-model.md +3 -1
  3. {agentgraph_server-0.5.4/.agents/skills/AgentGraph → agentgraph_server-0.6.1/.agents/skills/agentgraph}/references/operations.md +25 -4
  4. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/.agents/skills/slack-auth/SKILL.md +4 -2
  5. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/PKG-INFO +16 -15
  6. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/README.md +3 -3
  7. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/backends/sqlite/backend.py +75 -9
  8. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/backends/sqlite/schema.sql +2 -0
  9. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/cli.py +118 -40
  10. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/cli_query.py +19 -4
  11. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/connectors/base.py +33 -1
  12. agentgraph_server-0.6.1/agentgraph/connectors/command_effects.py +69 -0
  13. agentgraph_server-0.6.1/agentgraph/connectors/feed.py +153 -0
  14. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/connectors/registry.py +8 -0
  15. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/core/storage.py +13 -0
  16. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/demo.py +3 -1
  17. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/bookmark.py +23 -2
  18. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/delete.py +14 -0
  19. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/query.py +5 -4
  20. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/mcp/server.py +40 -8
  21. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/meta_api.py +1 -1
  22. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/observation.py +58 -2
  23. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/static/viewer.html +23 -9
  24. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/skills.py +6 -4
  25. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph_server.egg-info/PKG-INFO +16 -15
  26. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph_server.egg-info/SOURCES.txt +8 -4
  27. agentgraph_server-0.6.1/agentgraph_server.egg-info/requires.txt +38 -0
  28. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/pyproject.toml +15 -14
  29. agentgraph_server-0.6.1/tests/test_auth_status_methods.py +47 -0
  30. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_browse.py +7 -1
  31. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_cli.py +346 -34
  32. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_command_effects.py +58 -2
  33. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_connectors.py +34 -1
  34. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_docs_build.py +10 -5
  35. agentgraph_server-0.6.1/tests/test_entity_types.py +100 -0
  36. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_expiration.py +28 -0
  37. agentgraph_server-0.6.1/tests/test_feed_connectors.py +248 -0
  38. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_observation.py +54 -0
  39. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_query.py +152 -4
  40. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_registry.py +14 -0
  41. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_release_metadata.py +1 -1
  42. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_rss_connector.py +466 -25
  43. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_schema.py +97 -0
  44. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_slack_auth_artifacts.py +2 -1
  45. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_slack_oauth.py +32 -6
  46. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_upsert.py +60 -0
  47. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_viewer_layout_contract.py +38 -0
  48. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_web_connector.py +210 -7
  49. agentgraph_server-0.5.4/agentgraph/connectors/command_effects.py +0 -18
  50. agentgraph_server-0.5.4/agentgraph_server.egg-info/requires.txt +0 -37
  51. {agentgraph_server-0.5.4/.agents/skills/AgentGraph → agentgraph_server-0.6.1/.agents/skills/agentgraph}/references/commands.md +0 -0
  52. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/LICENSE +0 -0
  53. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/__init__.py +0 -0
  54. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/auth/__init__.py +0 -0
  55. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/auth/credentials.py +0 -0
  56. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/backends/__init__.py +0 -0
  57. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/backends/sqlite/__init__.py +0 -0
  58. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/backends/sqlite/vector.py +0 -0
  59. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/cli_sync.py +0 -0
  60. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/config.py +0 -0
  61. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/connectors/__init__.py +0 -0
  62. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/connectors/status.py +0 -0
  63. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/core/__init__.py +0 -0
  64. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/core/context.py +0 -0
  65. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/core/runtime.py +0 -0
  66. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
  67. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
  68. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/__init__.py +0 -0
  69. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/download.py +0 -0
  70. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/embeddings.py +0 -0
  71. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/expiration.py +0 -0
  72. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/fetch.py +0 -0
  73. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/link.py +0 -0
  74. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/operations.py +0 -0
  75. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/person.py +0 -0
  76. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/graph/upsert.py +0 -0
  77. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/logging.py +0 -0
  78. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/mcp/__init__.py +0 -0
  79. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/perf.py +0 -0
  80. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/__init__.py +0 -0
  81. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/app.py +0 -0
  82. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/cli_api.py +0 -0
  83. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/router.py +0 -0
  84. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/sync.py +0 -0
  85. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph/server/sync_api.py +0 -0
  86. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph_server.egg-info/dependency_links.txt +0 -0
  87. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph_server.egg-info/entry_points.txt +0 -0
  88. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/agentgraph_server.egg-info/top_level.txt +0 -0
  89. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/setup.cfg +0 -0
  90. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_auth.py +0 -0
  91. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_benchmarks.py +0 -0
  92. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_config.py +0 -0
  93. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_discord_attachments.py +0 -0
  94. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_embeddings.py +0 -0
  95. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_fetch.py +0 -0
  96. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_graph_operations.py +0 -0
  97. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_launch_demo.py +0 -0
  98. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_logging.py +0 -0
  99. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_router.py +0 -0
  100. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_server_imports.py +0 -0
  101. {agentgraph_server-0.5.4 → agentgraph_server-0.6.1}/tests/test_sync.py +0 -0
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: AgentGraph
2
+ name: agentgraph
3
3
  description: Use AgentGraph through its CLI or MCP tools to search and traverse selected local context, retrieve full source-backed evidence, fetch missing or stale resources, and troubleshoot connector availability.
4
4
  ---
5
5
 
@@ -11,8 +11,10 @@
11
11
  | `Message` | Chat messages. Chat images and uploads are stored in `metadata.attachments`. |
12
12
  | `Person` | Source identities and confirmed cross-source identity merges. |
13
13
  | `Spreadsheet` | Google Sheets and other spreadsheet resources. |
14
+ | `Task` | Tracked work items such as Jira issues. Issue key, status, and assignee live in `metadata`. |
15
+ | `Video` | Recorded videos such as Looms. The transcript is indexed as content and `metadata.web_url` links to the video. |
14
16
 
15
- The valid core model does not currently include `Task` or `Project`.
17
+ The valid core model does not currently include `Project`.
16
18
 
17
19
  ## Relationships
18
20
 
@@ -3,16 +3,16 @@
3
3
  ## Availability and authentication
4
4
 
5
5
  ```bash
6
- agentgraph connectors [--verify] [--json]
6
+ agentgraph list-connectors [--verify] [--json]
7
7
  agentgraph auth [--verify] [--json] status
8
8
  agentgraph auth <provider> [--add] [--account <account-id>]
9
9
  agentgraph auth remove <provider> [--account <account-id>] [--json]
10
10
  ```
11
11
 
12
- Use `agentgraph connectors --json` to discover installed connector source names,
12
+ Use `agentgraph list-connectors --json` to discover installed connector source names,
13
13
  valid platform values, URL ownership, polling delegation, and sync state. Do not rely
14
14
  on a hardcoded platform list. Use `--verify` only when a live provider check is
15
- needed.
15
+ needed. Without `--json`, the CLI renders the same status as an operator-facing table.
16
16
 
17
17
  MCP equivalents are `list_connectors_tool`, `list_auth_providers_tool`,
18
18
  `authenticate_provider_tool`, and `remove_auth_provider_tool`.
@@ -45,6 +45,22 @@ agentgraph connector gmail ingest [--account <account-id>] [--json]
45
45
  The MCP form is `run_connector_command_tool("gmail", ["ingest", ...])`. There is no
46
46
  top-level `agentgraph ingest` command or `ingest_connector_tool`.
47
47
 
48
+ For an oversized HTML page, request a one-off web fetch that removes style, script,
49
+ noscript, and comment blocks before applying the response-size limit:
50
+
51
+ ```bash
52
+ agentgraph connector web fetch <url> --compact [--json]
53
+ ```
54
+
55
+ The MCP form is `run_connector_command_tool("web", ["fetch", "<url>", "--compact"])`.
56
+ Compaction applies only to that command; ordinary fetches, bookmarks, observations, and
57
+ RSS article hydration retain their default behavior.
58
+
59
+ Configure browser observation rules with `agentgraph connector web observe <url-or-prefix>`
60
+ and remove them with `agentgraph connector web observe <url-or-prefix> --remove`. The MCP
61
+ forms are `run_connector_command_tool("web", ["observe", "<url-or-prefix>"])` and
62
+ `run_connector_command_tool("web", ["observe", "<url-or-prefix>", "--remove"])`.
63
+
48
64
  ## Files and retained context
49
65
 
50
66
  ```bash
@@ -71,7 +87,12 @@ commands, request permission to contact the configured localhost server and retr
71
87
  ```bash
72
88
  agentgraph mcp-config
73
89
  agentgraph mcp-serve
74
- agentgraph install-skill [AgentGraph] [--target user|project] [--no-claude] [--force] [--json]
90
+ agentgraph install-skill [agentgraph] [--target user|project] [--no-claude] [--force] [--json]
75
91
  ```
76
92
 
93
+ `agentgraph mcp-config` prints local stdio setup for ChatGPT Desktop Work Mode and
94
+ Claude Desktop. In ChatGPT Desktop, enter the printed executable in **Command to
95
+ launch** and `mcp-serve` in **Arguments**; for Claude Desktop, add the printed JSON
96
+ to its MCP configuration file.
97
+
77
98
  The skill installer is available only through the `agentgraph install-skill` CLI command.
@@ -28,8 +28,10 @@ permission in the target workspace.
28
28
  Admins create AgentGraph at `https://api.slack.com/apps` with **Create New App →
29
29
  From an app manifest**, select the target workspace, paste the manifest printed by
30
30
  AgentGraph into Slack's **JSON** tab, approve it, and enter its Client ID. The
31
- manifest includes the only
32
- supported callback, `http://localhost:8766/slack/oauth/callback`.
31
+ manifest includes the callback, which defaults to `http://localhost:8766/slack/oauth/callback`; set
32
+ `AGENTGRAPH_SLACK_OAUTH_CALLBACK_PORT` before setup to use another local port. The
33
+ manifest printed by AgentGraph uses that port, which must match the Slack app's
34
+ registered redirect URL.
33
35
 
34
36
  Non-admins choose either **Enter a Client ID provided by a Slack admin** or **Set up
35
37
  the AgentGraph Slack App**. The setup path checks whether the target workspace
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentgraph-server
3
- Version: 0.5.4
3
+ Version: 0.6.1
4
4
  Summary: A local-first CLI and MCP server that turns selected digital sources into a searchable graph for the AI agent you already use.
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
7
7
  License-File: LICENSE
8
8
  Requires-Dist: aiosqlite>=0.20.0
9
- Requires-Dist: agentgraph-connector-web<0.6,>=0.5.0
9
+ Requires-Dist: agentgraph-connector-web<0.7,>=0.5.4
10
10
  Requires-Dist: apscheduler>=3.11.2
11
11
  Requires-Dist: click<8.4
12
12
  Requires-Dist: fastapi>=0.135.2
@@ -16,26 +16,27 @@ Requires-Dist: mcp>=1.26.0
16
16
  Requires-Dist: numpy>=2.4.6
17
17
  Requires-Dist: pydantic>=2.12.5
18
18
  Requires-Dist: pydantic-settings>=2.13.1
19
+ Requires-Dist: questionary>=2.1.1
19
20
  Requires-Dist: rich>=14.3.3
20
21
  Requires-Dist: sqlite-vec>=0.1.9
21
22
  Requires-Dist: typer>=0.24.1
22
23
  Requires-Dist: uvicorn>=0.42.0
23
24
  Provides-Extra: google
24
- Requires-Dist: agentgraph-connector-google<0.6,>=0.5.0; extra == "google"
25
+ Requires-Dist: agentgraph-connector-google<0.7,>=0.5.4; extra == "google"
25
26
  Provides-Extra: slack
26
- Requires-Dist: agentgraph-connector-slack<0.6,>=0.5.0; extra == "slack"
27
+ Requires-Dist: agentgraph-connector-slack<0.7,>=0.5.4; extra == "slack"
27
28
  Provides-Extra: discord
28
- Requires-Dist: agentgraph-connector-discord<0.6,>=0.5.0; extra == "discord"
29
+ Requires-Dist: agentgraph-connector-discord<0.7,>=0.5.4; extra == "discord"
29
30
  Provides-Extra: rss
30
- Requires-Dist: agentgraph-connector-rss<0.6,>=0.5.0; extra == "rss"
31
+ Requires-Dist: agentgraph-connector-rss<0.7,>=0.5.4; extra == "rss"
31
32
  Provides-Extra: web
32
- Requires-Dist: agentgraph-connector-web<0.6,>=0.5.0; extra == "web"
33
+ Requires-Dist: agentgraph-connector-web<0.7,>=0.5.4; extra == "web"
33
34
  Provides-Extra: all
34
- Requires-Dist: agentgraph-connector-web<0.6,>=0.5.0; extra == "all"
35
- Requires-Dist: agentgraph-connector-google<0.6,>=0.5.0; extra == "all"
36
- Requires-Dist: agentgraph-connector-slack<0.6,>=0.5.0; extra == "all"
37
- Requires-Dist: agentgraph-connector-discord<0.6,>=0.5.0; extra == "all"
38
- Requires-Dist: agentgraph-connector-rss<0.6,>=0.5.0; extra == "all"
35
+ Requires-Dist: agentgraph-connector-web<0.7,>=0.5.4; extra == "all"
36
+ Requires-Dist: agentgraph-connector-google<0.7,>=0.5.4; extra == "all"
37
+ Requires-Dist: agentgraph-connector-slack<0.7,>=0.5.4; extra == "all"
38
+ Requires-Dist: agentgraph-connector-discord<0.7,>=0.5.4; extra == "all"
39
+ Requires-Dist: agentgraph-connector-rss<0.7,>=0.5.4; extra == "all"
39
40
  Dynamic: license-file
40
41
 
41
42
  # AgentGraph
@@ -46,7 +47,7 @@ AgentGraph is a local-first, self-hosted CLI and MCP server that turns the sourc
46
47
 
47
48
  > **AgentGraph stores context; your agent reasons over it.** It is not an agent, chatbot, hosted graph service, or replacement for the tools you already use.
48
49
 
49
- [See the demo](docs-src/demo.md) · [Install](docs-src/install.md) · [Documentation](https://simonexmachina.github.io/agent-graph/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
50
+ [See the demo](docs-src/demo.md) · [Install](docs-src/install.md) · [Documentation](https://agentgraph.simonwa.de/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
50
51
 
51
52
  <picture>
52
53
  <source media="(prefers-color-scheme: dark)" srcset="docs-src/assets/diagrams/architecture-overview-dark.svg">
@@ -110,7 +111,7 @@ agentgraph serve
110
111
  Install the [AgentGraph Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi), browse a supported resource, then verify that context landed:
111
112
 
112
113
  ```bash
113
- agentgraph connectors
114
+ agentgraph list-connectors
114
115
  agentgraph search "project kickoff notes"
115
116
  agentgraph traverse <entity-id> --depth 2
116
117
  ```
@@ -121,7 +122,7 @@ Connect the agent you already use:
121
122
  agentgraph mcp-config
122
123
  ```
123
124
 
124
- Use the printed stdio configuration with Claude Desktop, Claude Code, Codex, or another compatible MCP client. ChatGPT developer mode uses the streamable HTTP `/mcp` endpoint through HTTPS. See [Install](docs-src/install.md) for the complete setup path.
125
+ Use the printed instructions to connect ChatGPT Desktop Work Mode or Claude Desktop to the local stdio server. See [Install](docs-src/install.md) for the complete setup path.
125
126
 
126
127
  ## What AgentGraph is and is not
127
128
 
@@ -6,7 +6,7 @@ AgentGraph is a local-first, self-hosted CLI and MCP server that turns the sourc
6
6
 
7
7
  > **AgentGraph stores context; your agent reasons over it.** It is not an agent, chatbot, hosted graph service, or replacement for the tools you already use.
8
8
 
9
- [See the demo](docs-src/demo.md) · [Install](docs-src/install.md) · [Documentation](https://simonexmachina.github.io/agent-graph/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
9
+ [See the demo](docs-src/demo.md) · [Install](docs-src/install.md) · [Documentation](https://agentgraph.simonwa.de/) · [Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi)
10
10
 
11
11
  <picture>
12
12
  <source media="(prefers-color-scheme: dark)" srcset="docs-src/assets/diagrams/architecture-overview-dark.svg">
@@ -70,7 +70,7 @@ agentgraph serve
70
70
  Install the [AgentGraph Chrome extension](https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi), browse a supported resource, then verify that context landed:
71
71
 
72
72
  ```bash
73
- agentgraph connectors
73
+ agentgraph list-connectors
74
74
  agentgraph search "project kickoff notes"
75
75
  agentgraph traverse <entity-id> --depth 2
76
76
  ```
@@ -81,7 +81,7 @@ Connect the agent you already use:
81
81
  agentgraph mcp-config
82
82
  ```
83
83
 
84
- Use the printed stdio configuration with Claude Desktop, Claude Code, Codex, or another compatible MCP client. ChatGPT developer mode uses the streamable HTTP `/mcp` endpoint through HTTPS. See [Install](docs-src/install.md) for the complete setup path.
84
+ Use the printed instructions to connect ChatGPT Desktop Work Mode or Claude Desktop to the local stdio server. See [Install](docs-src/install.md) for the complete setup path.
85
85
 
86
86
  ## What AgentGraph is and is not
87
87
 
@@ -64,7 +64,7 @@ _LIST_PAGE_ORDER_BY = {
64
64
  _COLUMN_FILTERS = {"platform", "platform_entity_id", "entity_type"}
65
65
  _FTS_DELETE_CHUNK_SIZE = 500
66
66
  _BUSY_TIMEOUT_MS = 5_000
67
- _SCHEMA_VERSION = 2
67
+ _SCHEMA_VERSION = 4
68
68
 
69
69
 
70
70
  def _now() -> str:
@@ -208,7 +208,9 @@ class SQLiteBackend(StorageBackend):
208
208
  async def _run_migrations(self) -> None:
209
209
  conn = self._conn_or_raise()
210
210
  cursor = await conn.execute("PRAGMA table_info(entities)")
211
- entity_columns = {str(row["name"]): bool(row["notnull"]) for row in await cursor.fetchall()}
211
+ table_info = await cursor.fetchall()
212
+ entity_columns = {str(row["name"]): bool(row["notnull"]) for row in table_info}
213
+ entity_defaults = {str(row["name"]): row["dflt_value"] for row in table_info}
212
214
  columns = set(entity_columns)
213
215
  if "cumulative_dwell_ms" in columns:
214
216
  await conn.execute(
@@ -234,6 +236,8 @@ class SQLiteBackend(StorageBackend):
234
236
  "last_accessed" in columns
235
237
  or not entity_columns.get("created_at", False)
236
238
  or not entity_columns.get("updated_at", False)
239
+ or entity_defaults.get("created_at") is None
240
+ or entity_defaults.get("updated_at") is None
237
241
  or entity_columns.get("observed_at", False)
238
242
  or "source_created_at" not in columns
239
243
  or "source_updated_at" not in columns
@@ -287,6 +291,10 @@ class SQLiteBackend(StorageBackend):
287
291
  await conn.execute(
288
292
  "CREATE INDEX IF NOT EXISTS idx_entities_platform_type_observed_at ON entities(platform, entity_type, observed_at DESC)"
289
293
  )
294
+ await conn.execute(
295
+ "CREATE INDEX IF NOT EXISTS idx_entities_platform_type_feed_updated "
296
+ "ON entities(platform, entity_type, json_extract(metadata, '$.feed_url'), updated_at DESC)"
297
+ )
290
298
 
291
299
  async def _retention_policy_needs_migration(self) -> bool:
292
300
  """Return whether the entity policy constraint lacks persistent support."""
@@ -344,7 +352,8 @@ class SQLiteBackend(StorageBackend):
344
352
  observed = "NULL"
345
353
  if "observed_at" in columns and "cumulative_observation_duration_ms" in columns:
346
354
  observed = (
347
- "CASE WHEN entity_type IN ('Channel', 'Document', 'Email', 'Folder', 'Spreadsheet') "
355
+ "CASE WHEN entity_type IN "
356
+ "('Channel', 'Document', 'Email', 'Folder', 'Spreadsheet', 'Task', 'Video') "
348
357
  "AND cumulative_observation_duration_ms > 0 THEN observed_at ELSE NULL END"
349
358
  )
350
359
  observed = (
@@ -370,8 +379,8 @@ class SQLiteBackend(StorageBackend):
370
379
  content TEXT,
371
380
  content_embedding BLOB,
372
381
  metadata TEXT NOT NULL DEFAULT '{}',
373
- created_at TEXT NOT NULL,
374
- updated_at TEXT NOT NULL,
382
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
383
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
375
384
  source_created_at TEXT,
376
385
  source_updated_at TEXT,
377
386
  synced_at TEXT,
@@ -660,8 +669,8 @@ class SQLiteBackend(StorageBackend):
660
669
  """
661
670
  INSERT INTO entities
662
671
  (id, entity_type, platform, platform_entity_id, title, metadata,
663
- retention_policy, retention_parent_id)
664
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
672
+ created_at, updated_at, retention_policy, retention_parent_id)
673
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
665
674
  ON CONFLICT (platform, platform_entity_id) DO UPDATE SET
666
675
  entity_type = entities.entity_type
667
676
  RETURNING id
@@ -673,6 +682,8 @@ class SQLiteBackend(StorageBackend):
673
682
  e.platform_entity_id,
674
683
  e.title,
675
684
  json.dumps(dict(e.metadata)),
685
+ now,
686
+ now,
676
687
  e.retention_policy,
677
688
  parent_id,
678
689
  ],
@@ -756,8 +767,8 @@ class SQLiteBackend(StorageBackend):
756
767
  INSERT INTO entities
757
768
  (id, entity_type, platform, platform_entity_id, title, content,
758
769
  content_embedding, metadata, source_created_at, source_updated_at,
759
- synced_at, retention_policy, retention_parent_id)
760
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
770
+ synced_at, created_at, updated_at, retention_policy, retention_parent_id)
771
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
761
772
  ON CONFLICT (platform, platform_entity_id) DO UPDATE SET
762
773
  entity_type = CASE WHEN entities.entity_type = 'Document' THEN EXCLUDED.entity_type ELSE entities.entity_type END,
763
774
  title = COALESCE(EXCLUDED.title, entities.title),
@@ -784,6 +795,8 @@ class SQLiteBackend(StorageBackend):
784
795
  source_created,
785
796
  source_updated,
786
797
  now,
798
+ now,
799
+ local_updated,
787
800
  e.retention_policy,
788
801
  parent_id,
789
802
  local_updated,
@@ -1446,6 +1459,59 @@ class SQLiteBackend(StorageBackend):
1446
1459
  )
1447
1460
  return [_row_to_entity(row) for row in rows]
1448
1461
 
1462
+ async def list_recent_metadata_by_group(
1463
+ self,
1464
+ entity_type: str,
1465
+ filters: dict[str, str],
1466
+ group_metadata_key: str,
1467
+ group_values: list[str],
1468
+ per_group_limit: int,
1469
+ order_by: str,
1470
+ ) -> list[dict[str, Any]]:
1471
+ if not group_values or per_group_limit <= 0:
1472
+ return []
1473
+ if order_by not in _VALID_ORDER_BY:
1474
+ order_by = "updated_at"
1475
+
1476
+ params: list[Any] = [entity_type]
1477
+ clauses: list[str] = []
1478
+ for key, value in filters.items():
1479
+ if key in _COLUMN_FILTERS:
1480
+ clauses.append(f"e.{key} = ?")
1481
+ else:
1482
+ clauses.append(f"json_extract(e.metadata, '$.{key}') = ?")
1483
+ params.append(value)
1484
+
1485
+ group_path = f"$.{group_metadata_key}"
1486
+ value_placeholders = ", ".join("?" for _ in group_values)
1487
+ clauses.append(f"json_extract(e.metadata, ?) IN ({value_placeholders})")
1488
+ where_extra = " AND ".join(clauses)
1489
+
1490
+ with timed(
1491
+ "sqlite.list_recent_metadata_by_group",
1492
+ entity_type=entity_type,
1493
+ per_group_limit=per_group_limit,
1494
+ group_count=len(group_values),
1495
+ ):
1496
+ rows = await self._fetchall(
1497
+ f"""
1498
+ SELECT metadata FROM (
1499
+ SELECT e.metadata,
1500
+ json_extract(e.metadata, ?) AS group_value,
1501
+ ROW_NUMBER() OVER (
1502
+ PARTITION BY json_extract(e.metadata, ?)
1503
+ ORDER BY e.{order_by} DESC
1504
+ ) AS group_rank
1505
+ FROM entities e
1506
+ WHERE e.entity_type = ? AND {where_extra}
1507
+ )
1508
+ WHERE group_rank <= ?
1509
+ ORDER BY group_value, group_rank
1510
+ """,
1511
+ [group_path, group_path, *params, group_path, *group_values, per_group_limit],
1512
+ )
1513
+ return [json.loads(row["metadata"]) if row["metadata"] else {} for row in rows]
1514
+
1449
1515
  # --- Read: edges ---
1450
1516
 
1451
1517
  async def get_edges(
@@ -68,6 +68,8 @@ CREATE INDEX IF NOT EXISTS idx_entities_type_created_at ON entities(entity_type,
68
68
  CREATE INDEX IF NOT EXISTS idx_entities_type_updated_at ON entities(entity_type, updated_at DESC);
69
69
  CREATE INDEX IF NOT EXISTS idx_entities_platform_observed_at_id ON entities(platform, observed_at DESC, id ASC);
70
70
  CREATE INDEX IF NOT EXISTS idx_entities_platform_type_observed_at ON entities(platform, entity_type, observed_at DESC);
71
+ CREATE INDEX IF NOT EXISTS idx_entities_platform_type_feed_updated
72
+ ON entities(platform, entity_type, json_extract(metadata, '$.feed_url'), updated_at DESC);
71
73
 
72
74
  -- Edge indexes
73
75
  CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_entity_id);
@@ -4,11 +4,18 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json as _json
7
+ import urllib.error
8
+ import urllib.request
7
9
  from collections.abc import Iterator
8
10
  from contextlib import contextmanager
11
+ from importlib.metadata import PackageNotFoundError
12
+ from importlib.metadata import version as package_version
9
13
  from typing import TYPE_CHECKING, cast
10
14
 
15
+ import questionary # type: ignore[import-untyped]
11
16
  import typer
17
+ from rich.console import Console
18
+ from rich.table import Table
12
19
 
13
20
  if TYPE_CHECKING:
14
21
  from agentgraph.connectors.base import BaseConnector
@@ -18,6 +25,34 @@ app = typer.Typer(
18
25
  help="Local knowledge graph for AI agents.",
19
26
  no_args_is_help=True,
20
27
  )
28
+ console = Console()
29
+
30
+
31
+ def _version_callback(value: bool) -> None:
32
+ if not value:
33
+ return
34
+
35
+ try:
36
+ current = package_version("agentgraph-server")
37
+ except PackageNotFoundError:
38
+ current = "unknown"
39
+ typer.echo(f"agentgraph {current}")
40
+ raise typer.Exit()
41
+
42
+
43
+ @app.callback()
44
+ def main(
45
+ version: bool = typer.Option(
46
+ False,
47
+ "--version",
48
+ callback=_version_callback,
49
+ is_eager=True,
50
+ help="Show the installed version and exit.",
51
+ ),
52
+ ) -> None:
53
+ """Configure global CLI options."""
54
+
55
+
21
56
  demo_app = typer.Typer(
22
57
  help="Create reproducible local demo graphs.",
23
58
  no_args_is_help=True,
@@ -50,6 +85,34 @@ def _status_label(status: str) -> str:
50
85
  )
51
86
 
52
87
 
88
+ def _connector_auth_label(item: dict[str, object]) -> str:
89
+ """Build the authentication cell for a connector status row."""
90
+ status = item["auth_status"]
91
+ if status is None:
92
+ return ""
93
+
94
+ status_label = str(status)
95
+ auth = _status_label(status_label)
96
+ detail = item["auth_detail"]
97
+ if detail:
98
+ auth = f"{auth} ({detail})" if status_label != "ok" else f"{auth} as {detail}"
99
+ return f"{auth} via {item['auth_provider']}"
100
+
101
+
102
+ def _server_is_running() -> bool:
103
+ """Return whether the configured AgentGraph server responds to health checks."""
104
+ from agentgraph.config import get_settings
105
+
106
+ settings = get_settings()
107
+ host = "127.0.0.1" if settings.server_host in ("", "0.0.0.0", "::") else settings.server_host
108
+ url = f"http://{host}:{settings.server_port}/health"
109
+ try:
110
+ with urllib.request.urlopen(url, timeout=1) as response:
111
+ return response.status == 200
112
+ except (OSError, urllib.error.URLError):
113
+ return False
114
+
115
+
53
116
  @demo_app.command("add")
54
117
  def add_demo_command(
55
118
  json: bool = typer.Option(False, "--json", help="Output as JSON"),
@@ -322,7 +385,7 @@ def connector_command(
322
385
  help: bool = typer.Option(False, "--help", help="Show this message and exit."),
323
386
  ) -> None:
324
387
  """Run a connector-owned command."""
325
- from agentgraph.connectors.registry import bootstrap, get_connector
388
+ from agentgraph.connectors.registry import bootstrap, get_connector, get_connector_load_error
326
389
 
327
390
  if source is None:
328
391
  from typer.rich_utils import rich_format_help
@@ -333,6 +396,10 @@ def connector_command(
333
396
  bootstrap()
334
397
  connector = get_connector(source)
335
398
  if connector is None:
399
+ if error := get_connector_load_error(source):
400
+ typer.echo(f"Failed to load connector '{source}': {error}", err=True)
401
+ typer.echo("Reinstall AgentGraph and its connector dependencies, then retry.", err=True)
402
+ raise typer.Exit(code=1)
336
403
  typer.echo(f"Unknown connector '{source}'", err=True)
337
404
  raise typer.Exit(code=1)
338
405
 
@@ -349,6 +416,17 @@ def connector_command(
349
416
  from agentgraph.connectors.command_effects import execute_deletions
350
417
 
351
418
  result["deleted_entities"] = run_graph_operation(lambda: execute_deletions(effects))
419
+ if effects.fetch_references:
420
+ from agentgraph.cli_query import run_graph_operation
421
+ from agentgraph.connectors.command_effects import (
422
+ execute_fetches,
423
+ fetch_effect_error_hint,
424
+ )
425
+
426
+ result["fetched"] = run_graph_operation(
427
+ lambda: execute_fetches(effects),
428
+ error_hint=lambda error: fetch_effect_error_hint(effects, error, "cli"),
429
+ )
352
430
  if effects.poll:
353
431
  from agentgraph.cli_sync import queue_connector_poll
354
432
 
@@ -373,8 +451,8 @@ def connector_command(
373
451
  typer.echo(type(connector).format_cli_result(result))
374
452
 
375
453
 
376
- @app.command()
377
- def connectors(
454
+ @app.command(name="list-connectors")
455
+ def list_connectors(
378
456
  json: bool = typer.Option(False, "--json", help="Output as JSON"),
379
457
  verify: bool = typer.Option(
380
458
  False, "--verify", help="Live-check connector credentials with provider APIs"
@@ -398,23 +476,22 @@ def connectors(
398
476
  typer.echo(_json.dumps(items, indent=2))
399
477
  return
400
478
 
479
+ table = Table(title="Connectors", show_lines=True)
480
+ table.add_column("Connector", style="bold", no_wrap=True)
481
+ table.add_column("Description", ratio=1)
482
+ table.add_column("Auth")
483
+ table.add_column("Sync")
484
+ table.add_column("Last sync", no_wrap=True)
401
485
  for item in items:
402
- sync = str(item["sync"])
403
- last_sync = str(item["last_sync"])
404
486
  desc = item["description"] or item["source"]
405
- typer.echo(f" {item['source']:<12} {desc}")
406
- status = item["auth_status"]
407
- if status is None:
408
- typer.echo(f" {'':<12} sync: {sync} | last sync: {last_sync}")
409
- continue
410
- status_label = str(status)
411
- detail = item["auth_detail"]
412
- auth = _status_label(status_label)
413
- if detail:
414
- auth = f"{auth} ({detail})" if status_label != "ok" else f"{auth} as {detail}"
415
- typer.echo(
416
- f" {'':<12} auth: {auth} via {item['auth_provider']} | sync: {sync} | last sync: {last_sync}"
487
+ table.add_row(
488
+ str(item["source"]),
489
+ str(desc),
490
+ _connector_auth_label(item),
491
+ str(item["sync"]),
492
+ str(item["last_sync"]),
417
493
  )
494
+ console.print(table)
418
495
 
419
496
 
420
497
  @app.command()
@@ -430,6 +507,7 @@ def serve(
430
507
  settings = get_settings()
431
508
  configure_logging(settings.log_level, settings.log_file)
432
509
  typer.echo(f"AgentGraph config directory: {get_config_paths()[0]}")
510
+ typer.echo(f"AgentGraph log file: {settings.log_file.expanduser()}")
433
511
  uvicorn.run(
434
512
  "agentgraph.server.app:app",
435
513
  host=settings.server_host,
@@ -595,8 +673,15 @@ def onboard() -> None:
595
673
  if label not in seen:
596
674
  seen[label] = connector
597
675
 
598
- steps = list(seen.items())
599
- total = len(steps)
676
+ # Only connectors with an explicit onboarding prompt own an interactive setup flow.
677
+ # Generic connectors such as Web are configured through connector commands instead.
678
+ steps = [
679
+ (label, connector)
680
+ for label, connector in seen.items()
681
+ if getattr(connector, "onboard_prompt", None)
682
+ ]
683
+ steps.sort(key=lambda item: getattr(item[1], "onboard_last", False))
684
+ total = len(steps) + 1
600
685
 
601
686
  typer.echo("=== AgentGraph Setup ===\n")
602
687
 
@@ -604,18 +689,19 @@ def onboard() -> None:
604
689
  prompt: str = getattr(connector, "onboard_prompt", None) or f"Set up {label}?"
605
690
  description: str = getattr(connector, "auth_description", None) or label.title()
606
691
  typer.echo(f"Step {i}/{total}: {description}")
607
- if typer.confirm(f" {prompt}", default=True):
692
+ if questionary.confirm(f" {prompt}", default=True).ask():
608
693
  type(connector).run_auth_flow()
609
694
  else:
610
695
  typer.echo(" Skipped.")
611
696
  if i < total:
612
697
  typer.echo()
613
698
 
699
+ typer.echo(f"\nStep {total}/{total}: Install the AgentGraph Chrome Extension")
614
700
  typer.echo(
615
- "\nInstall the AgentGraph Chrome Extension: "
616
701
  "https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi"
617
702
  )
618
- typer.echo("Run `agentgraph serve` to start the server.")
703
+ if not _server_is_running():
704
+ typer.echo("Run `agentgraph serve` to start the server.")
619
705
 
620
706
 
621
707
  @app.command()
@@ -635,30 +721,22 @@ def mcp_config() -> None:
635
721
  }
636
722
  }
637
723
 
638
- typer.echo("\nFor stdio MCP clients, add this to your MCP client config:\n")
639
- typer.echo(" Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json")
640
- typer.echo(" Claude Code: ~/.claude/mcp.json (or .claude/mcp.json in your project)\n")
641
- typer.echo(json.dumps(config, indent=2))
642
- typer.echo()
643
- typer.echo("For ChatGPT developer mode, do not use the stdio JSON above.")
644
- typer.echo("Run a streamable HTTP MCP server:")
645
- typer.echo(f" {binary} mcp-serve --transport streamable-http --port 8808")
646
- typer.echo("Local endpoint:")
647
- typer.echo(" http://127.0.0.1:8808/mcp")
648
- typer.echo("ChatGPT requires a reachable HTTPS URL. Use Secure MCP Tunnel, ngrok,")
649
- typer.echo("or Cloudflare Tunnel, then create an app/connector in ChatGPT Developer mode")
650
- typer.echo("with the public URL ending in /mcp, for example:")
651
- typer.echo(" https://your-tunnel.example/mcp")
724
+ typer.echo("\nChatGPT Desktop Work Mode:")
725
+ typer.echo(" Add a local MCP server in the MCP configuration screen.")
726
+ typer.echo(" Command to launch:")
727
+ typer.echo(f" {binary}")
728
+ typer.echo(" Arguments:")
729
+ typer.echo(" mcp-serve")
652
730
  typer.echo()
653
- typer.echo("SSE is also supported by the server if your MCP client needs it:")
654
- typer.echo(f" {binary} mcp-serve --transport sse --port 8808")
655
- typer.echo(" http://127.0.0.1:8808/sse")
731
+ typer.echo("Claude Desktop:")
732
+ typer.echo(" Add this to ~/Library/Application Support/Claude/claude_desktop_config.json:\n")
733
+ typer.echo(json.dumps(config, indent=2))
656
734
  typer.echo()
657
735
 
658
736
 
659
737
  @app.command()
660
738
  def install_skill(
661
- skill: str = typer.Argument("AgentGraph", help="Bundled skill to install"),
739
+ skill: str = typer.Argument("agentgraph", help="Bundled skill to install"),
662
740
  target: str = typer.Option(
663
741
  "user",
664
742
  "--target",