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.
- agent_data_cli/PYPI_README.md +72 -0
- agent_data_cli/__init__.py +2 -0
- agent_data_cli/__main__.py +7 -0
- agent_data_cli/cli/__init__.py +2 -0
- agent_data_cli/cli/__main__.py +7 -0
- agent_data_cli/cli/commands/__init__.py +85 -0
- agent_data_cli/cli/commands/channel.py +78 -0
- agent_data_cli/cli/commands/common.py +97 -0
- agent_data_cli/cli/commands/config.py +270 -0
- agent_data_cli/cli/commands/content/__init__.py +29 -0
- agent_data_cli/cli/commands/content/common.py +172 -0
- agent_data_cli/cli/commands/content/interact.py +74 -0
- agent_data_cli/cli/commands/content/query.py +111 -0
- agent_data_cli/cli/commands/content/search.py +75 -0
- agent_data_cli/cli/commands/content/update.py +198 -0
- agent_data_cli/cli/commands/dashboard.py +87 -0
- agent_data_cli/cli/commands/group.py +128 -0
- agent_data_cli/cli/commands/help.py +44 -0
- agent_data_cli/cli/commands/hub.py +107 -0
- agent_data_cli/cli/commands/init.py +29 -0
- agent_data_cli/cli/commands/source.py +41 -0
- agent_data_cli/cli/commands/specs.py +241 -0
- agent_data_cli/cli/commands/sub.py +60 -0
- agent_data_cli/cli/formatters.py +537 -0
- agent_data_cli/cli/help.py +149 -0
- agent_data_cli/cli/main.py +46 -0
- agent_data_cli/core/__init__.py +2 -0
- agent_data_cli/core/base.py +222 -0
- agent_data_cli/core/capabilities.py +105 -0
- agent_data_cli/core/config.py +236 -0
- agent_data_cli/core/discovery.py +158 -0
- agent_data_cli/core/help.py +16 -0
- agent_data_cli/core/manifest.py +329 -0
- agent_data_cli/core/models.py +296 -0
- agent_data_cli/core/protocol.py +135 -0
- agent_data_cli/core/registry.py +353 -0
- agent_data_cli/core/source_defaults.py +24 -0
- agent_data_cli/dashboard/__init__.py +2 -0
- agent_data_cli/dashboard/adapters/__init__.py +2 -0
- agent_data_cli/dashboard/adapters/channel.py +73 -0
- agent_data_cli/dashboard/adapters/config.py +153 -0
- agent_data_cli/dashboard/adapters/content.py +350 -0
- agent_data_cli/dashboard/adapters/group.py +47 -0
- agent_data_cli/dashboard/adapters/help.py +32 -0
- agent_data_cli/dashboard/adapters/source.py +61 -0
- agent_data_cli/dashboard/adapters/sub.py +28 -0
- agent_data_cli/dashboard/context.py +29 -0
- agent_data_cli/dashboard/index.py +30 -0
- agent_data_cli/dashboard/pages/01_Source.py +57 -0
- agent_data_cli/dashboard/pages/02_Channel.py +99 -0
- agent_data_cli/dashboard/pages/03_Content_Search.py +64 -0
- agent_data_cli/dashboard/pages/04_Content_Query.py +79 -0
- agent_data_cli/dashboard/pages/05_Content_Update.py +103 -0
- agent_data_cli/dashboard/pages/06_Sub.py +51 -0
- agent_data_cli/dashboard/pages/07_Group.py +116 -0
- agent_data_cli/dashboard/pages/08_Config.py +114 -0
- agent_data_cli/dashboard/pages/09_Help.py +48 -0
- agent_data_cli/dashboard/pages/__init__.py +2 -0
- agent_data_cli/dashboard/runtime.py +208 -0
- agent_data_cli/dashboard/state.py +60 -0
- agent_data_cli/dashboard/widgets/__init__.py +2 -0
- agent_data_cli/dashboard/widgets/common.py +90 -0
- agent_data_cli/dashboard/widgets/forms.py +29 -0
- agent_data_cli/dashboard/widgets/tables.py +10 -0
- agent_data_cli/fetchers/__init__.py +2 -0
- agent_data_cli/fetchers/base.py +61 -0
- agent_data_cli/fetchers/browser.py +44 -0
- agent_data_cli/fetchers/http.py +313 -0
- agent_data_cli/fetchers/jina.py +44 -0
- agent_data_cli/hub/__init__.py +6 -0
- agent_data_cli/hub/models.py +20 -0
- agent_data_cli/hub/service.py +210 -0
- agent_data_cli/init_service.py +29 -0
- agent_data_cli/main.py +72 -0
- agent_data_cli/migration.py +53 -0
- agent_data_cli/runtime_paths.py +90 -0
- agent_data_cli/store/__init__.py +2 -0
- agent_data_cli/store/audit.py +42 -0
- agent_data_cli/store/channels.py +80 -0
- agent_data_cli/store/configs.py +134 -0
- agent_data_cli/store/content.py +770 -0
- agent_data_cli/store/db.py +298 -0
- agent_data_cli/store/groups.py +120 -0
- agent_data_cli/store/health.py +53 -0
- agent_data_cli/store/migrations.py +176 -0
- agent_data_cli/store/repositories.py +136 -0
- agent_data_cli/store/subscriptions.py +119 -0
- agent_data_cli/utils/__init__.py +2 -0
- agent_data_cli/utils/text.py +21 -0
- agent_data_cli/utils/time.py +63 -0
- agent_data_cli/utils/urls.py +8 -0
- agent_data_cli-0.1.0.dist-info/METADATA +104 -0
- agent_data_cli-0.1.0.dist-info/RECORD +97 -0
- agent_data_cli-0.1.0.dist-info/WHEEL +5 -0
- agent_data_cli-0.1.0.dist-info/entry_points.txt +2 -0
- agent_data_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- agent_data_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from agent_data_cli.cli.commands.config import CONFIG_CHECK_ACTION_IDS
|
|
4
|
+
from agent_data_cli.core.config import validate_config_value
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def list_cli_configs(registry, store) -> list[dict[str, object]]:
|
|
8
|
+
defaults_by_key = {
|
|
9
|
+
spec.key: registry.get_cli_config_default(spec.key)
|
|
10
|
+
for spec in registry.get_cli_config_specs()
|
|
11
|
+
}
|
|
12
|
+
explicit_by_key = {entry.key: entry for entry in store.list_cli_configs()}
|
|
13
|
+
rows: list[dict[str, object]] = []
|
|
14
|
+
for spec in registry.get_cli_config_specs():
|
|
15
|
+
entry = explicit_by_key.get(spec.key)
|
|
16
|
+
if entry is not None:
|
|
17
|
+
rows.append(
|
|
18
|
+
{
|
|
19
|
+
"scope": entry.source,
|
|
20
|
+
"key": entry.key,
|
|
21
|
+
"value": "***" if entry.is_secret else entry.value,
|
|
22
|
+
"type": entry.value_type,
|
|
23
|
+
"secret": entry.is_secret,
|
|
24
|
+
"origin": "explicit",
|
|
25
|
+
"updated_at": entry.updated_at,
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
continue
|
|
29
|
+
default_value = defaults_by_key.get(spec.key)
|
|
30
|
+
rows.append(
|
|
31
|
+
{
|
|
32
|
+
"scope": "cli",
|
|
33
|
+
"key": spec.key,
|
|
34
|
+
"value": default_value if default_value is not None else "unset",
|
|
35
|
+
"type": spec.type,
|
|
36
|
+
"secret": spec.secret,
|
|
37
|
+
"origin": "default" if default_value is not None else "unset",
|
|
38
|
+
"updated_at": "",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
return rows
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def set_cli_config(registry, store, key: str, value: str) -> list[dict[str, object]]:
|
|
45
|
+
spec = registry.get_cli_config_field_spec(key)
|
|
46
|
+
validate_config_value(spec, value, owner="cli")
|
|
47
|
+
store.set_cli_config(key, value, spec.type, spec.secret)
|
|
48
|
+
return list_cli_configs(registry, store)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def unset_cli_config(registry, store, key: str) -> list[dict[str, object]]:
|
|
52
|
+
_ = registry
|
|
53
|
+
store.unset_cli_config(key)
|
|
54
|
+
return list_cli_configs(registry, store)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def explain_cli_config(registry, key: str) -> dict[str, object]:
|
|
58
|
+
spec = registry.get_cli_config_field_spec(key)
|
|
59
|
+
return _config_field_details("cli", spec)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def list_source_configs(registry, store, source_name: str) -> list[dict[str, object]]:
|
|
63
|
+
source = registry.build(source_name)
|
|
64
|
+
resolved = source.config
|
|
65
|
+
resolved_entries = resolved.entries()
|
|
66
|
+
rows: list[dict[str, object]] = []
|
|
67
|
+
for spec in registry.get_source_config_specs(source_name):
|
|
68
|
+
entry = resolved_entries.get(spec.key)
|
|
69
|
+
if entry is None:
|
|
70
|
+
rows.append(
|
|
71
|
+
{
|
|
72
|
+
"scope": source_name,
|
|
73
|
+
"key": spec.key,
|
|
74
|
+
"value": "unset",
|
|
75
|
+
"type": spec.type,
|
|
76
|
+
"secret": spec.secret,
|
|
77
|
+
"origin": "unset",
|
|
78
|
+
"updated_at": "",
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
continue
|
|
82
|
+
origin = "explicit" if resolved.is_explicit(spec.key) else "inherited"
|
|
83
|
+
rows.append(
|
|
84
|
+
{
|
|
85
|
+
"scope": source_name,
|
|
86
|
+
"key": spec.key,
|
|
87
|
+
"value": "***" if entry.is_secret else entry.value,
|
|
88
|
+
"type": entry.value_type,
|
|
89
|
+
"secret": entry.is_secret,
|
|
90
|
+
"origin": origin,
|
|
91
|
+
"updated_at": entry.updated_at,
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
return rows
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def set_source_config(registry, store, source_name: str, key: str, value: str) -> list[dict[str, object]]:
|
|
98
|
+
spec = registry.get_source_config_field_spec(source_name, key)
|
|
99
|
+
validate_config_value(spec, value, owner=source_name)
|
|
100
|
+
store.set_source_config(source_name, key, value, spec.type, spec.secret)
|
|
101
|
+
return list_source_configs(registry, store, source_name)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def unset_source_config(registry, store, source_name: str, key: str) -> list[dict[str, object]]:
|
|
105
|
+
store.unset_source_config(source_name, key)
|
|
106
|
+
return list_source_configs(registry, store, source_name)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def explain_source_config(registry, source_name: str, key: str) -> dict[str, object]:
|
|
110
|
+
spec = registry.get_source_config_field_spec(source_name, key)
|
|
111
|
+
return _config_field_details(source_name, spec)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def check_source_config(registry, source_name: str, *, action_id: str | None = None, verb: str | None = None) -> dict[str, object]:
|
|
115
|
+
if action_id is not None and action_id not in CONFIG_CHECK_ACTION_IDS:
|
|
116
|
+
raise RuntimeError(f"unknown action id: {action_id}")
|
|
117
|
+
if action_id == "content.interact" and not verb:
|
|
118
|
+
raise RuntimeError("config source check --for content.interact requires --verb")
|
|
119
|
+
check = registry.config_check(source_name, action_id=action_id, verb=verb)
|
|
120
|
+
return {
|
|
121
|
+
"source": check.source,
|
|
122
|
+
"effective_mode": check.effective_mode,
|
|
123
|
+
"action_status": None if check.action_status is None else check.action_status.status,
|
|
124
|
+
"verb_status": None if check.verb_status is None else check.verb_status.status,
|
|
125
|
+
"items": [
|
|
126
|
+
{
|
|
127
|
+
"key": item.key,
|
|
128
|
+
"configured": item.configured,
|
|
129
|
+
"inherited": item.inherited,
|
|
130
|
+
"type": item.value_type,
|
|
131
|
+
"secret": item.secret,
|
|
132
|
+
"description": item.description,
|
|
133
|
+
"obtain_hint": item.obtain_hint,
|
|
134
|
+
"example": item.example,
|
|
135
|
+
"choices": list(item.choices),
|
|
136
|
+
}
|
|
137
|
+
for item in check.items
|
|
138
|
+
],
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _config_field_details(owner: str, spec) -> dict[str, object]:
|
|
143
|
+
return {
|
|
144
|
+
"scope": owner,
|
|
145
|
+
"key": spec.key,
|
|
146
|
+
"type": spec.type,
|
|
147
|
+
"secret": spec.secret,
|
|
148
|
+
"description": spec.description,
|
|
149
|
+
"inherits_from_cli": spec.inherits_from_cli,
|
|
150
|
+
"choices": list(spec.choices),
|
|
151
|
+
"obtain_hint": spec.obtain_hint,
|
|
152
|
+
"example": spec.example,
|
|
153
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from agent_data_cli.cli.commands.common import parse_since, require_action, require_option, resolve_limit, summarize_update_params, write_audit
|
|
6
|
+
from agent_data_cli.cli.commands.content.common import (
|
|
7
|
+
build_query_rows,
|
|
8
|
+
group_targets_by_source,
|
|
9
|
+
require_update_options,
|
|
10
|
+
resolve_query_sources,
|
|
11
|
+
validate_search_results,
|
|
12
|
+
)
|
|
13
|
+
from agent_data_cli.utils.time import since_datetime_to_iso
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def search_content(
|
|
17
|
+
registry,
|
|
18
|
+
source_name: str,
|
|
19
|
+
*,
|
|
20
|
+
channel: str | None = None,
|
|
21
|
+
query: str | None = None,
|
|
22
|
+
since: str | None = None,
|
|
23
|
+
limit: int | None = None,
|
|
24
|
+
) -> list[dict[str, object]]:
|
|
25
|
+
if channel is None and query is None:
|
|
26
|
+
raise RuntimeError("content search requires --channel or --query")
|
|
27
|
+
source = registry.build(source_name)
|
|
28
|
+
require_action(registry, source_name, "content.search")
|
|
29
|
+
if channel is not None:
|
|
30
|
+
require_option(registry, source_name, "content.search", "channel")
|
|
31
|
+
if query is not None:
|
|
32
|
+
require_option(registry, source_name, "content.search", "query")
|
|
33
|
+
since_value = parse_since(since) if since is not None else None
|
|
34
|
+
if since_value is not None:
|
|
35
|
+
require_option(registry, source_name, "content.search", "since")
|
|
36
|
+
if limit is not None:
|
|
37
|
+
require_option(registry, source_name, "content.search", "limit")
|
|
38
|
+
results = source.search_content(
|
|
39
|
+
channel_key=channel,
|
|
40
|
+
query=query,
|
|
41
|
+
since=since_value,
|
|
42
|
+
limit=resolve_limit(limit),
|
|
43
|
+
)
|
|
44
|
+
validate_search_results(source_name, results, registry)
|
|
45
|
+
view = source.get_content_search_view(channel)
|
|
46
|
+
rows: list[dict[str, object]] = []
|
|
47
|
+
for item in results:
|
|
48
|
+
if view is None:
|
|
49
|
+
row = {
|
|
50
|
+
"title": item.title,
|
|
51
|
+
"url": item.url,
|
|
52
|
+
"snippet": item.snippet,
|
|
53
|
+
"source": item.source,
|
|
54
|
+
}
|
|
55
|
+
if item.channel_key is not None:
|
|
56
|
+
row["channel_key"] = item.channel_key
|
|
57
|
+
else:
|
|
58
|
+
row = {column.header: column.getter(item) for column in view.columns}
|
|
59
|
+
if item.content_ref is not None:
|
|
60
|
+
row["content_ref"] = item.content_ref
|
|
61
|
+
rows.append(row)
|
|
62
|
+
return rows
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def query_content(
|
|
66
|
+
registry,
|
|
67
|
+
store,
|
|
68
|
+
*,
|
|
69
|
+
source_name: str | None = None,
|
|
70
|
+
channel_key: str | None = None,
|
|
71
|
+
group_name: str | None = None,
|
|
72
|
+
keywords: str | None = None,
|
|
73
|
+
content_type: str | None = None,
|
|
74
|
+
parent_ref: str | None = None,
|
|
75
|
+
children_ref: str | None = None,
|
|
76
|
+
depth: int | None = None,
|
|
77
|
+
since: str | None = None,
|
|
78
|
+
limit: int | None = None,
|
|
79
|
+
fetch_all: bool = False,
|
|
80
|
+
) -> dict[str, object]:
|
|
81
|
+
if channel_key is not None and source_name is None:
|
|
82
|
+
raise RuntimeError("--channel requires --source")
|
|
83
|
+
if parent_ref is not None and source_name is None:
|
|
84
|
+
raise RuntimeError("--parent requires --source")
|
|
85
|
+
if children_ref is not None and source_name is None:
|
|
86
|
+
raise RuntimeError("--children requires --source")
|
|
87
|
+
if parent_ref is not None and children_ref is not None:
|
|
88
|
+
raise RuntimeError("content query does not allow --parent with --children")
|
|
89
|
+
if depth is not None and parent_ref is None and children_ref is None:
|
|
90
|
+
raise RuntimeError("--depth requires --parent or --children")
|
|
91
|
+
if (parent_ref is not None or children_ref is not None) and channel_key is not None:
|
|
92
|
+
raise RuntimeError("content query does not allow --parent or --children with --channel")
|
|
93
|
+
if channel_key is not None and group_name is not None:
|
|
94
|
+
raise RuntimeError("content query does not allow --channel with --group")
|
|
95
|
+
if source_name is not None and group_name is not None:
|
|
96
|
+
raise RuntimeError("content query does not allow --source with --group")
|
|
97
|
+
if fetch_all and limit is not None:
|
|
98
|
+
raise RuntimeError("content query does not allow --all with --limit")
|
|
99
|
+
if depth is not None and depth != -1 and depth <= 0:
|
|
100
|
+
raise RuntimeError("--depth must be a positive integer or -1")
|
|
101
|
+
|
|
102
|
+
since_value = parse_since(since) if since is not None else None
|
|
103
|
+
target_sources = resolve_query_sources(registry, store, source=source_name, group=group_name)
|
|
104
|
+
if keywords is not None:
|
|
105
|
+
for target_source in target_sources:
|
|
106
|
+
if not registry.get_storage_spec(target_source).supports_keywords:
|
|
107
|
+
raise RuntimeError(f"{target_source} does not support --keywords")
|
|
108
|
+
if since_value is not None:
|
|
109
|
+
for target_source in target_sources:
|
|
110
|
+
if registry.get_storage_spec(target_source).time_field is None:
|
|
111
|
+
raise RuntimeError(f"{target_source} does not support --since")
|
|
112
|
+
|
|
113
|
+
rows = store.query_content(
|
|
114
|
+
source=source_name,
|
|
115
|
+
channel_key=channel_key,
|
|
116
|
+
group_name=group_name,
|
|
117
|
+
record_type=content_type,
|
|
118
|
+
parent_ref=parent_ref,
|
|
119
|
+
children_ref=children_ref,
|
|
120
|
+
depth=1 if depth is None else depth,
|
|
121
|
+
since=None if since_value is None else since_datetime_to_iso(since_value),
|
|
122
|
+
keywords=keywords,
|
|
123
|
+
limit=-1 if fetch_all else resolve_limit(limit),
|
|
124
|
+
fetch_all=fetch_all,
|
|
125
|
+
)
|
|
126
|
+
rendered_rows, column_options, native_view_headers = build_query_rows(rows, registry, store)
|
|
127
|
+
return {
|
|
128
|
+
"rows": rendered_rows,
|
|
129
|
+
"column_options": column_options,
|
|
130
|
+
"native_view_headers": sorted(native_view_headers),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def update_content(
|
|
135
|
+
registry,
|
|
136
|
+
store,
|
|
137
|
+
*,
|
|
138
|
+
source_name: str | None = None,
|
|
139
|
+
group_name: str | None = None,
|
|
140
|
+
channel_key: str | None = None,
|
|
141
|
+
since: str | None = None,
|
|
142
|
+
limit: int | None = None,
|
|
143
|
+
fetch_all: bool = False,
|
|
144
|
+
dry_run: bool = False,
|
|
145
|
+
) -> dict[str, object]:
|
|
146
|
+
if bool(source_name) == bool(group_name):
|
|
147
|
+
raise RuntimeError("content update requires exactly one of --source or --group")
|
|
148
|
+
if channel_key is not None and source_name is None:
|
|
149
|
+
raise RuntimeError("--channel requires --source")
|
|
150
|
+
if fetch_all and since is not None:
|
|
151
|
+
raise RuntimeError("content update does not allow --all with --since")
|
|
152
|
+
if fetch_all and limit is not None:
|
|
153
|
+
raise RuntimeError("content update does not allow --all with --limit")
|
|
154
|
+
if dry_run and group_name is None:
|
|
155
|
+
raise RuntimeError("--dry-run only works with content update --group")
|
|
156
|
+
|
|
157
|
+
since_value = parse_since(since) if since is not None else None
|
|
158
|
+
resolved_limit = None if fetch_all else resolve_limit(limit)
|
|
159
|
+
|
|
160
|
+
if group_name is not None:
|
|
161
|
+
return _update_group(
|
|
162
|
+
registry,
|
|
163
|
+
store,
|
|
164
|
+
group_name=group_name,
|
|
165
|
+
since=since_value,
|
|
166
|
+
since_arg=since,
|
|
167
|
+
limit=resolved_limit,
|
|
168
|
+
limit_arg=limit,
|
|
169
|
+
fetch_all=fetch_all,
|
|
170
|
+
dry_run=dry_run,
|
|
171
|
+
)
|
|
172
|
+
return _update_source(
|
|
173
|
+
registry,
|
|
174
|
+
store,
|
|
175
|
+
source_name=source_name,
|
|
176
|
+
channel_key=channel_key,
|
|
177
|
+
since=since_value,
|
|
178
|
+
since_arg=since,
|
|
179
|
+
limit=resolved_limit,
|
|
180
|
+
limit_arg=limit,
|
|
181
|
+
fetch_all=fetch_all,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _update_group(
|
|
186
|
+
registry,
|
|
187
|
+
store,
|
|
188
|
+
*,
|
|
189
|
+
group_name: str,
|
|
190
|
+
since: datetime | None,
|
|
191
|
+
since_arg: str | None,
|
|
192
|
+
limit: int | None,
|
|
193
|
+
limit_arg: int | None,
|
|
194
|
+
fetch_all: bool,
|
|
195
|
+
dry_run: bool,
|
|
196
|
+
) -> dict[str, object]:
|
|
197
|
+
targets = store.expand_group_update_targets(group_name)
|
|
198
|
+
if not targets:
|
|
199
|
+
raise RuntimeError(f"group has no subscribed update targets: {group_name}")
|
|
200
|
+
for source_name, channel_key in targets:
|
|
201
|
+
if not store.is_subscribed(source_name, channel_key):
|
|
202
|
+
raise RuntimeError(f"group target is not subscribed: {source_name}:{channel_key}")
|
|
203
|
+
grouped = group_targets_by_source(targets)
|
|
204
|
+
for source_name in grouped:
|
|
205
|
+
require_update_options(
|
|
206
|
+
registry,
|
|
207
|
+
source_name,
|
|
208
|
+
channel=False,
|
|
209
|
+
since=since_arg is not None,
|
|
210
|
+
limit=limit_arg is not None,
|
|
211
|
+
fetch_all=fetch_all,
|
|
212
|
+
)
|
|
213
|
+
if dry_run:
|
|
214
|
+
for source_name, grouped_targets in grouped.items():
|
|
215
|
+
write_audit(
|
|
216
|
+
store,
|
|
217
|
+
registry.build(source_name),
|
|
218
|
+
action="content.update",
|
|
219
|
+
target_kind="channel",
|
|
220
|
+
targets=grouped_targets,
|
|
221
|
+
dry_run=True,
|
|
222
|
+
params_summary=summarize_update_params(limit=limit, since=since, fetch_all=fetch_all),
|
|
223
|
+
status="ok",
|
|
224
|
+
error=None,
|
|
225
|
+
)
|
|
226
|
+
return {
|
|
227
|
+
"dry_run": True,
|
|
228
|
+
"targets": [{"source": source_name, "channel_key": channel_key} for source_name, channel_key in targets],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
summaries: list[dict[str, object]] = []
|
|
232
|
+
saved_count = 0
|
|
233
|
+
skipped_count = 0
|
|
234
|
+
for source_name, grouped_targets in grouped.items():
|
|
235
|
+
source = registry.build(source_name)
|
|
236
|
+
try:
|
|
237
|
+
for target_channel in grouped_targets:
|
|
238
|
+
summary = source.update(
|
|
239
|
+
channel_key=target_channel,
|
|
240
|
+
limit=limit,
|
|
241
|
+
since=since,
|
|
242
|
+
fetch_all=fetch_all,
|
|
243
|
+
)
|
|
244
|
+
summaries.append(_summary_row(summary))
|
|
245
|
+
saved_count += summary.saved_count
|
|
246
|
+
skipped_count += summary.skipped_count
|
|
247
|
+
write_audit(
|
|
248
|
+
store,
|
|
249
|
+
source,
|
|
250
|
+
action="content.update",
|
|
251
|
+
target_kind="channel",
|
|
252
|
+
targets=grouped_targets,
|
|
253
|
+
dry_run=False,
|
|
254
|
+
params_summary=summarize_update_params(limit=limit, since=since, fetch_all=fetch_all),
|
|
255
|
+
status="ok",
|
|
256
|
+
error=None,
|
|
257
|
+
)
|
|
258
|
+
except Exception as exc: # noqa: BLE001
|
|
259
|
+
write_audit(
|
|
260
|
+
store,
|
|
261
|
+
source,
|
|
262
|
+
action="content.update",
|
|
263
|
+
target_kind="channel",
|
|
264
|
+
targets=grouped_targets,
|
|
265
|
+
dry_run=False,
|
|
266
|
+
params_summary=summarize_update_params(limit=limit, since=since, fetch_all=fetch_all),
|
|
267
|
+
status="error",
|
|
268
|
+
error=str(exc),
|
|
269
|
+
)
|
|
270
|
+
raise
|
|
271
|
+
return {
|
|
272
|
+
"dry_run": False,
|
|
273
|
+
"saved_count": saved_count,
|
|
274
|
+
"skipped_count": skipped_count,
|
|
275
|
+
"summaries": summaries,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _update_source(
|
|
280
|
+
registry,
|
|
281
|
+
store,
|
|
282
|
+
*,
|
|
283
|
+
source_name: str,
|
|
284
|
+
channel_key: str | None,
|
|
285
|
+
since: datetime | None,
|
|
286
|
+
since_arg: str | None,
|
|
287
|
+
limit: int | None,
|
|
288
|
+
limit_arg: int | None,
|
|
289
|
+
fetch_all: bool,
|
|
290
|
+
) -> dict[str, object]:
|
|
291
|
+
require_update_options(
|
|
292
|
+
registry,
|
|
293
|
+
source_name,
|
|
294
|
+
channel=channel_key is not None,
|
|
295
|
+
since=since_arg is not None,
|
|
296
|
+
limit=limit_arg is not None,
|
|
297
|
+
fetch_all=fetch_all,
|
|
298
|
+
)
|
|
299
|
+
source = registry.build(source_name)
|
|
300
|
+
audit_targets = (
|
|
301
|
+
(channel_key,)
|
|
302
|
+
if channel_key is not None
|
|
303
|
+
else tuple(sorted(subscription.channel_key for subscription in source.list_subscriptions()))
|
|
304
|
+
)
|
|
305
|
+
try:
|
|
306
|
+
summary = source.update(
|
|
307
|
+
channel_key=channel_key,
|
|
308
|
+
limit=limit,
|
|
309
|
+
since=since,
|
|
310
|
+
fetch_all=fetch_all,
|
|
311
|
+
)
|
|
312
|
+
write_audit(
|
|
313
|
+
store,
|
|
314
|
+
source,
|
|
315
|
+
action="content.update",
|
|
316
|
+
target_kind="channel",
|
|
317
|
+
targets=audit_targets,
|
|
318
|
+
dry_run=False,
|
|
319
|
+
params_summary=summarize_update_params(limit=limit, since=since, fetch_all=fetch_all),
|
|
320
|
+
status="ok",
|
|
321
|
+
error=None,
|
|
322
|
+
)
|
|
323
|
+
except Exception as exc: # noqa: BLE001
|
|
324
|
+
write_audit(
|
|
325
|
+
store,
|
|
326
|
+
source,
|
|
327
|
+
action="content.update",
|
|
328
|
+
target_kind="channel",
|
|
329
|
+
targets=audit_targets,
|
|
330
|
+
dry_run=False,
|
|
331
|
+
params_summary=summarize_update_params(limit=limit, since=since, fetch_all=fetch_all),
|
|
332
|
+
status="error",
|
|
333
|
+
error=str(exc),
|
|
334
|
+
)
|
|
335
|
+
raise
|
|
336
|
+
return {
|
|
337
|
+
"dry_run": False,
|
|
338
|
+
"saved_count": summary.saved_count,
|
|
339
|
+
"skipped_count": summary.skipped_count,
|
|
340
|
+
"summaries": [_summary_row(summary)],
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _summary_row(summary) -> dict[str, object]:
|
|
345
|
+
return {
|
|
346
|
+
"source": summary.source,
|
|
347
|
+
"channel_key": summary.channel_key or "",
|
|
348
|
+
"saved_count": summary.saved_count,
|
|
349
|
+
"skipped_count": summary.skipped_count,
|
|
350
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def list_groups(store) -> list[dict[str, object]]:
|
|
5
|
+
return [{"group": item.group_name, "created_at": item.created_at} for item in store.list_groups()]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_group(store, group_name: str) -> list[dict[str, object]]:
|
|
9
|
+
store.create_group(group_name)
|
|
10
|
+
return list_groups(store)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def delete_group(store, group_name: str) -> list[dict[str, object]]:
|
|
14
|
+
store.delete_group(group_name)
|
|
15
|
+
return list_groups(store)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def list_group_members(store, group_name: str) -> list[dict[str, object]]:
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
"group": item.group_name,
|
|
22
|
+
"member_type": item.member_type,
|
|
23
|
+
"source": item.source,
|
|
24
|
+
"channel": item.channel_key or "",
|
|
25
|
+
}
|
|
26
|
+
for item in store.list_group_members(group_name)
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def add_source_to_group(store, group_name: str, source_name: str) -> list[dict[str, object]]:
|
|
31
|
+
store.add_group_source(group_name, source_name)
|
|
32
|
+
return list_group_members(store, group_name)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def add_channel_to_group(store, group_name: str, source_name: str, channel_key: str) -> list[dict[str, object]]:
|
|
36
|
+
store.add_group_channel(group_name, source_name, channel_key)
|
|
37
|
+
return list_group_members(store, group_name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def remove_source_from_group(store, group_name: str, source_name: str) -> list[dict[str, object]]:
|
|
41
|
+
store.remove_group_source(group_name, source_name)
|
|
42
|
+
return list_group_members(store, group_name)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def remove_channel_from_group(store, group_name: str, source_name: str, channel_key: str) -> list[dict[str, object]]:
|
|
46
|
+
store.remove_group_channel(group_name, source_name, channel_key)
|
|
47
|
+
return list_group_members(store, group_name)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from agent_data_cli.cli.commands import ROOT_COMMANDS, build_command_help_doc, build_global_help_doc
|
|
4
|
+
from agent_data_cli.cli.help import build_source_help_doc
|
|
5
|
+
from agent_data_cli.cli.commands.specs import CommandNodeSpec
|
|
6
|
+
from agent_data_cli.core.help import HelpDoc
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def list_help_topics(registry) -> dict[str, list[str]]:
|
|
10
|
+
return {
|
|
11
|
+
"commands": sorted(_collect_topics(ROOT_COMMANDS)),
|
|
12
|
+
"sources": registry.list_names(),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_help_doc_for_topic(registry, topic: str | None) -> HelpDoc:
|
|
17
|
+
normalized = "" if topic is None else topic.strip()
|
|
18
|
+
if not normalized or normalized == "global":
|
|
19
|
+
return build_global_help_doc()
|
|
20
|
+
if normalized in registry.list_names():
|
|
21
|
+
return build_source_help_doc(registry, normalized)
|
|
22
|
+
return build_command_help_doc(normalized)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _collect_topics(commands: tuple[CommandNodeSpec, ...], parent: tuple[str, ...] = ()) -> list[str]:
|
|
26
|
+
topics: list[str] = []
|
|
27
|
+
for command in commands:
|
|
28
|
+
current = parent + (command.name,)
|
|
29
|
+
topics.append(" ".join(current))
|
|
30
|
+
if command.children:
|
|
31
|
+
topics.extend(_collect_topics(command.children, current))
|
|
32
|
+
return topics
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from agent_data_cli.cli.commands.common import require_action
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def list_sources(registry) -> list[dict[str, object]]:
|
|
7
|
+
rows: list[dict[str, object]] = []
|
|
8
|
+
for descriptor in registry.list_descriptors():
|
|
9
|
+
rows.append(
|
|
10
|
+
{
|
|
11
|
+
"source": descriptor.name,
|
|
12
|
+
"display_name": descriptor.display_name,
|
|
13
|
+
"summary": descriptor.summary,
|
|
14
|
+
"mode": descriptor.effective_mode or "",
|
|
15
|
+
"channel_search": descriptor.action_statuses["channel.search"].status,
|
|
16
|
+
"content_search": descriptor.action_statuses["content.search"].status,
|
|
17
|
+
"update": descriptor.action_statuses["content.update"].status,
|
|
18
|
+
"query": descriptor.action_statuses["content.query"].status,
|
|
19
|
+
"interact": descriptor.action_statuses["content.interact"].status,
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
return rows
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_source_health(registry, store, source_name: str) -> dict[str, object]:
|
|
26
|
+
require_action(registry, source_name, "source.health")
|
|
27
|
+
source = registry.build(source_name)
|
|
28
|
+
health = source.health()
|
|
29
|
+
store.save_health(health)
|
|
30
|
+
return {
|
|
31
|
+
"source": health.source,
|
|
32
|
+
"status": health.status,
|
|
33
|
+
"checked_at": health.checked_at,
|
|
34
|
+
"latency_ms": health.latency_ms,
|
|
35
|
+
"error": health.error,
|
|
36
|
+
"details": health.details,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_source_config(registry, source_name: str, *, action_id: str | None = None, verb: str | None = None) -> dict[str, object]:
|
|
41
|
+
check = registry.config_check(source_name, action_id=action_id, verb=verb)
|
|
42
|
+
return {
|
|
43
|
+
"source": check.source,
|
|
44
|
+
"effective_mode": check.effective_mode,
|
|
45
|
+
"action_status": None if check.action_status is None else check.action_status.status,
|
|
46
|
+
"verb_status": None if check.verb_status is None else check.verb_status.status,
|
|
47
|
+
"items": [
|
|
48
|
+
{
|
|
49
|
+
"key": item.key,
|
|
50
|
+
"configured": item.configured,
|
|
51
|
+
"inherited": item.inherited,
|
|
52
|
+
"type": item.value_type,
|
|
53
|
+
"secret": item.secret,
|
|
54
|
+
"description": item.description,
|
|
55
|
+
"obtain_hint": item.obtain_hint,
|
|
56
|
+
"example": item.example,
|
|
57
|
+
"choices": list(item.choices),
|
|
58
|
+
}
|
|
59
|
+
for item in check.items
|
|
60
|
+
],
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def list_subscriptions(store, *, source_name: str | None = None) -> list[dict[str, object]]:
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
"subscription_id": item.subscription_id,
|
|
8
|
+
"source": item.source,
|
|
9
|
+
"channel_key": item.channel_key,
|
|
10
|
+
"display_name": item.display_name,
|
|
11
|
+
"created_at": item.created_at,
|
|
12
|
+
"last_updated_at": item.last_updated_at,
|
|
13
|
+
"enabled": item.enabled,
|
|
14
|
+
}
|
|
15
|
+
for item in store.list_subscriptions(source_name)
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def add_subscription(registry, source_name: str, channel_key: str, display_name: str | None = None) -> list[dict[str, object]]:
|
|
20
|
+
source = registry.build(source_name)
|
|
21
|
+
source.subscribe(channel_key, display_name=display_name)
|
|
22
|
+
return list_subscriptions(source.store, source_name=source_name)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def remove_subscription(registry, source_name: str, channel_key: str) -> list[dict[str, object]]:
|
|
26
|
+
source = registry.build(source_name)
|
|
27
|
+
source.unsubscribe(channel_key)
|
|
28
|
+
return list_subscriptions(source.store, source_name=source_name)
|