kaiten-cli 0.3.3__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 (99) hide show
  1. kaiten_cli/__init__.py +5 -0
  2. kaiten_cli/__main__.py +9 -0
  3. kaiten_cli/agent_gateway.py +577 -0
  4. kaiten_cli/app.py +1636 -0
  5. kaiten_cli/completion.py +570 -0
  6. kaiten_cli/discovery.py +39 -0
  7. kaiten_cli/errors.py +79 -0
  8. kaiten_cli/i18n.py +92 -0
  9. kaiten_cli/locales/ru.json +2967 -0
  10. kaiten_cli/localized_click.py +160 -0
  11. kaiten_cli/models.py +202 -0
  12. kaiten_cli/profiles.py +316 -0
  13. kaiten_cli/registry/__init__.py +260 -0
  14. kaiten_cli/registry/addons.py +392 -0
  15. kaiten_cli/registry/audit_and_analytics.py +451 -0
  16. kaiten_cli/registry/automation_schema.py +44 -0
  17. kaiten_cli/registry/automation_schemas.json +7417 -0
  18. kaiten_cli/registry/automations.py +342 -0
  19. kaiten_cli/registry/base.py +62 -0
  20. kaiten_cli/registry/blockers.py +320 -0
  21. kaiten_cli/registry/boards.py +520 -0
  22. kaiten_cli/registry/card_relations.py +376 -0
  23. kaiten_cli/registry/card_types.py +363 -0
  24. kaiten_cli/registry/cards.py +1329 -0
  25. kaiten_cli/registry/charts.py +539 -0
  26. kaiten_cli/registry/checklists.py +600 -0
  27. kaiten_cli/registry/columns.py +558 -0
  28. kaiten_cli/registry/comments.py +224 -0
  29. kaiten_cli/registry/custom_directories.py +980 -0
  30. kaiten_cli/registry/custom_properties.py +1460 -0
  31. kaiten_cli/registry/dashboards.py +564 -0
  32. kaiten_cli/registry/documents.py +761 -0
  33. kaiten_cli/registry/external_links.py +128 -0
  34. kaiten_cli/registry/files.py +738 -0
  35. kaiten_cli/registry/github_addon.py +613 -0
  36. kaiten_cli/registry/iterations.py +397 -0
  37. kaiten_cli/registry/lanes.py +205 -0
  38. kaiten_cli/registry/live_contracts.py +361 -0
  39. kaiten_cli/registry/members.py +336 -0
  40. kaiten_cli/registry/module_docs.py +108 -0
  41. kaiten_cli/registry/projects.py +369 -0
  42. kaiten_cli/registry/query.py +129 -0
  43. kaiten_cli/registry/restrictions.py +249 -0
  44. kaiten_cli/registry/roles_and_groups.py +1102 -0
  45. kaiten_cli/registry/scim.py +263 -0
  46. kaiten_cli/registry/service_desk.py +1270 -0
  47. kaiten_cli/registry/snapshot.py +187 -0
  48. kaiten_cli/registry/spaces.py +272 -0
  49. kaiten_cli/registry/subscribers.py +168 -0
  50. kaiten_cli/registry/tags.py +193 -0
  51. kaiten_cli/registry/time_logs.py +353 -0
  52. kaiten_cli/registry/tree.py +352 -0
  53. kaiten_cli/registry/utilities.py +287 -0
  54. kaiten_cli/registry/webhooks.py +265 -0
  55. kaiten_cli/runtime/__init__.py +1 -0
  56. kaiten_cli/runtime/automation_validation.py +115 -0
  57. kaiten_cli/runtime/behaviors.py +787 -0
  58. kaiten_cli/runtime/cache.py +733 -0
  59. kaiten_cli/runtime/client.py +387 -0
  60. kaiten_cli/runtime/endpoints.py +59 -0
  61. kaiten_cli/runtime/executor.py +269 -0
  62. kaiten_cli/runtime/fs_security.py +168 -0
  63. kaiten_cli/runtime/input.py +310 -0
  64. kaiten_cli/runtime/output.py +59 -0
  65. kaiten_cli/runtime/public_validation.py +35 -0
  66. kaiten_cli/runtime/snapshot_build.py +527 -0
  67. kaiten_cli/runtime/snapshot_common.py +363 -0
  68. kaiten_cli/runtime/snapshot_query.py +306 -0
  69. kaiten_cli/runtime/snapshot_store.py +1192 -0
  70. kaiten_cli/runtime/snapshots.py +62 -0
  71. kaiten_cli/runtime/sqlite_errors.py +23 -0
  72. kaiten_cli/runtime/support/__init__.py +1 -0
  73. kaiten_cli/runtime/support/addons.py +953 -0
  74. kaiten_cli/runtime/support/audit.py +70 -0
  75. kaiten_cli/runtime/support/batch.py +234 -0
  76. kaiten_cli/runtime/support/card_move_url.py +213 -0
  77. kaiten_cli/runtime/support/cards.py +174 -0
  78. kaiten_cli/runtime/support/checklists.py +21 -0
  79. kaiten_cli/runtime/support/company_users.py +117 -0
  80. kaiten_cli/runtime/support/dashboards.py +35 -0
  81. kaiten_cli/runtime/support/documents.py +175 -0
  82. kaiten_cli/runtime/support/files.py +561 -0
  83. kaiten_cli/runtime/support/markdown_export.py +525 -0
  84. kaiten_cli/runtime/support/pagination.py +167 -0
  85. kaiten_cli/runtime/support/projects.py +66 -0
  86. kaiten_cli/runtime/support/relations.py +86 -0
  87. kaiten_cli/runtime/support/restrictions.py +62 -0
  88. kaiten_cli/runtime/support/spaces.py +20 -0
  89. kaiten_cli/runtime/support/time_logs.py +61 -0
  90. kaiten_cli/runtime/support/tree.py +140 -0
  91. kaiten_cli/runtime/support/tree_sharing.py +447 -0
  92. kaiten_cli/runtime/trace.py +500 -0
  93. kaiten_cli/runtime/transforms.py +128 -0
  94. kaiten_cli/schema_docs.py +22 -0
  95. kaiten_cli/update_check.py +519 -0
  96. kaiten_cli-0.3.3.dist-info/METADATA +1110 -0
  97. kaiten_cli-0.3.3.dist-info/RECORD +99 -0
  98. kaiten_cli-0.3.3.dist-info/WHEEL +4 -0
  99. kaiten_cli-0.3.3.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,260 @@
