agentgraph-server 0.5.4__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.
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/PKG-INFO +1 -1
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/backends/sqlite/backend.py +16 -8
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/cli.py +58 -4
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/connectors/base.py +1 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/PKG-INFO +1 -1
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/pyproject.toml +1 -1
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_cli.py +115 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_connectors.py +6 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_schema.py +97 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/.agents/skills/AgentGraph/SKILL.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/.agents/skills/AgentGraph/references/commands.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/.agents/skills/AgentGraph/references/data-model.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/.agents/skills/AgentGraph/references/operations.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/.agents/skills/slack-auth/SKILL.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/LICENSE +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/README.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/auth/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/auth/credentials.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/backends/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/backends/sqlite/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/backends/sqlite/schema.sql +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/backends/sqlite/vector.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/cli_query.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/cli_sync.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/config.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/connectors/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/connectors/command_effects.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/connectors/registry.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/connectors/status.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/core/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/core/context.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/core/runtime.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/core/storage.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/demo.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/demo_fixtures/reliable-webhooks.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/demo_fixtures/retry-guidance.md +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/bookmark.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/delete.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/download.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/embeddings.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/expiration.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/fetch.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/link.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/operations.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/person.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/query.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/graph/upsert.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/logging.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/mcp/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/mcp/server.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/perf.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/__init__.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/app.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/cli_api.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/meta_api.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/observation.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/router.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/static/viewer.html +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/sync.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/server/sync_api.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/skills.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/SOURCES.txt +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/dependency_links.txt +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/entry_points.txt +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/requires.txt +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/top_level.txt +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/setup.cfg +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_auth.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_benchmarks.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_browse.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_command_effects.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_config.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_discord_attachments.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_docs_build.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_embeddings.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_expiration.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_fetch.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_graph_operations.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_launch_demo.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_logging.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_observation.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_query.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_registry.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_release_metadata.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_router.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_rss_connector.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_server_imports.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_slack_auth_artifacts.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_slack_oauth.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_sync.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_upsert.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_viewer_layout_contract.py +0 -0
- {agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/tests/test_web_connector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -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 =
|
|
67
|
+
_SCHEMA_VERSION = 3
|
|
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
|
-
|
|
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
|
|
@@ -370,8 +374,8 @@ class SQLiteBackend(StorageBackend):
|
|
|
370
374
|
content TEXT,
|
|
371
375
|
content_embedding BLOB,
|
|
372
376
|
metadata TEXT NOT NULL DEFAULT '{}',
|
|
373
|
-
created_at TEXT NOT NULL,
|
|
374
|
-
updated_at TEXT NOT NULL,
|
|
377
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
378
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
375
379
|
source_created_at TEXT,
|
|
376
380
|
source_updated_at TEXT,
|
|
377
381
|
synced_at TEXT,
|
|
@@ -660,8 +664,8 @@ class SQLiteBackend(StorageBackend):
|
|
|
660
664
|
"""
|
|
661
665
|
INSERT INTO entities
|
|
662
666
|
(id, entity_type, platform, platform_entity_id, title, metadata,
|
|
663
|
-
retention_policy, retention_parent_id)
|
|
664
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
667
|
+
created_at, updated_at, retention_policy, retention_parent_id)
|
|
668
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
665
669
|
ON CONFLICT (platform, platform_entity_id) DO UPDATE SET
|
|
666
670
|
entity_type = entities.entity_type
|
|
667
671
|
RETURNING id
|
|
@@ -673,6 +677,8 @@ class SQLiteBackend(StorageBackend):
|
|
|
673
677
|
e.platform_entity_id,
|
|
674
678
|
e.title,
|
|
675
679
|
json.dumps(dict(e.metadata)),
|
|
680
|
+
now,
|
|
681
|
+
now,
|
|
676
682
|
e.retention_policy,
|
|
677
683
|
parent_id,
|
|
678
684
|
],
|
|
@@ -756,8 +762,8 @@ class SQLiteBackend(StorageBackend):
|
|
|
756
762
|
INSERT INTO entities
|
|
757
763
|
(id, entity_type, platform, platform_entity_id, title, content,
|
|
758
764
|
content_embedding, metadata, source_created_at, source_updated_at,
|
|
759
|
-
synced_at, retention_policy, retention_parent_id)
|
|
760
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
765
|
+
synced_at, created_at, updated_at, retention_policy, retention_parent_id)
|
|
766
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
761
767
|
ON CONFLICT (platform, platform_entity_id) DO UPDATE SET
|
|
762
768
|
entity_type = CASE WHEN entities.entity_type = 'Document' THEN EXCLUDED.entity_type ELSE entities.entity_type END,
|
|
763
769
|
title = COALESCE(EXCLUDED.title, entities.title),
|
|
@@ -784,6 +790,8 @@ class SQLiteBackend(StorageBackend):
|
|
|
784
790
|
source_created,
|
|
785
791
|
source_updated,
|
|
786
792
|
now,
|
|
793
|
+
now,
|
|
794
|
+
local_updated,
|
|
787
795
|
e.retention_policy,
|
|
788
796
|
parent_id,
|
|
789
797
|
local_updated,
|
|
@@ -4,8 +4,12 @@ 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
|
|
|
11
15
|
import typer
|
|
@@ -18,6 +22,33 @@ app = typer.Typer(
|
|
|
18
22
|
help="Local knowledge graph for AI agents.",
|
|
19
23
|
no_args_is_help=True,
|
|
20
24
|
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _version_callback(value: bool) -> None:
|
|
28
|
+
if not value:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
current = package_version("agentgraph-server")
|
|
33
|
+
except PackageNotFoundError:
|
|
34
|
+
current = "unknown"
|
|
35
|
+
typer.echo(f"agentgraph {current}")
|
|
36
|
+
raise typer.Exit()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.callback()
|
|
40
|
+
def main(
|
|
41
|
+
version: bool = typer.Option(
|
|
42
|
+
False,
|
|
43
|
+
"--version",
|
|
44
|
+
callback=_version_callback,
|
|
45
|
+
is_eager=True,
|
|
46
|
+
help="Show the installed version and exit.",
|
|
47
|
+
),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Configure global CLI options."""
|
|
50
|
+
|
|
51
|
+
|
|
21
52
|
demo_app = typer.Typer(
|
|
22
53
|
help="Create reproducible local demo graphs.",
|
|
23
54
|
no_args_is_help=True,
|
|
@@ -50,6 +81,20 @@ def _status_label(status: str) -> str:
|
|
|
50
81
|
)
|
|
51
82
|
|
|
52
83
|
|
|
84
|
+
def _server_is_running() -> bool:
|
|
85
|
+
"""Return whether the configured AgentGraph server responds to health checks."""
|
|
86
|
+
from agentgraph.config import get_settings
|
|
87
|
+
|
|
88
|
+
settings = get_settings()
|
|
89
|
+
host = "127.0.0.1" if settings.server_host in ("", "0.0.0.0", "::") else settings.server_host
|
|
90
|
+
url = f"http://{host}:{settings.server_port}/health"
|
|
91
|
+
try:
|
|
92
|
+
with urllib.request.urlopen(url, timeout=1) as response:
|
|
93
|
+
return response.status == 200
|
|
94
|
+
except (OSError, urllib.error.URLError):
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
53
98
|
@demo_app.command("add")
|
|
54
99
|
def add_demo_command(
|
|
55
100
|
json: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
@@ -430,6 +475,7 @@ def serve(
|
|
|
430
475
|
settings = get_settings()
|
|
431
476
|
configure_logging(settings.log_level, settings.log_file)
|
|
432
477
|
typer.echo(f"AgentGraph config directory: {get_config_paths()[0]}")
|
|
478
|
+
typer.echo(f"AgentGraph log file: {settings.log_file.expanduser()}")
|
|
433
479
|
uvicorn.run(
|
|
434
480
|
"agentgraph.server.app:app",
|
|
435
481
|
host=settings.server_host,
|
|
@@ -595,8 +641,15 @@ def onboard() -> None:
|
|
|
595
641
|
if label not in seen:
|
|
596
642
|
seen[label] = connector
|
|
597
643
|
|
|
598
|
-
|
|
599
|
-
|
|
644
|
+
# Only connectors with an explicit onboarding prompt own an interactive setup flow.
|
|
645
|
+
# Generic connectors such as Web are configured through connector commands instead.
|
|
646
|
+
steps = [
|
|
647
|
+
(label, connector)
|
|
648
|
+
for label, connector in seen.items()
|
|
649
|
+
if getattr(connector, "onboard_prompt", None)
|
|
650
|
+
]
|
|
651
|
+
steps.sort(key=lambda item: getattr(item[1], "onboard_last", False))
|
|
652
|
+
total = len(steps) + 1
|
|
600
653
|
|
|
601
654
|
typer.echo("=== AgentGraph Setup ===\n")
|
|
602
655
|
|
|
@@ -611,11 +664,12 @@ def onboard() -> None:
|
|
|
611
664
|
if i < total:
|
|
612
665
|
typer.echo()
|
|
613
666
|
|
|
667
|
+
typer.echo(f"\nStep {total}/{total}: Install the AgentGraph Chrome Extension")
|
|
614
668
|
typer.echo(
|
|
615
|
-
"\nInstall the AgentGraph Chrome Extension: "
|
|
616
669
|
"https://chromewebstore.google.com/detail/agentgraph-extension/iilkfclglabllelhjacijldknapbhidi"
|
|
617
670
|
)
|
|
618
|
-
|
|
671
|
+
if not _server_is_running():
|
|
672
|
+
typer.echo("Run `agentgraph serve` to start the server.")
|
|
619
673
|
|
|
620
674
|
|
|
621
675
|
@app.command()
|
|
@@ -246,6 +246,7 @@ class BaseConnector(ABC):
|
|
|
246
246
|
auth_label: ClassVar[str | None] = None
|
|
247
247
|
auth_description: ClassVar[str | None] = None
|
|
248
248
|
onboard_prompt: ClassVar[str | None] = None
|
|
249
|
+
onboard_last: ClassVar[bool] = False
|
|
249
250
|
appears_in_auth_status: ClassVar[bool] = True
|
|
250
251
|
"""True for connectors backed by user/provider credentials.
|
|
251
252
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentgraph-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "agentgraph-server"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "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
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -58,6 +58,38 @@ def test_help() -> None:
|
|
|
58
58
|
assert "ingest" not in result.output
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def test_version() -> None:
|
|
62
|
+
result = runner.invoke(app, ["--version"])
|
|
63
|
+
|
|
64
|
+
assert result.exit_code == 0
|
|
65
|
+
assert result.output == "agentgraph 0.6.0\n"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_serve_outputs_log_file_path() -> None:
|
|
69
|
+
log_file = Path("/tmp/agentgraph-test/agentgraph.log")
|
|
70
|
+
settings = SimpleNamespace(
|
|
71
|
+
log_level="INFO",
|
|
72
|
+
log_file=log_file,
|
|
73
|
+
server_host="127.0.0.1",
|
|
74
|
+
server_port=8765,
|
|
75
|
+
)
|
|
76
|
+
with (
|
|
77
|
+
patch("agentgraph.config.get_settings", return_value=settings),
|
|
78
|
+
patch("agentgraph.logging.configure_logging"),
|
|
79
|
+
patch("uvicorn.run") as run,
|
|
80
|
+
):
|
|
81
|
+
result = runner.invoke(app, ["serve"])
|
|
82
|
+
|
|
83
|
+
assert result.exit_code == 0
|
|
84
|
+
assert f"AgentGraph log file: {log_file}" in result.output
|
|
85
|
+
run.assert_called_once_with(
|
|
86
|
+
"agentgraph.server.app:app",
|
|
87
|
+
host="127.0.0.1",
|
|
88
|
+
port=8765,
|
|
89
|
+
reload=False,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
61
93
|
def test_demo_add_creates_fixture_and_outputs_json(
|
|
62
94
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
63
95
|
) -> None:
|
|
@@ -781,10 +813,12 @@ def test_onboard_directs_users_to_install_chrome_extension() -> None:
|
|
|
781
813
|
with (
|
|
782
814
|
patch("agentgraph.connectors.registry.bootstrap"),
|
|
783
815
|
patch("agentgraph.connectors.registry.get_all_connectors", return_value=[_FakeConnector()]),
|
|
816
|
+
patch("agentgraph.cli._server_is_running", return_value=False),
|
|
784
817
|
):
|
|
785
818
|
result = runner.invoke(app, ["onboard"], input="n\n")
|
|
786
819
|
|
|
787
820
|
assert result.exit_code == 0
|
|
821
|
+
assert "Step 2/2: Install the AgentGraph Chrome Extension" in result.output
|
|
788
822
|
assert "Install the AgentGraph Chrome Extension" in result.output
|
|
789
823
|
assert (
|
|
790
824
|
"https://chromewebstore.google.com/detail/agentgraph-extension/"
|
|
@@ -793,6 +827,87 @@ def test_onboard_directs_users_to_install_chrome_extension() -> None:
|
|
|
793
827
|
assert "Run `agentgraph serve` to start the server." in result.output
|
|
794
828
|
|
|
795
829
|
|
|
830
|
+
def test_onboard_omits_server_hint_when_server_is_running() -> None:
|
|
831
|
+
with (
|
|
832
|
+
patch("agentgraph.connectors.registry.bootstrap"),
|
|
833
|
+
patch("agentgraph.connectors.registry.get_all_connectors", return_value=[_FakeConnector()]),
|
|
834
|
+
patch("agentgraph.cli._server_is_running", return_value=True),
|
|
835
|
+
):
|
|
836
|
+
result = runner.invoke(app, ["onboard"], input="n\n")
|
|
837
|
+
|
|
838
|
+
assert result.exit_code == 0
|
|
839
|
+
assert "Step 2/2: Install the AgentGraph Chrome Extension" in result.output
|
|
840
|
+
assert "Run `agentgraph serve` to start the server." not in result.output
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def test_onboard_skips_connectors_without_an_onboarding_flow() -> None:
|
|
844
|
+
class _FakeWebConnector:
|
|
845
|
+
source = "web"
|
|
846
|
+
auth_label = None
|
|
847
|
+
auth_description = "Web"
|
|
848
|
+
onboard_prompt = None
|
|
849
|
+
onboard_last = False
|
|
850
|
+
poll_interval = None
|
|
851
|
+
poll_delegates: list[str] = []
|
|
852
|
+
url_patterns: list[str] = []
|
|
853
|
+
|
|
854
|
+
with (
|
|
855
|
+
patch("agentgraph.connectors.registry.bootstrap"),
|
|
856
|
+
patch(
|
|
857
|
+
"agentgraph.connectors.registry.get_all_connectors",
|
|
858
|
+
return_value=[_FakeWebConnector()],
|
|
859
|
+
),
|
|
860
|
+
):
|
|
861
|
+
result = runner.invoke(app, ["onboard"])
|
|
862
|
+
|
|
863
|
+
assert result.exit_code == 0
|
|
864
|
+
assert "Step 1/1: Install the AgentGraph Chrome Extension" in result.output
|
|
865
|
+
assert "Set up web?" not in result.output
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def test_onboard_places_last_step_connectors_at_the_end() -> None:
|
|
869
|
+
class _FakeRssOnboardConnector:
|
|
870
|
+
source = "rss"
|
|
871
|
+
auth_label = "rss"
|
|
872
|
+
auth_description = "RSS"
|
|
873
|
+
onboard_prompt = "Set up RSS feeds?"
|
|
874
|
+
onboard_last = True
|
|
875
|
+
poll_interval = None
|
|
876
|
+
poll_delegates: list[str] = []
|
|
877
|
+
url_patterns: list[str] = []
|
|
878
|
+
auth_calls = 0
|
|
879
|
+
|
|
880
|
+
@classmethod
|
|
881
|
+
def run_auth_flow(cls) -> None:
|
|
882
|
+
cls.auth_calls += 1
|
|
883
|
+
|
|
884
|
+
class _FakeSlackOnboardConnector:
|
|
885
|
+
source = "slack"
|
|
886
|
+
auth_label = "slack"
|
|
887
|
+
auth_description = "Slack"
|
|
888
|
+
onboard_prompt = "Set up Slack?"
|
|
889
|
+
onboard_last = False
|
|
890
|
+
poll_interval = None
|
|
891
|
+
poll_delegates: list[str] = []
|
|
892
|
+
url_patterns: list[str] = []
|
|
893
|
+
|
|
894
|
+
@classmethod
|
|
895
|
+
def run_auth_flow(cls) -> None:
|
|
896
|
+
pass
|
|
897
|
+
|
|
898
|
+
with (
|
|
899
|
+
patch("agentgraph.connectors.registry.bootstrap"),
|
|
900
|
+
patch(
|
|
901
|
+
"agentgraph.connectors.registry.get_all_connectors",
|
|
902
|
+
return_value=[_FakeRssOnboardConnector(), _FakeSlackOnboardConnector()],
|
|
903
|
+
),
|
|
904
|
+
):
|
|
905
|
+
result = runner.invoke(app, ["onboard"], input="n\nn\n")
|
|
906
|
+
|
|
907
|
+
assert result.exit_code == 0
|
|
908
|
+
assert result.output.index("Step 1/3: Slack") < result.output.index("Step 2/3: RSS")
|
|
909
|
+
|
|
910
|
+
|
|
796
911
|
def test_auth_slack_accepts_noninteractive_options_after_provider() -> None:
|
|
797
912
|
from agentgraph_connector_slack import SlackConnector
|
|
798
913
|
|
|
@@ -215,6 +215,12 @@ def gdocs_connector() -> GoogleDocsConnector:
|
|
|
215
215
|
return GoogleDocsConnector()
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
def test_gdocs_onboarding_description_covers_google_connectors() -> None:
|
|
219
|
+
assert GoogleDocsConnector.auth_description == (
|
|
220
|
+
"Google: Gmail, Google Drive, Google Docs, and Google Sheets."
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
218
224
|
@pytest.mark.asyncio
|
|
219
225
|
async def test_gdocs_fetch_fresh_returns_empty_batch(gdocs_connector: GoogleDocsConnector) -> None:
|
|
220
226
|
"""When data is fresh, connector returns an empty batch without mutation."""
|
|
@@ -11,6 +11,7 @@ from unittest.mock import AsyncMock, patch
|
|
|
11
11
|
import pytest
|
|
12
12
|
|
|
13
13
|
from agentgraph.backends.sqlite.backend import SQLiteBackend
|
|
14
|
+
from agentgraph.connectors.base import EntityBatch, EntityRecord
|
|
14
15
|
from agentgraph.core.context import set_backend
|
|
15
16
|
|
|
16
17
|
|
|
@@ -378,6 +379,8 @@ async def test_existing_database_gets_columns_and_renames_threads_to_email(tmp_p
|
|
|
378
379
|
by_name = {row["name"]: row for row in columns}
|
|
379
380
|
assert by_name["created_at"]["notnull"] == 1
|
|
380
381
|
assert by_name["updated_at"]["notnull"] == 1
|
|
382
|
+
assert by_name["created_at"]["dflt_value"] is not None
|
|
383
|
+
assert by_name["updated_at"]["dflt_value"] is not None
|
|
381
384
|
assert by_name["observed_at"]["notnull"] == 0
|
|
382
385
|
|
|
383
386
|
entity = await migrated.get_entity_by_platform("gmail", "test-thread-001")
|
|
@@ -394,9 +397,103 @@ async def test_existing_database_gets_columns_and_renames_threads_to_email(tmp_p
|
|
|
394
397
|
assert updated is not None
|
|
395
398
|
assert updated["cumulative_observation_duration_ms"] == 2221
|
|
396
399
|
assert updated["observed_at"] is not None
|
|
400
|
+
await migrated.upsert_batch(
|
|
401
|
+
EntityBatch(
|
|
402
|
+
entities=[
|
|
403
|
+
EntityRecord(
|
|
404
|
+
entity_type="Message",
|
|
405
|
+
platform="slack",
|
|
406
|
+
platform_entity_id="T/C/123.456",
|
|
407
|
+
content="A migrated database must accept new entities",
|
|
408
|
+
)
|
|
409
|
+
]
|
|
410
|
+
),
|
|
411
|
+
{},
|
|
412
|
+
{},
|
|
413
|
+
)
|
|
414
|
+
inserted = await migrated.get_entity_by_platform("slack", "T/C/123.456")
|
|
415
|
+
assert inserted is not None
|
|
416
|
+
assert inserted["created_at"] is not None
|
|
417
|
+
assert inserted["updated_at"] is not None
|
|
397
418
|
event_type = await migrated._fetchval(
|
|
398
419
|
"SELECT event_type FROM observations WHERE id = ?", ["legacy-observation"]
|
|
399
420
|
)
|
|
400
421
|
assert event_type == "observation_threshold"
|
|
401
422
|
finally:
|
|
402
423
|
await migrated.close()
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
async def test_version_two_database_gets_timestamp_default_repair(tmp_path: Path) -> None:
|
|
427
|
+
db_path = tmp_path / "missing-timestamp-defaults.db"
|
|
428
|
+
conn = sqlite3.connect(db_path)
|
|
429
|
+
conn.executescript(
|
|
430
|
+
"""
|
|
431
|
+
CREATE TABLE entities (
|
|
432
|
+
id TEXT PRIMARY KEY,
|
|
433
|
+
entity_type TEXT NOT NULL,
|
|
434
|
+
platform TEXT NOT NULL,
|
|
435
|
+
platform_entity_id TEXT NOT NULL,
|
|
436
|
+
title TEXT,
|
|
437
|
+
content TEXT,
|
|
438
|
+
content_embedding BLOB,
|
|
439
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
440
|
+
created_at TEXT NOT NULL,
|
|
441
|
+
updated_at TEXT NOT NULL,
|
|
442
|
+
source_created_at TEXT,
|
|
443
|
+
source_updated_at TEXT,
|
|
444
|
+
synced_at TEXT,
|
|
445
|
+
observed_at TEXT,
|
|
446
|
+
retention_policy TEXT NOT NULL DEFAULT 'observed'
|
|
447
|
+
CHECK (retention_policy IN ('observed', 'owned', 'connected', 'persistent')),
|
|
448
|
+
retention_parent_id TEXT REFERENCES entities(id) ON DELETE CASCADE,
|
|
449
|
+
cumulative_observation_duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
450
|
+
bookmarked INTEGER NOT NULL DEFAULT 0,
|
|
451
|
+
UNIQUE (platform, platform_entity_id)
|
|
452
|
+
);
|
|
453
|
+
CREATE VIRTUAL TABLE entities_fts USING fts5(id UNINDEXED, title, content);
|
|
454
|
+
CREATE TABLE observations (
|
|
455
|
+
id TEXT PRIMARY KEY,
|
|
456
|
+
event_type TEXT NOT NULL,
|
|
457
|
+
url TEXT NOT NULL,
|
|
458
|
+
title TEXT,
|
|
459
|
+
tab_id INTEGER,
|
|
460
|
+
timestamp TEXT NOT NULL,
|
|
461
|
+
evaluated INTEGER NOT NULL DEFAULT 0,
|
|
462
|
+
meta TEXT,
|
|
463
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
464
|
+
);
|
|
465
|
+
INSERT INTO entities (
|
|
466
|
+
id, entity_type, platform, platform_entity_id, created_at, updated_at
|
|
467
|
+
) VALUES ('existing', 'Document', 'web', 'https://example.com', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z');
|
|
468
|
+
PRAGMA user_version=2;
|
|
469
|
+
"""
|
|
470
|
+
)
|
|
471
|
+
conn.commit()
|
|
472
|
+
conn.close()
|
|
473
|
+
|
|
474
|
+
migrated = SQLiteBackend(str(db_path))
|
|
475
|
+
await migrated.initialize()
|
|
476
|
+
try:
|
|
477
|
+
columns = {
|
|
478
|
+
row["name"]: row for row in await migrated._fetchall("PRAGMA table_info(entities)")
|
|
479
|
+
}
|
|
480
|
+
assert columns["created_at"]["dflt_value"] is not None
|
|
481
|
+
assert columns["updated_at"]["dflt_value"] is not None
|
|
482
|
+
await migrated.upsert_batch(
|
|
483
|
+
EntityBatch(
|
|
484
|
+
entities=[
|
|
485
|
+
EntityRecord(
|
|
486
|
+
entity_type="Message",
|
|
487
|
+
platform="slack",
|
|
488
|
+
platform_entity_id="T/C/123.456",
|
|
489
|
+
)
|
|
490
|
+
]
|
|
491
|
+
),
|
|
492
|
+
{},
|
|
493
|
+
{},
|
|
494
|
+
)
|
|
495
|
+
inserted = await migrated.get_entity_by_platform("slack", "T/C/123.456")
|
|
496
|
+
assert inserted is not None
|
|
497
|
+
assert inserted["created_at"] is not None
|
|
498
|
+
finally:
|
|
499
|
+
await migrated.close()
|
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/.agents/skills/AgentGraph/references/commands.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/connectors/command_effects.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/demo_fixtures/reliable-webhooks.md
RENAMED
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph/demo_fixtures/retry-guidance.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agentgraph_server-0.5.4 → agentgraph_server-0.6.0}/agentgraph_server.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|