agent-data-cli 0.1.0__py3-none-any.whl

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 (97) hide show
  1. agent_data_cli/PYPI_README.md +72 -0
  2. agent_data_cli/__init__.py +2 -0
  3. agent_data_cli/__main__.py +7 -0
  4. agent_data_cli/cli/__init__.py +2 -0
  5. agent_data_cli/cli/__main__.py +7 -0
  6. agent_data_cli/cli/commands/__init__.py +85 -0
  7. agent_data_cli/cli/commands/channel.py +78 -0
  8. agent_data_cli/cli/commands/common.py +97 -0
  9. agent_data_cli/cli/commands/config.py +270 -0
  10. agent_data_cli/cli/commands/content/__init__.py +29 -0
  11. agent_data_cli/cli/commands/content/common.py +172 -0
  12. agent_data_cli/cli/commands/content/interact.py +74 -0
  13. agent_data_cli/cli/commands/content/query.py +111 -0
  14. agent_data_cli/cli/commands/content/search.py +75 -0
  15. agent_data_cli/cli/commands/content/update.py +198 -0
  16. agent_data_cli/cli/commands/dashboard.py +87 -0
  17. agent_data_cli/cli/commands/group.py +128 -0
  18. agent_data_cli/cli/commands/help.py +44 -0
  19. agent_data_cli/cli/commands/hub.py +107 -0
  20. agent_data_cli/cli/commands/init.py +29 -0
  21. agent_data_cli/cli/commands/source.py +41 -0
  22. agent_data_cli/cli/commands/specs.py +241 -0
  23. agent_data_cli/cli/commands/sub.py +60 -0
  24. agent_data_cli/cli/formatters.py +537 -0
  25. agent_data_cli/cli/help.py +149 -0
  26. agent_data_cli/cli/main.py +46 -0
  27. agent_data_cli/core/__init__.py +2 -0
  28. agent_data_cli/core/base.py +222 -0
  29. agent_data_cli/core/capabilities.py +105 -0
  30. agent_data_cli/core/config.py +236 -0
  31. agent_data_cli/core/discovery.py +158 -0
  32. agent_data_cli/core/help.py +16 -0
  33. agent_data_cli/core/manifest.py +329 -0
  34. agent_data_cli/core/models.py +296 -0
  35. agent_data_cli/core/protocol.py +135 -0
  36. agent_data_cli/core/registry.py +353 -0
  37. agent_data_cli/core/source_defaults.py +24 -0
  38. agent_data_cli/dashboard/__init__.py +2 -0
  39. agent_data_cli/dashboard/adapters/__init__.py +2 -0
  40. agent_data_cli/dashboard/adapters/channel.py +73 -0
  41. agent_data_cli/dashboard/adapters/config.py +153 -0
  42. agent_data_cli/dashboard/adapters/content.py +350 -0
  43. agent_data_cli/dashboard/adapters/group.py +47 -0
  44. agent_data_cli/dashboard/adapters/help.py +32 -0
  45. agent_data_cli/dashboard/adapters/source.py +61 -0
  46. agent_data_cli/dashboard/adapters/sub.py +28 -0
  47. agent_data_cli/dashboard/context.py +29 -0
  48. agent_data_cli/dashboard/index.py +30 -0
  49. agent_data_cli/dashboard/pages/01_Source.py +57 -0
  50. agent_data_cli/dashboard/pages/02_Channel.py +99 -0
  51. agent_data_cli/dashboard/pages/03_Content_Search.py +64 -0
  52. agent_data_cli/dashboard/pages/04_Content_Query.py +79 -0
  53. agent_data_cli/dashboard/pages/05_Content_Update.py +103 -0
  54. agent_data_cli/dashboard/pages/06_Sub.py +51 -0
  55. agent_data_cli/dashboard/pages/07_Group.py +116 -0
  56. agent_data_cli/dashboard/pages/08_Config.py +114 -0
  57. agent_data_cli/dashboard/pages/09_Help.py +48 -0
  58. agent_data_cli/dashboard/pages/__init__.py +2 -0
  59. agent_data_cli/dashboard/runtime.py +208 -0
  60. agent_data_cli/dashboard/state.py +60 -0
  61. agent_data_cli/dashboard/widgets/__init__.py +2 -0
  62. agent_data_cli/dashboard/widgets/common.py +90 -0
  63. agent_data_cli/dashboard/widgets/forms.py +29 -0
  64. agent_data_cli/dashboard/widgets/tables.py +10 -0
  65. agent_data_cli/fetchers/__init__.py +2 -0
  66. agent_data_cli/fetchers/base.py +61 -0
  67. agent_data_cli/fetchers/browser.py +44 -0
  68. agent_data_cli/fetchers/http.py +313 -0
  69. agent_data_cli/fetchers/jina.py +44 -0
  70. agent_data_cli/hub/__init__.py +6 -0
  71. agent_data_cli/hub/models.py +20 -0
  72. agent_data_cli/hub/service.py +210 -0
  73. agent_data_cli/init_service.py +29 -0
  74. agent_data_cli/main.py +72 -0
  75. agent_data_cli/migration.py +53 -0
  76. agent_data_cli/runtime_paths.py +90 -0
  77. agent_data_cli/store/__init__.py +2 -0
  78. agent_data_cli/store/audit.py +42 -0
  79. agent_data_cli/store/channels.py +80 -0
  80. agent_data_cli/store/configs.py +134 -0
  81. agent_data_cli/store/content.py +770 -0
  82. agent_data_cli/store/db.py +298 -0
  83. agent_data_cli/store/groups.py +120 -0
  84. agent_data_cli/store/health.py +53 -0
  85. agent_data_cli/store/migrations.py +176 -0
  86. agent_data_cli/store/repositories.py +136 -0
  87. agent_data_cli/store/subscriptions.py +119 -0
  88. agent_data_cli/utils/__init__.py +2 -0
  89. agent_data_cli/utils/text.py +21 -0
  90. agent_data_cli/utils/time.py +63 -0
  91. agent_data_cli/utils/urls.py +8 -0
  92. agent_data_cli-0.1.0.dist-info/METADATA +104 -0
  93. agent_data_cli-0.1.0.dist-info/RECORD +97 -0
  94. agent_data_cli-0.1.0.dist-info/WHEEL +5 -0
  95. agent_data_cli-0.1.0.dist-info/entry_points.txt +2 -0
  96. agent_data_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  97. agent_data_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from agent_data_cli.core.registry import SourceRegistry, build_default_registry