1
+ """Tool registry and discovery helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import difflib
6
+ from typing import Any
7
+
8
+ from kaiten_cli.i18n import translate
9
+ from kaiten_cli.models import (
10
+ CACHE_POLICY_NONE,
11
+ CACHE_POLICY_PERSISTENT_HEAVY,
12
+ CACHE_POLICY_PERSISTENT_OPT_IN,
13
+ CACHE_POLICY_REQUEST_SCOPE,
14
+ ToolSpec,
15
+ example_commands,
16
+ format_schema_type,
17
+ )
18
+ from kaiten_cli.registry.addons import TOOLS as ADDON_TOOLS
19
+ from kaiten_cli.registry.audit_and_analytics import TOOLS as AUDIT_AND_ANALYTICS_TOOLS
20
+ from kaiten_cli.registry.automations import TOOLS as AUTOMATION_TOOLS
21
+ from kaiten_cli.registry.blockers import TOOLS as BLOCKER_TOOLS
22
+ from kaiten_cli.registry.boards import TOOLS as BOARD_TOOLS
23
+ from kaiten_cli.registry.card_relations import TOOLS as CARD_RELATION_TOOLS
24
+ from kaiten_cli.registry.card_types import TOOLS as CARD_TYPE_TOOLS
25
+ from kaiten_cli.registry.cards import TOOLS as CARD_TOOLS
26
+ from kaiten_cli.registry.charts import TOOLS as CHART_TOOLS
27
+ from kaiten_cli.registry.checklists import TOOLS as CHECKLIST_TOOLS
28
+ from kaiten_cli.registry.columns import TOOLS as COLUMN_TOOLS
29
+ from kaiten_cli.registry.comments import TOOLS as COMMENT_TOOLS
30
+ from kaiten_cli.registry.custom_directories import TOOLS as CUSTOM_DIRECTORY_TOOLS
31
+ from kaiten_cli.registry.custom_properties import TOOLS as CUSTOM_PROPERTY_TOOLS
32
+ from kaiten_cli.registry.dashboards import TOOLS as DASHBOARD_TOOLS
33
+ from kaiten_cli.registry.documents import TOOLS as DOCUMENT_TOOLS
34
+ from kaiten_cli.registry.external_links import TOOLS as EXTERNAL_LINK_TOOLS
35
+ from kaiten_cli.registry.files import TOOLS as FILE_TOOLS
36
+ from kaiten_cli.registry.github_addon import TOOLS as GITHUB_ADDON_TOOLS
37
+ from kaiten_cli.registry.iterations import TOOLS as ITERATION_TOOLS
38
+ from kaiten_cli.registry.lanes import TOOLS as LANE_TOOLS
39
+ from kaiten_cli.registry.live_contracts import get_live_contract, has_special_live_contract
40
+ from kaiten_cli.registry.members import TOOLS as MEMBER_TOOLS
41
+ from kaiten_cli.registry.projects import TOOLS as PROJECT_TOOLS
42
+ from kaiten_cli.registry.query import TOOLS as QUERY_TOOLS
43
+ from kaiten_cli.registry.roles_and_groups import TOOLS as ROLE_AND_GROUP_TOOLS
44
+ from kaiten_cli.registry.restrictions import TOOLS as RESTRICTION_TOOLS
45
+ from kaiten_cli.registry.scim import TOOLS as SCIM_TOOLS
46
+ from kaiten_cli.registry.service_desk import TOOLS as SERVICE_DESK_TOOLS
47
+ from kaiten_cli.registry.snapshot import TOOLS as SNAPSHOT_TOOLS
48
+ from kaiten_cli.registry.spaces import TOOLS as SPACE_TOOLS
49
+ from kaiten_cli.registry.subscribers import TOOLS as SUBSCRIBER_TOOLS
50
+ from kaiten_cli.registry.tags import TOOLS as TAG_TOOLS
51
+ from kaiten_cli.registry.time_logs import TOOLS as TIME_LOG_TOOLS
52
+ from kaiten_cli.registry.tree import TOOLS as TREE_TOOLS
53
+ from kaiten_cli.registry.utilities import TOOLS as UTILITY_TOOLS
54
+ from kaiten_cli.registry.webhooks import TOOLS as WEBHOOK_TOOLS
55
+
56
+ REGISTRY_MODULES: tuple[tuple[str, tuple[ToolSpec, ...]], ...] = (
57
+ ("spaces", SPACE_TOOLS),
58
+ ("automations", AUTOMATION_TOOLS),
59
+ ("restrictions", RESTRICTION_TOOLS),
60
+ ("addons", ADDON_TOOLS),
61
+ ("github_addon", GITHUB_ADDON_TOOLS),
62
+ ("boards", BOARD_TOOLS),
63
+ ("cards", CARD_TOOLS),
64
+ ("card_types", CARD_TYPE_TOOLS),
65
+ ("charts", CHART_TOOLS),
66
+ ("blockers", BLOCKER_TOOLS),
67
+ ("card_relations", CARD_RELATION_TOOLS),
68
+ ("columns", COLUMN_TOOLS),
69
+ ("lanes", LANE_TOOLS),
70
+ ("checklists", CHECKLIST_TOOLS),
71
+ ("comments", COMMENT_TOOLS),
72
+ ("custom_directories", CUSTOM_DIRECTORY_TOOLS),
73
+ ("custom_properties", CUSTOM_PROPERTY_TOOLS),
74
+ ("documents", DOCUMENT_TOOLS),
75
+ ("dashboards", DASHBOARD_TOOLS),
76
+ ("external_links", EXTERNAL_LINK_TOOLS),
77
+ ("files", FILE_TOOLS),
78
+ ("iterations", ITERATION_TOOLS),
79
+ ("tags", TAG_TOOLS),
80
+ ("members", MEMBER_TOOLS),
81
+ ("projects", PROJECT_TOOLS),
82
+ ("query", QUERY_TOOLS),
83
+ ("roles_and_groups", ROLE_AND_GROUP_TOOLS),
84
+ ("scim", SCIM_TOOLS),
85
+ ("service_desk", SERVICE_DESK_TOOLS),
86
+ ("audit_and_analytics", AUDIT_AND_ANALYTICS_TOOLS),
87
+ ("snapshot", SNAPSHOT_TOOLS),
88
+ ("time_logs", TIME_LOG_TOOLS),
89
+ ("subscribers", SUBSCRIBER_TOOLS),
90
+ ("tree", TREE_TOOLS),
91
+ ("utilities", UTILITY_TOOLS),
92
+ ("webhooks", WEBHOOK_TOOLS),
93
+ )
94
+ TOOL_SET: tuple[ToolSpec, ...] = tuple(
95
+ tool for _, module_tools in REGISTRY_MODULES for tool in module_tools
96
+ )
97
+ TOOLS_BY_CANONICAL: dict[str, ToolSpec] = {tool.canonical_name: tool for tool in TOOL_SET}
98
+ TOOLS_BY_ALIAS: dict[str, ToolSpec] = {tool.mcp_alias: tool for tool in TOOL_SET}
99
+
100
+
101
+ def iter_tools() -> tuple[ToolSpec, ...]:
102
+ return TOOL_SET
103
+
104
+
105
+ def iter_module_tools() -> tuple[tuple[str, tuple[ToolSpec, ...]], ...]:
106
+ return REGISTRY_MODULES
107
+
108
+
109
+ def resolve_tool(identifier: str) -> ToolSpec:
110
+ if identifier in TOOLS_BY_CANONICAL:
111
+ return TOOLS_BY_CANONICAL[identifier]
112
+ if identifier in TOOLS_BY_ALIAS:
113
+ return TOOLS_BY_ALIAS[identifier]
114
+ raise KeyError(identifier)
115
+
116
+
117
+ def search(query: str, limit: int = 5) -> list[ToolSpec]:
118
+ query_lc = query.strip().lower()
119
+ scores: list[tuple[float, ToolSpec]] = []
120
+ for tool in TOOL_SET:
121
+ haystack_parts = [
122
+ tool.canonical_name,
123
+ tool.mcp_alias,
124
+ tool.namespace,
125
+ tool.action,
126
+ tool.description,
127
+ tool.bulk_alternative or "",
128
+ " ".join(tool.usage_notes),
129
+ " ".join(tool.search_terms),
130
+ translate(tool.description, locale="ru"),
131
+ " ".join(example.command for example in tool.examples),
132
+ " ".join(example.description for example in tool.examples),
133
+ ]
134
+ haystack = " ".join(haystack_parts).lower()
135
+ score = 0.0
136
+ if tool.canonical_name == query_lc or tool.mcp_alias == query_lc:
137
+ score += 100.0
138
+ if query_lc in tool.canonical_name:
139
+ score += 30.0
140
+ if query_lc in tool.mcp_alias:
141
+ score += 25.0
142
+ tokens = [token for token in query_lc.replace("-", " ").replace("_", " ").split() if token]
143
+ score += sum(8.0 for token in tokens if token in haystack)
144
+ score += difflib.SequenceMatcher(a=query_lc, b=haystack).ratio() * 10.0
145
+ if score > 0:
146
+ scores.append((score, tool))
147
+ return [
148
+ tool
149
+ for _, tool in sorted(scores, key=lambda item: (-item[0], item[1].canonical_name))[:limit]
150
+ ]
151
+
152
+
153
+ def examples_for(identifier: str) -> list[str]:
154
+ tool = resolve_tool(identifier)
155
+ return example_commands(tool.examples)
156
+
157
+
158
+ def cache_guidance_for(tool: ToolSpec) -> dict[str, Any]:
159
+ common = {
160
+ "default_mode": "auto",
161
+ "available_modes": ["auto", "off", "readwrite", "refresh"],
162
+ "recommended_mode": "auto",
163
+ "off_hint": (
164
+ "Use --cache-mode off only for cache debugging, privacy-sensitive reads, "
165
+ "or high-churn polling."
166
+ ),
167
+ "readwrite_hint": (
168
+ "Use --cache-mode readwrite with an explicit --cache-ttl-seconds value "
169
+ "when a fixed TTL is required."
170
+ ),
171
+ }
172
+ if tool.cache_policy == CACHE_POLICY_NONE:
173
+ return {
174
+ **common,
175
+ "strategy": "none",
176
+ "guidance": "This command does not use persistent cache; use it for mutations, polling, downloads, or local-only reads.",
177
+ "refresh_hint": "No cache refresh is needed.",
178
+ }
179
+ if tool.cache_policy == CACHE_POLICY_PERSISTENT_HEAVY:
180
+ return {
181
+ **common,
182
+ "strategy": "heavy_persistent",
183
+ "guidance": "Default auto mode stores this expensive read path in persistent cache with a long adaptive TTL, especially for closed historical windows and repeated analytics.",
184
+ "refresh_hint": "Use --cache-mode refresh once when the same heavy window must be rebuilt from Kaiten API; do not put refresh inside a loop.",
185
+ }
186
+ if tool.cache_policy == CACHE_POLICY_PERSISTENT_OPT_IN:
187
+ return {
188
+ **common,
189
+ "strategy": "entity_or_reference_persistent",
190
+ "guidance": "Default auto mode reuses persistent cache for repeated safe entity/reference reads and extends dense same-family loops.",
191
+ "refresh_hint": "Use --cache-mode refresh once to force a fresh API read and rewrite the cache; do not put refresh inside an entity loop.",
192
+ }
193
+ if tool.cache_policy == CACHE_POLICY_REQUEST_SCOPE:
194
+ return {
195
+ **common,
196
+ "strategy": "request_scope",
197
+ "guidance": "Identical safe GETs are deduplicated inside one CLI execution; use bulk/snapshot tools for repeated cross-process analytics.",
198
+ "refresh_hint": "No disk cache is read by default for this command.",
199
+ }
200
+ return {
201
+ **common,
202
+ "strategy": tool.cache_policy,
203
+ "guidance": "Check command notes for cache behavior.",
204
+ "refresh_hint": "Use --cache-mode refresh only if the command supports persistent cache.",
205
+ }
206
+
207
+
208
+ def describe(identifier: str) -> dict:
209
+ tool = resolve_tool(identifier)
210
+ properties = tool.input_schema.get("properties", {})
211
+ required = set(tool.input_schema.get("required", []))
212
+ payload = {
213
+ "canonical_name": tool.canonical_name,
214
+ "mcp_alias": tool.mcp_alias,
215
+ "description": tool.description,
216
+ "method": tool.operation.method,
217
+ "mutation": tool.is_mutation,
218
+ "read_only_allowed": tool.read_only_allowed,
219
+ "remote_side_effects": tool.remote_side_effects,
220
+ "execution_mode": tool.execution_mode,
221
+ "cache_policy": tool.cache_policy,
222
+ "cache_guidance": cache_guidance_for(tool),
223
+ "path_template": tool.operation.path_template,
224
+ "input_modes": ["options", "from_file", "stdin_json"],
225
+ "input_schema": tool.input_schema,
226
+ "response_policy": {
227
+ "compact_supported": tool.response_policy.compact_supported,
228
+ "fields_supported": tool.response_policy.fields_supported,
229
+ "default_limit": tool.response_policy.default_limit,
230
+ "heavy": tool.response_policy.heavy,
231
+ "result_kind": tool.response_policy.result_kind,
232
+ "compact_default": tool.runtime_behavior.compact_default,
233
+ },
234
+ "arguments": [
235
+ {
236
+ "name": name,
237
+ "required": name in required,
238
+ "type": definition.get("type"),
239
+ "type_display": format_schema_type(definition),
240
+ "enum": definition.get("enum"),
241
+ "minimum": definition.get("minimum"),
242
+ "maximum": definition.get("maximum"),
243
+ "description": definition.get("description", ""),
244
+ }
245
+ for name, definition in properties.items()
246
+ ],
247
+ "examples": examples_for(identifier),
248
+ }
249
+ if has_special_live_contract(tool.canonical_name):
250
+ contract = get_live_contract(tool.canonical_name)
251
+ payload["live_contract"] = {
252
+ "status": contract.status,
253
+ "note": contract.note,
254
+ "expected_statuses": list(contract.expected_statuses),
255
+ }
256
+ if tool.usage_notes:
257
+ payload["usage_notes"] = list(tool.usage_notes)
258
+ if tool.bulk_alternative is not None:
259
+ payload["bulk_alternative"] = tool.bulk_alternative
260
+ return payload
@@ -0,0 +1,392 @@
1
+ """Addon catalog, space installation and per-addon data tool specs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from kaiten_cli.models import ExampleSpec, OperationSpec, RuntimeBehavior
6
+ from kaiten_cli.registry.base import (
7
+ PLAIN_ENTITY_POLICY,
8
+ PLAIN_LIST_POLICY,
9
+ SHAPED_LIST_POLICY,
10
+ make_tool,
11
+ shaping_properties,
12
+ )
13
+ from kaiten_cli.runtime.support.addons import execute_addon_uid, validate_addon_uid_payload
14
+
15
+ ADDON_UID_NOTE = (
16
+ "addon_uid is the addon's UUID, not its name. Take it from space-addons.list for the "
17
+ "space you are working in, or from company-addons.list; addons.list only shows the "
18
+ "published catalog and omits an addon registered privately by a company."
19
+ )
20
+ UID_DERIVATION_NOTE = (
21
+ "Deriving the UUID from a mount path is a guess: Kaiten stamps a derived UUID only on "
22
+ "on-premises installations whose addon iframe path is non-empty, and stores a random "
23
+ "UUID otherwise. Verify it against space-addons.list before relying on it."
24
+ )
25
+ SHARED_SCOPE_NOTE = (
26
+ "Shared data is one row per card visible to everyone (writing it needs card.update); "
27
+ "private data is a per-user row that only its owner reads and writes (card.read is enough)."
28
+ )
29
+ SHALLOW_MERGE_NOTE = (
30
+ "The server shallow-merges data over the stored row by top-level key, so send the full "
31
+ "replacement value for every key you set and omit keys you want to keep untouched."
32
+ )
33
+ ADDON_INSTALL_NOTE = (
34
+ "The addon must be installed in the card's space before its per-card data can be written; "
35
+ "otherwise the shared write is rejected with a permission error."
36
+ )
37
+
38
+ ADDON_UID_VALIDATION = RuntimeBehavior(payload_validator=validate_addon_uid_payload)
39
+ # An addon's data blob is opaque third-party JSON. The generic transforms walk
40
+ # into it - compact drops `description`, base64 stripping rewrites `data:` values -
41
+ # and this read is the documented input of a read-modify-write cycle, so a shaped
42
+ # answer would be written back as real data loss.
43
+ RAW_ADDON_DATA_READ = RuntimeBehavior(
44
+ payload_validator=validate_addon_uid_payload,
45
+ apply_common_transforms=False,
46
+ )
47
+
48
+
49
+ TOOLS = (
50
+ make_tool(
51
+ canonical_name="addons.list",
52
+ mcp_alias="kaiten_list_addons",
53
+ description="List the published Kaiten addon catalog.",
54
+ input_schema={
55
+ "type": "object",
56
+ "properties": {**shaping_properties()},
57
+ },
58
+ operation=OperationSpec(method="GET", path_template="/addons"),
59
+ response_policy=SHAPED_LIST_POLICY,
60
+ examples=(
61
+ ExampleSpec(
62
+ command="kaiten --json addons list --fields id,name",
63
+ description="Find the UUID of a published addon by its name.",
64
+ ),
65
+ ),
66
+ usage_notes=(
67
+ (
68
+ "The endpoint returns every non-archived addon with status published, without a "
69
+ "company filter; an addon a company registered privately is not in this list."
70
+ ),
71
+ ADDON_UID_NOTE,
72
+ ),
73
+ ),
74
+ make_tool(
75
+ canonical_name="addons.uid",
76
+ mcp_alias="kaiten_derive_addon_uid",
77
+ description="Derive an addon UUID locally from its mount path, without calling Kaiten.",
78
+ input_schema={
79
+ "type": "object",
80
+ "properties": {
81
+ "url_path": {
82
+ "type": "string",
83
+ "description": "Addon mount path, for example /github.",
84
+ },
85
+ },
86
+ "required": ["url_path"],
87
+ },
88
+ operation=OperationSpec(method="GET", path_template="/local/addons/uid"),
89
+ response_policy=PLAIN_ENTITY_POLICY,
90
+ runtime_behavior=RuntimeBehavior(
91
+ execution_mode="custom",
92
+ custom_executor=execute_addon_uid,
93
+ cache_policy="none",
94
+ requires_profile=False,
95
+ ),
96
+ examples=(
97
+ ExampleSpec(
98
+ command="kaiten --json addons uid --url-path /github",
99
+ description="Derive the GitHub addon UUID used by the addons-data endpoints.",
100
+ ),
101
+ ),
102
+ usage_notes=(
103
+ (
104
+ "Local computation only: UUID v5 of the normalized path under the fixed Kaiten "
105
+ "addons namespace, the same derivation the platform uses."
106
+ ),
107
+ UID_DERIVATION_NOTE,
108
+ (
109
+ "The platform derives from the path of the addon's iframe_initial_url, so pass "
110
+ "that path: an addon served from https://host/github/index.html derives from "
111
+ "/github/index.html, not /github."
112
+ ),
113
+ ),
114
+ ),
115
+ make_tool(
116
+ canonical_name="company-addons.list",
117
+ mcp_alias="kaiten_list_company_addons",
118
+ description="List the addons registered by the current company, published or not.",
119
+ input_schema={
120
+ "type": "object",
121
+ "properties": {**shaping_properties()},
122
+ },
123
+ operation=OperationSpec(method="GET", path_template="/company/addons"),
124
+ response_policy=SHAPED_LIST_POLICY,
125
+ examples=(
126
+ ExampleSpec(
127
+ command="kaiten --json company-addons list --fields id,name,status,iframe_initial_url",
128
+ description="Find the UUID of an addon the company registered itself.",
129
+ ),
130
+ ),
131
+ usage_notes=(
132
+ (
133
+ "This is where a privately registered addon lives; addons.list only covers the "
134
+ "published catalog."
135
+ ),
136
+ ADDON_UID_NOTE,
137
+ ),
138
+ ),
139
+ make_tool(
140
+ canonical_name="space-addons.list",
141
+ mcp_alias="kaiten_list_space_addons",
142
+ description="List addons installed in a Kaiten space.",
143
+ input_schema={
144
+ "type": "object",
145
+ "properties": {
146
+ "space_id": {"type": "integer", "description": "Space ID"},
147
+ **shaping_properties(),
148
+ },
149
+ "required": ["space_id"],
150
+ },
151
+ operation=OperationSpec(
152
+ method="GET",
153
+ path_template="/spaces/{space_id}/addons",
154
+ path_fields=("space_id",),
155
+ ),
156
+ response_policy=SHAPED_LIST_POLICY,
157
+ examples=(
158
+ ExampleSpec(
159
+ command="kaiten --json space-addons list --space-id 1 --fields id,name",
160
+ description="Check which addons a space has installed.",
161
+ ),
162
+ ),
163
+ usage_notes=(ADDON_UID_NOTE,),
164
+ ),
165
+ make_tool(
166
+ canonical_name="space-addons.install",
167
+ mcp_alias="kaiten_install_space_addon",
168
+ description="Install an addon into a Kaiten space or update its space settings.",
169
+ input_schema={
170
+ "type": "object",
171
+ "properties": {
172
+ "space_id": {"type": "integer", "description": "Space ID"},
173
+ "addon_uid": {"type": "string", "description": "Addon UUID"},
174
+ "settings": {
175
+ "type": "object",
176
+ "description": "Space-level addon settings object; omit to only install.",
177
+ },
178
+ },
179
+ "required": ["space_id", "addon_uid"],
180
+ },
181
+ operation=OperationSpec(
182
+ method="PATCH",
183
+ path_template="/spaces/{space_id}/addons/{addon_uid}",
184
+ path_fields=("space_id", "addon_uid"),
185
+ body_fields=("settings",),
186
+ ),
187
+ response_policy=PLAIN_ENTITY_POLICY,
188
+ runtime_behavior=ADDON_UID_VALIDATION,
189
+ examples=(
190
+ ExampleSpec(
191
+ command="kaiten --json space-addons install --space-id 1 --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990",
192
+ description="Install the GitHub addon into a space.",
193
+ ),
194
+ ),
195
+ usage_notes=(
196
+ ADDON_UID_NOTE,
197
+ (
198
+ "Installing an already installed addon without settings is rejected as a no-op; "
199
+ "pass settings when you only need to update configuration."
200
+ ),
201
+ ),
202
+ ),
203
+ make_tool(
204
+ canonical_name="space-addons.uninstall",
205
+ mcp_alias="kaiten_uninstall_space_addon",
206
+ description="Remove an addon from a Kaiten space.",
207
+ input_schema={
208
+ "type": "object",
209
+ "properties": {
210
+ "space_id": {"type": "integer", "description": "Space ID"},
211
+ "addon_uid": {"type": "string", "description": "Addon UUID"},
212
+ },
213
+ "required": ["space_id", "addon_uid"],
214
+ },
215
+ operation=OperationSpec(
216
+ method="DELETE",
217
+ path_template="/spaces/{space_id}/addons/{addon_uid}",
218
+ path_fields=("space_id", "addon_uid"),
219
+ ),
220
+ response_policy=PLAIN_ENTITY_POLICY,
221
+ runtime_behavior=ADDON_UID_VALIDATION,
222
+ examples=(
223
+ ExampleSpec(
224
+ command="kaiten --json space-addons uninstall --space-id 1 --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990",
225
+ description="Detach an addon from a space.",
226
+ ),
227
+ ),
228
+ usage_notes=(
229
+ (
230
+ "Uninstalling hides the addon in that space; per-card data rows written earlier are "
231
+ "not deleted by this call."
232
+ ),
233
+ ),
234
+ ),
235
+ make_tool(
236
+ canonical_name="card-addon-data.get",
237
+ mcp_alias="kaiten_get_card_addon_data",
238
+ description="Read the addon data rows stored on a card for one addon.",
239
+ input_schema={
240
+ "type": "object",
241
+ "properties": {
242
+ "card_id": {"type": "integer", "description": "Card ID"},
243
+ "addon_uid": {"type": "string", "description": "Addon UUID"},
244
+ },
245
+ "required": ["card_id", "addon_uid"],
246
+ },
247
+ operation=OperationSpec(
248
+ method="GET",
249
+ path_template="/cards/{card_id}/addons-data/{addon_uid}",
250
+ path_fields=("card_id", "addon_uid"),
251
+ ),
252
+ response_policy=PLAIN_LIST_POLICY,
253
+ runtime_behavior=RAW_ADDON_DATA_READ,
254
+ examples=(
255
+ ExampleSpec(
256
+ command="kaiten --json card-addon-data get --card-id 10 --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990",
257
+ description="Read the GitHub addon state stored on a card.",
258
+ ),
259
+ ),
260
+ usage_notes=(
261
+ ADDON_UID_NOTE,
262
+ (
263
+ "Returns every row the current user may see: the shared row (user_uid is null) plus "
264
+ "their own private row, if either exists."
265
+ ),
266
+ SHARED_SCOPE_NOTE,
267
+ ),
268
+ ),
269
+ make_tool(
270
+ canonical_name="card-addon-data.set",
271
+ mcp_alias="kaiten_set_card_addon_data",
272
+ description="Write addon data on a card in the shared or private scope.",
273
+ input_schema={
274
+ "type": "object",
275
+ "properties": {
276
+ "card_id": {"type": "integer", "description": "Card ID"},
277
+ "addon_uid": {"type": "string", "description": "Addon UUID"},
278
+ "type": {
279
+ "type": "string",
280
+ "enum": ["shared", "private"],
281
+ "description": "Data scope: shared for the whole card, private for the current user.",
282
+ },
283
+ "data": {
284
+ "type": "object",
285
+ "description": "Addon data object merged over the stored row by top-level key.",
286
+ },
287
+ },
288
+ "required": ["card_id", "addon_uid", "type", "data"],
289
+ },
290
+ operation=OperationSpec(
291
+ method="PATCH",
292
+ path_template="/cards/{card_id}/addons-data/{addon_uid}",
293
+ path_fields=("card_id", "addon_uid"),
294
+ body_fields=("type", "data"),
295
+ ),
296
+ response_policy=PLAIN_ENTITY_POLICY,
297
+ runtime_behavior=ADDON_UID_VALIDATION,
298
+ examples=(
299
+ ExampleSpec(
300
+ command="kaiten --json card-addon-data set --card-id 10 --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990 --type shared --data '{\"attachedPulls\": []}'",
301
+ description="Replace one addon key on a card.",
302
+ ),
303
+ ExampleSpec(
304
+ command="kaiten --json card-addon-data set --card-id 10 --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990 --type shared --data @payload.json",
305
+ description="Write an addon data object from a file.",
306
+ ),
307
+ ),
308
+ usage_notes=(
309
+ ADDON_UID_NOTE,
310
+ SHALLOW_MERGE_NOTE,
311
+ SHARED_SCOPE_NOTE,
312
+ ADDON_INSTALL_NOTE,
313
+ (
314
+ "The shared row has no version or ETag, so a read-modify-write races with the "
315
+ "addon UI and with another CLI run; keep the read and the write close together "
316
+ "and re-read before retrying."
317
+ ),
318
+ (
319
+ "This is also the repair path: the github-addon commands refuse to rewrite a key "
320
+ "whose stored value is not a plain list of objects, and this command can set it "
321
+ "back to a shape they accept."
322
+ ),
323
+ (
324
+ "For the GitHub addon prefer the github-addon commands: they keep the exact widget "
325
+ "payload shape and dedup attachments instead of overwriting the whole key."
326
+ ),
327
+ ),
328
+ ),
329
+ make_tool(
330
+ canonical_name="user-addon-data.get",
331
+ mcp_alias="kaiten_get_user_addon_data",
332
+ description="Read the current user's company-level addon data for one addon.",
333
+ input_schema={
334
+ "type": "object",
335
+ "properties": {
336
+ "addon_uid": {"type": "string", "description": "Addon UUID"},
337
+ },
338
+ "required": ["addon_uid"],
339
+ },
340
+ operation=OperationSpec(
341
+ method="GET",
342
+ path_template="/company/users/current/addons-data/{addon_uid}",
343
+ path_fields=("addon_uid",),
344
+ ),
345
+ response_policy=PLAIN_LIST_POLICY,
346
+ runtime_behavior=RAW_ADDON_DATA_READ,
347
+ examples=(
348
+ ExampleSpec(
349
+ command="kaiten --json user-addon-data get --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990",
350
+ description="Read the current user's addon-level settings.",
351
+ ),
352
+ ),
353
+ usage_notes=(
354
+ ADDON_UID_NOTE,
355
+ (
356
+ "This store is per user and per company; it holds addon-level user state, not "
357
+ "per-card state."
358
+ ),
359
+ ),
360
+ ),
361
+ make_tool(
362
+ canonical_name="user-addon-data.set",
363
+ mcp_alias="kaiten_set_user_addon_data",
364
+ description="Write the current user's company-level addon data for one addon.",
365
+ input_schema={
366
+ "type": "object",
367
+ "properties": {
368
+ "addon_uid": {"type": "string", "description": "Addon UUID"},
369
+ "data": {
370
+ "type": "object",
371
+ "description": "Addon data object merged over the stored row by top-level key.",
372
+ },
373
+ },
374
+ "required": ["addon_uid", "data"],
375
+ },
376
+ operation=OperationSpec(
377
+ method="PATCH",
378
+ path_template="/company/users/current/addons-data/{addon_uid}",
379
+ path_fields=("addon_uid",),
380
+ body_fields=("data",),
381
+ ),
382
+ response_policy=PLAIN_ENTITY_POLICY,
383
+ runtime_behavior=ADDON_UID_VALIDATION,
384
+ examples=(
385
+ ExampleSpec(
386
+ command="kaiten --json user-addon-data set --addon-uid 0ce23a01-560f-51e0-9982-1e3445dc5990 --data '{\"selectedRepo\": null}'",
387
+ description="Reset one key of the current user's addon state.",
388
+ ),
389
+ ),
390
+ usage_notes=(ADDON_UID_NOTE, SHALLOW_MERGE_NOTE),
391
+ ),
392
+ )