katalyst-plugin-context 0.1.1a1__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.
- katalyst_plugin_context/__init__.py +8 -0
- katalyst_plugin_context/adapters/__init__.py +0 -0
- katalyst_plugin_context/adapters/runtime_graph.py +126 -0
- katalyst_plugin_context/cli.py +139 -0
- katalyst_plugin_context/composition.py +49 -0
- katalyst_plugin_context/domain/__init__.py +0 -0
- katalyst_plugin_context/domain/context.py +762 -0
- katalyst_plugin_context/domain/graph.py +97 -0
- katalyst_plugin_context/domain/markdown.py +201 -0
- katalyst_plugin_context/domain/paths.py +114 -0
- katalyst_plugin_context/domain/policy.py +345 -0
- katalyst_plugin_context/domain/settings.py +92 -0
- katalyst_plugin_context/lint.py +149 -0
- katalyst_plugin_context/mcp_tools.py +132 -0
- katalyst_plugin_context/plugin.py +64 -0
- katalyst_plugin_context/py.typed +0 -0
- katalyst_plugin_context/rest.py +133 -0
- katalyst_plugin_context/schema.py +200 -0
- katalyst_plugin_context/service.py +319 -0
- katalyst_plugin_context-0.1.1a1.dist-info/METADATA +13 -0
- katalyst_plugin_context-0.1.1a1.dist-info/RECORD +23 -0
- katalyst_plugin_context-0.1.1a1.dist-info/WHEEL +4 -0
- katalyst_plugin_context-0.1.1a1.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""The context service: the policy for a node, and its map and context, on one call's graph.
|
|
2
|
+
|
|
3
|
+
It reads each layer of policy from where it is declared (the wing's and kind's
|
|
4
|
+
``plugin_settings["context"]``, the node's ``spec.extensions.context``, the plugin's config),
|
|
5
|
+
checks each against the plugin's schema, folds them, and asks the domain for the payload.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import fnmatch
|
|
11
|
+
import re
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
from typing import Any, cast
|
|
14
|
+
|
|
15
|
+
from katalyst_engine.schema.definition import VALUE_FLAG_BLOCKING
|
|
16
|
+
from katalyst_engine.schema.registry import SchemaRegistry
|
|
17
|
+
from katalyst_taxonomy.engine.relationships import CHILD_OF
|
|
18
|
+
|
|
19
|
+
from katalyst_plugin_context.domain.context import (
|
|
20
|
+
ContextError,
|
|
21
|
+
ContextRequest,
|
|
22
|
+
build_map,
|
|
23
|
+
build_result,
|
|
24
|
+
json_size,
|
|
25
|
+
)
|
|
26
|
+
from katalyst_plugin_context.domain.graph import Graph, GraphNode
|
|
27
|
+
from katalyst_plugin_context.domain.markdown import item_size, render_map, render_result
|
|
28
|
+
from katalyst_plugin_context.domain.paths import PathSyntaxError, parse_path
|
|
29
|
+
from katalyst_plugin_context.domain.policy import Layer, Policy, fold
|
|
30
|
+
from katalyst_plugin_context.domain.settings import ItemHints, View, item_hints, views_from
|
|
31
|
+
from katalyst_plugin_context.schema import (
|
|
32
|
+
CONFIG_SCHEMA,
|
|
33
|
+
NODE_SETTINGS_SCHEMA,
|
|
34
|
+
SETTINGS_SCHEMA,
|
|
35
|
+
check,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = ["NAMESPACE", "ContextService", "RegistrySchema"]
|
|
39
|
+
|
|
40
|
+
#: The plugin's name, and so the key of its settings sections everywhere.
|
|
41
|
+
NAMESPACE = "context"
|
|
42
|
+
|
|
43
|
+
FORMATS = ("json", "markdown")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RegistrySchema:
|
|
47
|
+
"""What the schema registry says about kinds and relationships, for the domain."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, registry: SchemaRegistry) -> None:
|
|
50
|
+
self._registry = registry
|
|
51
|
+
definition = registry.get_relationship("child_of")
|
|
52
|
+
self.parent_rel = definition.graph_label if definition is not None else CHILD_OF
|
|
53
|
+
self._hints: dict[str, ItemHints] = {}
|
|
54
|
+
|
|
55
|
+
def sections(self, kind: str) -> tuple[Mapping[str, Any] | None, Mapping[str, Any] | None, str]:
|
|
56
|
+
"""The wing's and the kind's context sections, and the wing's name."""
|
|
57
|
+
definition = self._registry.get_node_definition(kind)
|
|
58
|
+
if definition is None:
|
|
59
|
+
return None, None, ""
|
|
60
|
+
wing = self._registry.get_wing_definition(definition.wing) if definition.wing else None
|
|
61
|
+
return (
|
|
62
|
+
wing.plugin_settings.get(NAMESPACE) if wing is not None else None,
|
|
63
|
+
definition.plugin_settings.get(NAMESPACE),
|
|
64
|
+
definition.wing,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def kind_label(self, kind: str) -> str:
|
|
68
|
+
return self._registry.display_label_for(kind) if kind in self._registry else kind
|
|
69
|
+
|
|
70
|
+
def hints(self, kind: str) -> ItemHints:
|
|
71
|
+
if kind not in self._hints:
|
|
72
|
+
wing, own, _ = self.sections(kind)
|
|
73
|
+
self._hints[kind] = item_hints(wing, own)
|
|
74
|
+
return self._hints[kind]
|
|
75
|
+
|
|
76
|
+
def declared_fields(self, kind: str) -> Sequence[tuple[str, str]]:
|
|
77
|
+
definition = self._registry.get_node_definition(kind)
|
|
78
|
+
return [(f.name, f.field_type) for f in definition.fields] if definition else []
|
|
79
|
+
|
|
80
|
+
def blocking(self, kind: str, spec: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
81
|
+
definition = self._registry.get_node_definition(kind)
|
|
82
|
+
if definition is None:
|
|
83
|
+
return []
|
|
84
|
+
found: list[dict[str, Any]] = []
|
|
85
|
+
for field in definition.fields:
|
|
86
|
+
if not any(meta.flags for meta in field.value_meta):
|
|
87
|
+
continue
|
|
88
|
+
# A node that doesn't say holds the field's default, which may block.
|
|
89
|
+
value = spec.get(field.name, field.default)
|
|
90
|
+
if not isinstance(value, str) or value not in field.allowed_values:
|
|
91
|
+
continue
|
|
92
|
+
meta = field.meta_for(value)
|
|
93
|
+
found.append(
|
|
94
|
+
{
|
|
95
|
+
"field": field.name,
|
|
96
|
+
"value": value,
|
|
97
|
+
"blocking": meta is not None and VALUE_FLAG_BLOCKING in meta.flags,
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
return found
|
|
101
|
+
|
|
102
|
+
def relationship_description(self, rel: str) -> str:
|
|
103
|
+
definition = self._registry.get_relationship_by_graph_label(rel)
|
|
104
|
+
return definition.description if definition is not None else ""
|
|
105
|
+
|
|
106
|
+
def carried(self, kind: str) -> Sequence[tuple[str, frozenset[str]]]:
|
|
107
|
+
from katalyst_taxonomy.engine.relationships import carriers_of
|
|
108
|
+
|
|
109
|
+
return [(c.rel_type, c.target_kinds) for c in carriers_of(self._registry, kind)]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _glob(pattern: str) -> re.Pattern[str]:
|
|
113
|
+
"""An fqtn glob: ``*`` is one segment, ``**`` any number."""
|
|
114
|
+
out = ""
|
|
115
|
+
i = 0
|
|
116
|
+
while i < len(pattern):
|
|
117
|
+
if pattern.startswith("**", i):
|
|
118
|
+
out += ".*"
|
|
119
|
+
i += 2
|
|
120
|
+
elif pattern[i] == "*":
|
|
121
|
+
out += "[^.]*"
|
|
122
|
+
i += 1
|
|
123
|
+
else:
|
|
124
|
+
out += re.escape(pattern[i])
|
|
125
|
+
i += 1
|
|
126
|
+
return re.compile(f"^{out}$")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _matches(match: Mapping[str, Any], node: GraphNode, wing: str) -> bool:
|
|
130
|
+
"""Whether a profile rule's ``match`` selects *node*; every declared criterion must hold."""
|
|
131
|
+
|
|
132
|
+
def listed(key: str) -> list[str]:
|
|
133
|
+
return [str(v) for v in cast("list[object]", match.get(key) or [])]
|
|
134
|
+
|
|
135
|
+
if listed("backend") and node.backend not in listed("backend"):
|
|
136
|
+
return False
|
|
137
|
+
if listed("kind") and node.kind not in listed("kind"):
|
|
138
|
+
return False
|
|
139
|
+
if listed("wing") and wing not in listed("wing"):
|
|
140
|
+
return False
|
|
141
|
+
if listed("name") and not any(fnmatch.fnmatchcase(node.name, p) for p in listed("name")):
|
|
142
|
+
return False
|
|
143
|
+
if listed("fqtn") and not any(
|
|
144
|
+
_glob(p).match(node.key if ":" in p else node.fqn) for p in listed("fqtn")
|
|
145
|
+
):
|
|
146
|
+
return False
|
|
147
|
+
labels = cast("Mapping[str, str]", match.get("labels") or {})
|
|
148
|
+
node_labels = cast("Mapping[str, Any]", node.spec.get("labels") or {})
|
|
149
|
+
return all(str(node_labels.get(k)) == v for k, v in labels.items())
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class ContextService:
|
|
153
|
+
"""Map, context and policy for the nodes of one call's graph."""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
registry: SchemaRegistry,
|
|
158
|
+
graph: Graph,
|
|
159
|
+
*,
|
|
160
|
+
config: Mapping[str, Any] | None = None,
|
|
161
|
+
profile: str | None = None,
|
|
162
|
+
) -> None:
|
|
163
|
+
self._registry = registry
|
|
164
|
+
self._schema = RegistrySchema(registry)
|
|
165
|
+
self._graph = graph
|
|
166
|
+
self._config = dict(config or {})
|
|
167
|
+
self._profile = profile
|
|
168
|
+
check(CONFIG_SCHEMA, self._config, where=f"spec.plugins[{NAMESPACE}].config")
|
|
169
|
+
|
|
170
|
+
# ── addressing ──────────────────────────────────────────────────────
|
|
171
|
+
|
|
172
|
+
def resolve(self, address: str) -> str:
|
|
173
|
+
"""The graph key *address* names: ``backend:fqtn``, or an FQTN one backend holds.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
ContextError: ``NOT_FOUND`` (with the qualified names a bare name may mean)
|
|
177
|
+
or ``INVALID_INPUT`` (an FQTN several backends hold).
|
|
178
|
+
"""
|
|
179
|
+
text, _, kind = address.strip().partition("#")
|
|
180
|
+
nodes = self._graph.nodes.values()
|
|
181
|
+
|
|
182
|
+
def selected(node: GraphNode) -> bool:
|
|
183
|
+
return not kind or node.kind == kind
|
|
184
|
+
|
|
185
|
+
if ":" in text:
|
|
186
|
+
found = [n.key for n in nodes if f"{n.backend}:{n.fqn}" == text and selected(n)]
|
|
187
|
+
else:
|
|
188
|
+
found = [n.key for n in nodes if n.fqn == text and selected(n)]
|
|
189
|
+
if len(found) == 1:
|
|
190
|
+
return found[0]
|
|
191
|
+
if len(found) > 1:
|
|
192
|
+
raise ContextError(
|
|
193
|
+
"INVALID_INPUT",
|
|
194
|
+
f"{address!r} names more than one node; use one of: {', '.join(sorted(found))}",
|
|
195
|
+
{"candidates": sorted(found)},
|
|
196
|
+
)
|
|
197
|
+
leaf = text.rsplit(":", 1)[-1].rsplit(".", 1)[-1]
|
|
198
|
+
meant = sorted(n.key for n in nodes if n.name == leaf and selected(n))
|
|
199
|
+
hint = (
|
|
200
|
+
f"; a node is named by its qualified name: use {' or '.join(map(repr, meant[:10]))}"
|
|
201
|
+
if meant
|
|
202
|
+
else ""
|
|
203
|
+
)
|
|
204
|
+
raise ContextError(
|
|
205
|
+
"NOT_FOUND", f"Node {address!r} not found{hint}", {"node": address, "candidates": meant}
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# ── policy ──────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
def policy_for(self, key: str) -> tuple[Policy, dict[str, View]]:
|
|
211
|
+
"""The effective policy for the node at *key*, and the views its kind declares.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
ContextError: ``VALIDATION_ERROR`` if a layer breaks the plugin's schema.
|
|
215
|
+
"""
|
|
216
|
+
node = self._graph.nodes[key]
|
|
217
|
+
wing_section, kind_section, wing = self._schema.sections(node.kind)
|
|
218
|
+
layers: list[Layer] = []
|
|
219
|
+
for label, section in ((f"wing:{wing}", wing_section), (f"kind:{node.kind}", kind_section)):
|
|
220
|
+
if section is None:
|
|
221
|
+
continue
|
|
222
|
+
check(SETTINGS_SCHEMA, section, where=f"{label} plugin_settings.{NAMESPACE}")
|
|
223
|
+
layers.append(_layer(label, section))
|
|
224
|
+
own = node.extensions.get(NAMESPACE)
|
|
225
|
+
if own is not None:
|
|
226
|
+
check(NODE_SETTINGS_SCHEMA, own, where=f"{key} spec.extensions.{NAMESPACE}")
|
|
227
|
+
layers.append(_layer(f"node:{key}", cast("Mapping[str, Any]", own)))
|
|
228
|
+
layers.extend(self._profile_layers(node, wing))
|
|
229
|
+
try:
|
|
230
|
+
views = views_from(
|
|
231
|
+
wing_section,
|
|
232
|
+
kind_section,
|
|
233
|
+
wing_label=f"wing:{wing}",
|
|
234
|
+
kind_label=f"kind:{node.kind}",
|
|
235
|
+
)
|
|
236
|
+
policy = fold(layers, views=sorted(views))
|
|
237
|
+
except PathSyntaxError as exc:
|
|
238
|
+
raise ContextError("VALIDATION_ERROR", str(exc)) from exc
|
|
239
|
+
return policy, views
|
|
240
|
+
|
|
241
|
+
def _profile_layers(self, node: GraphNode, wing: str) -> list[Layer]:
|
|
242
|
+
config = self._config
|
|
243
|
+
layers: list[Layer] = []
|
|
244
|
+
if config.get("policy") or config.get("defaults"):
|
|
245
|
+
layers.append(_layer("plugin config", config))
|
|
246
|
+
profiles = cast("Mapping[str, Mapping[str, Any]]", config.get("profiles") or {})
|
|
247
|
+
section = profiles.get(self._profile or "") if self._profile else None
|
|
248
|
+
if section is None:
|
|
249
|
+
return layers
|
|
250
|
+
label = f"profile:{self._profile}"
|
|
251
|
+
layers.append(_layer(label, section))
|
|
252
|
+
for index, rule in enumerate(cast("list[Mapping[str, Any]]", section.get("rules") or [])):
|
|
253
|
+
if _matches(cast("Mapping[str, Any]", rule.get("match") or {}), node, wing):
|
|
254
|
+
layers.append(_layer(f"{label}#rules[{index}]", rule))
|
|
255
|
+
return layers
|
|
256
|
+
|
|
257
|
+
# ── payloads ────────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
def map(self, address: str) -> dict[str, Any]:
|
|
260
|
+
"""Everything around the node at *address*: relationships, inheritance, views, options."""
|
|
261
|
+
key = self.resolve(address)
|
|
262
|
+
policy, views = self.policy_for(key)
|
|
263
|
+
return build_map(self._graph, self._schema, key, policy, views)
|
|
264
|
+
|
|
265
|
+
def query(self, address: str, request: ContextRequest) -> dict[str, Any]:
|
|
266
|
+
"""The items *request* asks for around the node at *address*."""
|
|
267
|
+
if request.format not in FORMATS:
|
|
268
|
+
raise ContextError(
|
|
269
|
+
"INVALID_INPUT", f"format must be json or markdown; got {request.format!r}"
|
|
270
|
+
)
|
|
271
|
+
for text in request.paths or ():
|
|
272
|
+
try:
|
|
273
|
+
parse_path(text)
|
|
274
|
+
except PathSyntaxError as exc:
|
|
275
|
+
raise ContextError("INVALID_INPUT", str(exc), {"path": text}) from exc
|
|
276
|
+
key = self.resolve(address)
|
|
277
|
+
policy, views = self.policy_for(key)
|
|
278
|
+
size = item_size if request.format == "markdown" else json_size
|
|
279
|
+
return build_result(self._graph, self._schema, key, policy, views, request, size_of=size)
|
|
280
|
+
|
|
281
|
+
def explain(self, address: str) -> dict[str, Any]:
|
|
282
|
+
"""The effective policy for the node at *address*: layers, limits and notes."""
|
|
283
|
+
key = self.resolve(address)
|
|
284
|
+
policy, views = self.policy_for(key)
|
|
285
|
+
return {
|
|
286
|
+
"node": key,
|
|
287
|
+
"effective": policy.describe(),
|
|
288
|
+
"views": {
|
|
289
|
+
name: {
|
|
290
|
+
"paths": view.path_texts,
|
|
291
|
+
"declaredBy": view.declared_by,
|
|
292
|
+
"title": view.title,
|
|
293
|
+
}
|
|
294
|
+
for name, view in sorted(views.items())
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
def markdown_for_map(self, payload: Mapping[str, Any]) -> str:
|
|
299
|
+
return render_map(payload)
|
|
300
|
+
|
|
301
|
+
def markdown_for_result(self, address: str, payload: Mapping[str, Any]) -> str:
|
|
302
|
+
key = self.resolve(address)
|
|
303
|
+
_, views = self.policy_for(key)
|
|
304
|
+
ancestors = [
|
|
305
|
+
{"qualifiedName": a, "kind": self._graph.nodes[a].kind}
|
|
306
|
+
for a in self._graph.ancestors(key, self._schema.parent_rel)
|
|
307
|
+
]
|
|
308
|
+
declared = {
|
|
309
|
+
name: {"title": v.title, "description": v.description} for name, v in views.items()
|
|
310
|
+
}
|
|
311
|
+
return render_result(payload, views=declared, ancestors=ancestors)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _layer(label: str, section: Mapping[str, Any]) -> Layer:
|
|
315
|
+
return Layer(
|
|
316
|
+
label=label,
|
|
317
|
+
policy=cast("Mapping[str, Any]", section.get("policy") or {}),
|
|
318
|
+
defaults=cast("Mapping[str, Any]", section.get("defaults") or {}),
|
|
319
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: katalyst-plugin-context
|
|
3
|
+
Version: 0.1.1a1
|
|
4
|
+
Summary: Katalyst context plugin — the right context from the taxonomy for an agent working on a node
|
|
5
|
+
Author: eSimplicity
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: click>=8.0
|
|
9
|
+
Requires-Dist: jsonschema>=4
|
|
10
|
+
Requires-Dist: katalyst-engine<6,>=5
|
|
11
|
+
Requires-Dist: katalyst-taxonomy-cli<3,>=2
|
|
12
|
+
Requires-Dist: katalyst-taxonomy-mcp<5,>=4
|
|
13
|
+
Requires-Dist: katalyst-taxonomy<6,>=5
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
katalyst_plugin_context/__init__.py,sha256=L3hMpS4HTRZa1wuRAOLcmhuwTMkcGuKhwS8gM12zMZQ,306
|
|
2
|
+
katalyst_plugin_context/cli.py,sha256=ZULcaiaKuKmkXhtGjv-mnK_k8GF4EmiFvaubtzoojzY,4889
|
|
3
|
+
katalyst_plugin_context/composition.py,sha256=FdlswZYtQzNwAHbjlQ-J1DQM7IGFyUM6sujAgRnGa4w,1980
|
|
4
|
+
katalyst_plugin_context/lint.py,sha256=ag7sr-QNcgRg6-npC8pVD9f9idySzlFvSR_dxQWsIdw,6258
|
|
5
|
+
katalyst_plugin_context/mcp_tools.py,sha256=L8ITnVb4Yzxk8MCCBwe5llvNIbcDyBJU5lD1dosrjZQ,6035
|
|
6
|
+
katalyst_plugin_context/plugin.py,sha256=yrw7bT0X4TCJRMVJlxJXShCQnp7DKSJeZqEZalVXXZk,2432
|
|
7
|
+
katalyst_plugin_context/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
katalyst_plugin_context/rest.py,sha256=5GcLdfEaY7ntbwevt6op1b82pDJGvAOMlGCGLclgXTs,5898
|
|
9
|
+
katalyst_plugin_context/schema.py,sha256=vI_tfLmVtuioCv_KgXmiqKSx1sa2wLvcrYjunFBNLsk,6755
|
|
10
|
+
katalyst_plugin_context/service.py,sha256=sQttBS95ZmfXeTwOVOWe517piofwo9Y5Z2DcEnCOAMU,13375
|
|
11
|
+
katalyst_plugin_context/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
katalyst_plugin_context/adapters/runtime_graph.py,sha256=gOqCsC_zs1glLcMhZEhCZOsG1-uomhDqDnYzmXjuCcU,4847
|
|
13
|
+
katalyst_plugin_context/domain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
katalyst_plugin_context/domain/context.py,sha256=xe-7ASwIsnagrC53o4QxtJeOOU2rvMNQ3CItLkfbbIQ,27411
|
|
15
|
+
katalyst_plugin_context/domain/graph.py,sha256=bknXgiof25-2j4I2GPsIv-u0s76LqzHajsXCU5utYPA,3425
|
|
16
|
+
katalyst_plugin_context/domain/markdown.py,sha256=KbHhg8VWQuVEU7t1LT26Ie6cSEJfhkJCiP-o8XGaaBw,8779
|
|
17
|
+
katalyst_plugin_context/domain/paths.py,sha256=VhVHEkPdfi-gdD4SmnjSX3ybUddM0Lw64zprR9u9CJ4,3830
|
|
18
|
+
katalyst_plugin_context/domain/policy.py,sha256=8yLiYrhBSoIs3YKTTgDb5MjfF3dXWh-MirNJ65QfR7Q,13771
|
|
19
|
+
katalyst_plugin_context/domain/settings.py,sha256=PH-t52j3s-X0bOl5vcc9ZMqvxW2CTUa0nyqW-hrnens,3153
|
|
20
|
+
katalyst_plugin_context-0.1.1a1.dist-info/METADATA,sha256=qf13Dtxc0hOySDIRYUm-k8bdhe95HjTlVFpGTkHB5VI,455
|
|
21
|
+
katalyst_plugin_context-0.1.1a1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
22
|
+
katalyst_plugin_context-0.1.1a1.dist-info/entry_points.txt,sha256=0kZT_Rlbx8YcvLb2L_Heo4JPILmpERHySD-op6LDvpc,154
|
|
23
|
+
katalyst_plugin_context-0.1.1a1.dist-info/RECORD,,
|