7
+ from agent_data_cli.runtime_paths import resolve_runtime_paths
8
+ from agent_data_cli.store.db import Store
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class DashboardContext:
13
+ db_path: str
14
+ store: Store
15
+ registry: SourceRegistry
16
+
17
+
18
+ def build_dashboard_context(
19
+ *,
20
+ db_path: str | None = None,
21
+ sources_dir: Path | None = None,
22
+ ) -> DashboardContext:
23
+ resolved_db_path = str(resolve_runtime_paths().db_path) if db_path is None else db_path
24
+ Path(resolved_db_path).parent.mkdir(parents=True, exist_ok=True)
25
+ store = Store(resolved_db_path)
26
+ store.init_schema()
27
+ registry = build_default_registry(store, sources_dir=sources_dir)
28
+ store.init_schema(storage_specs=registry.list_storage_specs())
29
+ return DashboardContext(db_path=resolved_db_path, store=store, registry=registry)
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.runtime import get_dashboard_status
6
+ from agent_data_cli.dashboard.widgets.common import render_object_details, running_in_streamlit
7
+
8
+
9
+ def render_page() -> None:
10
+ st.set_page_config(page_title="Agent-Data-Cli Dashboard", layout="wide")
11
+ st.title("Agent-Data-Cli Dashboard")
12
+ st.caption("Streamlit Dashboard for Human to Understand the Projetc")
13
+
14
+ status = get_dashboard_status()
15
+ st.subheader("Runtime")
16
+ render_object_details(
17
+ {
18
+ "running": status.running,
19
+ "pid": status.pid,
20
+ "host": status.host,
21
+ "port": status.port,
22
+ "url": status.url,
23
+ "started_at": status.started_at,
24
+ "log_path": status.log_path,
25
+ }
26
+ )
27
+
28
+
29
+ if running_in_streamlit():
30
+ render_page()
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.source import check_source_config, check_source_health, list_sources
6
+ from agent_data_cli.dashboard.context import build_dashboard_context
7
+ from agent_data_cli.dashboard.state import ensure_page_result, get_page_state, save_page_result
8
+ from agent_data_cli.dashboard.widgets.common import render_exception, render_object_details, running_in_streamlit
9
+ from agent_data_cli.dashboard.widgets.forms import source_select
10
+ from agent_data_cli.dashboard.widgets.tables import render_rows
11
+ from agent_data_cli.utils.time import utc_now_iso
12
+
13
+
14
+ def render_page() -> None:
15
+ ctx = build_dashboard_context()
16
+ page_state = ensure_page_result(
17
+ st.session_state,
18
+ "source_page_state",
19
+ loader=lambda: {"rows": list_sources(ctx.registry)},
20
+ updated_at=utc_now_iso(),
21
+ )
22
+ st.title("Source")
23
+
24
+ if st.button("Refresh Sources", key="source_refresh"):
25
+ rows = list_sources(ctx.registry)
26
+ save_page_result(st.session_state, "source_page_state", inputs={}, result={"rows": rows}, updated_at=utc_now_iso())
27
+ page_state = get_page_state(st.session_state, "source_page_state")
28
+ render_rows(None if page_state["result"] is None else page_state["result"]["rows"], empty_message="No source data yet.")
29
+
30
+ selected_source = source_select(ctx.registry, key="source_page_source")
31
+ col1, col2 = st.columns(2)
32
+ with col1:
33
+ if st.button("Check Health", key="source_health") and selected_source is not None:
34
+ try:
35
+ result = check_source_health(ctx.registry, ctx.store, selected_source)
36
+ page_state["health_result"] = result
37
+ st.session_state["source_page_state"] = page_state
38
+ except Exception as exc: # noqa: BLE001
39
+ render_exception(exc)
40
+
41
+ if page_state.get("health_result") is not None:
42
+ render_object_details(page_state["health_result"], title="Health")
43
+
44
+ with col2:
45
+ if st.button("Config Check", key="source_config_check") and selected_source is not None:
46
+ try:
47
+ page_state["config_check_result"] = check_source_config(ctx.registry, selected_source)
48
+ st.session_state["source_page_state"] = page_state
49
+ except Exception as exc: # noqa: BLE001
50
+ render_exception(exc)
51
+
52
+ if page_state.get("config_check_result") is not None:
53
+ render_object_details(page_state["config_check_result"], title="Config Check")
54
+
55
+
56
+ if running_in_streamlit():
57
+ render_page()
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.channel import list_channel_options, list_channels, search_channels
6
+ from agent_data_cli.dashboard.adapters.group import add_channel_to_group
7
+ from agent_data_cli.dashboard.adapters.sub import add_subscription, remove_subscription
8
+ from agent_data_cli.dashboard.context import build_dashboard_context
9
+ from agent_data_cli.dashboard.state import get_page_state, invalidate_pages, save_page_result
10
+ from agent_data_cli.dashboard.widgets.common import render_exception, running_in_streamlit
11
+ from agent_data_cli.dashboard.widgets.forms import optional_channel_select, optional_group_select, source_select
12
+ from agent_data_cli.dashboard.widgets.tables import render_rows
13
+ from agent_data_cli.utils.time import utc_now_iso
14
+
15
+
16
+ def render_page() -> None:
17
+ ctx = build_dashboard_context()
18
+ st.title("Channel")
19
+ form_row1 = st.columns(2)
20
+ with form_row1[0]:
21
+ source_name = source_select(ctx.registry, key="channel_source")
22
+ with form_row1[1]:
23
+ query = st.text_input("Query", key="channel_query")
24
+ form_row2 = st.columns(2)
25
+ with form_row2[0]:
26
+ limit = st.number_input("Limit", min_value=1, value=20, key="channel_limit")
27
+ page_state = get_page_state(st.session_state, "channel_page_state")
28
+
29
+ with form_row2[1]:
30
+ action_row = st.columns(2)
31
+ with action_row[0]:
32
+ list_clicked = st.button("List Channels", key="channel_list", width="stretch")
33
+ with action_row[1]:
34
+ search_clicked = st.button("Search Channels", key="channel_search", width="stretch")
35
+
36
+ if list_clicked and source_name is not None:
37
+ try:
38
+ rows = list_channels(ctx.registry, ctx.store, source_name)
39
+ save_page_result(
40
+ st.session_state,
41
+ "channel_page_state",
42
+ inputs={"source": source_name, "mode": "list"},
43
+ result={"rows": rows},
44
+ updated_at=utc_now_iso(),
45
+ )
46
+ page_state = get_page_state(st.session_state, "channel_page_state")
47
+ except Exception as exc: # noqa: BLE001
48
+ render_exception(exc)
49
+ if search_clicked and source_name is not None:
50
+ try:
51
+ rows = search_channels(ctx.registry, ctx.store, source_name, query=query, limit=int(limit))
52
+ save_page_result(
53
+ st.session_state,
54
+ "channel_page_state",
55
+ inputs={"source": source_name, "query": query, "limit": int(limit)},
56
+ result={"rows": rows},
57
+ updated_at=utc_now_iso(),
58
+ )
59
+ page_state = get_page_state(st.session_state, "channel_page_state")
60
+ except Exception as exc: # noqa: BLE001
61
+ render_exception(exc)
62
+
63
+ render_rows(None if page_state["result"] is None else page_state["result"]["rows"], empty_message="Search or list channels.")
64
+
65
+ action_inputs = st.columns(2)
66
+ with action_inputs[0]:
67
+ selected_channel = optional_channel_select(
68
+ [] if source_name is None else list_channel_options(ctx.registry, ctx.store, source_name),
69
+ key="channel_selected",
70
+ label="Selected Channel",
71
+ )
72
+ with action_inputs[1]:
73
+ selected_group = optional_group_select(ctx.store, key="channel_group")
74
+ col1, col2, col3 = st.columns(3)
75
+ with col1:
76
+ if st.button("Subscribe", key="channel_sub") and source_name is not None and selected_channel:
77
+ try:
78
+ add_subscription(ctx.registry, source_name, selected_channel)
79
+ invalidate_pages(st.session_state, "sub_page_state", "channel_page_state", "content_update_page_state")
80
+ except Exception as exc: # noqa: BLE001
81
+ render_exception(exc)
82
+ with col2:
83
+ if st.button("Unsubscribe", key="channel_unsub") and source_name is not None and selected_channel:
84
+ try:
85
+ remove_subscription(ctx.registry, source_name, selected_channel)
86
+ invalidate_pages(st.session_state, "sub_page_state", "channel_page_state", "content_update_page_state")
87
+ except Exception as exc: # noqa: BLE001
88
+ render_exception(exc)
89
+ with col3:
90
+ if st.button("Add To Group", key="channel_group_add") and source_name is not None and selected_channel and selected_group:
91
+ try:
92
+ add_channel_to_group(ctx.store, selected_group, source_name, selected_channel)
93
+ invalidate_pages(st.session_state, "group_page_state", "content_update_page_state", "content_query_page_state")
94
+ except Exception as exc: # noqa: BLE001
95
+ render_exception(exc)
96
+
97
+
98
+ if running_in_streamlit():
99
+ render_page()
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.channel import list_channel_options
6
+ from agent_data_cli.dashboard.adapters.content import search_content
7
+ from agent_data_cli.dashboard.context import build_dashboard_context
8
+ from agent_data_cli.dashboard.state import get_page_state, save_page_result
9
+ from agent_data_cli.dashboard.widgets.common import render_exception, running_in_streamlit
10
+ from agent_data_cli.dashboard.widgets.forms import optional_channel_select, source_select
11
+ from agent_data_cli.dashboard.widgets.tables import render_rows
12
+ from agent_data_cli.utils.time import utc_now_iso
13
+
14
+
15
+ def render_page() -> None:
16
+ ctx = build_dashboard_context()
17
+ st.title("Content Search")
18
+ row1 = st.columns(2)
19
+ with row1[0]:
20
+ source_name = source_select(ctx.registry, key="content_search_source")
21
+ with row1[1]:
22
+ channel = optional_channel_select(
23
+ [] if source_name is None else list_channel_options(ctx.registry, ctx.store, source_name),
24
+ key="content_search_channel",
25
+ )
26
+ row2 = st.columns(2)
27
+ with row2[0]:
28
+ query = st.text_input("Query", key="content_search_query")
29
+ with row2[1]:
30
+ since = st.text_input("Since", key="content_search_since")
31
+ row3 = st.columns(2)
32
+ with row3[0]:
33
+ limit = st.number_input("Limit", min_value=1, value=20, key="content_search_limit")
34
+ page_state = get_page_state(st.session_state, "content_search_page_state")
35
+
36
+ with row3[1]:
37
+ run_clicked = st.button("Run Search", key="content_search_run", width="stretch")
38
+
39
+ if run_clicked and source_name is not None:
40
+ try:
41
+ rows = search_content(
42
+ ctx.registry,
43
+ source_name,
44
+ channel=channel or None,
45
+ query=query or None,
46
+ since=since or None,
47
+ limit=int(limit),
48
+ )
49
+ save_page_result(
50
+ st.session_state,
51
+ "content_search_page_state",
52
+ inputs={"source": source_name, "channel": channel, "query": query, "since": since, "limit": int(limit)},
53
+ result={"rows": rows},
54
+ updated_at=utc_now_iso(),
55
+ )
56
+ page_state = get_page_state(st.session_state, "content_search_page_state")
57
+ except Exception as exc: # noqa: BLE001
58
+ render_exception(exc)
59
+
60
+ render_rows(None if page_state["result"] is None else page_state["result"]["rows"], empty_message="Run a remote content search.")
61
+
62
+
63
+ if running_in_streamlit():
64
+ render_page()
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.channel import list_channel_options
6
+ from agent_data_cli.dashboard.adapters.content import query_content
7
+ from agent_data_cli.dashboard.context import build_dashboard_context
8
+ from agent_data_cli.dashboard.state import get_page_state, save_page_result
9
+ from agent_data_cli.dashboard.widgets.common import render_exception, running_in_streamlit
10
+ from agent_data_cli.dashboard.widgets.forms import optional_channel_select, optional_group_select, source_select
11
+ from agent_data_cli.dashboard.widgets.tables import render_rows
12
+ from agent_data_cli.utils.time import utc_now_iso
13
+
14
+
15
+ def render_page() -> None:
16
+ ctx = build_dashboard_context()
17
+ st.title("Content Query")
18
+ mode = st.radio("Mode", ["source", "group"], horizontal=True, key="content_query_mode")
19
+ row1 = st.columns(2)
20
+ with row1[0]:
21
+ source_name = source_select(ctx.registry, key="content_query_source") if mode == "source" else None
22
+ with row1[1]:
23
+ group_name = optional_group_select(ctx.store, key="content_query_group") if mode == "group" else None
24
+ row2 = st.columns(2)
25
+ with row2[0]:
26
+ channel_key = (
27
+ optional_channel_select(
28
+ [] if source_name is None else list_channel_options(ctx.registry, ctx.store, source_name),
29
+ key="content_query_channel",
30
+ )
31
+ if mode == "source"
32
+ else None
33
+ )
34
+ with row2[1]:
35
+ keywords = st.text_input("Keywords", key="content_query_keywords")
36
+ row3 = st.columns(2)
37
+ with row3[0]:
38
+ since = st.text_input("Since", key="content_query_since")
39
+ with row3[1]:
40
+ limit = st.number_input("Limit", min_value=1, value=20, key="content_query_limit")
41
+ page_state = get_page_state(st.session_state, "content_query_page_state")
42
+
43
+ run_clicked = st.button("Run Query", key="content_query_run")
44
+ if run_clicked:
45
+ try:
46
+ result = query_content(
47
+ ctx.registry,
48
+ ctx.store,
49
+ source_name=source_name,
50
+ group_name=group_name,
51
+ channel_key=channel_key or None,
52
+ keywords=keywords or None,
53
+ since=since or None,
54
+ limit=int(limit),
55
+ )
56
+ save_page_result(
57
+ st.session_state,
58
+ "content_query_page_state",
59
+ inputs={
60
+ "mode": mode,
61
+ "source": source_name,
62
+ "group": group_name,
63
+ "channel": channel_key,
64
+ "keywords": keywords,
65
+ "since": since,
66
+ "limit": int(limit),
67
+ },
68
+ result=result,
69
+ updated_at=utc_now_iso(),
70
+ )
71
+ page_state = get_page_state(st.session_state, "content_query_page_state")
72
+ except Exception as exc: # noqa: BLE001
73
+ render_exception(exc)
74
+
75
+ render_rows(None if page_state["result"] is None else page_state["result"]["rows"], empty_message="Query the local database.")
76
+
77
+
78
+ if running_in_streamlit():
79
+ render_page()
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.channel import list_subscribed_channel_options
6
+ from agent_data_cli.dashboard.adapters.content import update_content
7
+ from agent_data_cli.dashboard.context import build_dashboard_context
8
+ from agent_data_cli.dashboard.state import get_page_state, invalidate_pages, save_page_result
9
+ from agent_data_cli.dashboard.widgets.common import render_exception, render_object_details, running_in_streamlit
10
+ from agent_data_cli.dashboard.widgets.forms import optional_channel_select, optional_group_select, source_select
11
+ from agent_data_cli.dashboard.widgets.tables import render_rows
12
+ from agent_data_cli.utils.time import utc_now_iso
13
+
14
+
15
+ def build_result_overview(result: dict[str, object]) -> dict[str, object]:
16
+ overview: dict[str, object] = {}
17
+ for key in ("dry_run", "saved_count", "skipped_count"):
18
+ if key in result:
19
+ overview[key] = result[key]
20
+ return overview
21
+
22
+
23
+ def render_page() -> None:
24
+ ctx = build_dashboard_context()
25
+ st.title("Content Update")
26
+ mode = st.radio("Mode", ["source", "group"], horizontal=True, key="content_update_mode")
27
+ row1 = st.columns(2)
28
+ with row1[0]:
29
+ source_name = source_select(ctx.registry, key="content_update_source") if mode == "source" else None
30
+ with row1[1]:
31
+ group_name = optional_group_select(ctx.store, key="content_update_group") if mode == "group" else None
32
+ row2 = st.columns(2)
33
+ with row2[0]:
34
+ channel_key = (
35
+ optional_channel_select(
36
+ [] if source_name is None else list_subscribed_channel_options(ctx.store, source_name),
37
+ key="content_update_channel",
38
+ )
39
+ if mode == "source"
40
+ else None
41
+ )
42
+ with row2[1]:
43
+ since = st.text_input("Since", key="content_update_since")
44
+ row3 = st.columns(2)
45
+ with row3[0]:
46
+ limit = st.number_input("Limit", min_value=1, value=20, key="content_update_limit")
47
+ with row3[1]:
48
+ checkbox_row = st.columns(2)
49
+ with checkbox_row[0]:
50
+ fetch_all = st.checkbox("All", key="content_update_all")
51
+ with checkbox_row[1]:
52
+ dry_run = st.checkbox("Dry Run", key="content_update_dry_run")
53
+ page_state = get_page_state(st.session_state, "content_update_page_state")
54
+
55
+ run_clicked = st.button("Run Update", key="content_update_run")
56
+ if run_clicked:
57
+ try:
58
+ result = update_content(
59
+ ctx.registry,
60
+ ctx.store,
61
+ source_name=source_name,
62
+ group_name=group_name,
63
+ channel_key=channel_key or None,
64
+ since=since or None,
65
+ limit=int(limit),
66
+ fetch_all=fetch_all,
67
+ dry_run=dry_run,
68
+ )
69
+ save_page_result(
70
+ st.session_state,
71
+ "content_update_page_state",
72
+ inputs={
73
+ "mode": mode,
74
+ "source": source_name,
75
+ "group": group_name,
76
+ "channel": channel_key,
77
+ "since": since,
78
+ "limit": int(limit),
79
+ "all": fetch_all,
80
+ "dry_run": dry_run,
81
+ },
82
+ result=result,
83
+ updated_at=utc_now_iso(),
84
+ )
85
+ invalidate_pages(st.session_state, "content_query_page_state", "source_page_state")
86
+ page_state = get_page_state(st.session_state, "content_update_page_state")
87
+ except Exception as exc: # noqa: BLE001
88
+ render_exception(exc)
89
+
90
+ if page_state["result"] is not None:
91
+ overview = build_result_overview(page_state["result"])
92
+ if overview:
93
+ render_object_details(overview, title="Update Result")
94
+ if "summaries" in page_state["result"]:
95
+ render_rows(page_state["result"]["summaries"], empty_message="No updates.")
96
+ elif "targets" in page_state["result"]:
97
+ render_rows(page_state["result"]["targets"], empty_message="No targets.")
98
+ else:
99
+ st.info("Run an update against one source or one group.")
100
+
101
+
102
+ if running_in_streamlit():
103
+ render_page()
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.sub import list_subscriptions, remove_subscription
6
+ from agent_data_cli.dashboard.context import build_dashboard_context
7
+ from agent_data_cli.dashboard.state import get_page_state, invalidate_pages, save_page_result
8
+ from agent_data_cli.dashboard.widgets.common import render_exception, running_in_streamlit
9
+ from agent_data_cli.dashboard.widgets.forms import source_select
10
+ from agent_data_cli.dashboard.widgets.tables import render_rows
11
+ from agent_data_cli.utils.time import utc_now_iso
12
+
13
+
14
+ def render_page() -> None:
15
+ ctx = build_dashboard_context()
16
+ st.title("Subscriptions")
17
+ selected_source = st.checkbox("Filter by source", key="sub_filter_enabled")
18
+ source_name = source_select(ctx.registry, key="sub_source") if selected_source else None
19
+ page_state = get_page_state(st.session_state, "sub_page_state")
20
+
21
+ if st.button("Refresh Subscriptions", key="sub_refresh"):
22
+ rows = list_subscriptions(ctx.store, source_name=source_name)
23
+ save_page_result(
24
+ st.session_state,
25
+ "sub_page_state",
26
+ inputs={"source": source_name},
27
+ result={"rows": rows},
28
+ updated_at=utc_now_iso(),
29
+ )
30
+ page_state = get_page_state(st.session_state, "sub_page_state")
31
+
32
+ render_rows(None if page_state["result"] is None else page_state["result"]["rows"], empty_message="No subscriptions yet.")
33
+
34
+ channel_key = st.text_input("Channel To Remove", key="sub_remove_channel")
35
+ if st.button("Remove Subscription", key="sub_remove") and source_name is not None and channel_key:
36
+ try:
37
+ rows = remove_subscription(ctx.registry, source_name, channel_key)
38
+ save_page_result(
39
+ st.session_state,
40
+ "sub_page_state",
41
+ inputs={"source": source_name},
42
+ result={"rows": rows},
43
+ updated_at=utc_now_iso(),
44
+ )
45
+ invalidate_pages(st.session_state, "channel_page_state", "content_update_page_state")
46
+ except Exception as exc: # noqa: BLE001
47
+ render_exception(exc)
48
+
49
+
50
+ if running_in_streamlit():
51
+ render_page()
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from agent_data_cli.dashboard.adapters.group import (
6
+ add_channel_to_group,
7
+ add_source_to_group,
8
+ create_group,
9
+ delete_group,
10
+ list_group_members,
11
+ list_groups,
12
+ remove_channel_from_group,
13
+ remove_source_from_group,
14
+ )
15
+ from agent_data_cli.dashboard.context import build_dashboard_context
16
+ from agent_data_cli.dashboard.state import get_page_state, invalidate_pages, save_page_result
17
+ from agent_data_cli.dashboard.widgets.common import render_exception, running_in_streamlit
18
+ from agent_data_cli.dashboard.widgets.forms import optional_group_select, source_select
19
+ from agent_data_cli.dashboard.widgets.tables import render_rows
20
+ from agent_data_cli.utils.time import utc_now_iso
21
+
22
+
23
+ def render_page() -> None:
24
+ ctx = build_dashboard_context()
25
+ st.title("Group")
26
+ page_state = get_page_state(st.session_state, "group_page_state")
27
+
28
+ if st.button("Refresh Groups", key="group_refresh"):
29
+ rows = list_groups(ctx.store)
30
+ save_page_result(st.session_state, "group_page_state", inputs={}, result={"rows": rows}, updated_at=utc_now_iso())
31
+ page_state = get_page_state(st.session_state, "group_page_state")
32
+
33
+ render_rows(None if page_state["result"] is None else page_state["result"]["rows"], empty_message="No groups yet.")
34
+
35
+ input_row1 = st.columns(2)
36
+ with input_row1[0]:
37
+ group_name = st.text_input("Group Name", key="group_name")
38
+ with input_row1[1]:
39
+ selected_group = optional_group_select(ctx.store, key="group_selected")
40
+ input_row2 = st.columns(2)
41
+ with input_row2[0]:
42
+ source_name = source_select(ctx.registry, key="group_source")
43
+ with input_row2[1]:
44
+ channel_key = st.text_input("Channel", key="group_channel")
45
+
46
+ row1 = st.columns(2)
47
+ with row1[0]:
48
+ if st.button("Create Group", key="group_create") and group_name:
49
+ try:
50
+ rows = create_group(ctx.store, group_name)
51
+ save_page_result(
52
+ st.session_state,
53
+ "group_page_state",
54
+ inputs={},
55
+ result={"rows": rows},
56
+ updated_at=utc_now_iso(),
57
+ )
58
+ invalidate_pages(st.session_state, "content_update_page_state", "content_query_page_state")
59
+ except Exception as exc: # noqa: BLE001
60
+ render_exception(exc)
61
+ with row1[1]:
62
+ if st.button("Delete Group", key="group_delete") and selected_group:
63
+ try:
64
+ rows = delete_group(ctx.store, selected_group)
65
+ save_page_result(
66
+ st.session_state,
67
+ "group_page_state",
68
+ inputs={},
69
+ result={"rows": rows},
70
+ updated_at=utc_now_iso(),
71
+ )
72
+ invalidate_pages(st.session_state, "content_update_page_state", "content_query_page_state")
73
+ except Exception as exc: # noqa: BLE001
74
+ render_exception(exc)
75
+
76
+ row2 = st.columns(2)
77
+ with row2[0]:
78
+ if st.button("Show Members", key="group_members_show") and selected_group:
79
+ try:
80
+ st.write(list_group_members(ctx.store, selected_group))
81
+ except Exception as exc: # noqa: BLE001
82
+ render_exception(exc)
83
+ with row2[1]:
84
+ if st.button("Add Source", key="group_add_source") and selected_group and source_name:
85
+ try:
86
+ st.write(add_source_to_group(ctx.store, selected_group, source_name))
87
+ invalidate_pages(st.session_state, "content_update_page_state", "content_query_page_state")
88
+ except Exception as exc: # noqa: BLE001
89
+ render_exception(exc)
90
+
91
+ row3 = st.columns(2)
92
+ with row3[0]:
93
+ if st.button("Add Channel", key="group_add_channel") and selected_group and source_name and channel_key:
94
+ try:
95
+ st.write(add_channel_to_group(ctx.store, selected_group, source_name, channel_key))
96
+ invalidate_pages(st.session_state, "content_update_page_state", "content_query_page_state")
97
+ except Exception as exc: # noqa: BLE001
98
+ render_exception(exc)
99
+ with row3[1]:
100
+ if st.button("Remove Source", key="group_remove_source") and selected_group and source_name:
101
+ try:
102
+ st.write(remove_source_from_group(ctx.store, selected_group, source_name))
103
+ invalidate_pages(st.session_state, "content_update_page_state", "content_query_page_state")
104
+ except Exception as exc: # noqa: BLE001
105
+ render_exception(exc)
106
+
107
+ if st.button("Remove Channel", key="group_remove_channel") and selected_group and source_name and channel_key:
108
+ try:
109
+ st.write(remove_channel_from_group(ctx.store, selected_group, source_name, channel_key))
110
+ invalidate_pages(st.session_state, "content_update_page_state", "content_query_page_state")
111
+ except Exception as exc: # noqa: BLE001
112
+ render_exception(exc)
113
+
114
+
115
+ if running_in_streamlit():
116
+ render_page()